引用
引用只是一个对象的别名,必须在声明时就立刻初始化,在声明后可以直接用原对象的使用方法进行使用。
引用必须被初始化为指代一个有效的对象或函数
- 不存在 void 的引用,也不存在引用的引用,不存在引用的数组,不存在指向引用的指针。
- 引用不是对象,它们不必占用存储
int a = 0; int& r1;//错误引用变量r1需要初始值设定项 r1 = a; int& a[3]; // 错误,不存在引用的数组 int&* p; // 错误 int& & r; // 错误,不可以隔开 //以下是正确的 int* ptrs[10]; int arr[10] = {1,2,3,4,5,6,7,8}; int(&parray)[10] = arr;//是一个引用,引用一个含有10个整数的数组 int* (&array)[10] = ptrs;//是一个引用,引用一个数组,这个数组,包含10个指针 cout << parray[0] << endl;//输出1
左值和右值
左值:
- 可以由用户访问的存储单元,有一个相对稳定的内存地址,并且有一段较长的生命周期,即可以进行&(取地址),实际存储在内存或者寄存器中。左值可以分为常量左值引用和非常量左值引用。
- 使用左值引用声明符:& 进行表示
int b = 0; int& a = b;
常量左值引用
接收非常量左值,常量左值,右值进行初始化
int a = 0;
int& r1 = a;
const int & r2 = 2;//接收右值
const int& r3 = a;
非常量左值引用
只接收左值
int& a = 2; // # 左值引用绑定到右值,编译失败
右值:
- 则无法进行&(取地址),无法进行赋值,使用右值引用声明符&&来表示,&与&不可以隔开,字面常量,表达式返回值,函数的返回值(是值返回,而不是引用返回),右值不能出现在赋值符号的左边且不能取地址。
int&& a = 2;
- 注意:字符串字面量,是左值 ,i++是右值,++i是左值
const char* str = "hello world"; const char* (&r) = str; const char*&r = str; const char&*r = str;//错误,不允许使用指向引用的指针 const char&(*r) = str;//错误,不允许使用指向引用的指针 int a = 1, b = 2; &(a==b ? a : b); &(a==b ? 3 : 4);//错误:表达式必须为左值或函数指示符 //i++://----右值 int temp =i; i=i+1; return temp; //++i //-----左值 i=i+1; return i;
- 对于将一个左值赋给一个左值,转换为右值后,再左值会先进行一次隐式类型转换,先进行赋值。
int x=0; int &y=x;
右值引用
- 右值引用一般不能绑定左值,但可以用std::move()将左值转为右值
int a; int&& r1 = a;//错误 int&& r2 = std::move(a);
- 右值引用引用右值,右值就会有一份存储空间,右值引用变量就是是左值,可以对它取地址和赋值,可以用来延长右值的生命周期
int x = 1, y = 2; int&& r1 = x + y; int* ptr1 = &r1;//对右值取地址 int&& r2 = 6; int* ptr2 = &r2;//对右值取地址 int& r3 = x; int* ptr3 = &r3; cout << *ptr1 << endl;//输出3 cout << *ptr2 << endl;//输出6 cout << *ptr3 << endl;//输出1
纯右值
- 函数的返回值(返回的函数值,不能是引用)、运算表达式产生的临时变量,原始字面量lambda表达式等
将亡值
- 将要被移动,即将被销毁的对象、T&&函数返回值、std::move的返回值。可以引用左值和右值。
int x = 1, y = 2; int&& r1 = x + y; int&& r2 = 3;
判断是否为右值
- 使用is_same_v,使用is_same_v检查两个类型是否相同或is_lvalue_reference判断是否为左值
int n = 3; int& x = n; int&& y = std::move(x); bool ret1 = std::is_same_v<decltype(n), int>;//判断是否为左值 cout <<"n是否为左值 " << ret1 << endl; bool ret2 = std::is_same_v<decltype(x), int&>;//判断是否为左值 cout << "x是否为左值 " << ret2 << endl; bool ret3 = std::is_same_v<decltype(y), int&&>;//判断是否为将亡值 cout << "y是否为将亡值 " << ret3 << endl; bool ret4 = std::is_same_v<decltype(1), int>;//判断是否为纯右值 cout << "1是否为纯右值 " << ret4 << endl; cout << is_lvalue_reference<decltype(2)>::value << endl;//判断是否为左值
-
右值与移动构造函数
- 使用左值去初始化对象或对象赋值时,将调用拷贝构造函数或赋值运算符
class Test { public: Test() { cout << "调用了构造函数" << endl; } Test(const Test& t) { cout << "调用了拷贝构造函数" << endl; } Test(Test&& t) { cout << "调用了移动构造函数" << endl; } ~Test() { cout << "调用了析构函数" << endl; } Test& operator=(const Test& t) { cout << "调用了赋值运算符" << endl; return *this; } }; int main() { Test t1; Test t2(t1); t2 = t1; /* * * 运行结果: 调用了构造函数 调用了拷贝构造函数 调用了赋值运算符 调用了析构函数 调用了析构函数 */ return 0; }
- 这里还需要注意拷贝函数加上了const,代表其右值和左值都可以接收,而移动构造函数只接收右值。
class Test { public: Test() { cout << "调用了构造函数" << endl; } Test(const Test& t) { cout << "调用了拷贝构造函数" << endl; } //Test(Test&& t) { cout << "调用了移动构造函数" << endl; } }; int main() { { Test t1; Test t2 = move(t1);//拷贝函数不加上const时,错误:类"Test"没有适当的复制构造函数 } return 0; }
- 使用右值去初始化或赋值时,则调用的时移动构造函数或移动赋值运算符来进行移动,而不进行拷贝。
需要注意的是:Test t3(fun());和Test t4 = fun();这两句并没有调用移动构造函数和移动赋值运算符,只调用了fun函数变量里的构造函数,这是因为编译器进行了返回值优化,关掉就可以了。class Test { public: Test() { cout << "调用了构造函数" << endl; } Test(Test& t) { cout << "调用了拷贝构造函数" << endl; } Test(Test&& t) { cout << "调用了移动构造函数" << endl; } ~Test() { cout << "调用了析构函数" << endl; } Test& operator=( Test&& t) { cout << "调用了移动赋值运算符" << endl; return*this; } Test& operator=( Test& t) { cout << "调用了赋值运算符" << endl; return*this; } }; Test fun() { Test t; std::cout << "Test fun()" << std::endl; return t; } int main() { { Test t1; cout << "======================" << endl; Test t2(move(t1)); cout << "======================" << endl; t2 = move(t1); cout << "======================" << endl; } { cout << "======================" << endl; Test t3(fun()); } { cout << "======================" << endl; Test t4 = fun(); } /* * 运行结果: * 调用了构造函数 ====================== 调用了移动构造函数 ====================== 调用了移动赋值运算符 ====================== 调用了析构函数 调用了析构函数 ====================== 调用了构造函数 Test fun() 调用了析构函数 ====================== 调用了构造函数 Test fun() 调用了析构函数 */ return 0; }
- 而第一种是返回值优化(RVO),它允许编译器在函数返回时创建临时对象,而是世界在调用者的对象上构造结果,这可以使得直接在函数内部构造返回值,而不是在函数内部构造临时对象并返回来实现,这也通常会导致移动构造函数的调用被省略。
- 第二种是命名返回值优化(NVRO),NVRO是一种更为特定的情况,其中编译器允许避免创建临时对象,即使该对象在函数中有名称,在NRVO中,编译器将返回的对象直接在函数内部构造,而不是在函数内部构造一个有名称的临时对象再进行返回。