左值和右值:
左值 是=左边可被赋值的值;必须要在内存中有实体;
右值 是=右边取出值赋给其他变量的值;可在内存或寄存器。
一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。
“可以取地址的,有名字的,非临时的就是左值;”
“不能取地址的,没有名字的,临时的就是右值;”
引用
引用的本质还是靠指针来实现的(常指针)。
引用相当于变量的别名。
引用可以改变指针的指向,还可以改变指针所指向的值。
声明的时候必须初始化,一经声明绑定,不可变更绑定其他对象
可对引用,再次引用。多次引用的结果,是某一变量具有多个别名
对引用进行的操作,即是对原对象的操作
左值引用和右值引用
左值引用:类型 &a =左值表达式;
右值引用:类型 &&a =右值表达式;
左值引用要求=右边的值必须能够取地址,如果无法取地址,可以用常引用;
但使用常引用后,我们只能通过引用来读取数据,无法去修改数据,因为其被const修饰成常量引用了。
type-id & cast-expression
// demo
int a = 10
int &b = a
int &c = 10 // 错误,无法对一个立即数做引用
const int &d = 10 // 正确, 常引用引用常数量是ok的,
//其等价于 const int temp = 10; const int &d = temp
右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期
type-id && cast-expression
// demo
int &&var = 10; // ok
int a = 10
int &&b = a // 错误, a 为左值
int &&c = var // 错误,var 为左值
int &&d = move(a) // ok, 通过move得到左值的右值引用
在汇编层面右值引用做的事情和常引用是相同的,即产生临时量来存储常量。但是,唯一一点的区别是,右值引用可以进行读写操作,而常引用只能进行读操作。
右值引用的存在并不是为了取代左值引用,而是充分利用右值(特别是临时对象)的构造,就不需要对象构造和析构操作时new开辟空间,从而达到提高效率的目的。
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(int) // 构造s
Stack(int) // 构造tmp
Stack(const Stack&) // tmp拷贝构造main函数栈帧上的临时对象
~Stack() // tmp析构
operator=(Stack&) // 临时对象赋值给s
~Stack() // 临时对象析构
~Stack() // s析构
运用右值引用参数
// 带右值引用参数的拷贝构造函数
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;
}
运行结果
Stack(int) // 构造s
Stack(int) // 构造tmp
Stack(Stack&&) // 调用带右值引用的拷贝构造函数,直接将tmp的资源给临时对象
~Stack() // tmp析构
operator=(Stack&&) // 调用带右值引用的赋值运算符重载函数,直接将临时对象资源给s
~Stack() // 临时对象析构
~Stack() // s析构
程序自动调用了带右值引用的拷贝构造函数和赋值运算符重载函数,使得程序的效率得到了很大的提升,因为并没有重新开辟内存拷贝数据。
可以直接赋值的原因是临时对象即将销毁,不会出现浅拷贝的问题,我们直接把临时对象持有的资源赋给新对象就可以了。