左值
可以出现在赋值表达式左边的对象,一般为变量、数组元素、解引用后的指针等
具有名称的、指向内存空间的标识符
可以执行取地址操作的对象
int i = 10; //i为左值
int arr[5];
arr[0] = 20; //arr[0]为左值
int* x = new int(30);
*x = 40; // *x为左值特殊的左值
int i = 10;
++i = 20; // 前置自增++i可以作为左值,此时i的值就是20
int* p = &++i; //前置自增也可以取地址,和&i的结果是一样的
auto p2 = &"abc"; //取出静态数据区的字符串常量的指针右值
出现在赋值运算符右侧的值,一般为字面量、表达式等
int i = 10; //10为右值
const char* c = "abc"; // "abc"右值
i = 10 + 50; // 10+50为右值左值引用T&
左值引用就是引用,它是对变量的借用,是变量的别名
引用不管理原有变量的生命周期
声明时必须指向一个对象,并且后续不能重新指向为其它对象
在函数传参时不希望发生值拷贝带来的较大的消耗时,可以用左值引用,这非常常见
void func(const int& i) {
cout << &i << endl;
}
int main()
{
int i = 10;
int& ri = i;
ri = 10;
func(i); //不会发生值拷贝,形参i和调用者i是相同地址
return 0;
}右值引用T&&
可以绑定到右值,实现移动语义(在语义上与拷贝进行区分,一目了然的知道是一个即将被移动的对象),允许对右值进行移动而非拷贝,实际的移动逻辑在类的移动构造函数或移动赋值运算符中实现,而非类类型用右值引用没有实际用处。
int i = 10;
std::move(i); //int转换int&&
class A {} //默认就有拷贝构造A&和移动构造A&&
A a1;
A a2(std::move(a1)); //move返回A&&会执行移动构造左值、左值引用、常量左值引用之间的转换
#include <iostream>
class Base {
public:
Base() = default;
Base(Base& other) = delete;
};
int main() {
Base b1;
Base& refBase1 = b1; // T -> T&
const Base& crBase1 = b1; //T -> const T&
Base b2 = refBase1; //T& -> T,需要拷贝构造:Base(Base& other) {}
const Base& crBase2 = refBase1; //T& -> const T&
Base b3 = crBase1; // const T& -> T,需要拷贝构造:Base(Base& other) {}
Base& refBase2 = crBase1; // const T& -> T&,不能直接赋值,要用const_cast
Base& refBase2 = const_cast<Base&>(crBase1); // const T& -> T&
Base* ptrB1 = &b1;
Base b4 = *ptrB1; //T* -> T,需要拷贝构造:Base(Base& other) {}
}可以看出不论是const T&、T&、T*要转换到T都需要拷贝构造,也就是说会发生拷贝
const T&转到T&需要用const_cast