【C++11】左值引用 与 右值引用

定义

左值 / 左值引用

左值(Lvalue): 左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以 对它取地址 + 可以对它赋值左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

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

例子:

// 变量 a, b、指针变量 p 和解引用表达式 *p 都是左值
	int* p = new int(0);
	int a = 1;
	const int b = 2;

// 下面是对上述左值的引用
	int*& rp = p;
	int& ra = a;
	const int& rb = b;
	int& pValue = *p;

右值 / 右值引用

右值(Lvalue):右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址

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

double x = 1.0, y = 2.5;
// 几种常见的右值
10;
x + y;
fmax(x, y);
// cout << &(x + y) << endl; //对右值不能进行取地址

//以下是对上述右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmax(x, y);

左值引用 / 右值引用 特点比较

左值引用:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值
// 左值引用只能引用左值,不能引用右值
	int a = 10;
	int& ra1 = a;
	int& ra2 = 10; //编译失败,10是右值

// const左值引用既可以引用左值,也可以引用右值
	const int& ra3 = a;
	const int& ra4 = 10; // 不报错

右值引用:

  1. 右值引用只能右值,不能引用左值。
  2. 但是右值引用可以move以后的左值。
// 右值引用只能引用右值,不能引用左值
	int&& r1 = 10;

	int a = 10;
	int&& r2 = a; //编译失败: 无法将右值引用绑定到左值

// 右值引用可以引用 move 后的左值
	int&& r3 = std::move(a);

右值引用的应用场景 和 意义

上文了解到 const左值引用 可以引用右值,既然如此,右值引用的意义是什么?
实际上,左值引用有一定的短板,而右值引用可以弥补该缺陷:

首先看下面的代码:

namespace aiyimu
{
	class string
	{
	public:
		// 构造函数
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			::swap(_str, s._str); //std::swap -> ::swap
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) <---> 拷贝构造(深拷贝)" << endl;
			// 拷贝操作
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		// 移动构造
		// 以右值引用作参数
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) <---> 资源转移" << endl;
			swap(s);
		}
		
		// 拷贝赋值运算符
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) <---> 拷贝赋值(深拷贝)" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		// 移动赋值运算符
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) <---> 移动赋值(资源移动)" << endl;
			swap(s);

			return *this;
		}		

		// 析构
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

移动构造函数

定义:

移动构造(Move Constructor)是一种特殊的构造函数,它通过接收一个右值引用参数来创建新对象,并从传入的对象中“移动”资源而不是执行深拷贝。

详解:

我们在代码实现中 写了移动构造函数,与右值引用有一定的联系,详解在这里:

移动构造函数


引用构造 的博客中有具体写到:

  • 由于我们实现的 拷贝构造 的参数是const 类型,所以既可以进行左值引用也可以进行右值引用
  • 当存在移动构造时,传入右值优先调用移动构造,否则构造此时的拷贝构造。
  • 对上面的代码,to_string 的 参数是 右值,调用移动构造,但我们讲两种构造都作讨论。

认识了移动构造函数后,对下面的代码:

aiyimu::string to_string(int value)
{
	aiyimu::string str;

	// to_string的内容并不重要 
	// ... ... 这里省略
	
	return str;
}

int main()
{
	aiyimu::string ret("114");
	ret = to_string(514);
	return 0;
}

当执行的是拷贝构造+拷贝赋值时:

在这里插入图片描述

可以看出拷贝构造时 进行了两次深拷贝

而当执行的是移动构造+移动赋值时:

在这里插入图片描述

此时的右值引用 避免了两次深拷贝,避免了空间浪费。

可以看出 右值引用 配合 移动构造和移动赋值 的作用。


万能引用(引用折叠)

万能引用(Universal Reference)是由C++中的std::forward和模板推导机制引入的概念。它是一种特殊类型的引用,可以接受各种类型的参数(包括左值、右值)。

定义

首先,在C++中,几乎所有的标准 容器类都支持万能引用 的用法。

  1. 如果传递给万能引用的是一个左值,那么万能引用将被推导为左值引用。这意味着它将保留传递给它的参数的左值特性,并且不能绑定到临时对象或右值。

例如:

int x = 42;
int& lvalueRef = x;       // 左值引用
foo(lvalueRef);           // T 被推导为 int&
  1. 如果传递给万能引用的是一个右值,那么万能引用将被推导为普通的右值引用。这意味着它可以绑定到临时对象、右值或将对象视为右值的情况下。

例如:

int&& rvalueRef = 100;    // 普通的右值引用
foo(std::move(rvalueRef)); // T 被推导为 int&&

请注意,万能引用只能 通过模板来声明 。在函数模板中使用万能引用的时候,根据传递的参数类型和值类别,编译器会进行相应的类型推导。


在这里插入图片描述
在这里插入图片描述

查阅发现,C++11 在list类中添加了万能引用的实现。

声明

万能引用的格式是使用&&语法来声明,它的一般形式为:

template<typename T>
void foo(T&& arg) {
    // 根据 arg 的值类别进行不同的处理
    if constexpr (std::is_lvalue_reference_v<T>) {
        // 处理左值
        std::cout << "左值引用" << std::endl;
    } else {
        // 处理右值
        std::cout << "右值引用" << std::endl;
    }
}

用法

下面的代码展示万能引用的用法:

// 万能引用
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)
{
	// 完美转发: 保持t引用对象属性
	Func(std::forward<T>(t));
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	aiyimu::list<aiyimu::string> lt;
	aiyimu::string s1("hello");
	lt.push_back(s1);

	cout << endl;

	//lt.push_back(aiyimu::string("world"));
	lt.push_back("world");

	return 0;
}

在这里插入图片描述
根据执行结果,可以验证万能引用的作用。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值