定义
左值的定义:占据内存中某个可识别的位置
- 可修改左值是特殊的左值,不含有数组类型、不完整类型、const 修饰的类型。如果它是
struct
或 union
,它的成员都(递归地)不应含有 const 修饰的类型。
- 除了数组、函数、不完整类型的所有左值都可以转换为右值。
右值的定义:表示内存中一个不可识别的位置(不是左值的都是右值),对右值进行赋值是没有意义的,因为赋值对应的位置是一个临时的不可识别的位置
函数返回的结果一般都是一个右值,但当函数返回的是一个引用的时候,是可以对函数的返回结果直接赋值的,例如
1 2 3 4 5 6 7 8 9
| #include<iostream> int globalvar = 20; int& foo(){ return globalvar; } int main(){ foo() = 10; std::cout<<globalvar<<std::endl; }
|
另外一个常见的例子是
1 2
| std::map<int, float> mymap; mymap[10] = 5.6;
|
左右值的相互转换
左值转右值
1 2 3 4
| int a = 1; int b = 2; int c = a + b;
|
右值产生左值
右值是不能转化成左值的,但是可以通过某些方式产生一个左值
1 2 3
| int arr[] = {1, 2}; int* p = &arr[0]; *(p + 1) = 10;
|
对应的一元取址操作符号需要一个左值,返回一个右值
1 2 3 4
| int var = 10; int* bad_addr = &(var + 1); int* addr = &var; &var = 40;
|
取值符号还可以用于定义引用类型,引用类型又叫做“左值引用”。因此,不能将一个右值赋值给(非常量的)左值引用:
1
| std::string& sref = std::string();
|
常量的 左值引用可以使用右值赋值。因为你无法通过常量的引用修改变量的值,也就不会出现修改了右值的情况。这也使得 C++ 中一个常见的习惯成为可能:函数的参数使用常量引用接收参数,避免创建不必要的临时对象。
c++ move操作
符号&&
代表右值引用,c++11中增加了移动赋值函数,下面介绍一个完整的类应有的基本构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include<bits/stdc++.h> using namespace std;
class Intvec { public: explicit Intvec(size_t num = 0):m_size(num), m_data(new int[m_size]) { log("constructor"); } ~Intvec() { log("destructor"); if (m_data) { delete[] m_data; m_data = 0; } }
Intvec(const Intvec& other) : m_size(other.m_size), m_data(new int[m_size]) { log("copy constructor"); for (size_t i = 0; i < m_size; ++i) m_data[i] = other.m_data[i]; } Intvec& operator=(const Intvec& other) { log("copy assignment operator"); if(this==&other){ return *this; } Intvec tmp(other); std::swap(m_size, tmp.m_size); std::swap(m_data, tmp.m_data); return *this; } Intvec& operator=(Intvec&& other) { log("move assignment operator"); std::swap(m_size, other.m_size); std::swap(m_data, other.m_data); return *this; }
private: void log(const char* msg) { cout << "[" << this << "] " << msg << "\n"; } size_t m_size; int* m_data; };
int main(){ Intvec v1(20); Intvec v2;
cout << "\nassigning lvalue...\n"; v2 = v1; cout << "ended assigning lvalue...\n";
cout << "\nassigning rvalue...\n"; v2 = Intvec(33); cout << "ended assigning rvalue...\n\n"; }
|
对应结果
没有移动赋值函数时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [0x16d96b640] constructor [0x16d96b630] constructor
assigning lvalue... [0x16d96b630] copy assignment operator [0x16d96b5d0] copy constructor [0x16d96b5d0] destructor ended assigning lvalue...
assigning rvalue... [0x16d96b610] constructor [0x16d96b630] copy assignment operator [0x16d96b5d0] copy constructor [0x16d96b5d0] destructor [0x16d96b610] destructor ended assigning rvalue...
[0x16d96b630] destructor [0x16d96b640] destructor ~/repos/jingtao/algorithm-exercises
|
添加了移动赋值函数之后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| [0x16f3e3640] constructor [0x16f3e3630] constructor
assigning lvalue... [0x16f3e3630] copy assignment operator [0x16f3e35d0] copy constructor [0x16f3e35d0] destructor ended assigning lvalue...
assigning rvalue... // 该步骤变得相对简单一些【右值转左值】的情况 [0x16f3e3610] constructor [0x16f3e3630] move assignment operator [0x16f3e3610] destructor ended assigning rvalue...
[0x16f3e3630] destructor [0x16f3e3640] destructor ~/repos/jingtao/algorithm-exercises
|
参考资料