右值和左值引用

本文深入探讨了C++中左值引用与右值引用的区别及应用场景,包括它们在内存操作中的表现形式,并介绍了如何利用右值引用优化栈对象的复制过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

左值

引用

int main()
{
	int a = 1;
	int &b = a; // 定义一个左值引用变量b
	b = 2; // 通过左值引用修改引用内存的值
	return 0;
}

汇编指令如下:

	int a = 1;
0134851E  mov         dword ptr [a],1  // 这条mov指令把1放到a的内存中
	int &b = a; // 定义一个左值引用变量b
01348525  lea         eax,[a]          //把a的地址放在寄存器eax中
01348528  mov         dword ptr [b],eax  //把寄存器eax的内容放到b的内存中
	b = 2; // 通过左值引用修改引用内存的值
0134852B  mov         eax,dword ptr [b]  //把b内存中的值(a的地址)放到寄存器eax中
0134852E  mov         dword ptr [eax],2  //把2放到寄存器保存的地址的内存中

指针

int main()
{
	int a = 1;
	int *b =&a; // 定义指针b,指向a所在的内存
	*b = 2; // 通过指针解引用修改它所指向内存的值
	cout<<"a="<<a<<endl;
	return 0;
}

汇编指令如下:

	int a = 1;
0112851E  mov         dword ptr [a],1  
	int *b =&a; // 定义指针变量b
01128525  lea         eax,[a]  
01128528  mov         dword ptr [b],eax  
	*b = 2; // 通过指针解引用下修改它所指向内存的值
0112852B  mov         eax,dword ptr [b]  
0112852E  mov         dword ptr [eax],2  

可以看出,定义一个左值引用在汇编指令上和定义一个指针没有任何区别的

引用的初始化

int  &a=10;//  x

原因:定义引用变量,需要取右边10的地址进行存储,但是10是临时数字,没有在内存上存储,是无法取地址的。

解决方案

const  int &a=10;//用常引用可以引用10这个常量数字

汇编代码:

0133799E  mov         dword ptr [ebp-14h],0Ah  //把10放在临时变量的内存地址中
013379A5  lea         eax,[ebp-14h]            //把临时变量的地址放在寄存器中
013379A8  mov         dword ptr [a],eax        //把寄存器的值(临时变量的地址)存入a的内存地址中

原因:在内存上产生了一个临时量保存了10,a引用的是这个临时量,相当于下面的操作:

const int temp = 10; 
const int &b = temp;

存在的问题a被修饰为常引用,就只能读数据,不能修改数据。

解决方案右值引用

右值引用

	int && a=1;
//mov指令相当于是产生了临时量,起始地址ebp-14h
0125799E  mov         dword ptr [ebp-14h],1  

//把临时量的地址放入eax寄存器当中
012579A5  lea         eax,[ebp-14h]  

//再把eax的值(临时量的地址)放入b内存中(一个指针大小的内存)
012579A8  mov         dword ptr [a],eax  

可见,右值引用常引用实现是一样的。

但是,右值引用可以改变数据的值。

int && a=1;
	a=10;

总结

  • 有地址的用左值引用,没有地址的用右值引用
  • 有变量名字的用左值引用,没有变量名字的(比如临时量没有名字)用右值引用
  • int &&c=20;右值引用专门用来引用右值类型,指令上可以自动产生临时量,然后直接引用临时量
  • 右值引用本身是一个左值,只能用左值引用来引用它
  • 不能用一个右值引用变量来引用一个左值

C++11右值引用的应用

模拟一个栈

template<typename T>
class CStack
{
public:
	CStack(int size = 100000)
		:mtop(0)
	{
		cout << "CStack()" << endl;
		mpstack = new T[size];
	}
	~CStack()
	{
		cout << "~CStack()" << endl;
		delete[]mpstack;
	}
	CStack(const CStack<T> &src)
		:mtop(src.mtop)
	{
		cout << "CStack(const CStack<T> &src)" << endl;
		mpstack = new T[100000];
		for (int i = 0; i < mtop; ++i)
		{
			mpstack[i] = src.mpstack[i];
		}

	
	}

	CStack<T>& operator=(const CStack<T> &src)
	{
		cout << "operator=" << endl;
		if (this == &src)
			return *this;

		delete[]mpstack;

		mtop = src.mtop;
		mpstack = new T[100000];
		for (int i = 0; i < mtop; ++i)
		{
			mpstack[i] = src.mpstack[i];
		}
		return *this;
	}
	
	void push(const T &val)
	{
		mpstack[mtop++] = val;
	}
private:
	T *mpstack;
	int mtop;
};

CStack<int> GetStackObject()
{
	CStack<int> s;
	s.push(20);
	s.push(30);
	s.push(40);
	return s;
}


int main()
{
	CStack <int>s1;
	CStack <int>s2=GetStackObject();
	return 0; 
}


在GetStackObject()处,由于s是临时对象,出作用域会自动析构,只能通过拷贝构造s的临时对象取构造s2,但是构造完临时对象,s马上就析构了!!!

敲重点?为什么不能把s持有的内存资源直接给临时对象呢?非得给临时对象重新开辟内存拷贝一份s的数据,然后s的资源又没有什么用处,而且马上就要析构,这样只能造成代码运行效率低下。

C++11中的解决方式是提供带右值引用参数的拷贝构造函数和operator=赋值重载函数。

提供带右值引用参数的拷贝构造函数(转移构造函数)

	CStack(CStack<T> &&src)
		:mtop(src.mtop)
	{
		cout << "CStack(CStack<T> &&src)" << endl;
		//没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空
		mpstack = src.mpstack;
		src.mpstack = nullptr;
	}

提供带右值引用参数的赋值重载函数(转移赋值函数)

   CStack<T>& operator=(CStack<T> &&src)
	{
		cout << "operator=(&&)" << endl;
		if (this == &src)
			return *this;

		delete[]mpstack;

		mtop = src.mtop;
		//没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空
		mpstack = src.mpstack;
		src.mpstack = nullptr;
		return *this;
	}


由打印结果可知,在拷贝构造临时对象的时候自动使用了带右值引用参数的版本,效率大大提升。

std::move()函数

作用:将左值引用转换为右值引用

int a;
int &&r1 = a;              // 编译失败
int &&r2 = std::move(a);  // 编译通过

std::forward()可以保存参数的左值或右值特性

#include <iostream>
using namespace std;

template <typename T> void process_value(T & val)
{
    cout << "T &" << endl;
}

template <typename T> void process_value(T && val)
{
    cout << "T &&" << endl;
}

template <typename T> void process_value(const T & val)
{
    cout << "const T &" << endl;
}

template <typename T> void process_value(const T && val)
{
    cout << "const T &&" << endl;
}

//函数 forward_value 是一个泛型函数,它将一个参数传递给另一个函数 process_value
template <typename T> void forward_value(T && val) //参数为右值引用
{
    process_value( std::forward<T>(val) ); // C++11中,std::forward可以保存参数的左值或右值特性
}


void mytest()
{
    int a = 0;
    const int &b = 1;
    forward_value(std::move(a)); // T &&
    forward_value(b); // const T &
    forward_value(2); // T &&
    forward_value( std::move(b) ); // const T &&
    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}

函数模板参数类型推导

template<typename T>
void foo(T&&);

其中T为模板类型,T&&为参数类型。这种情况会产生两种结果:

  1. 当传给foo函数的参数是一个左值引用时,例如:
int i = 29;
foo(i);//i为左值引用

此时,T的类型为int的左值引用:int&,参数类型为int & &&,(既T&&),结合上面的引用折叠规则,最终参数的类型为int的左值引用:int&。

  1. 当传给foo函数的参数是一个右值引用时,例如:
foo(29);

T的类型为int,参数类型为int&&

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值