左值和右值

引用

引用只是一个对象的别名,必须在声明时就立刻初始化,在声明后可以直接用原对象的使用方法进行使用。
引用必须被初始化为指代一个有效的对象或函数

  1. 不存在 void 的引用,也不存在引用的引用,不存在引用的数组,不存在指向引用的指针。
  2. 引用不是对象,它们不必占用存储
    
    int a = 0;
    int& r1;//错误引用变量r1需要初始值设定项
    r1 = a;
    
    int& a[3]; // 错误,不存在引用的数组
    int&* p;   // 错误
    int& & r;   // 错误,不可以隔开
    
    //以下是正确的
    int* ptrs[10];
    int arr[10] = {1,2,3,4,5,6,7,8};
    int(&parray)[10] = arr;//是一个引用,引用一个含有10个整数的数组
    int* (&array)[10] = ptrs;//是一个引用,引用一个数组,这个数组,包含10个指针
    cout << parray[0] << endl;//输出1

左值和右值

左值:

  • 可以由用户访问的存储单元,有一个相对稳定的内存地址,并且有一段较长的生命周期,即可以进行&(取地址),实际存储在内存或者寄存器中。左值可以分为常量左值引用和非常量左值引用。
  • 使用左值引用声明符:& 进行表示       
    int b = 0;  
    int& a = b;

常量左值引用

接收非常量左值,常量左值,右值进行初始化

int a = 0;
int& r1 = a;
const int & r2 = 2;//接收右值
const int& r3 = a;

非常量左值引用

        只接收左值

int& a = 2;  //     # 左值引用绑定到右值,编译失败

右值:

  • 则无法进行&(取地址),无法进行赋值使用右值引用声明符&&来表示,&与&不可以隔开,字面常量,表达式返回值,函数的返回值(是值返回,而不是引用返回),右值不能出现在赋值符号的左边且不能取地址。
    int&& a = 2;
  • 注意:字符串字面量,是左值 ,i++是右值,++i是左值
    const char* str = "hello world";
    const char* (&r) = str;
    const char*&r = str;
    const char&*r = str;//错误,不允许使用指向引用的指针
    const char&(*r) = str;//错误,不允许使用指向引用的指针
    
    int a = 1, b = 2;
    &(a==b ? a : b);
    &(a==b ? 3 : 4);//错误:表达式必须为左值或函数指示符
    
    //i++://----右值
    int temp =i;
    i=i+1;
    return temp;
    
    //++i //-----左值
    i=i+1;
    return i;
    
  • 对于将一个左值赋给一个左值,转换为右值后,再左值会先进行一次隐式类型转换,先进行赋值。
    int x=0; 
    int &y=x;

右值引用

  • 右值引用一般不能绑定左值,但可以用std::move()将左值转为右值
    int a;
    int&& r1 = a;//错误
    int&& r2 = std::move(a);
  •  右值引用引用右值,右值就会有一份存储空间,右值引用变量就是是左值,可以对它取地址和赋值,可以用来延长右值的生命周期
    int x = 1, y = 2;
    int&& r1 = x + y;
    int* ptr1 = &r1;//对右值取地址
    int&& r2 = 6;
    int* ptr2 = &r2;//对右值取地址
    int& r3 = x;
    int* ptr3 = &r3;
    cout << *ptr1 << endl;//输出3
    cout << *ptr2 << endl;//输出6
    cout << *ptr3 << endl;//输出1
    

    纯右值

  • 函数的返回值(返回的函数值,不能是引用)、运算表达式产生的临时变量,原始字面量lambda表达式等

将亡值

  • 将要被移动,即将被销毁的对象、T&&函数返回值、std::move的返回值。可以引用左值和右值。
    int x = 1, y = 2;
    int&& r1 = x + y;
    int&& r2 = 3;

    判断是否为右值

  • 使用is_same_v,使用is_same_v检查两个类型是否相同或is_lvalue_reference判断是否为左值
    int n = 3;
    int& x = n;
    int&& y = std::move(x);
    bool ret1 = std::is_same_v<decltype(n), int>;//判断是否为左值
    cout <<"n是否为左值  " << ret1 << endl;
    bool ret2 = std::is_same_v<decltype(x), int&>;//判断是否为左值
    cout << "x是否为左值  " << ret2 << endl;
    bool ret3 = std::is_same_v<decltype(y), int&&>;//判断是否为将亡值
    cout << "y是否为将亡值  " << ret3 << endl;
    bool ret4 = std::is_same_v<decltype(1), int>;//判断是否为纯右值
    cout << "1是否为纯右值  " << ret4 << endl;
    
    cout << is_lvalue_reference<decltype(2)>::value << endl;//判断是否为左值

  • 右值与移动构造函数

  • 使用左值去初始化对象或对象赋值时,将调用拷贝构造函数或赋值运算符
    class Test
    {
    public:
    	Test() { cout << "调用了构造函数" << endl; }
    	Test(const Test& t) { cout << "调用了拷贝构造函数" << endl; }
    	Test(Test&& t) { cout << "调用了移动构造函数" << endl; }
    	~Test() { cout << "调用了析构函数" << endl; }
    	Test& operator=(const Test& t) { cout << "调用了赋值运算符" << endl; return *this; }
    };
    
    int main()
    {
    	Test t1;
    	Test t2(t1);
    	t2 = t1;
    	/*
    	*
    	* 运行结果:
    		调用了构造函数
    		调用了拷贝构造函数
    		调用了赋值运算符
    		调用了析构函数
    		调用了析构函数
    	*/
    	return 0;
    }
  • 这里还需要注意拷贝函数加上了const,代表其右值和左值都可以接收,而移动构造函数只接收右值。
    class Test
    {
    public:
    	Test() { cout << "调用了构造函数" << endl; }
    	Test(const Test& t) { cout << "调用了拷贝构造函数" << endl; }
    	//Test(Test&& t) { cout << "调用了移动构造函数" << endl; }
    	
    };
    int main()
    {
    	{
    		Test t1;
    		Test t2 = move(t1);//拷贝函数不加上const时,错误:类"Test"没有适当的复制构造函数
    	}
    	return 0;
    }

  • 使用右值去初始化或赋值时,则调用的时移动构造函数或移动赋值运算符来进行移动,而不进行拷贝。
    class Test
    {
    public:
    	Test() { cout << "调用了构造函数" << endl; }
    	Test(Test& t) { cout << "调用了拷贝构造函数" << endl; }
    	Test(Test&& t) { cout << "调用了移动构造函数" << endl; }
    	~Test() { cout << "调用了析构函数" << endl; }
    	Test& operator=( Test&& t) { cout << "调用了移动赋值运算符" << endl; return*this; }
    	Test& operator=( Test& t) { cout << "调用了赋值运算符" << endl; return*this; }
    };
    Test fun()
    {
    
    	Test t;
    	std::cout << "Test fun()" << std::endl;
    	return t;
    }
    int main()
    {
    	{
    		Test t1;
    		cout << "======================" << endl;
    		Test t2(move(t1));
    		cout << "======================" << endl;
    		t2 = move(t1);
    		cout << "======================" << endl;
    	}
    
    	{
    		cout << "======================" << endl;
    		Test t3(fun());
    	}
    	{
    		cout << "======================" << endl;
    		Test t4 = fun();
    	}
    	/*
    	* 运行结果:
    	*
    	调用了构造函数
    	 ======================
    	 调用了移动构造函数
    	 ======================
    	 调用了移动赋值运算符
    	 ======================
    	 调用了析构函数
    	 调用了析构函数
    	 ======================
    	 调用了构造函数
    	 Test fun()
    	 调用了析构函数
    	 ======================
    	 调用了构造函数
    	 Test fun()
    	 调用了析构函数
    	*/
    	return 0;
    }
    需要注意的是:Test t3(fun());和Test t4 = fun();这两句并没有调用移动构造函数和移动赋值运算符,只调用了fun函数变量里的构造函数,这是因为编译器进行了返回值优化,关掉就可以了。
  • 而第一种是返回值优化(RVO),它允许编译器在函数返回时创建临时对象,而是世界在调用者的对象上构造结果,这可以使得直接在函数内部构造返回值,而不是在函数内部构造临时对象并返回来实现,这也通常会导致移动构造函数的调用被省略。
  • 第二种是命名返回值优化(NVRO),NVRO是一种更为特定的情况,其中编译器允许避免创建临时对象,即使该对象在函数中有名称,在NRVO中,编译器将返回的对象直接在函数内部构造,而不是在函数内部构造一个有名称的临时对象再进行返回。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值