C++知识点
左值与右值引用
- 左值可以出现在赋值语句的左边或者右边,比如说变量
- 右值只能出现在赋值语句右边,比如说常量
左值和右值是针对表达式而言的
左值是指表达式结束后依然存在的持久对象
右值是指表达式结束后就不再存在的临时对象
一个区分左值和右值的便捷方法是:能否对表达式取地址,如果可以则为左值,否则为右值
对于右值对象,可以认为其存在于寄存器中,存在于CPU执行某条表达式的过程中,当执行完这条表达式之后,已经没有内存可以保存右值对象了,因此右值对象被摧毁了,除非将该右值对象赋值给另一个对象。但是如果把一个右值对象赋值给一个左值对象,在这个过程中发生了一次深拷贝。分配一个左值对象,将右值对象深拷贝给左值对象,然后释放这个右值对象。
在这个过程中,有一次多余的分配、深拷贝、释放的过程,因为反正右值对象在表达式结束之后也不会被其他地方引用到了,不如把右值对象里面包含的深层次内容直接赠与左值对象,然后简单的释放掉右值对象剩余的空架子。
C++11中添加T&&这个标记,代表编译器能够将它与类型T&识别出来,这样就可以支持函数重载,以区别对待左值引用和右值引用。
右值引用的作用不是为了取代左值引用,而是充分利用右值的构造来减少对象的构造和析构操作以达到提高效率的目的。
定义右值引用的格式如下:
类型 && 引用名 = 右值表达式;
使用右值引用之前
class Stack
{
public:
// 构造
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];
for (int i = 0; i < mtop; ++i) {
mpstack[i] = src.mpstack[i];
}
}
// 赋值重载
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];
for (int i = 0; i < mtop; ++i) {
mpstack[i] = src.mpstack[i];
}
return *this;
}
int getSize()
{
return msize;
}
private:
int *mpstack;
int mtop;
int msize;
};
Stack GetStack(Stack &stack)
{
Stack tmp(stack.getSize());
return tmp;
}
int main()
{
Stack s;
s = GetStack(s);
return 0;
}
使用右值引用之后
// 带右值引用参数的拷贝构造函数
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;
}
减少了拷贝构造过程中的复制过程