【C++学习入门】6.左值右值
指针和引用
浅拷贝和深拷贝
左值右值
左值引用和右值引用
移动语义和右值引用
指针和引用
指针是一个变量,它存储了另一个变量的地址;
引用是一个别名,它本身不拥有一个地址,只是给原来的变量取了另一个名字。
要点: &
和*
在等号左右边不同位置有不同含义
&
在等号左边表示引用。
&
对变量使用,就是获取这个变量的地址,然后可以再使用*
获取这个地址的指向的值。
*
在等号左边表示变量是一个地址。然后可以使用*
获取这个地址的值。
int n;
// 这里的*表示指针pt是一个地址
int *pt = new int;
const int b = 101;
// 这里获取n的引用,要注意.
// 这里的&表示引用,可以将int& 是一个整体,通用表示的话是 T&
int & rn = n;
// 这里的*是一个解引用操作符,会访问地址指向空间的值.
// 上面提到了 pt表示一个地址,所以这里的*pt就是获取pt指向的值
int & rt = *pt;
// 这里也可以将 const int & 看成一个整体.通用表示的话是 const T&
// 因为b是const int类型,要获取b的引用,规定只能是const T& 类型.
const int & rb = b;
浅拷贝和深拷贝
无论是各种指针,各种引用,无非就是要解决数据拷贝的问题。
拷贝分为浅拷贝和深拷贝,各种语言针对这两个的名词解释有些差别。
通常认为浅拷贝就是复制栈上的数据,在栈上的数据因为知道他的起始和终止的地址,因此可以直接复制一段连续的地址数据。
深拷贝,栈和堆上的数据都要复制,如果栈上是个指向堆内存的指针的话,不止要复制这个指针,还要复制指针指向的数据。
但是浅拷贝有个问题就是,如果浅拷贝了一个栈上数据,指向一个堆内存(一块动态的内存空间),那么拷贝后,就会有两个地方指向同一块动态空间。一个办法就是将动态空间的所有权转给新的对象。还有一种情况就是,之前的对象本身就是临时对象,程序临时创建,拷贝后马上就丢弃了。
左值右值
将亡值表达式: 返还值是临时的,返还类型是右值引用(&&),因此指代的对象即使非临时也会被认为可移动。
纯右值表达式: 返还值为临时值。
左值(lvalue): 指持久存在(有变量名)的对象或返还值类型为左值引用的返还值,是不可移动的。
右值(rvalue): 包含了将亡值(xvalue)、纯右值(prvalue),是可移动(可被右值引用类型匹配)的值。
堆栈来看左值和右值:
左值是和变量有绑定的值,无论这个值是本身保存在栈上,还是栈上只保存了地址而实际数据在堆上。只要有绑定关系,就能通过栈找到。
右值其实就是现在还是没人要数据,他可能在栈上,堆上或者寄存器上,但是呢,没有一个变量和他绑定,表达式结束后它必须,也一定会被回收掉。
int a = 3;// a是左值,3是右值
如果感觉理解起来麻烦。粗浅的认为等号左边为左值,右边为右值也没什么问题,毕竟写代码才是主要目的。
左值引用和右值引用
下面内容可以忽略,现代c++语言核心特性讲解第六章有更好的讲述。
左值引用T&
就是我们正常理解的引用
int n;
int &rn = n;
注意: 一个常量只能绑定到常左值const T&
引用
const int b = 101;
const int &rb = b;
右值引用 &&
,含义就是可关联到右值.
int x = 10;
int y = 23;
// && 表示右值引用
int && r1 = 13;
// 这里获取的是x+y表达式当前计算后的,值.后面改变x或y的值,不会影响到r2
int && r2 = x+y;
double && r3 = std::sqrt(2.0);
cout << r1 << endl;
// 将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址。
// 也就是说,虽然不能将运算符&用于13,但可将其用于r1。
// 通过将数据与特定的地址关联,使得可以通过右值引用来访问该数据。
cout << *&r1 << endl;
移动语义和右值引用
对大多数程序员来说,右值引用带来的主要好处并非是让他们能够编写使用右值引用的代码,而是能够使用利用右值引用实现移动语义的库代码。例如,STL类现在都有复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符。
移动的语义就是将原来的值移动到另外的地方,而不是复制到另外的地方。也就是说转移了所有权。
// 传入一个右值引用a,相当于就是转移了所有权。
// std::move()可以将一个左值,变成右值,转变后之前的变量不应该再用了。
// 如果是移动构造函数的话,会讲之前的值指向null
void f(A && a){}
构造函数
// 1. 默认构造函数
class A{int x,y;public:A(){cout << "Default constructor called!";}
};
int main(){A obj;return 0;
}
// 2. 复制构造函数
A(const A & old ){x = old.x;y = old.y;cout << "Copy Constructor called!\n";
}
int main(){A obj(10,20);A obj2(obj); return 0;
}
// 3. 移动构造函数
A(const A && old ){x = old.x;y = old.y;old.x = nullptr;old.y = nullptrcout << "Move Constructor called!\n";
}
int main(){A obj(10,20);A obj2(std::move(obj)); return 0;
}
// 4. 移动赋值构造
A & operator= (A && a){x = old.x;y = old.y;old.x = nullptr;old.y = nullptrreturn *this;
}
int main(){A obj(10);A obj2(20);obj = std::move(obj2);return 0;
}
参考
https://www.cnblogs.com/KillerAery/p/12802771.html