基本概念
左值和右值
左值是什么?
左值是一个表示数据的表达式 比如说变量名还有解引用的指针
他有下面的两个特点
- 左值可以被取地址 可以被修改 (被const修饰的左值除外)
- 左值既可以出现在表达式的左边 也可以出现在表达式的右边
下面给出一段代码示例
// 以下的p b c *p都是左值 int* p = new int(0); int b = 1; const int c = 2;
右值也是一个表示数据的表达式 比如说字符常量 表达式的返回值 函数的返回值(不能是左值引用返回)等等
他有下面的两个特点
- 右值不能被取地址 也不能被修改
- 右值只能出现在表达式的右边 不能出现在表达式左边
下面给出一段代码示例
double x = 1.1; double y = 2.2; // 以下是常见的右值 10; x + y; fmin(x, y); // 以下为错误示例 (右值不能出现在赋值符号的左边) // 10 = 1; // x + y = 1; // fmin(x, y) = 1;
- 右值的本质是临时变量或常量 比如说上面代码中的10就是常量 x+y 和 fmin(x, y)就是临时变量
- 临时变量和常量并没有被储存起来 这也是为什么它们不能被取地址的原因
- 这里说的函数的返回值是右值 指的是传值返回的函数 因为传值返回的函数在返回对象的时候返回的是对象的拷贝 是一份临时变量
这里有一点需要特别说明的是 对于左值引用返回的函数来说 它们的返回值是左值
下面是一段代码示例
class string { public: // 重载方括号运算符 char& operator[](size_t i) { assert(i < _size); return _str[i]; } // .. private: char* _str; size_t _size; };
我们这里使用【】运算符重载返回字符串中一个字符的引用 因为他要支持读写 所以采用的是左值引用返回
之所以说这里的返回值是一个左值 是因为返回的字符是被储存起来的 他是string类中_str数组中的某个字符 所以说它是可以被取地址的
左值引用和右值引用
传统的C++语法中就有引用的语法 而在C++11中更新了右值引用的语法
为了进行区分 我们将C++11之前的引用叫做左值引用 将C++11之后更新的引用叫做右值引用
不论是左值引用还是右值引用 它们的本质都是 “取别名”
左值引用
左值引用就是对于左值的引用 即对左值取别名 通过&来声明
下面是一段代码示例
// 以下的p b c *p都是左值 int* p = new int(0); int b = 1; const int c = 2; // 以下几个是对上面左值的左值引用 int*& rp = p; int& rb = b; const int& rc = c; int& pvalue = *p;
右值引用
右值引用就是对于右值的引用 即对右值取别名 通过&&来声明
下面是一段代码示例
double x = 1.1; double y = 2.2; // 以下是常见的右值 10; x + y; fmin(x, y); // 以下为右值引用代码 int&& rr1 = 10; int&& rr2 = x + y; int&& rr3 = fmin(x, y);
这里有一点需要特别注意的 右值是不能取地址的 但是我们给右值取别名之后它会被储存到特定的位置 而此时这个右值就可以被取地址也可以被修改了
如果我们不想让右值可以被取地址和修改我们可以在前面使用const来修饰右值引用
下面是一段代码示例
int&& rr2 = x + y; const int&& rr3 = fmin(x, y); rr2 = 20; // 修改成功 rr3 = 30; // 因为被const修饰了 所以值不能被修改
左值引用可以引用右值嘛
- 左值引用不能引用右值 这里涉及到一个权限放大的问题 左值是可以被修改的 而右值是不可以被修改的
- 如果想要用左值引用来引用右值 需要用到const关键字来修饰左值引用 因为经过const修饰后左值引用就没有修改的权限了
因此const既可以引用左值也可以引用右值
template<class T> void func(const T& val) { cout << val << endl; } int main() { string s("hello"); func(s); //s为变量 为左值 func("world"); // "world"是常量 为右值 return 0; }
右值引用可以引用左值嘛
- 右值引用只能引用右值 不能引用左值
- 如果想要用右值引用来引用左值 需要用到move函数
move函数是C++11标准提供的一个函数 被move后的左值能够被右值引用引用
我们可以发现没有被move函数修饰之前是不可以被右值引用的
但是被修饰之后就可以引用了