左值 locator value 代表一个在内存中占用确定位置的对象,就是有一个地址
右值 real value
var = 4;
赋值运算符要求一个 lvalue 作为它的左操作数,当然 var 是一个左值,因为它是一个占确定空间的对象,另一方面,下面的代码是错误的
4 = var; (var+10) = 4;
常量 4 和代码表达式 var + 10 都不是 lvalue,他们是右值,因为它们都是表达式的临时结果,没有确定的内存空间,换句话说,他们只是计算机的周期驻留在临时的寄存器中,因此给它们赋值没有语义。再比如
foo() = 4;
编译器期待在赋值运算符的左值部分看到一个 lvalue,而 foo() 返回的是一个临时变量,属于右值,但不是所有的函数调用的结果赋值都是无效的,比如C++ 的引用 reference
int globalval = 20;
int& foo(){
return globalval;
}
int main(){
foo() = 10;
return 0;
}
如果输出的话,可以看到 globalval 的值变成了 10
因为这里 foo() 返回的是一个引用,这是一个左值,C++从函数中返回左值的能力对于实现一些重载运算符很重要,一个普通的例子是在类中为实现某种查找访问而重载的括号运算符 []
std::mapmyMap; myMap[10] = 4.2;
这里能给 myMap[10] 赋值合法是因为非 const 的重载符 std::map::operator[] 返回一个可以被赋值的引用。
int a = 1; //a是左值 int b = 2; //b是左值 int c = a + b; // + 需要右值 a和 b都转换为右值 并返回一个左值
右值不能转换为左值,违反了左值的语义。
int arr[] = {1,2};
int *p = &arr[0];
*(p+1) = 10; //对的 p+1是一个右值 *(p+1)是一个左值
int var = 10;
int* bad_addr = &(var+1); //错的 ‘&’操作符需要左值参数
int* addr = &var; //对的 var是左值
&var = 40; //错误 赋值操作符的左操作数需要是左值
假定 x 的定义为 int x = 0;
x++; // 右值 先创建一个临时对象,为其赋值,修改x的值,最后返回临时对象 ++x; // 左值 修改自身值,并返回自身
一元运算符 * 解引用,拿一个右值作为参数而产生一个左值作为结果。
int arr[] = {1, 2};
int *p = &arr[0]; //p是一个指针变量 我是这样想的,p是一个左值 因为 可以有 int**pp = &p p是可以取地址的
*(p+1) = 10; // p+1 是一个右值 *(p+1) 就是左值 因为 p+1是一个临时表达式
p是一个左值,是一个指针变量,p的值是 arr[0] 的地址,如果是对 p 进行解引得到 *p 的话,*p代表的就是arr[0]的空间或者内容,但具体用的是变量arr[0]的空间还是内容取决于它是左值还是右值
int a = 10; int *p = &a; *p=20; //*p为左值,即a为左值,所以用的是a的空间,此处把20放入a的空间。 int b = *p//*p为右值,即a为右值,所以用的是a的内容,此处把a的内容放入b的空间。
#includeint main() { int a = 10; int *p = &a;//p指向a printf("a的地址=%p,n", &a); printf("a的内容=%d,n", a); printf("p的内容=%p,n", p); printf("p指向的内容=%d,n", *p);//此时*p为右值,即用a的内容 printf("n"); *p = 20; printf("a的内容=%d,n", a); printf("n"); int b = *p; printf("a的内容=%d,n", a); printf("b的内容=%d,n", b); return 0; }
深入理解 :
当p指向a后, *p 就相当于 a,但指针变量p 还是p ,有其自己的空间和内容
#includeint main() { int a = 10; int b = 20; int *p = &a;//p指向a p = &b;//见下述文字解释 int *q= p;//见下述文字解释 system("pause"); return 0; }
p = &b p 是左值,用的是p内容,此处把b的地址写入p的空间,p指向了b,不再指向a了。
int *q = p 初次定义指针变量q,q为左值,用的是q的空间,p是右值,用的是p内容(p经过 p = &b 语句后,p中的内容为b的地址)
此时指针变量p和q中的内容为b的地址,即指针变量p和q均指向b。
#includeint main() { int a = 10; int b = 20; int *p = &a; p = &b; int *q= p; printf("b的内容=%dn", b); printf("p指向的内容=%dn", *p); printf("q指向的内容=%dn", *q); return 0; }
相反的,一元取值运算符 & 拿一个左值作为参数生成一个右值
int var = 10; int* bad_addr = &(var+1); //var+1是表达式的临时结果,没有确定的内存空间,是一个右值 所以会报错 int* addr = &var; &var = 40; // 也是错的 var是右值,而&需要一个左值
& 符号 在C++中扮演着一个重要的角色,它允许定义类类型,这被称为 “左值引用” ,非const左值引用不能被赋右值,因为这将要求一个无效的右值到左值的转换
std::string& ref = std::string(); //非const左值引用不能被赋右值 所以这一句话会报错
常量左值引用可以被赋右值,因为它们是常量,不能通过引用被修改,这样C++中接收常量引用作为函数形参成为可能。避免了一些不必要的临时对象的拷贝和构造
const string& s = string("hello"); //这样是对的
前面大部分解释了左值和右值的主要区别之一是左值可以被修改,而右值不能,C++11对这个区别添加了一个关键的转换,通过一些特殊的情况允许我们定义右值的引用,然后修改
// 右值引用 格式
// 类型 && 引用名 = 右值表达式
int &&var = 10;
//在汇编层面,右值引用和常引用做的事情是一样的 即产生临时量来存储常量 但是
//唯一一点的区别是 右值引用可以进行读写操作 而常引用只能进行读操作
var = 20;
//右值引用的存在并不是为了取代左值引用 而是充分利用右值(特别是临时对象)的构造来减少
//对象构造和析构操作来达到提高效率的目的
cout << var << endl << endl; //输出20
右值引用本身是一个左值
可以这样理解,右值引用是一个变量,而变量一般都是左值
其实在汇编层面,右值引用和常引用做的事情是一样的,连汇编代码都是一样的,即产生临时变量来存储常量,但是唯一的区别就是 右值引用可以进行读写操作,而常引用只能进行读操作。
右值引用的存在并不是为了取代左值引用,而是充分利用右值(特别是临时对象)的构造来减少对象构造和析构操作来达到提高效率的目的。
class Buffer{
public:
Buffer(){
buf = (char*)malloc(100);
}
~Buffer(){
if(buf != nullptr){
free(buf);
buf = nullptr;
}
}
char* buf = nullptr;
};
Buffer getBuffer(){
Buffer buf;
return buf;
} //对于将亡值来说 ,getbuffer调用结束后,buf会被销毁
//其实setbuffer是可以利用这一段空间的
void setBuffer(const Buffer & buf){
cout << "const Buffer &buf" << endl;
}
void setBuffer(Buffer && buf){ //在右值引用中 我们知道这个值马上就要销毁了,所以可以对其内存进行重用
cout << "Buffer && buf" << endl;
char * b = buf.buf;
//TODO
buf.buf = nullptr;
}
int main(){
setBuffer(getBuffer());
Buffer buf;
setBuffer(buf);
return 0;
}
通过右值引用,可以有效的区分不同的调用方式,进而得到进一步的性能优化。
Matrix r = a + b + c + d; a + b 会赋值给一个 temp_ab,调用一次构造函数和拷贝构造函数 temp_ab + c 的值会通过拷贝构造函数给到 temp_abc temp_abc + d 也会调用一次构造函数和一次拷贝构造函数给到temp_abcd r = temp_abcd
#include#include using namespace std; class Matrix{ public: Matrix(int _row, int _col){ cout << "Matrix(int _row, int _col)" << endl; row = _row; col = _col; data = new float* [row]; for(int i = 0 ; i < row ; i++){ data[i] = new float[col]; for(int j = 0 ; j < col ; j++){ data[i][j] = 0; } } } //拷贝构造函数 Matrix(const Matrix & mat){ cout << "Matrix(const Matrix & mat)" << endl; row = mat.row; col = mat.col; data = new float* [row]; for(int i = 0 ; i < row ; i++){ data[i] = new float[col]; for(int j = 0 ; j < col ; j++) data[i][j] = mat.data[i][j]; } } //移动构造函数 Matrix(Matrix && mat){ cout << "Matrix(Matrix && mat)" << endl; row = mat.row; col = mat.col; data = mat.data; mat.data = nullptr; } ~Matrix(){ if(data != nullptr){ for(int i = 0 ; i < row ; i++){ if(data[i]){ delete [] data[i]; data[i] = nullptr; } } delete [] data; data = nullptr; } } // Matrix operator+(const Matrix & mat){ // if(mat.row != row || mat.col != col) // cout << "Error" << endl; // Matrix res(mat.row, mat.col); // for(int i = 0 ; i < mat.row ; i++) // for(int j = 0 ; j < mat.col ; j++){ // res.data[i][j] = data[i][j] + mat.data[i][j]; // } // return res; 返回的时候幽会调用一次拷贝构造函数 因为结果会给一个临时变量 // } friend Matrix operator + (const Matrix & a, const Matrix & b){ cout << "friend Matrix operator + (const Matrix & a, const Matrix & b)" << endl; if(a.row != b.row || a.col != b.col){ cout << "Error" << endl; } Matrix res(a.row, a.col); for(int i = 0 ; i < a.row ; i++) for(int j = 0 ; j < a.col ; j++){ res.data[i][j] = a.data[i][j] + b.data[i][j]; } return res; } friend Matrix operator + (Matrix && a, const Matrix & b){ // cout << "a : " << a.col << endl; cout << "friend Matrix operator + (Matrix && a, const Matrix & b)" << endl; if(a.row != b.row || a.col != b.col){ cout << "Error" << endl; } // a是一个将亡值 // Matrix res = a; 右值引用是一个左值 Matrix res = std::move(a); for(int i = 0 ; i < res.row ; i++) for(int j = 0 ; j < res.col ; j++){ // res.data[i][j] = a.data[i][j] + b.data[i][j]; res.data[i][j] = res.data[i][j] + b.data[i][j]; } return res; } void Printa(Matrix& a){ cout << "a = " << a.col << endl; cout << "data = " << a.data[0][0] << endl; } int row = 0; int col = 0; float** data = nullptr; }; int main(){ Matrix a(3,4); Matrix b(3,4); Matrix c(3,4); Matrix d(3,4); Matrix r = a + b + c + d; // Matrix r(std::move(a)+b); // a.Printa(a); return 0; }
有没有方法能避免构造函数的调用,在C++11之后,当函数试图返回一个对象,首先尝试调用移动构造函数,当找不到移动构造函数的时候,再调用拷贝构造函数
//移动构造函数
Matrix(Matrix && mat){ //&& 表示这个参数在函数调用之后就会被销毁,所以可以重用这个参数的资源
cout << "Matrix(Matrix && mat)" << endl;
row = mat.row;
col = mat.col;
data = mat.data;
mat.data = nullptr;
}
首先看 Matrix && m = getMatrix();
我们假设getMatrix() 会返回一个将亡值,或者说是匿名值,m是一个指向这个将亡值的右值引用,m本身不是一个将亡值,而是一个类型 为 右值引用 的 左值。那下面的代码就是利用 std::move() 函数将 a 的资源给到了res,使用完这行代码之后,理论上就不能再用 a 的资源了 ,但是这里实验了一下,a.row 和 a.col 还是能够正常使用的,这里我猜测应该是在栈上的资源不受影响,是a 的堆上的资源给了 res
对于 a + b ,为了提高效率,可以重载 + 运算符
friend Matrix operator + (Matrix && a, const Matrix & b){
cout << "a : " << a.col << endl;
cout << "friend Matrix operator + (Matrix && a, const Matrix & b)" << endl;
if(a.row != b.row || a.col != b.col){
cout << "Error" << endl;
}
// a是一个将亡值
// Matrix res = a; 右值引用是一个左值
Matrix res = std::move(a);
for(int i = 0 ; i < res.row ; i++)
for(int j = 0 ; j < res.col ; j++){
// res.data[i][j] = a.data[i][j] + b.data[i][j];
res.data[i][j] = res.data[i][j] + b.data[i][j];
}
return res;
}
运行完之后可以看到,只在 a + b 的时候调用了一次构造函数,剩下都是移动构造函数



