汇小川积流水,坚持多学一点点,开心就会多一点点。
左值引用和右值引用有什么区别?
左值引用:
可以代表左值,变量的别名,可以直接访问数据的地址,类似于直接访问指针。
右值引用:
绑定的必须是一个右值,是在等号右边的值
汇编代码下,左值引用和右值引用一样吗?
提供一个网站,该网站能够将C++代码直接编译成汇编代码
https://godbolt.org/z/UHsDRj
引用在汇编层面上相当于指针,调用的时候相当于指针的解引用。
局部对象返回值的处理方法
此处的局部对象处理返回值,只针对于在堆上开辟内存空间的类。转换成右值处理。
讲解代码示例:
#include <iostream>
using namespace std;
class Stack
{
public:
// size表示栈初始的内存大小
Stack(int size = 1000)
:msize(size), mtop(0)
{
cout << "Stack(int)" << endl;
mpstack = new int[size];
}
// 栈的析构函数
~Stack()
{
cout << "~Stack()" << endl;
delete[]mpstack;
mpstack = nullptr;
}
// 栈的拷贝构造函数
Stack(const Stack &src)
:msize(src.msize), mtop(src.mtop)
{
cout << "Stack(const Stack&)" << endl;
mpstack = new int[src.msize];
memcpy(mpstack, src.mpstack, sizeof(int)*mtop);
}
// 栈的赋值重载函数
Stack& operator=(const Stack &src)
{
cout << "operator=" << endl;
if (this == &src)
return *this;
delete[]mpstack;
msize = src.msize;
mtop = src.mtop;
mpstack = new int[src.msize];
memcpy(mpstack, src.mpstack, sizeof(int)*mtop);
return *this;
}
// 带右值引用参数的拷贝构造函数
Stack(Stack &&src)
:msize(src.msize), mtop(src.mtop)
{
cout << "Stack(Stack&&)" << endl;
/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
mpstack = src.mpstack;
src.mpstack = nullptr;
}
// 带右值引用参数的赋值重载函数
Stack& operator=(Stack &&src)
{
cout << "operator=(Stack&&)" << endl;
if (this == &src)
return *this;
delete[]mpstack;
msize = src.msize;
mtop = src.mtop;
/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
mpstack = src.mpstack;
src.mpstack = nullptr;
return *this;
}
// 返回栈的长度
int getSize()const {
return msize; }
private:
int *mpstack;
int mtop;
int msize;
};
Stack GetStack(Stack &stack)
{
// 这里构造新的局部对象tmp
Stack tmp(stack.getSize());
/* 因为tmp是函数的局部对象,不能出函数作用域, 所以这里tmp需要拷贝构造生成在main函数栈帧上 的临时对象,因此这里会调用拷贝构造函数,完成 后进行tmp局部对象的析构操作 */
return tmp;
}
int testStack()
{
//提问: 考虑,如果使用左值,多了哪些开销?如果使用右值,那优化了哪些步骤?
Stack s;
/* GetStack返回的临时对象给s赋值,该语句结束,临时对象 析构,所以此处调用operator=赋值重载函数,然后调用 析构函数 */
s = GetStack(s);
return 0;
}
template <typename T>
void func(T && val)
{
std::cout << " val : " << val << std::endl;
T temp = val;
temp++;
std::cout << "temp : " << temp << " , val " << val << std::endl;
}
&&一定是右值引用吗?
答:不一定。也有可能是引用折叠,折叠后可能变成左值引用。
引用折叠的概念主要用于函数模板类推导参数类型时会用到,观察该类型是否包含&
int main()
{
//先预估一下下面的 值,如果全部预估正确,则对其知识点有一定的了解。
int a = 10;
int &b = a;
int &&c = 10;
func(10);//10-11-10
func(a);//10-11-11
b = 30;
func(b);//11-12-12 30-31-31
func(a);//31-32-32
func(c);//10-11-11
//int &d = 20; // 这样写无法通过编译,原因是因为20是一个立即数字,不会在内存上存储地址
const int &d = 20;//可通过编译,常引用,如果通过汇编代码,即可发现,20会生成一个临时变量地址在栈上,然后通过临时变量地址加载到d,d是左值常引用,const修饰无法修改d值
//d = 30;错误
int &&e = 20;//汇编代码显示,和const int&d = 20; 是一样的。唯一不同的是,如果要改e值,汇编层面是支持修改的,可去查看汇编代码。
e = 30;
//所以,&& 可以读取临时量,也可以修改其值。 const T& 只可读,不可修改
//结论: 有地址的用左值引用,没有地址的用右值引用;有变量名字的用左值引用,没有变量名字的(比如临时量没有名字)用右值引用。
std::cout << "Hello World!\n";
testStack();
}
结论:右值引用只涉及到资源的转移,没有资源的拷贝开销; 有地址的用左值引用,没有地址的用右值引用;有变量名字的用左值引用,没有变量名字的(比如临时量没有名字)用右值引用。
1、左值引用和右值引用有什么区别?
2、汇编代码下,观察这两种一样吗?
3、如果是局部对象直接当作返回值这样好吗?不好的话该怎么做呢?
4、如果出现&&一定就是右值引用吗?什么情况下会出现不是右值的情况呢?
5、引用折叠是什么?
文章评论