【C++】C++11右值引用
👀樊梓慕:个人主页
🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》
🌝每一个不曾起舞的日子,都是对生命的辜负
目录
前言
1.什么是左值&&什么是右值
左值
右值
2.什么是左值引用&&什么是右值引用
左值引用
右值引用
3.左值引用与右值引用的比较
左值引用总结
右值引用总结
4.右值引用的使用场景和意义
传值返回场景分析
移动构造
移动赋值
总结
容器的插入场景分析
move的简单解释
右值被右值引用后,该右值引用是左值
那为什么这样设计呢?
5.完美转发
万能引用
完美转发保持值的属性不变
前言
今天我们正式进入C++11的学习,C++11引入的一个非常重要的语法就是右值引用,在C++11之前的C++版本我们所提的引用都是左值引用,那么右值引用与左值引用又有什么区别呢?什么是左值?什么是右值?右值引用的价值体现在哪里?以及完美转发和万能引用的相互配合?那么接下来我们就来学习有关右值引用的相关知识。
欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。
=========================================================================
GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟
=========================================================================
1.什么是左值&&什么是右值
左值
左值是一个表示数据的表达式,如变量名或解引用的指针。
- 我们可以获取左值的地址,一般情况下也可以被修改(const修饰的左值除外)。
- 左值既可以出现在赋值符号的左边,也可以出现在赋值符号的右边。
int main() { //以下的p、b、c、*p都是左值 int* p = new int(0); int b = 1; const int c = 2; return 0; }
右值
右值也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等。
- 不可以获取右值的地址。
- 右值可以出现在赋值符号的右边,但是『 不能』出现在赋值符号的左边。
int main() { double x = 1.1, y = 2.2; //以下几个都是常见的右值 10; x + y; fmin(x, y); //错误示例(右值不能出现在赋值符号的左边) //10 = 1; //x + y = 1; //fmin(x, y) = 1; return 0; }
其实右值一般都是一个临时变量或常量值,比如代码中的10就是常量值,表达式x+y和函数fmin的返回值就是临时变量,这些都叫做右值,而我们知道这些临时变量和常量值实际上并没有被存储起来,当然也就不存在地址。
//这里x是左值 int func1() { static int x = 0; return x; } //这里x是左值 int& func2() { static int x = 0; return x; }
- 当返回值没有引用标记时,返回的是临时拷贝x的一份临时变量;
- 当返回值有引用标记时,返回的是x本身(注意销毁的问题)。
2.什么是左值引用&&什么是右值引用
传统的C++语法中就有引用的语法,而C++11中新增了右值引用的语法特性,为了进行区分,于是将C++11之前的引用就叫做左值引用。但是无论左值引用还是右值引用,本质都是给对象取别名。
左值引用
左值引用就是对左值的引用,给左值取别名,通过“&”来声明。比如:
int main() { //以下的p、b、c、*p都是左值 int* p = new int(0); int b = 1; const int c = 2; //以下几个是对上面左值的左值引用 int*& rp = p; int& rb = b; const int& rc = c; int& pvalue = *p; return 0; }
右值引用
右值引用就是对右值的引用,给右值取别名,通过“&&”来声明。比如:
int main() { double x = 1.1, y = 2.2; //以下几个都是常见的右值 10; x + y; fmin(x, y); //以下几个都是对右值的右值引用 int&& rr1 = 10; double&& rr2 = x + y; double rr3 = fmin(x, y); return 0; }
很多人到这里就有疑惑了,引用的本质就是起别名,但是右值我们知道是没有地址的,如果一个引用可以标记在右值上,那又有什么意义呢?
是的,既然有右值引用存在,那么右值引用一定是将这个临时变量存放到了某个确定的地址上,让这个右值可以被取到地址,并且可以被修改,当然如果不想让被引用的右值被修改,可以用const修饰右值引用。比如:
int main() { double x = 1.1, y = 2.2; int&& rr1 = 10; const double&& rr2 = x + y; rr1 = 20; rr2 = 5.5; //报错 return 0; }
3.左值引用与右值引用的比较
左值引用总结
- 左值引用只能引用左值,不能引用右值。
- 但是const左值引用既可引用左值,也可引用右值。
int main() { // 左值引用只能引用左值,不能引用右值。 int a = 10; int& ra1 = a; // ra为a的别名 //int& ra2 = 10; // 编译失败,因为10是右值 // const左值引用既可引用左值,也可引用右值。 const int& ra3 = 10; const int& ra4 = a; return 0; }
右值引用总结
- 右值引用只能右值,不能引用左值。
- 但是右值引用可以move以后的左值。
int main() { // 右值引用只能右值,不能引用左值。 int&& r1 = 10; // error C2440: “初始化”: 无法从“int”转换为“int &&” // message : 无法将左值绑定到右值引用 int a = 10; int&& r2 = a; // 右值引用可以引用move以后的左值 int&& r3 = std::move(a); return 0; }
4.右值引用的使用场景和意义
在探究右值引用的使用场景和意义之前,我们来回忆以下左值引用给我们带来的优点:
左值引用可以避免一些没有必要的拷贝操作,比如传参或函数返回值。
但是左值引用在修饰函数返回值时却容易出现问题,因为函数返回值是一个的局部变量,出了函数作用域就被销毁了,如果给加上了左值引用,就会导致左值引用出现问题,所以这种情况下不能使用左值引用作为返回值,只能以传值方式返回,这就是『 左值引用的短板』。
既然是右值,我们就可以使用右值引用,但是右值引用解决这里的问题是『 间接解决的』。
什么叫间接解决??
右值引用不能直接加到返回类型上直接解决么,答案当然是不能的,因为不管你给返回值加左值引用还是右值引用,都改变不了它即将被销毁的事实。
所以我们只能间接解决,怎么间接解决呢?
传值返回场景分析
移动构造
我们想要避免拷贝构造的发生,那就要设法让编译器在遇到右值引用时调用其他构造方式,这里采用的就是『 移动构造』。
而移动构造说白了就是利用swap函数将『 将亡值』与当前对象进行交换,获得『 将亡值』的数据,通过一个swap即可得到数据,不需要调用拷贝构造既节省了时间也节省了空间。
这种swap其实是一种非常危险的行为,只能适用于『 将亡值』,可以理解为是一种资源的掠夺。
将亡值:即将销毁的变量,比如返回值x这种。
增加移动构造之后,由于移动构造采用的是右值引用接收参数,因此如果拷贝构造对象时传入的是右值,那么就会调用移动构造函数(编译器最匹配原则)。
比如:
// 拷贝构造 -- 左值 string(const string& s) :_str(nullptr) { cout