左值、右值和move语义


参考教程
lvalues and rvalues in C++
Move Semantics in C++

左值、右值

左值:可以获取地址的表达式,因为常常被放在等号左侧来被赋值所以叫左值

右值:通常为临时值,可以是常量值、函数返回值、lambda表达式等。(因为是临时值,没有被分配具体地址,所以有些地方对右值的定义是无法获取地址 )

int GetValue() {
	return 10;	//返回的是临时的常量,所以属于右值
}
int& GetValue2() {
	int a = 10;
	return a;	//返回的是地址,属于左值
}

int main()
{
	//1.
    //等号左侧必须是左值,等号右侧可以为左值也可以为右值
    int i = 10;			//i为左值,10为右值
    int j = i;			//i为左值,也可以放在等号右
    
    j = GetValue();		//GetValue()返回的是常数,属于右值
    /*错误案例
	10 = i;				//10是右值,不能放在等号左侧,错误
	GetValue() = j;		//同上
	*/
	GetValue2() = j;	//GetValue2()属于左值,不会报错

    return 0;
}

左值引用

传统c++的引用就指左值引用。例如 int &a = i;

左值引用特性: 不能把一个右值赋值给左值引用,(毕竟&是取址作用,不能从一个没有地址的临时变量中取址)但是可以在左值引用前加const 修饰,来在右值中获取: const int &a = 1;(可以理解为因为是const ,所以直接给该临时值分配一个地址,然后赋值给&a)

void SetValue2(int& value){
}		//需要一个左值引用参数
void SetValue(const int& value) {
}		//加const后就可以用右值传参了

int main(){
	int i = 1;
	SetValue2(i);
	/*错误案例
	SetValue2(2);	//常量2 属于右值,不能转换为左值引用 
	*/
	SetValue(2);
	return 0;
}

右值引用

C++11引入的新概念,用&&表示,例如int &&a = 1;
右值引用特性: 不能把一个左值赋值给右值引用。

void PrintName(std::string&& name) {	//需要一个右值引用参数
	std::cout << name << std::endl;
}

int main(){
	std::string firstName = "CSSSS";
	std::string lastName = "DNNNNN";				//这里firstName 和 lastName都是左值
	std::string fullName = firstName + lastName;	//firstName 和 lastName相加的时候用临时变量存储相加的结果,属于右值
	
	//PrintName(fullName); 错误,不能用左值传给右值引用
	PrintName(firstName + lastName);
	
	return 0;
}

  明明可以用 const &name 做到两个都兼容,为什么要用 &&name 呢 ?左值引用和右值引用分开有什么用呢,我们回到上面PrintName(std::string&& name)这个案例中,可以这样写

void PrintName(const std::string& name) {		//处理左值
	std::cout << "[左值] " << name << std::endl;
}

void PrintName(std::string&& name) {			//处理右值,即临时值
	std::cout << "[右值] " << name << std::endl;
}

int main()
{
	std::string firstName = "CSSSS";
	std::string lastName = "DNNNNN";
	std::string fullName = firstName + lastName;	//firstName 和 lastName相加的时候用临时变量存储相加的结果,属于右值
	
	PrintName(fullName);				//[左值] CSSSSDNNNNN
	PrintName(firstName + lastName);	//[右值] CSSSSDNNNNN
	return 0;
}

我们可以用重载来分别出来左值引用和右值引用,也就是说判断该参数是不是临时值来进行分别处理。(注意第一个函数我们加了const 关键字,但C++依然可以识别到是右值引用并调用第二个函数)

左值右值最常见的作用,要属移动构造。

move语义

#include<iostream>

class String
{
public:
	String() = default;
	String(const char* string)
	{
		printf("Created!\n");
		m_Size = strlen(string);
		m_Data = new char[m_Size];
		memcpy(m_Data, string, m_Size);
	}

	String(const String& other)
	{
		printf("Copy!\n");
		m_Size = other.m_Size;
		m_Data = new char[m_Size];
		memcpy(m_Data, other.m_Data, m_Size);
	}
	~String() {
		printf("Destroy!\n");
		delete[] m_Data;
	}

	void Print()
	{
		for (uint32_t i = 0;i < m_Size;i++)
			printf("%c", m_Data[i]);
		printf("\n");
	}
private:
	char* m_Data;
	uint32_t m_Size;
};


class Entity
{
public:
	Entity(const String& name) 
		:m_Name(name)
	{
	}

	void PrintName()
	{
		m_Name.Print();
	}

private:
	String m_Name;
};
int main()
{
	Entity entity(String("Cherno"));
	entity.PrintName();

	system("pause");
	return 0;
}

运行这段代码,得到的结果是
在这里插入图片描述
  这里有一个问题,我们要生成一个entity的实体,但是构造了一次String,又拷贝构造了一次,这里一共分配空间、memcpy()了两次,挺多余的,用 移动构造就可以优化这个问题。

加入这些代码

class String
{
 ...
 	String(String&& other) noexcept	//移动构造
	{
		printf("Moved!\n");
		m_Size = other.m_Size;		
		m_Data = other.m_Data;

		other.m_Size = 0;			
		other.m_Data = nullptr;		//注意这里要把之前的实体清除,防止析构时影响到新的实体
	} 
...
};

class Entity
{
	...
 		Entity(String&& name)
			:m_Name((String&&)name)	//显式转换成右值引用,来调用移动构造
		{
		}
	...
};

在这里插入图片描述
调用了一次构造函数,一次移动构造函数,而移动构造函数又是性能消耗很小的浅拷贝,所以说优于之前的那种方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值