C++ 右值引用

一、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[] = {12};
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 的时候调用了一次构造函数,剩下都是移动构造函数

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值