实例代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
//一:左值和右值
int i0 = 20; //对象:一块内存区域;
i0 = 20;
//左值: “能用在赋值语句等号左侧的东西”,它能够代表一个地址。
//右值: “不能作为左值的值就是右值”;右值不能出现在赋值语句中等号的左侧。
//结论:
//C++ 的一条表达式,要么就是右值,要么就是左值,不可能两者都不是。
//左值有的时候能够被当做右值使用。
i0 = i0 + 1; // i0是个左值,不是个右值,虽然它出现在了等号右边。
// i0用在等号右边的时候,我们说i有一种右值属性(不是右值)。
// i0出现在等号左边,用的是i代表的内存中的地址, 我们说i 有一种左值属性。
//一个左值 它可能同时具有左值属性和右值属性。
//用到左值的运算符有哪些:
//a) 赋值运算符
//int a;
//prinf("%d\n", a = 4); // 4
//整个赋值语句的结果仍然是左值。
//(a = 4) = 8;
//b) 取地址 &
int a = 5; // 变量就是左值
&a; //&8;
//c) string vector 下标[]都需要左值。迭代器。
string abc = "I Love China!";
vector<int>::iterator iter;
//...
iter++; iter--; // 9--;
abc[0];
//d) 通过看一个运算符在一个字面值上能不能操作,我们就可以判断运算符是否用到的是左值。
//i0++; 5++;
//左值表达式 就是左值, 右值表达式 说的就是右值。
//左值: 代表一个地址,所以左值表达式的求值结果,就得是一个对象,就得有地址。
// 求值结果为对象的表达式,不代表 一定是左值,具体在分析。
//100 :它显然是个右值。
//二:引用分类
int value = 10;
int &refval = value;
refval = 13; // value = 13;
//三种形式的引用
//(1) 左值引用(绑定到左值);
//(2) const引用(常量引用),也是左值引用。我们不希望改变值的对象。
const int &refval2 = value;
//refval2 = 18;
//(3) 右值引用(绑定到右值):它是个引用。
int &&refrightvalue = 3; // 绑定到一个常量。
refrightvalue = 5;
//三:左值引用
//引用左值,绑定到左值上。
char *p = nullptr; //指针有空指针的说法。
//没有空引用的说法,所以左值引用初始化的时候就绑定左值
int a1 = 1;
int &b{ a1 }; // b绑定到a
//int &c; //错误,引用必须要初始化
//int &c = 1; //不可以,左值引用不能绑定到右值,必须绑定到左值。
const int &c = 1; // const 引用可以绑定到右值,所以const引用特殊。
int tempvalue = 1; //临时变量
const int &d = tempvalue;
//四:右值引用: 就是引用右值,也就是说,绑定到右值。
//必须是绑定到右值的引用。
//&& ,希望用右值引用来绑定一些即将销毁的或者是一些临时的对象上。
//右值 引用 也是 引用。
int &&refrightvalue1 = 3; //右值引用大家理解成一个对象的名字。
refrightvalue1 = 5;
//int value11 = 10;
//int &refval11 = 5;
//能绑定到左值上的引用,一般都不能绑定到右值。
//int value22 = 5;
//int &&refrightvalue = value22; //右值引用也绑不到左值上。
string strtest{ "I Love China!" };
string &r1{ strtest }; //可以,左值引用绑定左值
//string &r2{ "I love China" }; //不可以,左值引用不能绑定到临时变量。临时变量被系统当做右值
const string &r3{ "I love China" }; //可以,创建个临时变量,绑定到左值r3上去。
//const引用不但可以绑定到右值,还可以执行到string的隐式类型转换并将所得到的值放到string临时变量中;
//string && r4 {strtest}; //不可以,右值引用不能绑定到左值
string &&r5{ "I love China" }; //可以,绑定到一个临时变量,临时变量的内容 "I love China"
int i = 10;
int &r6 = i; //正确 左值引用
//int &&r7 = i; //不可以,不能将右值引用绑定到左值上。
int &&r8 = i * 100; //可以,右值引用绑定到右值
//int &r9 = i * 100;// 左值引用不能绑定到右值。
const int &r10 = i * 100; //绑定到右值
int &&r11 = i * 100; //正确。右值引用绑定到右值
//总结:返回左值引用的函数,连同赋值,下标,解引用和前置递增减运算符(--i),都是返回左值表达式的例子;我们可以将一个左值引用绑定到这类表达式的结果上。
//返回非引用类型的函数,连同算术,关系,位以及后置递增运算符(i--),都生成右值,不能将一个左值引用绑定这类表达式上,
//但是我们可以将以const的左值引用或者一个右值引用绑定到这类表达式上。
//++i; //左值表达式。++i直接给变量 i+1, 然后返回i本身
//因为i是变量,所以可以被赋值。
int i1 = 100;
(++i1) = 199; // i 被赋值成199了。
//i++ : 先用后加,为什么就是右值表达式呢?
//i++ 先产生一个临时变量 tempi, 记录i(tempi = i) 的值用于使用的目的,再给i+1, 接着返回这个临时变量,临时变量这个东西,右值
//(i++) = 199; //语法错误
int i2 = 1;
int &&r12 = i++; //成功绑定右值,但是此后r12的值和i没有关系。
//i += 5;
//int &r13 = i++; // 不可以,不能绑定到右值表达式上去
int &r14 = ++i; //r14绑到i , r14就变成i的别名了。
i += 5;
//int &&r15 = ++i; // 不可以,右值不能绑定左值表达式
//重点强调:
//(1) r12虽然是右值引用(绑定到了右值),但是r12本身它是左值(你要把r12看成一个变量)。因为它在=左边呆着
int &r16 = r12; // r12是左值。
//int &&r17 = r12;
//(2) 所有变量,看成左值,因为他们是有地址的。而且你用右值引用也绑定不上。
//int &&r11 = i; //失败。
//(3) 任何函数里边的形参都是左值,void f(int i , int &&w) : w是右值引用,但w本身是左值。
//(4) 临时对象都是右值。下节详细讲解临时对象。
//(4.1) 右值引用的引入目的
//a) C++11 引入,&&,代表一种新数据类型,引入新数据类型肯定有目的。
//b) 提高程序运行效率。把拷贝对象变成移动对象来提高程序运行效率。
//c) 移动对象如何发生。&&(应付移动构造函数, 应付移动赋值运算符用的) &
//五: std::move函数
//C++ 11标准库里的新函数。
//std::move :移动。实际上这个函数根本就没有做移动的操作。
//std::move 的能力只有一个:把一个左值强制转换成一个右值。带来的结果就是我一个右值可以绑上去了;
int i10 = 10;
//int &&ri20 = i10; //不可以
int &&ri20 = std::move(i10); // 可以, 把一个左值转成一个右值,这就是move的能力;
i10 = 20;
ri20 = 15; //ri20就代表i10了。
int &&ri30 = 100;
//int &&ri31 = ri30;
int &&ri31 = std::move(ri30); //绑定
ri30 = 68;
ri31 = 46;
string st = "I Love China!";
const char *p1 = st.c_str();
string def = std::move(st); //string里的移动构造函数把st的内容移动到了def中去了。而不是std::move干的;
const char *q = def.c_str();
std::move(st);
string &&rdef = std::move(st);
st = "abd";
rdef = "ffghshg";
//六: 左值右值总结说明
int &&ri32 = 67;
system("pause");
return 0;
}