26 C++ 左值 ,右值,左值引用,右值引用,move函数将左值变成右值。

左值

具有以下特征:

  • 可通过取地址运算符获取其地址
  • 可修改的左值可用作内建赋值和内建符合赋值运算符的左操作数
  • 可以用来初始化左值引用(后面有讲)

那么哪些都是左值呢?查了相关资料,做了些汇总,基本覆盖了所有的类型:

  • 变量名、函数名以及数据成员名
  • 返回左值引用的函数调用
  • 由赋值运算符或复合赋值运算符连接的表达式,如(a=b, a-=b等)
  • 解引用表达式*ptr
  • 前置自增和自减表达式(++a, ++b)
  • 成员访问(点)运算符的结果
  • 由指针访问成员( -> )运算符的结果
  • 下标运算符的结果([])
  • 字符串字面值("abc")
class Teacher72{
public:
	Teacher72() {

	};
	Teacher72(int age):mage(age) {

	};
	int mage;

	int getAge() {
		return mage;
	}
};

int & func72() {
	int a = 90;
	return a;
}
int func73() {
	return 0;
}
void main() {
	//
	cout << "左值" << endl;
	//变量名、数据成员名
	int a = 1;

	typedef int(*FUNCTYPE)();// 定义一种类型,函数指针
	FUNCTYPE  aa = func73;

	//返回左值引用的函数调用
	func72() = 90;

	//由赋值运算符或复合赋值运算符连接的表达式,如(a = b, a -= b等)
	int a1 = 89;
	int b1 = 987;
	a1 = b1 = 888;
	cout << "a1 = " << a1 << " b1 = " << b1 << endl;
	a1 += b1;
	cout << "a1 = " << a1 << " b1 = " << b1 << endl;

	//解引用表达式*ptr
	int *p1 = &a1;
	*p1 = b1;
	cout << "*p1 = " << *p1  << endl;

	//前置自增和自减表达式(++a, ++b)
	int a2 = 8;
	++a2 = 8989898;// ++a 的理解是:a2 = a2+1; 然后将a2 return 出去,然后给a2 = 8989898,因为是将a return出去的,因此可以给a赋值
	cout << "a2 = " << a2 << endl;

	//成员访问(点)运算符的结果
	Teacher72 tea;
	tea.mage = 88;

	//由指针访问成员(->)运算符的结果
	Teacher72 * ptea = &tea;
	ptea->mage = 909;

	//下标运算符的结果([])
	int arr[3] = { 10,20,30 };
	arr[0] = 909090;
	cout << "范围for语句 start" << endl;
	for(auto in:arr)
	{
		cout << in << endl;
	}
	cout << "范围for语句 end" << endl;

//字符串字面值("abc"), 为什么字符串字面值 可以做为左值呢?
	"abc" ;//这个是个左值,怎么理解呢?
	//字符串字面值为左值,例如:hello, world,
	//因为字符串字面值在内存中就是按字符数组保存的,有实实在在的地址。
	const char ss[6]  = "sssss"; //在内存中是按照const char []数组保存的,因此有实实在在的地址的
	//但是这个当做左值有啥用呢?左值引用的时候吗?
	//"abc" = 0X898987; build error
	//"abc" = "NMN"; build error

}

右值

纯右值

在前面有提过,自C++11开始,纯右值(pvalue, pure ravlue)相当于之前的右值,那么什么是纯右值呢?

  • 字面值或者函数返回的非引用都是纯右值。
  • 以下表达式的值都是纯右值:
  • 字面值(字符串字面值除外),例如1,'a', true等
  • 返回值为非引用的函数调用或操作符重载,例如:str.substr(1, 2), str1 + str2, or it++
  • 后置自增和自减表达式(a++, a--)
  • 算术表达式
  • 逻辑表达式
  • 比较表达式
  • 取地址表达式
  • lambda表达式

为了加深对右值的理解,下面的例子是常见的纯右值:

nullptr;
true;
1;
int fun();
fun();

int a = 1;
int b = 2;
a + b;

a++; 先给一个临时变量 int tempa = a; 将临时变量tempa return 出去,然后再给a = a+1;因为这时候return出去的是的tempa,是个临时变量,因此是个右值。
b--;

a > b;
a && b;

右值(rvalue, right value)

是指可以移动的表达式。prvalue和xvalue都是rvalue,具体的示例见下文。

rvalue具有以下特征:

  • 无法对rvalue进行取地址操作。例如:&1,&(a + b),这些表达式没有意义,也编译不过。
  • rvalue不能放在赋值或者组合赋值符号的左边,例如:3 = 5,3 += 5,这些表达式没有意义,也编译不过。
  • rvalue可以用来初始化const左值引用(见下文)。例如:const int& a = 1。
  • rvalue可以用来初始化右值引用(见下文)。
  • rvalue可以影响函数重载:当被用作函数实参且该函数有两种重载可用,其中之一接受右值引用的形参而另一个接受 const 的左值引用的形参时,右值将被绑定到右值引用的重载之上。

将要完蛋的值

临时变量

左值引用 

//左值引用,和const 左值引用
//等号右边需要是 左值
//左值引用 绑定左值后, 一荣俱荣,一损俱损。一改都改

void main() {

	//左值引用举例一
	int a = 10;
	int &b = a; // b是左值引用,a 是左值
	cout << "a = " << a << " b = " << b << endl;

	b = 80;
	cout << "a = " << a << " b = " << b << endl;
	
	///左值引用举例2
	std::string str1 = "abc";
	std::string &str2 = str1;

	str2 = "ccc";
	cout << "str1 = " << str2 << " str2 = " << str2 << endl;

	//const左值引用  举例,const 左值引用不允许改动值
	int c = 10;
	const int &d = c;
	cout << "c = " << c << " d = " << d << endl;//结果为10,10
	//d = 20; build error
	c = 90;
	cout << "c = " << c << " d = " << d << endl;//结果为90,90,一荣俱荣,一损俱损

}

右值引用,

C++11引入 “右值引用” 的目的是:提供程序效率

右值引用 用 && 表示,我们可以认为是一种新的类型。

如何提高程序效率:右值引用 会将 拷贝对象 变成 移动对象 处理

//右值引用,
//等号右边需要是 右值
//一旦绑定,一荣俱荣,一损俱损
//右值引用也是引用,目的是延长即将销毁变量的声明周期
//右值引用使用 && 表示

void main() {
	int &&aaa = 90;
	cout << "aaa = " << aaa<	int &&rrb3 = 898998; // 正确,898998为右值,rrb3为右值引用,但是rrb3本身是左值
	int &cccccc = rrb3;// 将左值rrb3 赋值给 cccccc这个左值引用上
	cout << cccccc << endl; //结果为898998

	cccccc = 9;
	cout << cccccc << "   " << rrb3<< endl; //结果为9 he  9 

std::move(T t)函数

该函数没有任何move的操作,作用是:把一个左值变成右值。目的是为了 移动构造函数做准本,下一章节会记录这个  移动构造函数

move(T t)执行后,t就不要使用,虽然在vs2017 即使使用了也没有问题,但是这个是不稳定的,随时有可能出现问题。

	//std::move(T t)函数
	//该函数没有任何move的操作,作用是:把一个左值变成右值。目的是为了 移动构造函数做准本,下一章节会记录这个  移动构造函数
	//move后,之前的参数不能再用,我们如下的例子还是用了 bb1,这是不对的,虽然结果是对的。

	int aa1 = 89;
	int &bb1 = aa1;//bb1是左值引用, aa1是左值
	int &&cc1 = move(bb1);//将bb1通过move函数变成了 右值引用

	cout << "aa1 = " << aa1 << "  bb1 = " << bb1 << "  cc1 = " << cc1 << endl;//结果都是89


	aa1 = 99;
	cout << "aa1 = " << aa1 << "  bb1 = " << bb1 << "  cc1 = " << cc1 << endl;//结果都是99


	bb1 = 109;//不要这么用,bb1 是左值引用,已经被move函数操作过了,因此不要用
	cout << "aa1 = " << aa1 << "  bb1 = " << bb1 << "  cc1 = " << cc1 << endl;//结果都是109

	cc1 = 119;
	cout << "aa1 = " << aa1 << "  bb1 = " << bb1 << "  cc1 = " << cc1 << endl;//结果都是119

	//move函数容易出现的误会。



	string  st1 = "dfe";
	string st2 = move(st1);//在move之后,st1中的字符串已经不存在了,
	//  看起来好像是move函数将st1中的字符串转移到st2中去了,实际上不是的,
	//	是string里的移动构造函数,把st中的内容转移到st2中了
	// 注意后,move(st1),将st1从左值变成右值,


	string st3 = "abc";
	string && st4 = move(st3);//将st3通过move转成右值,然后给 右值引用st4 上绑定st3,之后,st3和st4穿同一条裤子,一个改动,另一个改动。
	//注意的是, st3在move之后,不应该再次使用


	cout << "断点在这里" << endl;

}

你可能感兴趣的:(c++)