☰
【C++进阶】C++ 11(中)
2026/10/1 8:34:46 网站建设 项目流程

目录

1 左值与右值

2 左值引用与右值引用

3 引用延长临时对象生命周期

4 重载匹配

5 移动构造 & 移动赋值

6 值类别分类(C++11)

7 引用折叠

8 完美转发 std::forward

1 左值与右值

  • 左值 (lvalue):可以取地址,对象拥有持久存储,内存实体;可以出现在赋值号左边 / 右边。const修饰左值不能赋值,但是依旧可以取地址。

变量、解引用*p、数组元素s[0]都是左值。

  • 右值 (rvalue):不能取地址;字面量常量、函数返回临时对象、表达式运算结果。只能出现在赋值符号右侧。

如10、x+y、fmin(a,b)、string("abc")临时对象。

现代 C++ 概念: lvalue = locator value,有存储位置,可以取地址; rvalue = read value,只读,没有持久身份,大多为临时。

#include<iostream> #include<string> using namespace std; int main() { // 左值,可以取地址 int* p = new int(0); int b = 1; const int c = b; *p = 10; string s("111111"); s[0] = 'x'; cout << &c << endl; cout << (void*)&s[0] << endl; // 右值,不能取地址 double x = 1.1,y = 2.2; 10; x+y; fmin(x,y); string("11111"); // cout << &10; //编译报错,右值不能取地址 return 0; }

2 左值引用与右值引用

  • Type&:左值引用,绑定左值;const Type&可以绑定左值也可以绑定右值。
  • Type&&:右值引用,绑定右值;右值引用变量本身是左值!
  • std::move():不是移动,只是强制类型转换,把左值转成右值,底层本质是static_cast<T&&>。
#include<iostream> #include<string> using namespace std; int main() { int b = 10; int& r1 = b; // 左值引用绑定左值 int&& rr1 = 20; // 右值引用绑定右值 const int& rx = 30; // const左值引用可以绑定右值 // int&& rr2 = b; //报错,右值引用不能直接绑定左值 int&& rr2 = move(b); // move将左值转为右值 // 重点:rr1、rr2是变量,表达式中属于左值! // int&& rr3 = rr1; //报错,rr1是左值 int&& rr3 = move(rr1); return 0; }

3 引用延长临时对象生命周期

const T&和T&&都可以延长临时对象生命周期;const 引用不能修改对象,非 const 右值引用可以修改临时对象。

#include<iostream> #include<string> using namespace std; int main() { std::string s1 = "Test"; // std::string&& r1 = s1; //错误,不能绑定左值 const std::string& r2 = s1 + s1; // const左值引用延长临时对象 // r2 += "Test"; // const不可修改 std::string&& r3 = s1 + s1; // 右值引用延长生命周期 r3 += "Test"; // 可以修改 std::cout << r3 << '\n'; return 0; }

4 重载匹配

函数同时重载T&/const T&/T&&,实参类型会匹配对应版本:

#include<iostream> using namespace std; void f(int& x) { std::cout << "左值引用重载 f(" << x << ")\n"; } void f(const int& x) { std::cout << "const左值引用重载 f(" << x << ")\n"; } void f(int&& x) { std::cout << "右值引用重载 f(" << x << ")\n"; } int main() { int i = 1; const int ci = 2; f(i); // f(int&) f(ci); // f(const int&) f(3); // f(int&&),没有右值版本会回落const左值引用 f(std::move(i)); // f(int&&) int&& x = 1; // f(x); //x变量是左值,调用f(int&) f(std::move(x)); return 0; }

5 移动构造 & 移动赋值

适用场景:管理堆内存资源的类(string、vector)。

  • 拷贝构造:开辟新内存,完整拷贝全部资源,开销大。
  • 移动构造:只窃取源对象内部资源指针,不拷贝内存;源对象置空,效率极高。移动构造函数参数必须是该类型的右值引用;移动赋值同理。

模拟简易 bit::string 实现,演示移动语义:

#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<assert.h> #include<cstring> #include<algorithm> using namespace std; namespace bit { class string { public: typedef char* iterator; iterator begin(){return _str;} iterator end(){return _str + _size;} string(const char* str = "") :_size(strlen(str)),_capacity(_size) { cout << "string(char* str)-构造" << endl; _str = new char[_capacity + 1]; strcpy(_str, str); } void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //拷贝构造 string(const string& s) { cout << "string(const string& s) -- 拷贝构造" << endl; _size = s._size; _capacity = s._capacity; _str = new char[_capacity + 1]; strcpy(_str, s._str); } //移动构造,参数是右值引用 string(string&& s) { cout << "string(string&& s)--移动构造" << endl; swap(s); } //拷贝赋值 string& operator=(const string& s) { cout << "string& operator=(const string& s)--拷贝赋值"<<endl; if(this != &s) { delete[] _str; _size = s._size; _capacity = s._capacity; _str = new char[_capacity+1]; strcpy(_str, s._str); } return *this; } //移动赋值 string& operator=(string&& s) { cout << "string& operator=(string&& s)--移动赋值"<<endl; swap(s); return *this; } ~string() { cout << "~string()--析构"<<endl; delete[] _str; _str = nullptr; } char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } void reserve(size_t n) { if(n > _capacity) { char* tmp = new char[n+1]; if(_str) strcpy(tmp,_str); delete[] _str; _str = tmp; _capacity = n; } } void push_back(char ch) { if(_size >= _capacity) { size_t newcap = _capacity == 0 ? 4 : _capacity*2; reserve(newcap); } _str[_size++] = ch; _str[_size] = '\0'; } string& operator+=(char ch) { push_back(ch); return *this; } const char* c_str() const { return _str; } size_t size() const { return _size; } private: char* _str = nullptr; size_t _size = 0; size_t _capacity = 0; }; } int main() { bit::string s1("xxxxx"); bit::string s2 = s1; //拷贝构造 bit::string s3 = bit::string("yyyyy"); //临时对象,移动构造,编译器可优化消除 bit::string s4 = move(s1); //移动构造 return 0; }

返回局部对象场景:函数返回局部对象,局部对象是右值,会触发移动构造;编译器 RVO/NRVO 返回值优化可以直接消除拷贝 / 移动。 g++ 编译关闭优化:g++ test.cpp -fno-elide-constructors,可以看到完整移动 / 拷贝调用。

容器接口也增加右值引用版本push_back(T&& val),传入临时对象会走移动,避免深拷贝。

右值引⽤和移动语义在传参中的提效

6 值类别分类(C++11)

表达式分为两大分支:

  1. glvalue 泛左值:lvalue左值 +xvalue将亡值 (expiring value)
  2. rvalue 右值:xvalue将亡值 +prvalue纯右值
  • prvalue 纯右值:字面量、运算表达式、传值返回的临时对象;等价 C++98 时代传统右值。
  • xvalue 将亡值:std::move(obj)转换之后的对象,即将被销毁的资源对象。

图解逻辑: expression ├─ glvalue 泛左值 │ ├─ lvalue 左值 │ └─ xvalue 将亡值 └─ rvalue 右值 ├─ xvalue 将亡值 └─ prvalue 纯右值

7 引用折叠

C++ 不允许直接手写int&&&引用的引用,但是模板、typedef 会间接生成引用的引用,此时触发引用折叠规则:

  1. T& &→T&
  2. T& &&→T&
  3. T&& &→T&
  4. T&& &&→T&&

总结:只要存在左值引用,折叠结果就是左值引用;只有两个都是右值引用,才得到右值引用。

模板参数T&&叫做万能引用(转发引用):

  • 如果传入实参是左值,T 推导为Type&,折叠之后形参变成Type&左值引用;
  • 如果传入实参是右值,T 推导为Type,形参变成Type&&右值引用。

8 完美转发 std::forward

问题:万能引用接收参数,参数变量本身是左值;直接向下一层函数传递,会丢失原来的值类别(原本右值变成左值)。std::forward<T>(arg):保留实参原本的值类别,左值继续作为左值,右值继续作为右值向下传递。底层依靠引用折叠 + static_cast 实现。

小结

  1. 区分左值右值:看是否可以取地址;右值引用变量本体属于左值。
  2. move 仅仅做强制类型转换,不会移动资源;移动构造 / 移动赋值才窃取资源。
  3. 万能引用T&&依靠引用折叠;变量是左值,会丢失值类别,需要forward完美转发。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询