左值
引用
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&&为参数类型。这种情况会产生两种结果:
- 当传给foo函数的参数是一个左值引用时,例如:
int i = 29;
foo(i);//i为左值引用
此时,T的类型为int的左值引用:int&,参数类型为int & &&,(既T&&),结合上面的引用折叠规则,最终参数的类型为int的左值引用:int&。
- 当传给foo函数的参数是一个右值引用时,例如:
foo(29);
T的类型为int,参数类型为int&&
本文深入探讨了C++中左值引用与右值引用的区别及应用场景,包括它们在内存操作中的表现形式,并介绍了如何利用右值引用优化栈对象的复制过程。
679

被折叠的 条评论
为什么被折叠?



