一、C++11中的左值和右值
左值 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::map<int,float> myMap;
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的空间。
#include <stdio.h>
int 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 ,有其自己的空间和内容
#include <stdio.h>
int 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。
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int *p = &a;
p = &b;
int *q= p;
printf("b的内容=%d\n", b);
printf("p指向的内容=%d\n", *p);
printf("q指向的内容=%d\n", *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;
}
通过右值引用,可以有效的区分不同的调用方式,进而得到进一步的性能优化。
五、std::move()
Matrix r = a + b + c + d;
a + b 会赋值给一个 temp_ab,调用一次构造函数和拷贝构造函数
temp_ab + c 的值会通过拷贝构造函数给到 temp_abc
temp_abc + d 也会调用一次构造函数和一次拷贝构造函数给到temp_abcd
r = temp_abcd
#include <iostream>
#include <stdio.h>
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 的时候调用了一次构造函数,剩下都是移动构造函数