C++11 核心知识点整理

本文围绕列表初始化、左右值引用、移动语义、移动构造与赋值等核心知识点展开,结合代码实例与底层原理,系统讲解C++11如何解决深拷贝效率问题、统一初始化语法,并优化容器与函数返回值性能。


一、C++11 发展历史

C++11 是 C++98 之后最重要的版本更新,2011 年 8 月 12 日正式采纳,之前曾用名"C++0x"。

版本迭代节奏:C++98(1998) → C++11(2011) → C++14(2014) → C++17(2017) → C++20(2020) → C++23(2023),之后每 3 年更新一次。

核心新增:移动语义、右值引用、列表初始化、std::initializer_list、Lambda 表达式、智能指针等。

二、列表初始化({} 初始化)

2.1 C++98 传统 {} 初始化

仅支持数组、结构体的初始化:

cpp 复制代码
struct Point { int _x; int _y; };
int array1[] = {1, 2, 3, 4, 5}; // 数组初始化
int array2[5] = {0};            // 数组部分初始化
Point p = {1, 2};               // 结构体初始化

2.2 C++11 统一列表初始化

目标:实现一切对象皆可用 {} 初始化,统一初始化语法。

支持类型:内置类型、自定义类型、容器等。

语法特性:

可省略 =:Point p1{1,2}; / int x2{2};

单参数类型转换:Date d3{2025}; 等价于 Date d3 = 2025;

容器便捷初始化:vector<Date> v = {{2025,1,1}, {2024,7,25}};

编译器优化:自定义类型 {} 初始化本质是"构造临时对象 + 拷贝构造",编译器会优化为直接构造,避免拷贝。

cpp 复制代码
#include <iostream>
#include <vector>
#include <map>
#include <string>
using namespace std;

struct Point
{
	int _x;
	int _y;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		, _month(d._month)
		, _day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	// C++98 支持的初始化
	int a1[] = { 1, 2, 3, 4, 5 };
	int a2[5] = { 0 };
	Point p = { 1, 2 };

	// C++11 支持的初始化
	// 1. 内置类型支持
	int x1 = { 2 };  // 列表初始化
	int x2 = 2;      // 传统赋值
	int x3{ 2 };     // 省略 = 的列表初始化

	// 2. 自定义类型支持
	// 本质:先构造临时对象 Date(2025,1,1),再拷贝构造 d1
	// 编译器优化后:直接在 d1 的内存上构造,**不会调用拷贝构造**
	Date d1 = { 2025, 1, 1 };
	Date d20(2025, 1, 1);  // 直接构造,和上面优化后等价

	// const 左值引用绑定临时对象,延长临时对象生命周期
	const Date& d2 = { 2024, 7, 25 };

	// 单参数构造的类型转换(C++98 就支持,C++11 用 {} 更统一)
	Date d3 = { 2025 };  // C++11 列表初始化写法
	Date d4 = 2025;      // C++98 隐式类型转换写法
	string str = "1111"; // 字符串也是单参数构造的类型转换

	// 3. 省略 = 的列表初始化(C++11 特性)
	Point p1{ 1, 2 };
	Date d6{ 2024, 7, 25 };
	const Date& d7{ 2024, 7, 25 };

	// ❌ 不支持:只有 {} 初始化才能省略 =
	// Date d8 2025;

	// 4. 容器的列表初始化
	vector<Date> v;
	v.push_back(d1);                  // 左值:拷贝构造
	v.push_back(Date(2025, 1, 1));    // 匿名对象:移动构造(C++11)
	v.push_back({ 2025, 1, 1 });     // 更简洁:直接用 {} 构造临时对象,性价比更高

	map<string, string> dict;
	dict.insert({ "xxx", "yyyy" }); // pair 的列表初始化隐式转换

	// 5. vector 列表初始化的不同写法
	vector<int> v1{ 1,2,3,4 };                // 省略 =
	vector<int> v2 = { 10,20,30,1,1,1,1,1,1,1,1,1 }; // 带 =
	const vector<int>& v4 { 10,20,30,1,1,1,1,1,1,1,1,1 }; // 引用绑定临时对象
	vector<int> v3({ 10,20,30,1,1,1,1,1,1,1,1,1 }); // 显式调用 initializer_list 构造

	// 6. initializer_list 本质
	initializer_list<int> il1 = { 10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
	int aa1[] = { 10, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; // 和数组类似,底层是连续内存

	// 7. map 的嵌套列表初始化
	map<string, string> dict2 = { { "xxx", "yyyy" }, { "sort", "zzzz" } };

	return 0;
}

核心知识点总结

  1. C++98 vs C++11 初始化差异

C++98:仅支持数组、结构体的 {} 初始化,单参数构造支持隐式类型转换。

C++11:统一列表初始化,内置类型、自定义类型、容器都能用 {} 初始化,且可省略 =。

  1. 编译器优化(关键)

Date d1 = {2025,1,1}; 理论上是「构造临时对象 + 拷贝构造 d1」,但编译器会优化为直接构造 d1,完全跳过拷贝构造,运行时只会打印一次构造函数。

关闭优化(如 g++ -fno-elide-constructors)才能看到完整的「临时对象构造 + 拷贝构造」流程。

  1. std::initializer_list 作用

容器(vector/map 等)支持 {} 初始化,本质是实现了接受 initializer_list<T> 的构造函数。

initializer_list<T> 底层是一个只读数组,用两个指针标记首尾,支持范围 for 遍历。

  1. 引用绑定临时对象

const Date& d2 = {2024,7,25};:const 左值引用可以绑定右值临时对象,并延长临时对象生命周期到引用本身的生命周期。

const vector<int>& v4 { ... };:同理,引用绑定容器临时对象。

  1. 容器插入的性价比

v.push_back({2025,1,1}); 比 v.push_back(Date(2025,1,1)) 更简洁,本质都是构造临时对象后移动插入,代码更短、可读性更好。

2.3 std::initializer_list

本质:底层是一个数组,用两个指针指向数组首尾,支持迭代器遍历。

容器支持:STL 容器(vector/list/map 等)都实现了接受 initializer_list 的构造函数,从而支持 {x1,x2,x3...} 初始化。

示例:

cpp 复制代码
vector<int> v1 = {1,2,3,4,5}; // 调用 initializer_list 构造函数
auto il = {10,20,30};         // il 类型为 std::initializer_list<int>

三、左值与右值

3.1 定义与核心区别

|----|----------------------------|------------------|
| 类型 | 定义 | 核心特征 |
| 左值 | 有持久存储、可取地址的表达式(变量名、指针解引用等) | 可出现在赋值号左右,能取地址 |
| 右值 | 字面常量、表达式求值产生的临时对象等,生命周期短暂 | 仅能出现在赋值号右侧,不能取地址 |

左值

左值:有持久存储、可以取地址的表达式,生命周期较长。

示例:p(指针)、b(变量)、*p(解引用)、s[0](数组元素)等。

特征:可以出现在赋值号左边,也能被 & 取地址。

右值

右值:临时产生、不能取地址的表达式,生命周期极短。

示例:字面常量 10、表达式 x+y、函数返回值 fmin(x,y)、临时对象 string("11111")。

特征:只能出现在赋值号右边,无法用 & 取地址。

cpp 复制代码
// 左值示例
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 << endl;
// cout << &(x+y) << endl;
// cout << &(fmin(x, y)) << endl;
// cout << &string("11111") << endl;

命名来源:lvalue = "locator value"(可寻址),rvalue = "read value"(可读但不可寻址)。

3.2 左值引用与右值引用

左值引用(Type&):给左值取别名,只能绑定左值,const Type& 可绑定右值(延长临时对象生命周期)。只能绑定左值,给左值取别名。非常量左值引用不能绑定右值。

cpp 复制代码
// 左值引用绑定左值
int& r1 = b;          // r1 是 b 的别名
int*& r2 = p;         // r2 是指针 p 的别名
int& r3 = *p;         // r3 是 *p 的别名
string& r4 = s;       // r4 是 string 对象 s 的别名
char& r5 = s[0];      // r5 是 s[0] 的别名

const 左值引用(const Type&):可以绑定右值,并延长临时对象的生命周期。绑定后不能修改对象,但可以安全访问。

cpp 复制代码
// const 左值引用绑定右值(延长临时对象生命周期)
const int& rx1 = 10;
const double& rx2 = x + y;
const double& rx3 = fmin(x, y);
const string& rx4 = string("11111");

右值引用(Type&&):给右值取别名,C++11 新增,用于实现移动语义。不能直接绑定左值,但可通过 std::move() 将左值转为右值引用。

cpp 复制代码
// 右值引用绑定右值
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
string&& rr4 = string("11111");

// 右值引用绑定被 move 的左值
int&& rrx1 = move(b);
int*&& rrx2 = move(p);
int&& rrx3 = move(*p);
string&& rrx4 = move(s);
string&& rrx5 = (string&&)s; // 强制类型转换,等价于 move(s)

注意:右值引用变量本身是左值(可取地址),若要继续传递右值属性需再次 move。

关键细节:右值引用变量本身是左值

右值引用变量(如 rr1)是一个有名字的变量,因此它本身是左值(可以取地址)。

如果要将右值引用变量继续作为右值传递,必须再次调用 move()。

cpp 复制代码
int&& rr1 = 10;
cout << &rr1 << endl; // 合法:rr1 是左值,可取地址

int& r6 = rr1;        // 合法:左值引用绑定左值 rr1

// int&& rrx6 = rr1;   // 编译错误:rr1 是左值,不能直接绑定右值引用
int&& rrx6 = move(rr1); // 正确:move(rr1) 将 rr1 转为右值

模板函数中的引用匹配

cpp 复制代码
template<class T>
void func(const T& x)
{}

该模板函数能接受左值、const 左值、右值,因为 const T& 是万能引用的前身,能匹配所有值类别。

这是C++11之前处理"既接受左值又接受右值"的常用方式,C++11之后则用T&&(万能引用)更灵活。

|--------------|-------------|---------------|
| 引用类型 | 可绑定对象 | 核心作用 |
| Type& | 左值 | 给左值取别名,可修改对象 |
| const Type& | 左值/右值 | 只读访问,延长右值生命周期 |
| Type&& | 右值/move(左值) | 实现移动语义,窃取右值资源 |

3.3 引用延长生命周期

const 左值引用 和 右值引用 都能延长临时对象的生命周期,但该对象无法被修改(const 引用)或需通过 move 操作。

cpp 复制代码
const string& r2 = s1 + s1; // const 左值引用延长临时对象生命周期
string&& r3 = s1 + s1;      // 右值引用延长临时对象生命周期

3.4 函数重载与参数匹配

C++11 支持按引用类型重载:

cpp 复制代码
void f(int& x)       { cout << "左值引用重载\n"; }
void f(const int& x) { cout << "const 左值引用重载\n"; }
void f(int&& x)      { cout << "右值引用重载\n"; }

匹配规则:左值实参 → 匹配 f(int&) const 左值实参 → 匹配 f(const int&)

右值实参 / std::move(左值) → 匹配 f(int&&)

cpp 复制代码
#include <iostream>
using namespace std;

// 重载版本1:匹配 非const 左值
void f(int& x)
{
	cout << "左值引用重载 f(" << x << ")\n";
}

// 重载版本2:匹配 const 左值 / 无右值重载时的右值
void f(const int& x)
{
	cout << "到 const 的左值引用重载 f(" << x << ")\n";
}

// 重载版本3:匹配 右值(包括字面量、move后的左值)
void f(int&& x)
{
	cout << "右值引用重载 f(" << x << ")\n";
}

int main()
{
	int i = 1;
	const int ci = 2;

	// 1. 传入左值 i
	f(i);        // 匹配 f(int&) → 输出:左值引用重载 f(1)

	// 2. 传入 const 左值 ci
	f(ci);       // 匹配 f(const int&) → 输出:到 const 的左值引用重载 f(2)

	// 3. 传入右值字面量 3
	f(3);        // 匹配 f(int&&) → 输出:右值引用重载 f(3)
	// 若注释掉 f(int&&),则会降级匹配 f(const int&)

	// 4. 传入 move(i) → 右值
	f(std::move(i)); // 匹配 f(int&&) → 输出:右值引用重载 f(1)

	// 5. 右值引用变量 x 本身是左值
	int&& x = 1;
	f(x);        // x 是变量 → 左值 → 匹配 f(int&) → 输出:左值引用重载 f(1)
	f(std::move(x)); // move(x) 转为右值 → 匹配 f(int&&) → 输出:右值引用重载 f(1)

	return 0;
}

核心匹配规则总结

|--------------------|-------|-------------------|
| 实参类型 | 匹配优先级 | 最终调用版本 |
| 非const 左值 | 1 | f(int&) |
| const 左值 | 1 | f(const int&) |
| 右值(字面量/move(左值)) | 1 | f(int&&) |
| 右值(无 f(int&&) 时) | 2 | f(const int&) |
| 右值引用变量(如 x) | 1 | f(int&)(变量本身是左值) |

关键知识点

右值引用变量是左值:int&& x = 1; 中 x 是有名字的变量,属于左值,可被 int& 绑定。

std::move() 的作用:将左值转换为右值,让它能匹配右值引用重载。

重载优先级:右值引用重载优先级高于 const 左值引用,所以右值会优先匹配 f(int&&)。

四、移动构造与移动赋值

4.1 核心概念

目的:避免深拷贝类(如 string/vector)在返回值、传参时的昂贵拷贝,窃取右值对象的资源,提升效率。

移动构造函数:string(string&& s),参数为右值引用。

移动赋值运算符:string& operator=(string&& s),参数为右值引用。

4.2 gxy::string 类实现示例

cpp 复制代码
#include <iostream>    // 输入输出流,用于 cout 打印
#include <assert.h>    // 断言,用于数组下标越界检查
#include <cstring>     // C 风格字符串函数:strlen、strcpy
using namespace std;

// 自定义命名空间,防止命名冲突
namespace gxy
{
	// 模拟实现 STL 中的 string 类(C++11 版本,支持移动语义)
	class string
	{
	public:
		// 迭代器类型重定义
		typedef char* iterator;              // 普通迭代器,指向的内容可修改
		typedef const char* const_iterator; // const 迭代器,指向内容只读

		// ------------------- 迭代器接口 -------------------
		// 获取起始迭代器(指向第一个字符)
		iterator begin()
		{
			return _str;
		}

		// 获取结束迭代器(指向最后一个字符的下一个位置)
		iterator end()
		{
			return _str + _size;
		}

		// const 对象调用的起始迭代器(只读)
		const_iterator begin() const
		{
			return _str;
		}

		// const 对象调用的结束迭代器(只读)
		const_iterator end() const
		{
			return _str + _size;
		}

		// ------------------- 构造函数 -------------------
		// 1. 普通构造函数
		// 参数:C 风格字符串,默认值为空字符串
		string(const char* str = "")
			:_size(strlen(str))          // 有效字符长度 = 传入字符串长度
			, _capacity(_size)           // 初始容量 = 有效长度
		{
			cout << "string(char* str)-构造" << endl;
			_str = new char[_capacity + 1]; // 开辟堆空间,+1 存放 '\0'
			strcpy(_str, str);              // 拷贝字符串内容
		}

		// ------------------- 拷贝构造(深拷贝) -------------------
		// 2. 拷贝构造:用已存在的对象构造新对象
		string(const string& s)
			:_str(nullptr)  // 先将指针置空,避免野指针
		{
			cout << "string(const string& s) -- 拷贝构造" << endl;
			reserve(s._capacity);       // 将当前对象容量扩容到和 s 一致
			for (auto ch : s)          // 范围 for 遍历 s,逐个字符尾插
			{
				push_back(ch);
			}
		}

		// ------------------- 资源交换函数 -------------------
		// 交换两个 string 对象的所有资源(指针、大小、容量)
		void swap(string& ss)
		{
			::swap(_str, ss._str);      // 交换字符数组指针(全局 swap)
			::swap(_size, ss._size);    // 交换有效长度
			::swap(_capacity, ss._capacity); // 交换容量
		}

		// ------------------- 移动构造(C++11) -------------------
		// 3. 移动构造:参数为右值引用,用于临时对象/即将被销毁的对象
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);  // 直接交换资源,窃取 s 的内存,无数据拷贝,O(1)
		}

		// ------------------- 拷贝赋值运算符重载 -------------------
		// 4. 拷贝赋值:深拷贝,适用于左值赋值
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
			if (this != &s)  // 防止自赋值(自己给自己赋值)
			{
				_str[0] = '\0';  // 清空原有数据
				_size = 0;       // 有效长度置 0
				reserve(s._capacity); // 扩容到目标容量
				// 逐个拷贝字符
				for (auto ch : s)
				{
					push_back(ch);
				}
			}
			return *this;  // 返回自身,支持连续赋值
		}

		// ------------------- 移动赋值运算符重载(C++11) -------------------
		// 5. 移动赋值:参数为右值引用,直接交换资源
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);  // 直接窃取资源,效率极高
			return *this;
		}

		// ------------------- 析构函数 -------------------
		~string()
		{
			// 释放堆上的字符数组,防止内存泄漏
			delete[] _str;
			_str = nullptr;  // 指针置空,避免野指针
		}

		// ------------------- 下标访问运算符重载 -------------------
		// 支持 s[pos] 读写操作
		char& operator[](size_t pos)
		{
			assert(pos < _size);  // 断言检查:下标不能越界
			return _str[pos];      // 返回对应位置字符的引用
		}

		// ------------------- 容量操作 -------------------
		// 扩容函数:将容量调整到 n(只扩不缩)
		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)
		{
			// 容量不足时扩容:初始为4,满了则2倍扩容
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;     // 放入字符
			++_size;              // 有效长度+1
			_str[_size] = '\0';   // 末尾补结束符,保证 C 字符串规范
		}

		// += 运算符重载:追加单个字符
		string& operator+=(char ch)
		{
			push_back(ch);  // 复用尾插接口
			return *this;   // 支持链式调用
		}

		// ------------------- 访问接口 -------------------
		// 返回 C 风格字符串指针(const 保证不修改数据)
		const char* c_str() const
		{
			return _str;
		}

		// 返回有效字符个数(不含 '\0')
		size_t size() const
		{
			return _size;
		}

	private:
		// 底层成员变量(模拟标准 string 实现)
		char* _str = nullptr;    // 指向堆区字符数组的指针
		size_t _size = 0;        // 有效字符数量(实际存储的字符个数)
		size_t _capacity = 0;    // 容量:当前可存储的最大有效字符数
	};
}


// 测试主函数
int main()
{
    // 用 const char* 构造对象 ------ 调用普通构造
    gxy::string s1("hello");

    // 用左值 s1 初始化 s2 ------ 调用拷贝构造
    gxy::string s2 = s1;

    // 用匿名临时对象(右值)初始化 s3 ------ 调用移动构造
    // 先构造临时对象 → 临时对象是右值 → 触发移动构造,直接交换资源,不拷贝数据
    gxy::string s3 = gxy::string("world");

    // move(s1) 将 s1 转为右值 ------ 调用移动赋值
    // std::move(s1) 把左值强转为右值引用 → 触发移动赋值,s2 窃取 s1 资源。
    s2 = move(s1);

    return 0;
}

输出结果

cpp 复制代码
string(char* str)-构造          // s1("hello")
string(const string& s) -- 拷贝构造  // s2 = s1
string(char* str)-构造          // string("world") 临时对象构造
string(string&& s) -- 移动构造    // s3 接收临时对象
string& operator=(string&& s) -- 移动赋值  // s2 = move(s1)

核心知识点完整整理

一、核心成员变量

_str:指向堆区字符数组,存储实际字符串 _size:有效字符个数(不含 \0)

_capacity:已分配容量,满了需要扩容

二、构造与析构

  1. 普通构造:接收 const char*,初始化 _size、_capacity,开辟堆空间,使用 strcpy 拷贝字符串

  2. 拷贝构造(深拷贝)

参数:const string& s(左值引用) 行为:重新开辟内存,逐个字符拷贝,两个对象独立,互不影响

  1. 移动构造(C++11 重点)

参数:string&& s(右值引用) 行为:不新开内存,直接 swap 交换资源

意义:对临时对象/即将销毁对象,把资源"偷"过来,O(1) 复杂度

  1. 析构函数:释放 _str 指向的堆空间,避免内存泄漏

三、赋值运算符重载

  1. 拷贝赋值:深拷贝,清空原有内容,重新拷贝数据;自赋值判断 this != &s 防止错误

  2. 移动赋值:参数:string&& s;直接交换资源,效率极高,用于右值赋值场景

四、核心工具接口

  1. reserve(n):扩容到 n,不改变有效数据,新空间 > 原有容量才执行

  2. push_back(ch):尾插字符,满容则 2 倍扩容,维护 _size 与末尾 \0

  3. operator[]:下标访问,支持读写,断言检查越界

  4. 迭代器 begin/end:普通/const 两个版本,支持范围for:for(auto ch : s)

五、移动语义核心价值(对比拷贝)

拷贝构造/赋值:O(n),开辟新空间 + 复制数据

移动构造/赋值:O(1),仅交换指针,无数据拷贝

适用场景:函数返回值、临时对象、std::move 后的左值

4.3 移动语义的价值

场景:函数返回局部对象、容器插入临时对象时,原本需要深拷贝,现在通过移动构造/赋值窃取资源,时间复杂度从 O(n) 降为 O(1)。

示例:vector 插入右值对象时调用移动构造,插入左值对象时调用拷贝构造。

五、右值引用解决传值返回问题

5.1 传统传值返回的问题

函数返回局部对象时,会经历:局部对象 → 临时对象 → 目标对象,两次拷贝构造(深拷贝类代价极高)

编译器优化(RVO/NRVO):会将多次拷贝合并为直接构造,但关闭优化(如 g++ -fno-elide-constructors)后可观察到完整拷贝/移动流程。

5.2 移动语义下的传值返回

若类实现了移动构造/赋值,返回局部对象时:

未优化:局部对象 → 临时对象(移动构造) → 目标对象(移动构造)

优化后:直接构造目标对象,无任何拷贝/移动。

示例:addStrings 函数返回 string 对象:

cpp 复制代码
#include <algorithm>   // 用于 reverse 反转函数
using namespace std;

// 大数加法解决方案类
class Solution {
public:
	/**
	 * 字符串相加:实现两个大数的加法(数字以字符串形式传入,避免溢出)
	 * @param num1 数字字符串1(传值,会调用拷贝/移动构造)
	 * @param num2 数字字符串2(传值,会调用拷贝/移动构造)
	 * @return 相加后的结果字符串
	 */
	gxy::string addStrings(gxy::string num1, gxy::string num2) {
		// 存储结果的字符串(最终为逆序,需反转)
		gxy::string str;
		// 从两个字符串末尾开始遍历(个位)
		int end1 = num1.size() - 1, end2 = num2.size() - 1;
		// 进位标记:记录上一位相加产生的进位,初始为0
		int next = 0;

		// 循环条件:任意一个字符串未遍历完 或 还有进位
		while (end1 >= 0 || end2 >= 0)
		{
			// 取当前位数字,指针前移,无数字则取0
			int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
			int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;

			// 当前位总和 = 数字1 + 数字2 + 进位
			int ret = val1 + val2 + next;
			// 更新进位:除以10取整
			next = ret / 10;
			// 当前位结果:取余10
			ret = ret % 10;

			// 将当前位字符追加到结果串
			str += ('0' + ret);
		}

		// 循环结束后仍有进位,额外追加字符'1'
		if (next == 1)
			str += '1';

		// 反转字符串:因为我们是从低位加到高位,存储顺序是逆序的
		reverse(str.begin(), str.end());

		// 传值返回:返回局部对象str
		// C++11下优先调用移动构造,避免深拷贝
		return str;
	}
};

// ==================== 测试用例 ====================

// 测试1:接收返回值并赋值调用
int main()
{
	// 定义接收结果的对象
	gxy::string ret;

	// 调用大数加法,返回值通过移动赋值给ret
	ret = Solution().addStrings("111111111111111","222222222222222222222");
	// 打印C风格字符串结果
	cout << ret.c_str() << endl;

	return 0;
}

// 测试2:移动语义与拷贝构造对比
int main()
{
	// 构造字符串对象
	gxy::string s1("11111111111111111");

	// 拷贝构造:用左值s1初始化s3
	gxy::string s3 = s1;
	// 移动构造:用匿名临时对象(右值)初始化s4
	gxy::string s4 = gxy::string("222222222");
	// 移动构造:move将s1转为右值,转移资源
	gxy::string s5 = move(s1);

	return 0;
}

// 测试3:引用绑定临时对象(延长生命周期)
int main()
{
	// const左值引用绑定临时对象,延长生命周期
	const gxy::string& lr = gxy::string("111111");
	// 右值引用绑定临时对象,延长生命周期
	gxy::string&& rr = gxy::string("111111");

	cout << "xxxxxxxxxxxxxxxxxxxxx" << endl;

	return 0;
}

调用:string ret = addStrings("11111", "2222");

优化前:str → 临时对象(移动构造)→ ret(移动构造)

优化后:直接在 ret 内存上构造 str,无移动/拷贝。

核心注释说明

  1. 函数设计要点

传值传参:num1 / num2 为值传递,会触发拷贝/移动构造,函数内修改不影响外部实参

传值返回:返回局部对象 str,C++11 自动识别为右值,优先调用移动构造,性能远优于深拷贝

大数思路:模拟竖式加法,从低位加到高位,处理进位,最后反转得到正确顺序

  1. 关键逻辑

end1 / end2:双指针从字符串尾部向前遍历 next:进位变量,控制每一位的计算

reverse:修正逆序存储的结果 字符转数字:字符 - '0';数字转字符:数字 + '0'

  1. 测试用例用途

main1:演示函数返回值赋值调用,观察移动赋值

main2:对比拷贝构造、匿名对象移动构造、move 移动构造

main3:演示 const 左值引用 和 右值引用 绑定临时对象,延长生命周期

六、容器的右值引用接口与性能提升

6.1 STL 容器的接口更新

C++11 后,STL 容器的 push_back/insert 等接口新增右值引用版本:

cpp 复制代码
void push_back(const T& val);  // 左值版本:拷贝构造
void push_back(T&& val);       // 右值版本:移动构造

实参是右值时,容器内部调用移动构造,窃取资源;实参是左值时,调用拷贝构造,深拷贝数据

6.2 自定义容器的移动支持

示例:gxy::list 容器的 push_back 重载:

cpp 复制代码
namespace gxy {
// 双向链表的节点结构体
template<class T>
struct ListNode {
    ListNode<T>* _next;  // 指向后一个节点
    ListNode<T>* _prev;  // 指向前一个节点
    T _data;             // 存储的数据

    // 左值版本构造函数:拷贝传入数据
    ListNode(const T& data = T()) : _next(nullptr), _prev(nullptr), _data(data) {}
    // 右值版本构造函数:移动传入数据(避免深拷贝,提高效率)
    ListNode(T&& data) : _next(nullptr), _prev(nullptr), _data(move(data)) {}
};

// 链表迭代器模板:Ref是数据引用类型,Ptr是数据指针类型
template<class T, class Ref, class Ptr>
struct ListIterator {
    typedef ListNode<T> Node;
    typedef ListIterator<T, Ref, Ptr> Self;  // 迭代器自身类型
    Node* _node;  // 指向当前节点的指针

    // 用节点指针构造迭代器
    ListIterator(Node* node) : _node(node) {}

    // 前置++:移动到下一个节点,返回自身引用
    Self& operator++() {
        _node = _node->_next;
        return *this;
    }

    // 解引用:返回节点数据的引用(Ref类型)
    Ref operator*() { return _node->_data; }

    // 不等比较:比较节点指针是否不同
    bool operator!=(const Self& it) { return _node != it._node; }
};

// 双向链表类
template<class T>
class list {
    typedef ListNode<T> Node;
public:
    // 普通迭代器:Ref=T&,Ptr=T*
    typedef ListIterator<T, T&, T*> iterator;
    // const迭代器:Ref=const T&,Ptr=const T*
    typedef ListIterator<T, const T&, const T*> const_iterator;

    // 返回首节点迭代器(_head的下一个)
    iterator begin() { return iterator(_head->_next); }
    // 返回尾后迭代器(指向_head本身)
    iterator end() { return iterator(_head); }

    // 初始化空链表:创建哨兵节点,形成循环链表
    void empty_init() {
        _head = new Node();       // 哨兵节点,不存有效数据
        _head->_next = _head;     // 循环指向自己
        _head->_prev = _head;
    }

    // 默认构造:初始化空链表
    list() { empty_init(); }

    // 尾插:左值版本(拷贝构造节点数据)
    void push_back(const T& x) { insert(end(), x); }
    // 尾插:右值版本(移动构造节点数据,避免深拷贝)
    void push_back(T&& x) { insert(end(), move(x)); }

    // 在pos位置前插入节点:左值版本
    iterator insert(iterator pos, const T& x) {
        Node* cur = pos._node;          // 当前位置节点
        Node* newnode = new Node(x);    // 新节点(拷贝x)
        Node* prev = cur->_prev;        // 当前节点的前驱

        // 双向链表插入逻辑:将新节点链接到prev和cur之间
        prev->_next = newnode;
        newnode->_prev = prev;
        newnode->_next = cur;
        cur->_prev = newnode;

        return iterator(newnode);  // 返回新节点迭代器
    }

    // 在pos位置前插入节点:右值版本(移动语义)
    iterator insert(iterator pos, T&& x) {
        Node* cur = pos._node;          // 当前位置节点
        Node* newnode = new Node(move(x)); // 新节点(移动x)
        Node* prev = cur->_prev;        // 当前节点的前驱

        // 双向链表插入逻辑:将新节点链接到prev和cur之间
        prev->_next = newnode;
        newnode->_prev = prev;
        newnode->_next = cur;
        cur->_prev = newnode;

        return iterator(newnode);  // 返回新节点迭代器
    }

private:
    Node* _head;  // 哨兵节点指针,链表头尾都依赖它实现循环
};
}

// 测试代码
int main() {
    gxy::list<gxy::string> lt;  // 实例化存储gxy::string的链表
    gxy::string s1("11111111111111111111"); // 构造长字符串

    lt.push_back(s1);                // 传入左值s1 → 调用push_back(const T&) → 节点数据拷贝构造
    lt.push_back(gxy::string("22222"));// 传入临时对象(右值)→ 调用push_back(T&&) → 节点数据移动构造
    lt.push_back("33333");            // 字符串字面量隐式转为gxy::string右值 → 移动构造
    lt.push_back(move(s1));          // move(s1)将左值转为右值 → 调用push_back(T&&) → 节点数据移动构造(s1变为有效但未定义状态)
    return 0;
}

核心知识点总结

  1. 移动语义优化:ListNode(T&& data) 和 insert(T&& x) 利用右值引用和 std::move,将临时对象或 move 后的对象资源转移到新节点,避免了 T(如 gxy::string)的深拷贝,大幅提升插入效率。左值版本仍保留拷贝语义,保证代码兼容性。

  2. 双向链表结构:使用哨兵节点 _head 实现循环链表,简化边界处理(begin() 是 _head->_next,end() 是 _head)。insert 函数是核心插入逻辑,尾插 push_back 本质是在 end() 前插入。

  3. 迭代器设计:通过模板参数 Ref/Ptr 实现普通迭代器和 const 迭代器的复用,解引用时返回对应引用类型。迭代器本质是对节点指针的封装,++ 操作直接跳转节点。

  4. 测试用例意图:覆盖了左值、临时右值、字面量右值、move 后的右值四种插入场景,验证移动构造和拷贝构造的调用逻辑,体现 C++11 移动语义在容器中的实际价值。

6.3 杨辉三角示例(传值返回与拷贝代价)

版本1

cpp 复制代码
class Solution {
public:
    // 传值返回,vector 深拷贝代价大,C++11 后移动构造优化
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> vv(numRows);
        for (int i = 0; i < numRows; ++i) {
            vv[i].resize(i + 1, 1);
        }
        for (int i = 2; i < numRows; ++i) {
            for (int j = 1; j < i; ++j) {
                vv[i][j] = vv[i-1][j] + vv[i-1][j-1];
            }
        }
        return vv; // C++11 后自动调用移动构造,避免深拷贝
    }
};

问题:vector<vector<int>> 是深拷贝类,传值返回时会拷贝整个二维数组,C++11 之前只能用输出型参数解决;优点:写法直观,符合直觉;缺点:数据量大时拷贝代价高(旧C++会深拷贝,C++11后支持移动返回优化)

C++11 解决:vector 实现了移动构造,返回时会自动调用移动构造,将 vv 的资源窃取到返回值,避免拷贝。

详细拆解

  1. 为什么会触发移动构造?

vv 是函数内部的局部变量,return vv; 意味着它即将被销毁。

编译器会识别到这种"即将死亡"的局部对象,将其视为右值。

对于 vector 这类管理堆内存的容器,C++11 提供了移动构造函数:

cpp 复制代码
vector(vector&& other) noexcept;

它会直接"偷"走 other(也就是这里的 vv)的内部资源(数组指针、容量、大小),而不是重新分配内存并拷贝所有元素。

  1. 移动构造 vs 深拷贝

|--------------|----------------|-----------------|
| 操作 | 时间复杂度 | 开销 |
| 深拷贝(C++98) | O(N),N 为所有元素总数 | 极大,数据量大时非常慢 |
| 移动构造(C++11+) | O(1) | 极小,仅复制几个内部指针和变量 |

  1. 更进一步:编译器优化(NRVO)

在很多现代编译器(GCC、Clang、MSVC)开启优化后,甚至连移动构造都不会调用,直接执行 NRVO(命名返回值优化):

编译器会直接在函数外部的返回值内存地址上构造 vv,完全跳过了"局部对象创建 → 移动/拷贝 → 局部对象销毁"的整个过程。这是比移动构造更极致的优化,效率和用引用传参的版本几乎一样。

版本2

cpp 复制代码
	// 输出型参数版本(通过引用接收vector,无拷贝,效率更高)
	void generate(int numRows, vector<vector<int>>& vv) {
		// 重置传入的vector大小,直接操作参数,不创建局部变量遮蔽
		vv.resize(numRows);
		// 每一行初始化为全1
		for (int i = 0; i < numRows; ++i)
		{
			vv[i].resize(i + 1, 1);
		}
		// 填充中间数值
		for (int i = 2; i < numRows; ++i)
		{
			for (int j = 1; j < i; ++j)
			{
				vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
			}
		}
	}
};

直接在实参对象上修改,零拷贝,效率高;没有返回值,结果通过参数带出

左值引用 vector<vector<int>>& vv:引用就是变量别名,不产生新对象; 函数内修改 = 函数外实参修改

cpp 复制代码
// 主函数:测试两个generate函数(有效入口,其余main注释)
int main()
{
	// 调用传值返回版本,接收返回结果
	vector<vector<int>> ret1 = Solution().generate(100);

	// 调用输出型参数版本
	vector<vector<int>> ret2;
	Solution().generate(100, ret2);

	return 0;
}

代码解释

cpp 复制代码
vector<vector<int>> ret1 = Solution().generate(100);

generate 内部的局部变量 vv,在 return vv 时:

• C++11 以前:会用拷贝构造,把整个二维 vector 完整复制一份,开销很大

• C++11 以后:vv 是即将销毁的局部对象,编译器把它当成右值,自动调用移动构造;,编译器自动隐式当做 std::move(vv) 处理,把 vv 从左值转为右值,从而匹配移动构造/移动赋值,而不是拷贝。

只转移内部指针、大小、容量,O(1) 开销,不拷贝数据; 等价于:return std::move(vv);(编译器自动帮你做了)

移动构造只认右值:vector<vector<int>> ret1 = 函数返回的右值;

等号右边是右值 → 调用移动构造;等号右边是左值 → 调用拷贝构造

和下面这句对比

cpp 复制代码
vector<vector<int>> ret2;
Solution().generate(100, ret2);

用的是左值引用,直接在 ret2 上修改;全程没有构造、拷贝、移动,和移动构造完全无关

总结

ret1 接收返回值:用到了移动构造,是 C++11 对"返回大对象"的核心优化

ret2 引用传参:没有任何资源转移,效率最高,和移动构造无关

  1. 列表初始化:统一了 C++ 初始化语法,std::initializer_list 是容器支持 {} 初始化的核心。

  2. 右值引用:是实现移动语义的基础,核心是区分"可持久的左值"和"临时的右值"。

  3. 移动语义:通过窃取右值对象资源,避免深拷贝,大幅提升 string/vector 等类在传参、返回值场景下的性能。

  4. 编译器优化:RVO/NRVO 会进一步消除移动/拷贝,直接构造目标对象,是性能优化的重要手段。

  5. 容器升级:STL 容器新增右值引用接口,配合移动语义,实现了高效的临时对象插入。

6.4 类型分类

C++11 对表达式的值类别进行了更细致的划分,核心是将右值拆分为:

• 纯右值 (prvalue, pure rvalue):字面常量、求值结果为字面量/不具名临时对象的表达式。

例子:42、true、nullptr、str.substr(1,2)、str1+str2、a++、a+b 等。

C++11 中纯右值等价于 C++98 的右值。

• 将亡值 (xvalue, expiring value):返回右值引用的函数/转换函数的调用表达式。

例子:std::move(x)、static_cast<X&&>(x)。

• 泛左值 (glvalue, generalized lvalue):包含左值 (lvalue) 和将亡值 (xvalue)。

表达式值类别树形结构

cpp 复制代码
expression
├── glvalue (泛左值)
│   ├── lvalue (左值)
│   └── xvalue (将亡值)
└── rvalue (右值)
    ├── xvalue (将亡值)
    └── prvalue (纯右值)

6.5 引用折叠

核心规则

C++ 不能直接定义"引用的引用"(如 int& && r = i; 会报错),但通过模板/typedef 可间接构成,此时遵循引用折叠规则:

• 右值引用的右值引用 → 折叠为右值引用 (&& && → &&)

• 其他所有组合 → 折叠为左值引用 (& &, & &&, && & → &)

代码示例(typedef 演示)

cpp 复制代码
int main()
{
    typedef int&  lref;  // lref = int&
    typedef int&& rref;  // rref = int&&
    int n = 0;

    lref&  r1 = n;  // int& &  → int&
    lref&& r2 = n;  // int& && → int&
    rref&  r3 = n;  // int&& & → int&
    rref&& r4 = 1;  // int&& && → int&&
}

一句话记忆:只要出现一个 &,结果就是左值引用;只有两个 && 相遇,才会折叠成右值引用。

模板函数示例

cpp 复制代码
// 无论 T 是什么,f1 实例化后总是左值引用
template<class T>
void f1(T& x) {}

// 万能引用:可实例化为左值/右值引用,取决于实参
template<class T>
void f2(T&& x) {}

调用与实例化结果

cpp 复制代码
// 模板函数 f1:参数为左值引用 T&
// 无论 T 被实例化为哪种类型,经过引用折叠后,参数最终一定是左值引用
template<class T>
void f1(T& x)
{}

// 模板函数 f2:参数为万能引用 T&&
// 根据实参类型,经过引用折叠后,参数可以是左值引用 或 右值引用
template<class T>
void f2(T&& x)
{}

int main()
{
	typedef int&  lref;   // lref 代表 int&(左值引用)
	typedef int&& rref;   // rref 代表 int&&(右值引用)
	int n = 0;            // n 是一个左值

	// -------------------------- 引用折叠演示 --------------------------
	lref&  r1 = n;  // 展开为 int& & → 折叠为 int&(左值引用)
	lref&& r2 = n;  // 展开为 int& && → 折叠为 int&(左值引用)
	rref&  r3 = n;  // 展开为 int&& & → 折叠为 int&(左值引用)
	rref&& r4 = 1;  // 展开为 int&& && → 折叠为 int&&(右值引用)

	// -------------------------- f1 模板调用与实例化 --------------------------
	// 1. T = int:无折叠,实例化为 void f1(int& x)
	f1<int>(n);        // 合法:左值 n 绑定到 int&
	//f1<int>(0);      // 报错:右值 0 无法绑定到非 const 左值引用 int&

	// 2. T = int&:折叠为 int& & → int&,实例化为 void f1(int& x)
	f1<int&>(n);       // 合法
	//f1<int&>(0);     // 报错:右值无法绑定到 int&

	// 3. T = int&&:折叠为 int&& & → int&,实例化为 void f1(int& x)
	f1<int&&>(n);      // 合法
	//f1<int&&>(0);    // 报错:右值无法绑定到 int&

	// 4. T = const int&:折叠为 const int& & → const int&,实例化为 void f1(const int& x)
	f1<const int&>(n); // 合法:左值 n 绑定到 const int&
	f1<const int&>(0); // 合法:const 左值引用可以绑定右值 0

	// 5. T = const int&&:折叠为 const int&& & → const int&,实例化为 void f1(const int& x)
	f1<const int&&>(n); // 合法
	f1<const int&&>(0); // 合法:const 左值引用可以绑定右值 0

	// -------------------------- f2 模板调用与实例化 --------------------------
	// 1. T = int:无折叠,实例化为 void f2(int&& x)(右值引用)
	//f2<int>(n);       // 报错:左值 n 无法绑定到右值引用 int&&
	f2<int>(0);         // 合法:右值 0 绑定到 int&&

	// 2. T = int&:折叠为 int& && → int&,实例化为 void f2(int& x)(左值引用)
	f2<int&>(n);        // 合法:左值 n 绑定到 int&
	//f2<int&>(0);      // 报错:右值 0 无法绑定到非 const 左值引用 int&

	// 3. T = int&&:折叠为 int&& && → int&&,实例化为 void f2(int&& x)(右值引用)
	//f2<int&&>(n);     // 报错:左值 n 无法绑定到右值引用 int&&
	f2<int&&>(0);       // 合法:右值 0 绑定到 int&&

	return 0;
}

核心规律总结

  1. f1(T& x):

无论 T 是 int、int& 还是 int&&,最终参数类型都折叠为 int&(或 const int&)。

只有当参数是 const 左值引用时,才能绑定右值。

f1(T& x) 的参数是 T&(左值引用),不管 T 是什么,最终都会被折叠成左值引用:

|---------------|------------------|-------------|------------------------|
| 模版实参 | 展开后类型 | 折叠结果 | 最终函数签名 |
| int | int& | int& | void f1(int& x) |
| int& | int& & | int& | void f1(int& x) |
| int&& | int&& & | int& | void f1(int& x) |
| const int& | const int& & | const int& | void f1(const int& x) |
| const int&& | const int&& & | const int& | void f1(const int& x) |

结论:

只要是 f1(T& x),最终参数一定是左值引用。

非 const 的左值引用(int&)不能绑定右值(比如 0、临时对象),所以 f1<int>(0) 会报错。

只有 const 左值引用(const int&)可以绑定右值,所以 f1<const int&>(0) 是合法的。

  1. f2(T&& x)(万能引用):

传入左值 → T 推导为 U& → 折叠为 U&(左值引用)。

传入右值 → T 推导为 U → 折叠为 U&&(右值引用)。 这是实现完美转发的基础。

f2(T&& x) 的参数是 T&&,这就是 C++ 里的万能引用,它的最终类型完全由传入的实参是左值还是右值决定:

1) 传入左值(比如变量 n)

编译器推导 T = int& 展开:int& && 引用折叠:& && → & 最终参数类型:int&(左值引用)

函数签名:void f2(int& x)

2) 传入右值(比如字面量 0、std::move(n))

编译器推导 T = int 展开:int&& 引用折叠:&& && → && 最终参数类型:int&&(右值引用)

函数签名:void f2(int&& x)

结论:万能引用 T&& 会自动适配实参的类型:实参是左值 → 变成左值引用;实参是右值 → 变成右值引用。这让一个模板函数就能同时处理左值和右值,而不用写两个重载版本。

Function(T&& t) 模板推导示例

cpp 复制代码
// 万能引用模板函数
// T&& 是万能引用,可接收左值/右值,T 的类型由传入实参决定
template<class T>
void Function(T&& t)
{
	// 定义局部变量 a
	int a = 0;

	// 关键点:T 是推导出来的类型
	// 1. 传入左值 → T = int& / const int& → T x = a 等价于 int& x = a(引用)
	// 2. 传入右值 → T = int / const int → T x = a 等价于 int x = a(普通变量)
	T x = a;

	// x++ 被注释原因:
	// 若 T 是 const int / const int&,x 带 const 修饰,自增会编译报错
	//x++;

	// 打印 a 的地址
	cout << &a << endl;
	// 打印 x 的地址
	// 左值场景:x 是引用 → &x == &a
	// 右值场景:x 是新变量 → &x != &a
	cout << &x << endl << endl;
}

int main()
{
	// 10 是纯右值
	// 推导:T = int
	// 实例化:void Function(int&& t)
	Function(10);

	int a;
	// a 是左值
	// 推导:T = int&
	// 引用折叠:int& && → int&
	// 实例化:void Function(int& t)
	Function(a);

	// std::move(a) 将左值转为右值引用,属于右值(将亡值)
	// 推导:T = int
	// 实例化:void Function(int&& t)
	Function(std::move(a));

	const int b = 8;
	// b 是 const 左值
	// 推导:T = const int&
	// 引用折叠:const int& && → const int&
	// 实例化:void Function(const int& t)
	// 内部 T x = a → const int& x = a
	// x++ 报错:const 对象不可修改
	Function(b);

	// std::move(b) 是 const 右值(将亡值)
	// 推导:T = const int
	// 实例化:void Function(const int&& t)
	// 内部 T x = a → const int x = a
	// x++ 报错:const 对象不可修改
	Function(std::move(b));

	return 0;
}

核心规律总结

  1. 万能引用 T&& 推导规则

实参是左值 → T = 类型& 实参是右值 → T = 类型

  1. T x = a 的两种行为

T = int& → int& x = a(引用,同地址)

T = int → int x = a(拷贝,不同地址)

T = const int& → const int& x = a(只读引用)

T = const int → const int x = a(只读拷贝)

  1. x++ 报错的唯一原因:推导后 x 带有 const 限定,不能修改。

  2. 地址观察: 传左值:&a == &x 传右值:&a != &x

6.6 完美转发

问题背景:在 Function(T&& t) 中,变量 t 本身是左值(具名变量),若直接传递给下层函数 Fun(t),会始终匹配左值版本,丢失了原实参的右值属性。

完美转发 就是要保留实参原本的左值/右值属性,将其精确传递给下层函数。

std::forward 原理

std::forward 是模板函数,通过引用折叠实现类型精确转发:

cpp 复制代码
template <class T>
T&& forward(typename remove_reference<T>::type& arg) noexcept;

template <class T>
T&& forward(typename remove_reference<T>::type&& arg) noexcept;

• 若实参是左值 → T 推导为 U& → forward 返回 U&(左值引用)

• 若实参是右值 → T 推导为 U → forward 返回 U&&(右值引用)

代码示例

cpp 复制代码
// 四个重载函数,分别匹配四种参数类型
// 匹配:普通左值引用(非const左值)
void Fun(int& x) { cout << "左值引用" << endl; }
// 匹配:const左值引用(const左值 + 右值都可以绑定)
void Fun(const int& x) { cout << "const 左值引用" << endl; }
// 匹配:普通右值引用(非const右值)
void Fun(int&& x) { cout << "右值引用" << endl; }
// 匹配:const右值引用(const右值)
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 万能引用模板函数
// T&&:既能接收左值,也能接收右值
template <class T>
void Function(T&& t)
{
	// std::forward<T>(t):完美转发
	// 保持实参原本的值类型(左值/右值、const属性),转发给Fun
	// 如果直接写 Fun(t),t是具名变量,永远是左值,只会匹配左值版本
	Fun(forward<T>(t));
}

int main()
{
	// 10 是纯右值
	// 推导 T = int
	// forward 转发为 int&&
	Function(10);                     // 输出:右值引用

	int a;
	// a 是普通左值
	// 推导 T = int&
	// 引用折叠后为 int&,forward 转发为 int&
	Function(a);                      // 输出:左值引用

	// std::move(a) 将左值强转为右值(将亡值)
	// 推导 T = int
	// forward 转发为 int&&
	Function(std::move(a));           // 输出:右值引用

	const int b = 8;
	// b 是 const 左值
	// 推导 T = const int&
	// forward 转发为 const int&
	Function(b);                      // 输出:const 左值引用

	// std::move(b) 是 const 右值(将亡值)
	// 推导 T = const int
	// forward 转发为 const int&&
	Function(std::move(b));           // 输出:const 右值引用

	return 0;
}

输出结果

cpp 复制代码
右值引用
左值引用
右值引用
const 左值引用
const 右值引用

Function(T&& t) 是万能引用,能接住所有左值/右值、const/非const

forward<T>(t) 是完美转发,把参数原本的类型属性原封不动传给下一层函数

如果不用 forward,直接 Fun(t),所有调用都会匹配左值引用版本,丢失右值属性

List.h 代码示例

cpp 复制代码
#pragma once
#include <assert.h>
#include <initializer_list>
#include <utility> // std::forward / std::move

namespace gxy
{
    // 双向链表节点结构体
    template<class T>
    struct list_node
    {
        T _data;               // 节点存储的数据
        list_node<T>* _next;   // 指向后一个节点
        list_node<T>* _prev;   // 指向前一个节点

        // 默认构造:编译器自动生成,用于哨兵节点
        list_node() = default;

        // 模板构造函数:万能引用 + 完美转发
        // 接收任意类型 X(左值/右值),将 data 完美转发给 T 的构造函数
        template<class X>
        list_node(X&& data)
            : _data(std::forward<X>(data))  // 保留左值/右值属性,触发拷贝/移动构造
            , _next(nullptr)
            , _prev(nullptr)
        {}
    };

    // 链表迭代器模板:Ref 是数据引用类型,Ptr 是数据指针类型
    template<class T, class Ref, class Ptr>
    struct list_iterator
    {
        typedef list_node<T> Node;
        typedef list_iterator<T, Ref, Ptr> Self;  // 迭代器自身类型
        Node* _node;  // 指向当前节点的指针

        // 用节点指针构造迭代器
        list_iterator(Node* node) : _node(node) {}

        // 解引用:返回节点数据的引用(Ref 类型)
        Ref operator*() { return _node->_data; }

        // 箭头运算符:返回数据指针,用于访问结构体成员(如 it->_a1)
        Ptr operator->() { return &_node->_data; }

        // 前置++:移动到下一个节点,返回自身引用
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }

        // 前置--:移动到前一个节点,返回自身引用
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }

        // 后置++:返回旧迭代器,自身后移
        Self operator++(int)
        {
            Self tmp(*this);
            _node = _node->_next;
            return tmp;
        }

        // 后置--:返回旧迭代器,自身前移
        Self operator--(int)
        {
            Self tmp(*this);
            _node = _node->_prev;
            return tmp;
        }

        // 不等比较:比较节点指针
        bool operator!=(const Self& s) const { return _node != s._node; }

        // 相等比较:比较节点指针
        bool operator==(const Self& s) const { return _node == s._node; }
    };

    // 双向链表类
    template<class T>
    class list
    {
        typedef list_node<T> Node;
    public:
        // 普通迭代器:Ref=T&, Ptr=T*
        typedef list_iterator<T, T&, T*> iterator;
        // const 迭代器:Ref=const T&, Ptr=const T*
        typedef list_iterator<T, const T&, const T*> const_iterator;

        // 返回首节点迭代器(哨兵节点的下一个)
        iterator begin() { return _head->_next; }
        // 返回尾后迭代器(指向哨兵节点本身)
        iterator end() { return _head; }

        // const 版本:返回首节点迭代器
        const_iterator begin() const { return _head->_next; }
        // const 版本:返回尾后迭代器
        const_iterator end() const { return _head; }

        // 初始化空链表:创建哨兵节点,形成循环链表
        void empty_init()
        {
            _head = new Node;       // 哨兵节点(无有效数据)
            _head->_next = _head;   // 循环指向自己
            _head->_prev = _head;
            _size = 0;              // 元素个数初始为 0
        }

        // 默认构造:初始化空链表
        list() { empty_init(); }

        // 初始化列表构造:用 initializer_list 初始化链表
        list(std::initializer_list<T> il)
        {
            empty_init();
            for (auto& e : il) { push_back(e); }
        }

        // 拷贝构造:深拷贝另一个链表
        list(const list<T>& lt)
        {
            empty_init();
            for (auto& e : lt) { push_back(e); }
        }

        // 赋值运算符:拷贝交换法(现代写法)
        list<T>& operator=(list<T> lt)
        {
            swap(lt);  // 交换 *this 和临时对象 lt,lt 销毁时释放旧资源
            return *this;
        }

        // 析构函数:清空所有节点,释放哨兵节点
        ~list()
        {
            clear();       // 删除所有有效节点
            delete _head;  // 释放哨兵节点
            _head = nullptr;
        }

        // 清空链表:删除所有有效节点,保留哨兵节点
        void clear()
        {
            auto it = begin();
            while (it != end()) { it = erase(it); }
        }

        // 交换两个链表的资源(O(1))
        void swap(list<T>& lt)
        {
            std::swap(_head, lt._head);  // 交换哨兵节点指针
            std::swap(_size, lt._size);  // 交换元素个数
        }

        // -------------------------- 万能引用 + 完美转发 --------------------------
        // 尾插:万能引用版本,接收任意类型 X(左值/右值)
        template<class X>
        void push_back(X&& x)
        {
            insert(end(), std::forward<X>(x));  // 完美转发给 insert
        }

        // 头插:左值版本(拷贝构造)
        void push_front(const T& x) { insert(begin(), x); }

        // 插入:万能引用版本,在 pos 前插入节点
        template<class X>
        iterator insert(iterator pos, X&& x)
        {
            Node* cur = pos._node;          // 当前位置节点
            Node* prev = cur->_prev;        // 当前节点的前驱
            // 新节点:完美转发 x,触发 T 的拷贝/移动构造
            Node* newnode = new Node(std::forward<X>(x));

            // 双向链表插入逻辑:将新节点链接到 prev 和 cur 之间
            newnode->_next = cur;
            cur->_prev = newnode;
            newnode->_prev = prev;
            prev->_next = newnode;

            ++_size;  // 元素个数 +1
            return newnode;  // 返回新节点迭代器
        }
        // -----------------------------------------------------------------------

        // 尾删:删除最后一个节点
        void pop_back() { erase(--end()); }
        // 头删:删除第一个节点
        void pop_front() { erase(begin()); }

        // 删除 pos 位置的节点,返回下一个节点的迭代器
        iterator erase(iterator pos)
        {
            assert(pos != end());  // 不能删除尾后迭代器

            Node* prev = pos._node->_prev;  // 被删节点的前驱
            Node* next = pos._node->_next;  // 被删节点的后继

            // 双向链表删除逻辑:跳过被删节点
            prev->_next = next;
            next->_prev = prev;
            delete pos._node;  // 释放被删节点

            --_size;  // 元素个数 -1
            return next;  // 返回后继节点迭代器
        }

        // 返回元素个数
        size_t size() const { return _size; }
        // 判断链表是否为空
        bool empty() const { return _size == 0; }

    private:
        Node* _head;    // 哨兵节点指针(循环链表的核心)
        size_t _size;   // 链表中有效元素的个数
    };

    // 测试用结构体:用于验证 operator-> 的用法
    struct AA
    {
        int _a1 = 1;
        int _a2 = 1;
    };
}
cpp 复制代码
#include "List.h"

int main()
{
	// 定义存储 gxy::string 的双向链表
	gxy::list<gxy::string> lt;

	// 1. 构造左值字符串 s1
	gxy::string s1("11111111111");
	// 传入左值 s1
	// push_back 万能引用推导 X=gxy::string&
	// forward 转发为左值,节点构造触发 string 拷贝构造
	lt.push_back(s1);

	// 2. 构造左值字符串 s2
	gxy::string s2("33333333333");
	// move(s2) 将左值强转为右值引用
	// push_back 万能引用推导 X=gxy::string&&
	// forward 转发为右值,节点构造触发 string 移动构造(高效,无深拷贝)
	lt.push_back(move(s2));

	// 3. 传入字符串字面量,隐式构造临时 gxy::string(右值)
	// push_back 万能引用推导 X=const char(&)[N],最终转发为右值
	// 节点构造直接移动临时对象,效率极高
	lt.push_back("22222222222");

	return 0;
}

关键调用链路(对应 List 里的 forward 设计)

  1. lt.push_back(s1)

X = bit::string& forward<X>(x) → 左值 new Node(左值) → string 拷贝构造

  1. lt.push_back(move(s2))

X = bit::string&& forward<X>(x) → 右值 new Node(右值) → string 移动构造

  1. lt.push_back("22222222222")

字面量构造临时 string(右值) forward 转发为右值 new Node(右值) → string 移动构造

为什么这里必须用 forward?

因为你用了万能引用 X&&:形参 x 是具名变量,本身永远是左值。不用 forward<X>(x),所有插入都会走拷贝构造。用了 forward,才能保留实参原本的左值/右值属性,让移动语义真正生效。

核心设计亮点解析 💡

  1. 万能引用 + std::forward 的极致优化

list_node::list_node(X&& data):用模板万能引用 X&& 接收任意左值/右值,通过 std::forward<X>(data) 完美转发给 T::_data 的构造函数。左值 → 触发 T 的拷贝构造,右值 → 触发 T 的移动构造,避免不必要的深拷贝。

list::push_back(X&& x) + list::insert(iterator pos, X&& x):同样用万能引用 + std::forward,将参数类型信息原封不动传递给节点构造,实现了一套代码同时支持左值/右值插入,替代了之前的两个重载版本。

  1. 迭代器设计

通过模板参数 Ref/Ptr 复用同一套迭代器代码,实现了普通迭代器和 const 迭代器:

iterator:Ref=T&, Ptr=T* → 可修改数据。

const_iterator:Ref=const T&, Ptr=const T* → 只读数据。

operator->() 特殊处理:返回数据指针,支持 it->_a1 这种直观写法(编译器会自动补全为 it.operator->()->_a1)。

  1. 循环链表与哨兵节点

用 _head 作为哨兵节点,形成循环链表,简化了边界处理:begin() = _head->_next(第一个有效节点)。end() = _head(尾后迭代器)。空链表时 _head->_next == _head,无需额外判断。

  1. 现代 C++ 特性

拷贝交换法:operator=(list<T> lt) 利用传值调用的临时对象,自动完成旧资源的释放,安全且高效。

初始化列表构造:支持 list<int> lt = {1,2,3,4} 这种直观写法。

std::swap:高效交换两个链表的资源,时间复杂度 O(1)。

七、 可变参数模板

7.1 基本语法及原理

C++11 支持可变参数模板,即支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:

模板参数包:表示零或多个模板参数 函数参数包:表示零或多个函数参数

核心语法

cpp 复制代码
// 1. 基础形式:按值传递参数包
template <class ...Args>
void Func(Args... args) {}

// 2. 左值引用传递参数包
template <class ...Args>
void Func(Args&... args) {}

// 3. 右值引用传递参数包(完美转发常用)
template <class ...Args>
void Func(Args&&... args) {}

语法规则

模板参数列表:class... 或 typename... 表示接下来的参数是零或多个类型列表

函数参数列表:类型名后接 ... 表示接下来的形参是零或多个对象列表

引用折叠规则:函数参数包可以是左值引用或右值引用,实例化时遵循引用折叠规则

本质原理:编译器会实例化对应类型和个数的多个函数,本质是模板实例化的扩展

参数包大小计算

使用 sizeof... 运算符计算参数包中参数的个数:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

template <class ...Args>
void Print(Args&&... args)
{
    // 输出参数包的参数数量
    cout << sizeof...(args) << endl;
}

int main()
{
    double x = 2.2;
    Print();                  // 输出 0(0个参数)
    Print(1);                 // 输出 1(1个参数)
    Print(1, string("xxxxx"));// 输出 2(2个参数)
    Print(1.1, string("xxxxx"), x); // 输出 3(3个参数)
    return 0;
}

编译期实例化原理

编译器会结合引用折叠规则,实例化出对应重载函数:

cpp 复制代码
// 对应 Print()
void Print();

// 对应 Print(1)
void Print(int&& arg1);

// 对应 Print(1, string("xxxxx"))
void Print(int&& arg1, string&& arg2);

// 对应 Print(1.1, string("xxxxx"), x)
void Print(double&& arg1, string&& arg2, double&& arg3);

本质:可变参数模板是语法糖,底层是编译器自动生成不同参数数量的函数模板,替代手动编写:

cpp 复制代码
template <class T1>
void Print(T1&& arg1);

template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);

template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
// ... 无限扩展

7.2 包扩展(参数包展开)

对于参数包,除了计算大小,唯一能做的操作就是扩展,扩展模式用于处理每个元素,将参数包分解为构成元素,通过在模式右侧放 ... 触发扩展。

递归展开方式(最常用)

通过递归函数模板,逐个取出参数包的第一个参数,剩余参数继续传递:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 递归终止条件:参数包为空时调用
void ShowList()
{
    cout << endl;
}

// 递归展开:取出第一个参数,剩余参数包继续传递
template <class T, class ...Args>
void ShowList(T x, Args... args)
{
    cout << x << " ";
    ShowList(args...); // 剩余参数包继续展开
}

// 可变参数模板入口:接收任意参数包,调用递归展开
template <class ...Args>
void Print(Args... args)
{
    ShowList(args...);
}

int main()
{
    Print();                  // 输出换行
    Print(1);                 // 输出 "1 " + 换行
    Print(1, string("xxxxx"));// 输出 "1 xxxxx " + 换行
    Print(1, string("xxxxx"), 2.2); // 输出 "1 xxxxx 2.2 " + 换行
    return 0;
}

编译期推导过程

以 Print(1, string("xxxxx"), 2.2) 为例,编译器会推导为:

cpp 复制代码
// 最终实例化的函数链
void ShowList(double x)
{
    cout << x << " ";
    ShowList();
}

void ShowList(string x, double z)
{
    cout << x << " ";
    ShowList(z);
}

void ShowList(int x, string y, double z)
{
    cout << x << " ";
    ShowList(y, z);
}

void Print(int x, string y, double z)
{
    ShowList(x, y, z);
}

函数调用展开方式

将参数包中的每个元素传入另一个函数,再组合成新的参数包:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 单参数模板:处理单个参数并打印
template <class T>
const T& GetArg(const T& x)
{
    cout << x << " ";
    return x;
}

// 可变参数模板:仅作为参数包的"容器",不做实际操作
template <class ...Args>
void Arguments(Args... args)
{}

// 可变参数模板:入口函数,触发包扩展
template <class ...Args>
void Print(Args... args)
{
    // 关键:包扩展 → 将每个 args 传入 GetArg,再组合成新参数包
    Arguments(GetArg(args)...);
}

int main()
{
    // 调用 Print,参数包为 {int, string, double}
    Print(1, string("xxxxx"), 2.2); 
    return 0;
}

当调用 Print(1, string("xxxxx"), 2.2) 时,编译器会做以下推导:

  1. 模板实例化:Print 被实例化为:void Print(int, string, double);

  2. 包扩展展开:代码 Arguments(GetArg(args)...); 会被展开为:

cpp 复制代码
Arguments(
    GetArg(1),          // 处理 int 参数
    GetArg(string("xxxxx")), // 处理 string 参数
    GetArg(2.2)         // 处理 double 参数
);
  1. 函数调用顺序:为了构造 Arguments 的参数包,编译器会按顺序执行 GetArg(1)、GetArg(string("xxxxx"))、GetArg(2.2),从而依次输出 1 、xxxxx 、2.2 。

编译期推导过程

cpp 复制代码
void Print(int x, string y, double z)
{
    Arguments(GetArg(x), GetArg(y), GetArg(z));
}

注意:可变参数模板是编译期展开,不支持运行期遍历(如 for 循环遍历参数包),必须通过模板扩展机制处理。

GetArg 的作用:既是参数处理函数,也是包扩展的"模式",每个参数都会被传入并执行打印。

Arguments 的作用:仅作为参数包的接收方,本身不做任何操作,目的是触发包扩展的语法。

编译期行为:整个展开过程发生在编译期,运行时只是顺序执行 GetArg 调用,没有循环或动态遍历。

7.3 emplace 系列接口(可变参数模板的典型应用)

1. emplace_back vs push_back

假设我们要往链表里放一个 string 对象:

cpp 复制代码
gxy::list<string> lt;

// 写法1:用 push_back
string s("hello");
lt.push_back(s);          // 拷贝构造
lt.push_back(string("world")); // 先构造临时string,再移动构造

// 写法2:用 emplace_back
lt.emplace_back("hello"); // 直接构造!

核心区别:

push_back:你得先造好一个对象(不管是变量还是临时对象),再把它拷贝/移动到容器里。

emplace_back:你直接把构造对象需要的参数传给它,它会在容器的内存里就地构造一个对象,完全省掉了拷贝/移动的步骤。

底层到底发生了什么?

我们以 lt.emplace_back("apple", 10)(构造 pair<string, int>)为例:

  1. 调用 emplace_back
cpp 复制代码
template <class... Args>
void emplace_back(Args&&... args)
{
    insert(end(), std::forward<Args>(args)...);
}

它把参数包 "apple", 10 完美转发给 insert。

  1. 调用 insert
cpp 复制代码
template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
    Node* newnode = new Node(std::forward<Args>(args)...);
    // ... 链表指针操作
}

这里 new Node(...) 会调用 ListNode 的可变参数构造函数。

  1. 调用 ListNode 可变构造
cpp 复制代码
template <class... Args>
ListNode(Args&&... args)
    :_data(std::forward<Args>(args)...) // 关键!
{}

_data 是 pair<string, int>,所以 std::forward<Args>(args)... 会被展开成:

_data("apple", 10); // 直接调用 pair<string, int> 的构造函数!

✅ 最终结果:pair<string, int> 对象直接在 ListNode::_data 的内存地址上构造完成,没有任何临时对象、没有任何拷贝/移动。

为什么说 emplace 更高效?

|------|----------------------|----------------|
| 操作 | push_back | emplace_back |
| 构造次数 | 2次:先构造对象 → 再拷贝/移动到容器 | 1次:直接在容器内存构造对象 |
| 临时对象 | 可能产生 | 完全避免 |
| 适用场景 | 已经有现成对象要放入容器 | 直接用构造参数创建新对象 |

举个极端例子:

cpp 复制代码
// 构造一个复杂的大对象,需要很多参数
struct BigObj {
    BigObj(int a, string b, double c, bool d) { /* 很耗时的构造 */ }
};

gxy::list<BigObj> lst;

// push_back:必须先造一个临时对象
lst.push_back(BigObj(1, "test", 3.14, true));
// 流程:构造临时BigObj → 移动构造到容器 → 临时对象销毁

// emplace_back:直接传参数,一步到位
lst.emplace_back(1, "test", 3.14, true);
// 流程:直接在容器内存里构造BigObj → 结束

emplace_back 少了一次对象移动和临时对象销毁,在频繁插入大对象时,性能优势非常明显。

注意事项(容易踩坑)

  1. 参数要严格匹配构造函数
cpp 复制代码
// pair<string, int> 的构造函数需要 (const string&, int)
lp.emplace_back("apple", 10); // ✅ 正确,"apple" 会隐式转成 string
lp.emplace_back(string("apple"), 10); // ✅ 也正确
// lp.emplace_back(10, "apple"); // ❌ 错误,参数顺序反了
  1. 不要和 push_back 混用逻辑

push_back 接收的是对象(const T& 或 T&&); emplace_back 接收的是构造 T 的参数

  1. 不是所有场景都更快

如果已经有一个现成的对象,push_back(std::move(obj)) 和 emplace_back(std::move(obj)) 效率几乎一样。只有在需要当场构造新对象时,emplace 才体现优势。

总结

emplace 系列接口就是让你把"构造对象的参数"直接传给容器,由容器在内部内存里帮你把对象造出来,从而避免了临时对象的创建和拷贝/移动,让代码更简洁、运行更快。

2. 接口实现

C++11 后 STL 新增 emplace 系列接口,均为可变参数模板,功能兼容 push/insert,且支持直接传入构造参数,在容器空间上直接构造对象,效率更高。

核心接口

cpp 复制代码
// 在容器末尾构造元素
template <class... Args>
void emplace_back(Args&&... args);

// 在指定位置构造元素
template <class... Args>
iterator emplace(const_iterator position, Args&&... args);

核心优势:避免临时对象的拷贝/移动构造,直接在容器内存上构造对象;支持传递构造函数的参数包,无需手动创建临时对象;推荐优先使用 emplace 系列替代 push/insert

示例代码(自定义 list 容器实现)

cpp 复制代码
// List.h
namespace gxy
{
    // 链表节点
    template<class T>
    struct ListNode
    {
        ListNode<T>* _next;
        ListNode<T>* _prev;
        T _data;

        // 拷贝构造节点
        ListNode(T& data)
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(move(data)) // 移动构造数据
        {}

        // 可变参数构造:接收任意参数包,通过 std::forward<Args>(args)... 
        // 完美转发给 T 的构造函数,直接在节点内存上构造 T 对象,避免临时对象的产生。
        template <class... Args>
        ListNode(Args&&... args)
            :_next(nullptr)
            ,_prev(nullptr)
            ,_data(std::forward<Args>(args)...) // 完美转发参数包
        {}
    };

    // 迭代器
    template<class T, class Ref, class Ptr>
    struct ListIterator
    {
        typedef ListNode<T> Node;
        typedef ListIterator<T, Ref, Ptr> Self;
        Node* _node;

        ListIterator(Node* node) :_node(node) {}

        // 迭代器自增/自减
        Self& operator++()
        {
            _node = _node->_next;
            return *this;
        }
        Self& operator--()
        {
            _node = _node->_prev;
            return *this;
        }

        // 解引用与比较
        Ref operator*() { return _node->_data; }
        bool operator!=(const Self& it) { return _node != it._node; }
    };

    // 链表容器
    template<class T>
    class list
    {
        typedef ListNode<T> Node;
    public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T*> const_iterator;

        iterator begin() { return iterator(_head->_next); }
        iterator end() { return iterator(_head); }

        // 初始化空链表
        void empty_init()
        {
            _head = new Node();
            _head->_next = _head;
            _head->_prev = _head;
        }
        list() { empty_init(); }

        // 普通 push_back(拷贝/移动构造)
        void push_back(const T& x) { insert(end(), x); }
        void push_back(T&& x) { insert(end(), move(x)); }

        // 插入函数(基础版:接收左值)
        iterator insert(iterator pos, const T& x)
        {
            Node* cur = pos._node;
            Node* newnode = new Node(x); // 拷贝构造节点
            Node* prev = cur->_prev;

            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = cur;
            cur->_prev = newnode;
            return iterator(newnode);
        }
        // 插入函数(基础版:接收右值)
        iterator insert(iterator pos, T&& x)
        {
            Node* cur = pos._node;
            Node* newnode = new Node(move(x)); // 移动构造节点
            Node* prev = cur->_prev;

            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = cur;
            cur->_prev = newnode;
            return iterator(newnode);
        }

        // ========== 可变参数 emplace 接口 ==========
        template <class... Args>
        void emplace_back(Args&&... args)
        {
            insert(end(), std::forward<Args>(args)...);
        }

        template <class... Args>
        iterator insert(iterator pos, Args&&... args)
        {
            Node* cur = pos._node;
            // 直接用参数包构造 T 对象,避免临时对象
            Node* newnode = new Node(std::forward<Args>(args)...);
            Node* prev = cur->_prev;

            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = cur;
            cur->_prev = newnode;
            return iterator(newnode);
        }

    private:
        Node* _head;
    };
}

可变参数 emplace 接口

cpp 复制代码
// ========== 可变参数 emplace 接口 ==========
template <class... Args>
void emplace_back(Args&&... args)
{
    insert(end(), std::forward<Args>(args)...);
}

template <class... Args>
iterator insert(iterator pos, Args&&... args)
{
    Node* cur = pos._node;
    // 直接用参数包构造 T 对象,避免临时对象
    Node* newnode = new Node(std::forward<Args>(args)...);
    Node* prev = cur->_prev;

    prev->_next = newnode;
    newnode->_prev = prev;
    newnode->_next = cur;
    cur->_prev = newnode;
    return iterator(newnode);
}

核心优势:

  1. 直接构造:接收 T 构造函数的参数包,直接在节点内存上构造 T 对象,完全避免临时对象的拷贝/移动。

  2. 完美转发:std::forward<Args>(args)... 保持参数的右值/左值属性,最大化效率。

  3. 泛型兼容:支持任意数量、任意类型的构造参数,适配 T 的所有构造函数。

• 对比 push_back:

push_back:T obj → 拷贝/移动到节点 → 两次构造。

emplace_back:直接在节点构造 T → 一次构造,性能更优。

使用示例

cpp 复制代码
int main()
{
	list<gxy::string> lt;
	gxy::string s1("111111111111");
	gxy::string s2("111111111111");

	// 1. emplace_back(左值)
	lt.emplace_back(s1); 
	// 走可变参构造+完美转发,s1 为左值
	// _data 调用 string 拷贝构造,无非法 move,安全
	cout << "*********************************" << endl;

	// 2. push_back(左值)
	lt.push_back(s1); 
	// 走 insert(const T&) -> new Node(s1)
	// 匹配 ListNode(T& data),内部执行 _data(move(data))
	// 强行将左值转为右值,依赖 string 实现,可能是移动/拷贝,写法不标准
	cout << "*********************************" << endl;

	// 3. emplace_back(右值)
	lt.emplace_back(move(s1)); 
	// 走可变参构造+完美转发,保持右值属性
	// _data 调用 string 移动构造,高效且标准
	cout << "*********************************" << endl;

	// 4. push_back(右值)
	lt.push_back(move(s2)); 
	// 走 insert(T&&) -> new Node(move(x))
	// 匹配 ListNode(T& data),_data(move(data)) 为合法右值移动
	cout << "*********************************" << endl;

	// 5. emplace_back(构造参数) ------ emplace 核心优势
	lt.emplace_back("111111111111"); 
	// 直接将 const char* 转发给 string 构造函数
	// 在节点 _data 内存就地构造,无临时对象、无拷贝/移动开销
	cout << "*********************************" << endl;

	// 6. push_back(字符串字面量)
	lt.push_back("111111111111"); 
	// 先隐式构造临时 string 对象
	// 再将临时对象移动/拷贝到节点,比 emplace 多一次临时对象开销
	cout << "*********************************" << endl;

	return 0;
}
cpp 复制代码
int main()
{
	gxy::list<pair<gxy::string, int>> lt1;
	pair<gxy::string, int> kv("苹果", 1);

	// 1. emplace_back(左值pair)
	lt1.emplace_back(kv); 
	// 行为:拷贝构造 pair 到节点
	// 底层:转发 kv → _data(kv) → pair 的拷贝构造
	cout << "*********************************" << endl;

	// 2. emplace_back(右值pair)
	lt1.emplace_back(move(kv)); 
	// 行为:移动构造 pair 到节点
	// 底层:转发 move(kv) → _data(move(kv)) → pair 的移动构造
	cout << "*********************************" << endl;

	// 3. emplace_back(构造pair的参数包) ✨核心优势✨
	lt1.emplace_back("苹果", 1 ); 
	// 行为:直接在节点内存构造 pair 对象
	// 底层:转发 "苹果" 和 1 → _data("苹果", 1)
	// 直接调用 pair<string, int>(const char*, int) 构造函数,无临时对象
	cout << "*********************************" << endl;

	// 4. push_back(初始化列表)
	lt1.push_back({ "苹果", 1 }); 
	// 行为:先构造临时 pair,再移动到节点
	// 底层:隐式构造 pair<string, int>("苹果", 1) → 移动构造到 _data
	// 对比 emplace_back:多了一次临时 pair 的构造和销毁
	cout << "*********************************" << endl;

	return 0;
}

结论:当传入已存在的 pair 对象时,emplace_back 和 push_back 效率一致。当传入构造 pair 的多个参数(如 "苹果", 1)时,emplace_back 可以直接转发参数包给 pair 的构造函数,这是 push_back 做不到的,push_back 必须先构造一个临时 pair 对象。

一句话总结

push_back:接收的是已经构造好的对象,把它拷贝/移动到容器里。

emplace_back:接收的是构造对象的参数,直接在容器内部内存里构造对象。

什么时候用 emplace_back?

  1. 需要当场构造新对象,且构造需要多个参数时(比如 pair、自定义复杂对象)。

  2. 传入构造参数(如字符串字面量)时,比 push_back 更高效。

什么时候用 push_back?

  1. 已经有现成的对象,直接移动进去(push_back(move(obj)))。

  2. 简单类型(如 int),两者效率几乎无差别。

编译期原理

编译器会根据参数包生成对应重载函数,例如:

cpp 复制代码
// 对应 lt.emplace_back("11111111111")
void emplace_back(const char* s)
{
    insert(end(), std::forward<const char*>(s));
}

// 对应 lt1.emplace_back("苹果", 1)
iterator insert(iterator pos, const char* arg1, int arg2)
{
    Node* newnode = new Node(std::forward<const char*>(arg1), std::forward<int>(arg2));
    // ... 链表插入逻辑
}

核心总结

  1. 参数包:可变参数模板的核心,分为模板参数包和函数参数包

  2. 基本语法:class...Args 定义模板参数包,Args...args 定义函数参数包

  3. 包扩展:通过递归或函数调用模式,在编译期分解参数包

  4. 关键运算符:sizeof...(args) 计算参数包大小

  5. 完美转发:std::forward<Args>(args)... 保持参数的右值/左值属性

  6. 典型应用:STL emplace 系列接口,直接在容器上构造对象,提升效率

  7. 本质:编译器自动生成不同参数数量的函数模板,是泛型编程的强大工具

八、新的类功能

8.1 默认的移动构造和移动赋值

  1. C++类默认成员函数

原有6个默认成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、const取地址重载(后两个实际用途较少)。

C++11 新增:移动构造函数、移动赋值运算符重载。

  1. 默认移动构造生成规则

条件:未手动实现移动构造函数,且未实现析构函数、拷贝构造函数、拷贝赋值重载中的任意一个。

行为:

内置类型成员:按字节拷贝(浅拷贝)。

自定义类型成员:若该成员实现了移动构造,则调用其移动构造;否则调用拷贝构造。

  1. 默认移动赋值生成规则

条件:未手动实现移动赋值运算符重载,且未实现析构函数、拷贝构造函数、拷贝赋值重载中的任意一个。

行为:与默认移动构造完全一致。

  1. 关键约束:若手动提供了移动构造或移动赋值,编译器将不会自动生成拷贝构造和拷贝赋值。
cpp 复制代码
class Person {
public:
    Person(const char* name = "", int age = 0)
        : _name(name), _age(age)
    {}

    // 若注释掉拷贝构造/赋值,编译器会自动生成移动构造/赋值
    // Person(const Person& p) : _name(p._name), _age(p._age) {}
    // Person& operator=(const Person& p) { ... }

private:
    gxy::string _name;
    int _age;
};

int main() {
    Person s1;
    Person s2 = s1;       // 调用拷贝构造
    Person s3 = std::move(s1); // 调用移动构造
    Person s4;
    s4 = std::move(s2);   // 调用移动赋值
    return 0;
}

8.2 成员变量声明时给缺省值

作用:为初始化列表提供默认值。

规则:若未在初始化列表显式初始化,则自动使用该缺省值初始化成员变量。

8.3 default 和 delete

  1. default:显式指定生成默认函数

场景:手动实现了某些默认函数(如拷贝构造),导致编译器不再生成其他默认函数(如移动构造),可使用 default 强制生成。
Person(Person&& p) = default; // 强制编译器生成默认移动构造

  1. delete:禁止生成默认函数

作用:指示编译器不生成对应函数的默认版本,被 delete 修饰的函数称为删除函数。

对比 C++98:C++98 需将函数设为 private 且只声明不定义,C++11 直接用 delete 更简洁。
Person(const Person& p) = delete; // 禁止拷贝构造

8.4 final 与 override

final:修饰类时,类不能被继承;修饰虚函数时,该函数不能被子类重写。

override:显式标记子类函数为重写的基类虚函数,编译器会检查是否匹配基类虚函数签名,避免写错函数名/参数。

九、STL 中的变化

  1. 新容器

常用:unordered_map、unordered_set(哈希表实现,查询/插入平均 O(1))。

了解:array(固定大小数组)、forward_list(单向链表)、bitset(位容器)。

  1. 新接口

右值引用相关:push/insert/emplace 系列接口(支持移动语义,避免拷贝)、移动构造/移动赋值。

初始化列表:initializer_list 版本构造函数,支持用 {} 初始化容器。

范围 for 遍历:for (auto& e : container) 简化遍历。

其他:cbegin/cend(常量迭代器)等,需查阅文档。

十、Lambda 表达式

10.1 Lambda 表达式语法

本质:匿名函数对象,可定义在函数内部,无实际类型,通常用 auto 或模板参数接收。

格式:[capture-list] (parameters) -> return_type { function_body }

capture-list\]:捕捉列表,必写(即使为空 \[\]),用于捕捉上下文变量供 lambda 内部使用。 (parameters):参数列表,与普通函数一致,无参数可省略 ()。 -\> return_type:返回值类型,可省略,由编译器自动推导。 {function_body}:函数体,必写(即使为空 {})。 ```cpp // 完整写法 auto add1 = [](int x, int y)->int { return x + y; }; // 省略返回值类型 auto add2 = [](int x, int y) { return x + y; }; // 无参数,省略参数列表 auto func1 = [] { cout << "hello world" << endl; }; // 引用参数 auto swap1 = [](int& x, int& y) { int tmp = x; x = y; y = tmp; }; ``` 1. 完整写法 auto add1 = \[\](int x, int y)-\>int { return x + y; }; \[\]:捕捉列表(空,表示不捕捉任何外部变量) (int x, int y):参数列表,和普通函数一样指定参数类型 -\>int:显式返回值类型,明确告诉编译器返回 int { return x + y; }:函数体,实现加法逻辑 auto add1:用 auto 接收这个匿名函数对象,后续可像函数一样调用:add1(1, 2) → 返回 3 2. 省略返回值类型 auto add2 = \[\](int x, int y) { return x + y; }; 当函数体只有单一 return 语句时,编译器可以自动推导返回值类型,所以 -\>int 可以省略 调用方式和 add1 完全一样:add2(3, 4) → 返回 7 注意:如果函数体有多条语句或没有 return,就不能省略返回值类型 3. 无参数,省略参数列表 auto func1 = \[\] { cout \<\< "hello bit" \<\< endl; }; 没有参数时,参数列表 () 可以完全省略,直接写 \[\] { ... } 调用方式:func1() → 输出 hello world 这是 lambda 最简洁的写法之一 4. 引用参数 ```cpp auto swap1 = [](int& x, int& y) { int tmp = x; x = y; y = tmp; }; ``` 参数用 int\&(左值引用),表示可以修改传入的实参(和普通函数的引用参数作用一致) 调用示例: int a = 1, b = 2; swap1(a, b); // 交换后 a=2, b=1 这里也可以用 auto swap1 = \[\](auto\& x, auto\& y) { ... } 实现泛型版本,支持交换任意类型 💡 核心总结 • lambda 本质是匿名函数对象,用 auto 接收后可像普通函数一样调用 • 语法简化规则: 1. 单一 return → 可省略 -\>返回值类型 2. 无参数 → 可省略 () 3. 捕捉列表 \[\] 永远不能省略;引用参数 T\& 可以修改外部变量,值参数 T 只会拷贝 ### 10.2 捕捉列表 1. 捕捉规则:lambda 内部默认只能使用自身函数体、参数及全局/静态变量,外层局部变量必须通过捕捉列表获取。 2. 捕捉方式 |--------|--------------|-----------------| | 捕捉方法 | 语法 | 说明 | | 显式值捕捉 | \[x, y\] | 拷贝捕捉 x、y,内部不可修改 | | 显式引用捕捉 | \[\&x,\& y\] | 引用捕捉 x、y,内部可修改 | | 隐式值捕捉 | \[=\] | 自动拷贝捕捉所有使用的外层变量 | | 隐式引用捕捉 | \[\&\] | 自动引用捕捉所有使用的外层变量 | | 混合捕捉1 | \[=, \&x\] | 默认值捕捉,仅 x 引用捕捉 | | 混合捕捉2 | \[\&, x\] | 默认引用捕捉,仅 x 值捕捉 | ```cpp #include using namespace std; // 全局变量y:全局变量在lambda中无需捕捉,可直接访问/修改 int y = 0; int main() { // 外层局部变量:lambda默认无法直接访问,需通过捕捉列表获取 int a = 0, b = 1, c = 2, d = 3; // -------------------------- func1:显式捕捉 + mutable -------------------------- // [a, &b]:显式捕捉列表 // - a:值捕捉,lambda内部存储a的拷贝,默认const,不可修改 // - &b:引用捕捉,lambda内部直接操作外部变量b,可修改 // (int x):参数列表,接收一个int类型参数x // mutable:修饰lambda,允许修改值捕捉的变量拷贝(仅修改拷贝,不影响外部原变量a) auto func1 = [a, &b](int x) mutable { // 值捕捉的变量拷贝,默认const,必须加mutable才能修改 a++; // 引用捕捉的变量,可直接修改外部原变量b b++; // y是全局变量,无需捕捉,直接访问 int ret = a + b + x + y; return ret; }; // 调用func1,传入x=1 cout << "func1(1) = " << func1(1) << endl; // 验证:a是值捕捉,外部a不变;b是引用捕捉,外部b被修改 cout << "func1执行后:a = " << a << ", b = " << b << "\n" << endl; // -------------------------- func2:隐式值捕捉 [=] -------------------------- // [=]:隐式值捕捉,编译器自动捕捉lambda中使用的所有外层变量(a、b、c),均为值拷贝 // 特点:所有捕捉变量都是拷贝,默认const,不可修改,不影响外部原变量 auto func2 = [=] { // 自动值捕捉a、b、c,直接使用拷贝 int ret = a + b + c; return ret; }; cout << "func2() = " << func2() << endl; cout << "func2执行后:a = " << a << ", b = " << b << ", c = " << c << "\n" << endl; // -------------------------- func3:隐式引用捕捉 [&] -------------------------- // [&]:隐式引用捕捉,编译器自动捕捉lambda中使用的所有外层变量(a、c、d),均为引用 // 特点:所有捕捉变量都是引用,可直接修改外部原变量 auto func3 = [&] { a++; // 引用捕捉,修改外部a c++; // 引用捕捉,修改外部c d++; // 引用捕捉,修改外部d }; func3(); // 调用func3 cout << "func3执行后:a = " << a << ", b = " << b << ", c = " << c << ", d = " << d << "\n" << endl; // -------------------------- func4:混合捕捉 [&, a, b] -------------------------- // [&, a, b]:混合捕捉 // - 第一个元素&:默认所有变量为引用捕捉 // - 后续a、b:单独指定为值捕捉 // 规则:混合捕捉时,第一个元素必须是&或=,后续变量为相反捕捉方式 auto func4 = [&, a, b] { // a、b是值捕捉,拷贝默认const,不可修改(去掉注释会编译报错) // a++; // b++; // c、d是隐式引用捕捉,可直接修改外部原变量 c++; d++; return a + b + c + d; }; cout << "func4() = " << func4() << endl; cout << "func4执行后:a = " << a << ", b = " << b << ", c = " << c << ", d = " << d << "\n" << endl; return 0; } ``` 运行结果 ```cpp func1(1) = 4 func1执行后:a = 0, b = 2 func2() = 4 func2执行后:a = 0, b = 2, c = 2 func3执行后:a = 1, b = 2, c = 3, d = 4 func4() = 12 func4执行后:a = 1, b = 2, c = 4, d = 5 ``` 3. 特殊规则 全局/静态局部变量:无需捕捉,可直接使用。 mutable:修饰 lambda 时,可修改值捕捉的变量(本质是修改拷贝,不影响外部原变量),此时参数列表 () 不可省略。 ```cpp auto func7 = [=]() mutable { a++; // 可修改值捕捉的变量 b++; }; ``` ### 10.3 Lambda 的应用 替代繁琐的函数指针/仿函数: ```cpp #include #include #include #include // 包含sort算法 using namespace std; // 商品结构体:存储商品名称、价格、评价等信息 struct Goods { string _name; // 商品名称 double _price; // 商品价格 int _evaluate; // 商品评价(分数越高越好) // 构造函数:初始化商品信息 Goods(const char* str, double price, int evaluate) : _name(str) , _price(price) , _evaluate(evaluate) {} }; // 仿函数Compare1:按价格升序比较 struct Compare1 { // 重载()运算符,使对象可像函数一样调用 bool operator()(const Goods& gl, const Goods& gr) { // 升序:左商品价格 < 右商品价格 return gl._price < gr._price; } }; // 仿函数Compare2:按价格降序比较 struct Compare2 { bool operator()(const Goods& gl, const Goods& gr) { // 降序:左商品价格 > 右商品价格 return gl._price > gr._price; } }; int main() { // 初始化商品列表:用initializer_list直接初始化vector vector v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } }; // -------------------------- 传统仿函数写法 -------------------------- // 价格升序排序:传入Compare1临时对象作为比较器 // sort(v.begin(), v.end(), Compare1()); // 价格降序排序:传入Compare2临时对象作为比较器 // sort(v.begin(), v.end(), Compare2()); // -------------------------- lambda写法:更简洁灵活 -------------------------- // 1. 按价格升序排序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._price < g2._price; }); // 2. 按价格降序排序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._price > g2._price; }); // 3. 按评价升序排序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._evaluate < g2._evaluate; }); // 4. 按评价降序排序 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) { return g1._evaluate > g2._evaluate; }); // 可选:打印排序后结果验证 cout << "按评价降序排序结果:" << endl; for (const auto& g : v) { cout << "名称:" << g._name << ",价格:" << g._price << ",评价:" << g._evaluate << endl; } return 0; } ``` ```cpp 按评价降序排序结果: 名称:苹果,价格:2.1,评价:5 名称:香蕉,价格:3,评价:4 名称:菠萝,价格:1.5,评价:4 名称:橙子,价格:2.2,评价:3 ``` 其他场景:线程入口函数、智能指针定制删除器、回调函数等。 ### 10.4 Lambda 的原理 底层:编译器会将 lambda 生成一个匿名仿函数类,operator() 对应 lambda 函数体,捕捉列表变量作为该类的成员变量,并在构造时初始化。 等价代码: ```cpp // lambda 写法 auto r2 = [rate](double money, int year) { return money * rate * year; }; // 编译器生成的仿函数等价于 class lambda_1 { private: double _rate; public: lambda_1(double rate) : _rate(rate) {} double operator()(double money, int year) { return money * _rate * year; } }; auto r2 = lambda_1(rate); ``` 代码示例 ```cpp #include using namespace std; // 仿函数类:用于计算利息/收益 class Rate { public: // 构造函数:传入利率,初始化成员变量 _rate Rate(double rate) : _rate(rate) {} // 重载 () 运算符,使对象可以像函数一样调用 // 参数:money 本金,year 存期/年数 // 返回值:总收益 = 本金 * 利率 * 年数 double operator()(double money, int year) { return money * _rate * year; } private: double _rate; // 利率成员变量 }; // main 函数不写注释,其余部分完整标注 int main() { double rate = 0.49; // [rate]:值捕获外部变量rate; 参数:money,year; 功能:和仿函数 Rate 一模一样 auto r2 = [rate](double money, int year) { return money * rate * year; }; Rate r1(rate); // 构造仿函数对象,传入利率 r1(10000, 2); // 调用仿函数 r2(10000, 2); // 调用 Lambda // 简单无参 Lambda,无捕获、无参数,调用后输出:hello world auto func1 = [] { cout << "hello world" << endl; }; func1(); return 0; } ``` 核心总结 1. 仿函数:通过类 + 重载 operator() 实现,可携带状态(如 _rate)。 2. Lambda:语法糖,底层也是生成类,\[捕获\] 就相当于仿函数的成员变量。 3. 本例中:Rate r1(rate) 对应 \[rate\];operator()(money, year) 对应 (double money, int year) 两者功能、性能完全一致,Lambda 写法更简洁。 ## 十一、包装器(std::function 与 std::bind) ### 11.1 std::function 定义:\ 头文件中的类模板,是可调用对象的包装器,统一封装函数指针、仿函数、lambda、bind 表达式等。 原型: template \ class function\; 用法: ```cpp #include int f(int a, int b) { return a + b; } struct Functor { int operator()(int a, int b) { return a + b; } }; int main() { // 包装普通函数 function f1 = f; // 包装仿函数 function f2 = Functor(); // 包装 lambda function f3 = [](int a, int b) { return a + b; }; // 包装静态成员函数 function f4 = &Plus::plusi; // 包装普通成员函数(需绑定对象) function f5 = &Plus::plusd; return 0; } ``` 代码示例 ```cpp #include #include // 标准库包装器头文件,包含function、bind等 #include #include using namespace std; // 普通全局函数 int f(int a, int b) { return a + b; } // 仿函数(函数对象):重载operator(),可像函数一样调用 struct Functor { public: int operator() (int a, int b) { return a + b; } }; // 测试类:包含静态成员函数、普通成员函数 class Plus { public: // 构造函数,默认_n=10 Plus(int n = 10) :_n(n) {} // 静态成员函数:属于类,无this指针,可直接通过类名调用 static int plusi(int a, int b) { return a + b; } // 非静态成员函数:依赖对象调用,隐含this指针,使用成员变量_n double plusd(double a, double b) { return (a + b) * _n; } private: int _n; // 成员变量,用于放大计算结果 }; // main函数内部逐行注释 int main() { // function<返回值(参数类型列表)>:统一包装可调用对象 // 包装普通全局函数 function f1 = f; // 包装仿函数对象 function f2 = Functor(); // 包装lambda表达式 function f3 = [](int a, int b) {return a + b; }; cout << f1(1, 1) << endl; // 调用普通函数 cout << f2(1, 1) << endl; // 调用仿函数 cout << f3(1, 1) << endl; // 调用lambda // 包装类的静态成员函数:必须加&,指定类域Plus:: function f4 = &Plus::plusi; cout << f4(1, 1) << endl; // 包装非静态成员函数:第一个参数必须是对象指针/对象/右值引用 // 函数原型:double plusd(double a, double b),隐含this,因此function参数列表为(Plus*, double, double) function f5 = &Plus::plusd; Plus pl; // 定义Plus对象 cout << f5(&pl, 1.111, 1.1) << endl; // 传入对象地址 // 参数为对象值(拷贝传参) function f6 = &Plus::plusd; cout << f6(pl, 1.1, 1.1) << endl; // 传入对象 cout << f6(Plus(), 1.1, 1.1) << endl; // 传入匿名对象 // 参数为右值引用(移动语义) function f7 = &Plus::plusd; cout << f7(move(pl), 1.1, 1.1) << endl; // 转移pl的所有权 cout << f7(Plus(), 1.1, 1.1) << endl; // 绑定临时对象 // map + function 实现字符串到运算逻辑的映射(策略模式/命令分发) // 常用于计算器、逆波兰表达式求值等场景 map> opFuncMap = { {"+", [](int x, int y) {return x + y; }}, // 加法 {"-", [](int x, int y) {return x - y; }}, // 减法 {"*", [](int x, int y) {return x * y; }}, // 乘法 {"/", [](int x, int y) {return x / y; }}, // 除法 {"&", [](int x, int y) {return x + y; }}, // 按位与(示例复用加法) {"|", [](int x, int y) {return x | y; }}, // 按位或 {"^", [](int x, int y) {return x ^ y; }} // 按位异或 }; return 0; } ``` 1. f1(1, 1) → 2 2. f2(1, 1) → 2 3. f3(1, 1) → 2 4. f4(1, 1) → 2 5. f5(\&pl, 1.111, 1.1) → (1.111+1.1)\*10 = 22.11 6. f6(pl, 1.1, 1.1) → (1.1+1.1)\*10 = 22 7. f6(Plus(), 1.1, 1.1) → 22 8. f7(move(pl), 1.1, 1.1) → 22 9. f7(Plus(), 1.1, 1.1) → 22 应用:逆波兰表达式求值,用 map\\> 映射运算符,便于扩展。 ### 11.2 std::bind 定义:\ 头文件中的函数模板,是函数适配器,可调整可调用对象的参数个数、参数顺序,返回新的可调用对象。 原型: template \ auto bind(Fn\&\& fn, Args\&\&... args); 占位符:_1、_2... 来自 std::placeholders,代表新可调用对象的参数位置。 用法示例: ```cpp int Sub(int a, int b) { return (a - b) * 10; } // 1. 保持原参数顺序 auto sub1 = bind(Sub, _1, _2); // 2. 交换参数顺序 auto sub2 = bind(Sub, _2, _1); // 3. 绑定固定参数(减少参数个数) auto sub3 = bind(Sub, 100, _1); // 等价于 Sub(100, x) // 4. 绑定成员函数(自动绑定 this) function f7 = bind(&Plus::plusd, Plus(), _1, _2); ``` 代码示例 ```cpp #include #include using namespace std; using namespace placeholders; // 直接使用 _1, _2, _3 int Sub(int a, int b) { return (a - b) * 10; } int SubX(int a, int b, int c) { return (a - b - c) * 10; } class Plus { public: Plus(int n = 10) :_n(n) {} double plusd(double a, double b) { return (a + b) * _n; } private: int _n; }; int main() { // 1. 正常绑定,参数顺序不变:a=_1, b=_2 auto sub1 = bind(Sub, _1, _2); cout << "sub1(10, 5) = " << sub1(10, 5) << endl; // 2. 交换参数顺序:a=_2, b=_1 auto sub2 = bind(Sub, _2, _1); cout << "sub2(10, 5) = " << sub2(10, 5) << endl; // 3. 绑死第一个参数为100,只传一个参数给b auto sub3 = bind(Sub, 100, _1); cout << "sub3(5) = " << sub3(5) << endl; // 4. 绑死第二个参数为100,只传一个参数给a auto sub4 = bind(Sub, _1, 100); cout << "sub4(5) = " << sub4(5) << endl; // 5. 三参数函数 SubX 绑定演示 // 绑死a=100,b=_1, c=_2 auto sub5 = bind(SubX, 100, _1, _2); cout << "sub5(5, 1) = " << sub5(5, 1) << endl; // 绑死b=100,a=_1, c=_2 auto sub6 = bind(SubX, _1, 100, _2); cout << "sub6(5, 1) = " << sub6(5, 1) << endl; // 绑死c=100,a=_1, b=_2 auto sub7 = bind(SubX, _1, _2, 100); cout << "sub7(5, 1) = " << sub7(5, 1) << endl; // 6. 绑定成员函数:绑死匿名对象,后续只传两个double参数 function f6 = bind(&Plus::plusd, Plus(), _1, _2); cout << "f6(1.1, 1.1) = " << f6(1.1, 1.1) << endl; return 0; } ``` ```cpp sub1(10, 5) = 50 sub2(10, 5) = -50 sub3(5) = 950 sub4(5) = -950 sub5(5, 1) = 940 sub6(5, 1) = -960 sub7(5, 1) = -960 f6(1.1, 1.1) = 22 ``` 核心知识点总结 1. std::bind 作用:调整参数顺序;绑定固定值,减少参数个数;绑定成员函数时,自动绑定对象,简化调用 2. 占位符 _1, _2, _3:代表新函数调用时的第1、2、3个实参 3. 成员函数绑定要点:bind(\&类名::成员函数, 对象, _1, _2...);对象可以是:值、指针、std::move 对象、匿名对象 4. 常用场景:把多参数函数适配成少参数;把成员函数适配成普通二元/一元函数,配合 function 使用 应用场景:固定部分参数,生成新的专用函数(如固定利率、年份,计算复利)。 ```cpp #include #include using namespace std; using namespace placeholders; int main() { // 计算复利利息的lambda表达式 // 参数:rate 年利率, money 本金, year 存期 // 公式:本息和 = 本金 * (1+rate)^year // 返回值:利息 = 本息和 - 本金 auto func1 = [](double rate, double money, int year)->double { double ret = money; // 循环复利计算 for (int i = 0; i < year; i++) { ret += ret * rate; // 等价于 ret *= (1 + rate) } return ret - money; // 最终返回纯利息 }; // 使用bind绑死利率、年限,只保留本金作为参数 // 格式:func3_1_5 代表 3年期、1.5%利率 function func3_1_5 = bind(func1, 0.015, _1, 3); function func5_1_5 = bind(func1, 0.015, _1, 5); function func10_1_5 = bind(func1, 0.015, _1, 10); // 2.5% 利率,不同年限 function func3_2_5 = bind(func1, 0.025, _1, 3); function func5_2_5 = bind(func1, 0.025, _1, 5); function func10_2_5 = bind(func1, 0.025, _1, 10); // 传入本金 1000000,计算利息 cout << func3_1_5(1000000) << endl; cout << func5_1_5(1000000) << endl; cout << func10_1_5(1000000) << endl; cout << func3_2_5(1000000) << endl; cout << func5_2_5(1000000) << endl; cout << func10_2_5(1000000) << endl; return 0; } ``` 运行输出结果(利息,本金 1000000) ```cpp 45678.375 77284.003884 160540.825025 76890.625 131408.212891 280084.544196 ``` bind 绑参逻辑 bind(func1, 0.015, _1, 3) 第1参数:固定为 0.015(利率) 第2参数:_1 → 调用时传入的第1个参数(本金) 第3参数:固定为 3(年限) 最终得到只需要传本金的新函数。 ## 十二、逆波兰表达式求值(function 应用示例) ```cpp // 传统写法 class Solution { public: int evalRPN(vector& tokens) { stack st; for (auto& str : tokens) { if (str == "+" || str == "-" || str == "*" || str == "/") { int right = st.top(); st.pop(); int left = st.top(); st.pop(); switch (str[0]) { case '+': st.push(left + right); break; case '-': st.push(left - right); break; case '*': st.push(left * right); break; case '/': st.push(left / right); break; } } else { st.push(stoi(str)); } } return st.top(); } }; // function + map 扩展写法 class Solution { public: int evalRPN(vector& tokens) { stack st; map> opFuncMap = { {"+", [](int x, int y) { return x + y; }}, {"-", [](int x, int y) { return x - y; }}, {"*", [](int x, int y) { return x * y; }}, {"/", [](int x, int y) { return x / y; }} }; for (auto& str : tokens) { if (opFuncMap.count(str)) { int right = st.top(); st.pop(); int left = st.top(); st.pop(); st.push(opFuncMap[str](left, right)); } else { st.push(stoi(str)); } } return st.top(); } }; ```

相关推荐
panzer_maus1 小时前
Java多线程介绍
java·开发语言
炽烈小老头2 小时前
【 每天学习一点算法 2026/03/14】二叉搜索树中第K小的元素
学习·算法
别催小唐敲代码2 小时前
个人笔记网站搭建完整教程
笔记·学习·个人博客
echome8882 小时前
Python 装饰器详解:从入门到实战的完整指南
开发语言·python
一条大祥脚2 小时前
WQS二分(Alien Trick)
算法
xiaoye-duck2 小时前
《算法题讲解指南:递归,搜索与回溯算法--二叉树中的深搜》--6.计算布尔二叉树的值,7.求根节点到叶节点数字之和
c++·算法·深度优先·递归
AMoon丶2 小时前
Golang--多种控制结构详解
java·linux·c语言·开发语言·后端·青少年编程·golang
greatofdream2 小时前
VIP和普通用户排队
算法
小鸡脚来咯2 小时前
正则表达式考点
java·开发语言·前端