IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++中左值(引用)及右值(引用)详解 -> 正文阅读

[C++知识库]C++中左值(引用)及右值(引用)详解

  • 写C++代码编译时,有时会出现左值问题错误或右值错误,那左值和右值究竟是什么呢???

一、左值与右值

  • 啥是左值和右值呢?

  • 左值:在内存有确定存储地址、有变量名,表达式结束依然存在的值,简单来说左值就是非临时对象。

  • 右值:就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值,简单来说右值就是临时对象。

    int a = 0;  // 在这条语句中,a 是左值,0 是临时值,就是右值。
    
  • 左值可以分为两类:非常量左值和常量左值;

    int a=10;              	// a 为非常量左值(有确定存储地址,也有变量名)
    const int a1=10;      //a1 为常量左值(有确定存储地址,也有变量名)
    const int a2=20;      //a2 为常量左值(有确定存储地址,也有变量名)
    
  • 同理,右值也可以分为两类:非常量右值和常量左值。

     int a=10;              	// 10 为非常量右值
    const int a1=10;      
    const int a2=20;      
    a1+a2               	// (a1+a2) 为常量右值
    

二、左值引用于右值引用

  • 知道了左值与右值了,那啥是左值引用与右值引用呢?

  • 左值引用:其实就是绑定到左值的引用,通过&来获得左值引用。

    • 左值引用举例:
    int a=10;              //非常量左值(有确定存储地址,也有变量名)
    const int a1=10;       //常量左值(有确定存储地址,也有变量名)
    const int a2=20;       //常量左值(有确定存储地址,也有变量名)
     
    //非常量左值引用
    int &b1=a;            //正确,a是一个非常量左值,可以被非常量左值引用绑定
    int &b2=a1;           //错误,a1是一个常量左值,不可以被非常量左值引用绑定
    int &b3=10;           //错误,10是一个非常量右值,不可以被非常量左值引用绑定
    int &b4=a1+a2;        //错误,(a1+a2)是一个常量右值,不可以被非常量左值引用绑定
    
    //常量左值引用
    const int &c1=a;      //正确,a是一个非常量左值,可以被非常量右值引用绑定
    const int &c2=a1;     //正确,a1是一个常量左值,可以被非常量右值引用绑定
    const int &c3=a+a1;   //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定
    const int &c4=a1+a2;  //正确,(a1+a2)是一个常量右值,可以被非常量右值引用绑定
    
    
    • 总结归纳:非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。
  • 右值引用:其实也是绑定到右值的引用,通过&&来获得右值引用。

    • 左值引用举例:
    int a=10;             //非常量左值(有确定存储地址,也有变量名)
    const int a1=20;      //常量左值(有确定存储地址,也有变量名)
    const int a2=20;      //常量左值(有确定存储地址,也有变量名)
    
    //非常量右值引用
    int &&b1=a;            //错误,a是一个非常量左值,不可以被非常量右值引用绑定
    int &&b2=a1;           //错误,a1是一个常量左值,不可以被非常量右值引用绑定
    int &&b3=10;           //正确,10是一个非常量右值,可以被非常量右值引用绑定
    int &&b4=a1+a2;        //错误,(a1+a2)是一个常量右值,不可以被非常量右值引用绑定
    
    //常量右值引用
    const int &&c1=a;      //错误,a是一个非常量左值,不可以被常量右值引用绑定
    const int &&c2=a1;     //错误,a1是一个常量左值,不可以被常量右值引用绑定
    const int &&c3=a+a1;   //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定
    const int &&c4=a1+a2;  //正确,(a1+a2)是一个常量右值,不可以被常量右值引用绑定
    
    
    • 总结归纳:非常量右值引用只能绑定到非常量右值上;常量右值引用可以绑定到非常量右值、常量右值上。

在这里插入图片描述

从上述可以发现,常量左值引用可以绑定到右值上,但右值引用不能绑定任何类型的左值,若想利用右值引用绑定左值该怎么办呢?

  • C++11中提供了一个标准库move函数获得绑定到左值上的右值引用,即直接调用std::move告诉编译器将左值像对待同类型右值一样处理,但是被调用后的左值将不能再被使用。

    • std::move()函数举例
    int a=10;                 //非常量左值(有确定存储地址,也有变量名)
    const int a1=20;          //常量左值(有确定存储地址,也有变量名)
    
    //非常量右值引用
    int &&d1=std::move(a);    //正确,将非常量左值a转换为非常量右值,可以被非常量右值引用绑定
    int &&d2=std::move(a1);    //错误,将常量左值a1转换为常量右值,不可以被非常量右值引用绑定
    
    //常量右值引用
    const int &&c1=std::move(a);      //正确,将非常量左值a转换为非常量右值,可以被常量右值引用绑定
    const int &&c2=std::move(a1);     //正确,将常量左值a1转换为常量右值,可以被常量右值引用绑定
    
    

最后可以发现,编译器利用std::move将左值强制转换为相同类型的右值之后,引用情况跟右值是一模一样的。

三、右值引用与左值引用的区别

  • 1、左值引用绑定到有确定存储空间以及变量名的对象上,表达式结束后对象依然存在;

  • 2、右值引用绑定到要求转换的表达式、字面常量、返回右值的表达式等临时对象上,赋值表达式结束后就对象就会被销毁。

  • 3、左值引用后可以利用别名修改左值对象;右值引用绑定的值不能修改。

四、引入右值引用的原因

  • 1、替代需要销毁对象的拷贝,提高效率:某些情况下,需要拷贝一个对象然后将其销毁,如:临时类对象的拷贝就要先将旧内存的资源拷贝到新内存,然后释放旧内存,引入右值引用后,就可以让新对象直接使用旧内存并且销毁原对象,这样就减少了内存和运算资源的使用,从而提高了运行效率;
  • 2、移动含有不能共享资源的类对象:像IO、unique_ptr这样的类包含不能被共享的资源(如:IO缓冲、指针),因此,这些类对象不能拷贝但可以移动。这种情况,需要先调用std::move将左值强制转换为右值,再进行右值引用。

在这举例里用一下别的文章里的代码来说明一下引入右值引用的好处:如下原代码的文章在该链接点击

  有如下string类,实现了拷贝构造函数和赋值运算符重载。
class MyString {
private:
	char* _data;
	size_t   _len;
	void _init_data(const char *s) {
		_data = new char[_len + 1];
		memcpy(_data, s, _len);
		_data[_len] = '\0';
	}
public:
	MyString() {
		_data = NULL;
		_len = 0;
	}

	MyString(const char* p) {
		_len = strlen(p);
		_init_data(p);
	}

	MyString(const MyString& str) {
		_len = str._len;
		_init_data(str._data);
		std::cout << "Copy Constructor is called! source: " << str._data << std::endl;
	}

	MyString& operator=(const MyString& str) {
		if (this != &str) {
			_len = str._len;
			_init_data(str._data);
		}
		std::cout << "Copy Assignment is called! source: " << str._data << std::endl;
		return *this;
	}

	virtual ~MyString() {
		if (_data != NULL) {
			std::cout << "Destructor is called! " << std::endl; 
			free(_data);
		}
	}
};

int main() { 
	MyString a; 
	a = MyString("Hello"); 
	std::vector<MyString> vec; 
	vec.push_back(MyString("World")); 
}

运行结果:

Copy Assignment is called! source: Hello
Destructor is called!
Copy Constructor is called! source: World
Destructor is called!
Destructor is called!
Destructor is called!

总共执行了2次拷贝,MyString(“Hello”)和MyString(“World”)都是临时对象,临时对象被使用完之后会被立即析构,在析构函数中free掉申请的内存资源。 如果能够直接使用临时对象已经申请的资源,并在其析构函数中取消对资源的释放,这样既能节省资源,有能节省资源申请和释放的时间。 这正是定义转移语义的目的。

通过加入定义转移构造函数和转移赋值操作符重载来实现右值引用(即复用临时对象):

MyString(MyString&& str) { 
		std::cout << "Move Constructor is called! source: " << str._data << std::endl; 
		_len = str._len; 
		_data = str._data; 
		str._len = 0; 
		str._data = NULL;   // ! 防止在析构函数中将内存释放掉
	}

	MyString& operator=(MyString&& str) { 
		std::cout << "Move Assignment is called! source: " << str._data << std::endl; 
		if (this != &str) { 
			_len = str._len; 
			_data = str._data; 
			str._len = 0; 
			str._data = NULL;  // ! 防止在析构函数中将内存释放掉
		} 
		return *this; 
	}

运行结果:

Move Assignment is called! source: Hello
Move Constructor is called! source: World
Destructor is called!
Destructor is called!

需要注意的是:右值引用并不能阻止编译器在临时对象使用完之后将其释放掉的事实,所以转移构造函数和转移赋值操作符重载函数 中都将_data赋值为了NULL,而且析构函数中保证了_data != NULL才会释放。

若发现该文章上有错处的地方可在下方留言告知一下,十分感谢!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-17 11:49:20  更:2021-10-17 11:50:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:19:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码