当前位置: 首页 > article >正文

左值引用与右值引用详解

一、左值和右值

1.左值

左值是一个表示数据的表达式,比如:变量名、解引用的指针变量

一般地,我们可以获取它的地址对它赋值,  定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。

总体而言,可以取地址的对象就是左值。

int main()
 {
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;

 }

2.右值

右值也是一个表示数据的表达式,比如:字面常量、表达式返回值,传值返回函数的返回值(是传值返回,而非传引用返回)右值不能出现在赋值符号的左边且不能取地址

右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。右值引用就是对右值的引用,给右值取别名

总体而言,不可以取地址的对象就是右值。

int main()
{
     double x = 1.1, y = 2.2;

     // 以下几个都是常见的右值
     10;
     x + y;
     fmin(x, y);
}

二、左值引用和右值引用

左值引用与右值引用是C++11中出现的新语法 , 无论左值引用还是右值引用,都是给对象取别名。

1.左值引用

左值引用就是对左值的引用,给左值取别名。

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;
}

2.右值引用

右值引用就是对右值的引用,给右值取别名。

右值引用的表示是在具体的变量类型名称后加两个 &,比如:int&& rr = x+y;。 

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);

	// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1;
	x + y = 1;
	fmin(x, y) = 1;
	return 0;
}

三、左值引用与右值引用对比

左值引用:

左值引用只能引用左值,不能引用右值

但const修饰的左值引用可以引用左值, 也可以引用右值.

这个在C++98中解释为, 右值是临时对象, 临时对象具有常性, 因此要引用要加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;
}

move(x):将x从左值强制转化为右值,通过返回值的方式输出

注意:右值引用变量是一个左值, 可以被取地址与修改, 但const修饰的右值引用变量不能被修改 

 四、使用场景及实际意义

与C++98不同的是C++11出现了右值引用, 要了解右值引用的引用就要先了解左值引用有什么意义.

左值引用的意义:

传值传参和传值返回都会产生拷贝,有的甚至是深拷贝,代价很大。而左值引用的实际意义在于做参数和做返回值都可以减少拷贝,从而提高效率。

int test1()
{
	int n = 0;
	n++;
	return n;
}
int& test2()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret1 = test1();
	int ret2 = test2();
	return 0;
}

在上面的代码中,出现了test1和test2两个函数, test是传值返回, 而test2是传引用返回, 在传引用返回中就出现了左值引用返回. 

但也出现了许多问题:如

当对象出了函数作用域以后仍然存在时,可以使用左值引用返回,这是没问题的。

但当对象(对象是函数内的局部对象)出了函数作用域以后不存在时,就不可以使用左值引用返回了。此时只能使用传值返回. 

>C++11就出现了右值引用

右值引用的意义

于是,为了解决上述传值返回的拷贝问题,C++11标准就增加了右值引用移动语义

1.移动语义

(1)移动构造

下面就是一个移动构造的例子:

string(string&& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	swap(s);
}

但要注意的是:

在进行交换的时候,如果交换的对象是一个move标记的对象,就可以使交换的对象改变.如:

string str;
string str1=str;
string str2=move(str);

对于str1对象调用拷贝构造函数, 对于str2调用移动构造函数, 此时str2将交换str的资源内容.

A

拷贝构造函数和移动构造函数都是构造函数的重载函数,所不同的是:

  1. 拷贝构造函数的参数是 const左值引用,接收左值或右值;
  2. 移动构造函数的参数是右值引用,接收右值或被 move 的左值。

在存在移动构造函数的时候, 如果传来的参数是一个右值, 就会自动匹配移动构造函数.

因此:

若是左值做参数,那么就会调用拷贝构造函数,做一次拷贝(如果是像 string 这样的在堆空间上存在资源的类,那么每调用一次拷贝构造就会做一次深拷贝)。


若是右值做参数,那么就会调用移动构造,而调用移动构造就会减少拷贝(如果是像 string 这样的在堆空间上存在资源的类,那么每调用一次移动构造就会少做一次深拷贝)。

只有拷贝构造没有移动构造: 

既有拷贝构造也有移动构造:

因为函数中的 str 是将亡值(右值) ,在构造的时候直接调用移动拷贝

但现在的编译器一般都会进行优化:

因为临时对象有 ret 来接收,先拷贝构造出临时对象再用它移动构造出 ret ,临时对象好像没必要产生一样,不如省略掉。既然 str 是 to_string 函数栈帧的局部对象,最后还是要销毁,不如将 str 视为右值,直接转移 str 的资源用来构造 ret .

只有拷贝构造没有移动构造:

既有拷贝构造也有移动构造: 

 此外,C++11标准的STL 容器的相关接口函数也增加了右值引用版本

3.完美转发

在此之前我们需要知道什么是万能引用:

确定类型的 && 表示右值引用(比如:int&& ,string&&),
但函数模板中的 && 不表示右值引用,而是万能引用,模板类型必须通过推断才能确定,其接收左值后会被推导为左值引用,接收右值后会被推导为右值引用

注意区分右值引用和万能引用:下面的函数的 T&& 并不是万能引用,因为类的实例化的时候就确定了 T 的类型.

template<typename T>
class A
{
	void func(T&& t);  // 模板实例化时T的类型已经确定,调用函数时T是一个确定类型,所以这里是右值引用
};
template<typename T>
void f(T&& t)  // 万能引用
{
	//...
}

int main()
{
	int a = 5;  // 左值
	f(a);  // 传参后万能引用被推导为左值引用
	
	const string s("hello");  // const左值
	f(s);  // 传参后万能引用被推导为const左值引用
	
	f(to_string(1234));  // to_string函数会返回一个string临时对象,是右值,传参后万能引用被推导为右值引用

	const double d = 1.1;
	f(std::move(d));  // const左值被move后变成const右值,传参后万能引用被推导为const右值引用
	
	return 0;
}

在上文中提到, 右值引用的变量是左值,因此右值属性在函数传递中可能被改变

因而出现C++的完美转化

(2)概念

完美转发是指在函数模板中,完全依照模板的参数类型,将参数传递给当前函数模板中的另外一个函数。

因此,为了实现完美转发,除了使用万能引用之外,我们还要用到std::forward(C++11),它在传参的过程中保留对象的原生类型属性

void Func(int& x) { cout << "左值引用" << endl; }

void Func(const int& x) { cout << "const左值引用" << endl; }

void Func(int&& x) { cout << "右值引用" << endl; }

void Func(const int&& x) { cout << "const右值引用" << endl; }


template<typename T>
void PerfectForward(T&& t)  // 万能引用
{
	Func(std::forward<T>(t));  // 根据参数t的类型去匹配合适的重载函数
}

int main()
{
	int a = 4;  // 左值
	PerfectForward(a);

	const int b = 8;  // const左值
	PerfectForward(b);

	PerfectForward(10); // 10是右值

	const int c = 13;
	PerfectForward(std::move(c));  // const左值被move后变成const右值

	return 0;
}

实现完美转发需要用到万能引用和 std::forward 。

例子

#include<iostream>
using namespace std;

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};


template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}

	void PushBack(const T& x)  // 左值引用
	{
		Insert(_head, x);
	}

	void PushFront(const T& x)  // 左值引用
	{
		Insert(_head->_next, x);
	}

	void PushBack(T&& x)  // 右值引用
	{
		Insert(_head, forward<T>(x));  // 关键位置:保留对象的原生类型属性
	}

	void PushFront(T&& x)  // 右值引用
	{
		Insert(_head->_next, forward<T>(x));  // 关键位置:保留对象的原生类型属性
	}

	template<class TPL>    // 该函数模板实现了完美转发
	void Insert(Node* pos, TPL&& x)  // 万能引用
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = forward<TPL>(x);  // 关键位置:保留对象的原生类型属性

		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}

private:
	Node* _head;
};

--------------------------------------------------------------------------------------------------------------------------------

 本文的讲解到此结束,谢谢大家的观看,有问题欢迎给我留评论。


http://www.kler.cn/a/564472.html

相关文章:

  • Autosar_RTE基础概念整理
  • 为AI聊天工具添加一个知识系统 之125 详细设计之66 智能语义网络
  • leetcode 912. 排序数组
  • 需求: 使用 minio 做一个 企业微信对话的下载、存储,利用deepseek进行对话回复
  • 在VSCode中安装jupyter跑.ipynb格式文件
  • PhpStorm 绿色版 安装包 Win/Mac/Linux 商业的PHP集成开发环境 2025全栈开发终极指南:从零配置到企业级实战
  • 腾讯云的海外轻量云套餐
  • 【学写LibreCAD】3 qmetaobject库介绍
  • Pytorch加载数据的Dateset类和DataLoader类
  • .hive-staging_hive临时文件处理
  • 【人工智能】数据挖掘与应用题库(201-300)
  • Python连接SQL SEVER数据库全流程
  • python 网络安全常用库 python做网络安全
  • Cookie与Session:Web开发中的状态管理机制
  • 怎么进行稀疏矩阵转化
  • fd,重定向与缓冲区
  • React + TypeScript 实现数据模型驱动 SQL 脚本生成
  • 阿里开源正式开园文生视频、图生视频模型-通义万相 WanX2.1
  • 使用AGM迭代公式和高精度数学计算开源库gmp计算圆周率小数点后1000位
  • SpringBoot整合SpringSecurity、MyBatis-Plus综合实例:认证、授权