1.c++左值(lvalue)、右值(rvalue)和三/五法则
简单来说,左值就是在内存中有确定地址的变量,相当于一个可更改值的容器;相反,右值是一些临时变量,在内存中没有确定地址,相当于放进容器的值。赋值运算符(=
)要求左操作数必须为左值,右操作数必须为右值。TRIANGLES的这篇博客解释了左右值的概念以及左右值的互相转换(左值可以直接隐式转换为右值,而右值只能转换为常量左值,如示例1);他的下一篇博客右值引用与移动语义中,介绍了右值引用,右值引用解决了右值只能转换为常量左值的问题,让右值也可以转换为非常量的左值。
示例1:
int x=1,y=2; //x,y是左值;1,2是右值
int z=x+y; //+运算符要求两个右值作为输入参数;左值x,y可以直接隐式转换为右值
int a[]={1,2,3};
int *ap=&a[0]; //&运算符将一个左值(a[0])取地址变成右值(a[0]的地址)
cout<<*(ap+1)<<endl;//*运算符将一个右值(ap+1)解引用变成一个左值(ap+1地址指向的int)
int &zref=z; //左值可以直接赋给一个左值引用
int &ref=7; //右值不可以直接赋给一个引用,因为引用类型应初始化为一个左值
const int &ref2=8; //但是右值可以赋给常量引用,因为常量引用本身也不可改变,可以防止更改临时变量值
示例2:
int &&rref = 7 + 8; //可以把右值直接赋给右值引用
cout << rref << endl;
rref++; //之后可以更改这个引用
cout << rref << endl;
为了说明把右值赋给右值引用时究竟发生了什么,测试如下:
class A
{
public:
int id;
A() :id(0) { cout << "A constructed:this=" << this << endl; }
A(int id_) :id(id_) { cout << "A constructed:this=" << this << ",id=" << id << endl; }
~A() { cout << "A destructed:this=" << this << endl; }
};
int main()
{
{
A &&arref = A();
cout << "address of arref=" << &arref << endl;
cout << "arref.id=" << arref.id << endl;
arref.id++;
cout << "arref.id=" << arref.id << endl;
//A a1(3);
//A &&a1rref = a1; //错误,无法将右值引用或绑定到左值
}
system("pause");
return 0;
}
输出如下:
可以看到,arref与构造临时变量的地址是一样,说明这里没有复制发生,arref就是生成的临时变量。
所谓的三/五法则就是:一般编译器会给类自动加上一些默认的函数——析构函数、拷贝构造函数与赋值运算符重载函数——如果要自定义其中的一个,最好是三个都自定义(三法则);而五法则则涉及到移动语义,即如果自定义的类需要移动语义,还需要定义移动构造函数与移动赋值运算符重载函数(共五个函数),在右值引用与移动语义中介绍了。
这两篇博客就不做翻译了