左值与右值
一、左值 左值表示一个占据内存中可识别位置的一个对象,更进一步地,可以对左值取地址
int a = 10;
int *p = &a;
int **q = &p;
a,p,q都是很经典的左值,可以通过标识符a,p,q,取出内存地址中对应的对象
int a;
a = 4;
①如果在函数中执行该语句的话,变量a会在栈帧中开辟一个4字节的内存空间其值未定义。所以a为左值,能够取其地址 ②赋值语句中左操作数必须是一个左值,赋值操作本质上是对内存进行更新,所以我们必须要找到内存地址才能更新 二、右值 判断右值的一个简单方法就是能不能对变量或者表达式取地址,如果不能,他就是右值
int foo{return 10;};
x+1;
函数返回的对象(非引用,非指针)是一种典型的右值,这些对象在函数返回之后会被立刻销毁,也就不存在说取地址这样的操作了。如foo() = 20是一个典型的错误 加减乘除等表达式也是右值,因为x+1的结果保存在临时寄存器中,并不会输出到内存,因为没有办法对这个结果取地址,因为它们也是左值
a+1 = 4;
foo() = 10;
a+1以及foo()返回的值均为右值,它们都是一个临时的值,在表达式结束时,生命周期结束
三、左值和右值的转换 1.左值和右值在不同的表达式中有不同的定义
int a = 10,b = 20;
int c = a + b;
在第一行中,a,b都是左值,但在第二行中,由于要执行加法,所以会将左值隐式地转换成右值,其结果也是一个右值,保存在临时寄存器中,而后写入c的内存中 2.解引用操作符*(作用于右值,返回左值) 解引用操作符作用于指针,取出指针指向的内存内容,该结果可以作为左值使用
int *p = &a;
*(p+1) = 20;
p+1的结果是一个右值,但是*(p+1)的结果是左值 3.取地址操作符&(作用于左值,返回右值) 取地址操作符& 一定是作用在左值上,这也是前面定义左值使用的方式
int a[] = {1,2,3};
int *p = &a[2];
int *q = &(a+1);
四丶左值引用 引用类型又称之为”左值引用“,顾名思义,引用只能引用一个左值,而不能是右值
string& s = string("hello");
但常量引用可以引用一个右值
const string& s = string("hello");
void foo(string& s){};
foo("hello");
void foo1(const string& s){};
foo1("hello");
int a = 10;
const int b = 20;
const int& c = a;
const int& d = b;
const int& e = 10+20
五、右值引用 右值引用是C++11的新特性,主要解决”移动语义“,以及”完美转发“,右值引用的标志是&&,顾名思义,右值引用专门为右值而生,可以指向右值,不能指向左值
int &&a = 5;
int b = 5;
int &&aa = b;
a = 6;
右值引用本身是一个左值,这句话听起来有点难理解,但是右值引用是一个变量,而变量一般都是左值
int && s = 1;
int && q = s;
右值引用由于引用的是右值,而右值又是临时的,随时可能被销毁的对象。因此在使用右值引用的地方,我们可以随意接管所引用对象的资源,而不用担心内存泄漏,数据被更改等情况
六丶移动语义 在一般的拷贝赋值函数中,我们通常会使用临时对象+swap的方式来实现异常安全以及数据安全
class Buz {
private:
int* m_ptr;
public:
Buz(int *ptr = 0):m_ptr(ptr){}
~Buz() { delete m_ptr; }
Buz(const Buz& other);
Buz& operator=(const Buz& rhs) {
Buz temp = Buz(rhs);
swap(this->m_ptr, temp.m_ptr);
return *this;
}
};
在上述代码中,首先使用rhs通过拷贝构造函数构造出了临时对象temp,然后交换this和temp中的数据。当函数返回时,temp对象将被自动调用其析构函数并销毁,从而释放掉原来this的数据。这样的写法是异常安全的
在buz = Buz();这一赋值后面发生了很多事,首先创建Buz对象,然后将这个临时对象赋值给buz,此时将会调用拷贝赋值,而在拷贝赋值又会生成一个临时对象,如此一来,Buz对象相当于被创建了两次
在使用了右值引用以后,我们就可以使用移动的方式来编写移动赋值函数,直接将右值中的数据搬到自己这边来
Buz& operator = (Buz&& rhs) {
swap(this->ptr, rhs.ptr);
return *this;
}
buz = Buz();中的Buz()是一个右值,所以该赋值语句将调用移动赋值函数
移动语义利用的基本事实就是右值本身就是临时的,随时可能被销毁的,那么我们从右值中“窃取”数据就不会有任何副作用。当我们窃取完数据以后,甚至可以将一些我们需要销毁的数据挂在右值上,右值销毁时带着这些数据一并销毁
我们可以通过move()函数将一个左值强制类型转成一个右值引用,注意move()本身不具有移动语义,它只是一个类型转换函数而已,内部可以通过static_cast实现
|