左值和右值
- 左值:能用在赋值语句等号左侧的东西;能够代表一个地址;
- 右值:不能作为左值的值就是右值,右值不能出现在赋值语句等号的左侧;
结论:
- C++的表达式,要么是左值,要么是右值,不能能两者都不是;
- 左值有的时候能够被当做右值使用;
int i = 10;
i = i + 1; //i是个左值,不是右值,虽然它出现等号右边
//i用在等号右边的时候,i有一种右值属性;不是右值;
//i用在等号左边的时候,用的是i代表的内存中的地址,i有一种左值属性;
//一个左值,他可能给同时具有左值属性和右值属性
用到左值的运算符有哪些:
-
赋值运算符
int a; cout << a = 4 << endl; (a = 4) = 8; //整个赋值语句的结果是左值;
-
取地址 &
int a = 5; //变量a是个左值 &a; //
-
容器中的下标/迭代器
string str = "hello world!"; str[0]; vector<int>::iterator iter; iter++; iter--;
-
通过判断一个运算符在一个字面值上能不能操作,可以判断运算符是否用到的是左值;
左值表达式、右值表达式
- 左值表达式当成左值;左值代表一个地址,所以左值表达式的求值结果,就得是一个对象,就得有地址;求值结果为对象的表达式,不代表一定是左值,具体再分析;
- 右值表达式当成右值;
总结:
- 左值,赋值运算符等号左边的东西;代表一个地址;
- 右值,代表赋值运算符右边的东西;代表一个值;
引用分类
三种形式的引用
-
左值引用(绑定到左值)
int value = 10; int &ref_val = value; ref_val = 13; // value = 13;
-
const引用(常量引用),也是左值引用,不希望改变值的对象;
int value = 10; const int &ref_val = value; //ref_val = 18; 不可修改
-
右值引用,绑定到右值:是个引用,主要用于临时对象;
int &&ref_right_value = 3; //绑定到一个值上 ref_right_value = 5;
左值引用
将变量绑定到左值上;
- 没有空引用的说法,所以左值引用初始化的时候就绑定左值;
- 引用必须初始化
- 左值引用必须绑定到左值,不能绑定到右值
- const引用可以绑定到右值;相当于系统产生一个临时变量;将常引用绑定到临时变量上;
int a = 1; int &b{a}; //b绑定到a //int &c; 错误,引用必须要初始化 //int &c = 1; 错误,左值引用必须绑定到左值,不能绑定到右值 const int &c = 1; //const引用可以绑定到右值 //相当于系统产生一个临时变量,将c绑定到临时变量中;
右值引用
引用右值,也就是说,绑定到右值;必须是绑定到右值的引用;通过&&
-
&&右值引用,用来绑定一些即将销毁的或者是一些临时对象上;
-
右值引用也是引用,可以把右值引用理解成一个对象的名字;
int &&ref_right_value = 3; ref_right_value = 5;
-
能绑定到左值引用上的,一般都不能绑定到右值;
-
右值引用也绑定不到左值上;
string str_te{"Hello world"}; string &ref_str_1{str_te}; //可以,左值引用绑定到左值; //string &ref_str_2{"Hello world"}; 不可以,左值引用不能绑定到临时变量; 临时变量被系统当做右值 const string *const_ref_str{"hello world"}; //可以,创建一个临时对象,绑定到左值上,const不仅可以绑定到右值,还可以执行到string的隐式类型转换并将所得到的的值放到string临时变量中 //string &&ref_right_1{str_te}; //右值引用不能绑定到一个左值; string &&ref_right_2{"hello world"}; //可以,绑定到一个临时变量,临时变量的内容“hello world” int i = 10; int &ri_1 = i; //正确,左值引用 int &&ri_2 = i; //错误,不能将右值引用绑定到左值上; int &&ri_3 = i * 100; //正确,右值引用绑定到右值 int &r4 = i * 100; //左值引用不能绑定到右值 const int &r5 = i * 100; //const引用可以绑定到右值;
总结
- 返回左值引用的函数,连同赋值,下标,解引用和前置递增递减运算符,都是左值表达式的例子;可以将一个左值引用绑定到这类表达式上;
- 返回非引用类型的函数,连同算术,关系,位以及后置递增运算符,都生成右值;不能将一个左值引用绑定到这类表达式上,但是可以将一个const的左值引用或者一个右值引用绑定到这类表达式上;
前置++和后置++的区别
-
前置++
++i; //左值表达式,++i直接给变量i+1,然后返回i本身 //因为i是变量,所以可以被赋值; int i = 100; (++i) = 199; //i = 199; int &r1 = ++i; //成功绑定左值 r3就是i的别名;
-
后置++
i++; //右值表达式,i++先产生一个临时变量,记录i的值用于使用的目的,i的值被使用之后,再给i+1,接着返回这个临时变量 //(i++) = 199; //错误 右值无法赋值; int i = 1; int &&ref = i++; //成功绑定右值; 此后ref的值和i没有关系 // int &r2 = i++; //左值引用不能绑定到右值表达式上
重点
-
ref虽然是右值引用(绑定到了右值),但是ref本身是左值(要把ref看成是一个变量);因为它在等号左边;并且左值引用可以绑定到ref这个左值上;
-
所有变量看成左值,因为他们有地址,用右值引用无法绑定;
-
任何函数中的形参都是左值;
void func(int &&v); //w是右值引用,但w本身是左值;
-
临时对象都是右值;
右值引用的引入目的
- C++11引入的,&&,代表一种新数据类型;
- 通过将拷贝对象变成移动对象来提高程序运行效率;
- 移动对象如何发生:
- 移动拷贝构造函数
- 移动赋值运算符
- 参数类型是右值引用
- 拷贝对象如何发生
- 拷贝构造函数
- 拷贝赋值运算符
- 参数类型是左值引用
std::move函数
- C++11标准库的新函数;std::move:移动;实际函数没有做移动操作;
- std::move的作用:将一个左值强制转换成一个右值;右值引用可以绑定上去了;
int i = 10;
int &&ref = std::move(i);
i = 20; //i的值改变,ref的值也变量; ref代表i;
int &&ri = 100; //ri是左值
int &&re = std::move(ri);
//string的移动构造函数做了哪些事情
// 1. 将str清空; 2. 给def分配内存 3.将str的内容移动到def
string str = "hello world";
const char* q = str.c_str(); //q是str实际所占内存
string def = std::move(str); //=会调用string的移动构造函数,导致str中的内容移动到def中
const char* p = def.c_str(); //p是def实际所占内存
string str = "hello world";
std::move(str); //单独执行 str内容没空,只是将str转成右值;
string str = "hello world";
string &&def_r = std::move(str); //def_r右值引用 绑定到str上;
//建议:使用了std::move之后,不要用str了;
原文链接: https://www.cnblogs.com/NaughtyCoder/p/13343862.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/368682
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!