【可变参数模板 + empalce接口 + 新的类功能】目录
- 前言:
- [------------ 可变参数模板 ------------](#------------ 可变参数模板 ------------)
-
- [1. 什么是可变参数模板?](#1. 什么是可变参数模板?)
- [2. 怎么使用可变参数模板?](#2. 怎么使用可变参数模板?)
- [3. 参数包的关键特性有哪些?](#3. 参数包的关键特性有哪些?)
- [4. 参数包可以干什么?](#4. 参数包可以干什么?)
- [5. 什么是参数包展开?](#5. 什么是参数包展开?)
- [6. 怎么进行参数包的展开?](#6. 怎么进行参数包的展开?)
-
- [6.1:递归展开(C++11 经典方式)](#6.1:递归展开(C++11 经典方式))
- [6.2:折叠表达式(C++17 简化方式)](#6.2:折叠表达式(C++17 简化方式))
- [------------ empalce系列接口 ------------](#------------ empalce系列接口 ------------)
-
- [1. 什么是emplace系列接口?](#1. 什么是emplace系列接口?)
- [2. emplace 系列接口是怎么实现的?](#2. emplace 系列接口是怎么实现的?)
- [3. emplace 系列接口的核心优势是什么?](#3. emplace 系列接口的核心优势是什么?)
- [4. emplace 系列接口为什么要实现完美转发?](#4. emplace 系列接口为什么要实现完美转发?)
- [5. emplace 系列接口在list容器种的使用](#5. emplace 系列接口在list容器种的使用)
- [6. 使用emplace系列接口有什么建议?](#6. 使用emplace系列接口有什么建议?)
- [------------ 新的类功能 ------------](#------------ 新的类功能 ------------)
- 一、默认函数
- 二、缺省值
- 三、defult和delete
- 四、final与override
- 五、STL新变化

往期《C++初阶》回顾:
往期《C++进阶》回顾:
/------------ 继承多态 ------------/
【普通类/模板类的继承 + 父类&子类的转换 + 继承的作用域 + 子类的默认成员函数】
【final + 继承与友元 + 继承与静态成员 + 继承模型 + 继承和组合】
【多态:概念 + 实现 + 拓展 + 原理】/------------ STL ------------/
【二叉搜索树】
【AVL树】
【红黑树】
【set/map 使用介绍】
【set/map 模拟实现】
【哈希表】
【unordered_set/unordered_map 使用介绍】
【unordered_set/unordered_map 模拟实现】/------------ C++11 ------------/
【列表初始化 + 右值引用】
【移动语义 + 完美转发】
前言:
"hi~小伙伴们大家......"
评论区就传来一句带着点小委屈的质问(。•́︿•̀。) :"打住打住!你昨天怎么没按时更新呀?" 原来是位蹲点等内容的小伙伴,语气里满是 "没学够" 的着急。
鼠鼠赶紧把食指凑到嘴边,压低声音 "嘘 ------ 嘘 ------" (。>﹏<。) 了两声,又飞快地从随身的小口袋里掏出一颗包装亮晶晶的水果糖,偷偷塞到这位小伙伴手里,眼睛弯成月牙:"别跟其他人说哦~这是迟到的小补偿,今天咱们把内容补得满满当当的!" (。•̀ᴗ-)✧ ʕ•̀ω•́ʔ♡
好啦,言归正传!今天咱们继续深入 C++11 的新特性,重点聊聊 【可变参数模板 + emplace 接口 + 新的类功能】 这三组 "提速小能手"。鼠鼠一直觉得,C++ 每一次更新迭代,核心思路都离不开 "让代码跑得更快一点、让开发更灵活一点"------ 毕竟在实际开发里,效率和灵活性可是能帮咱们少走很多弯路的关键。(≧ڡ≦*)ゞ
而今天要学的这三个特性,正是把 "快" 和 "灵活" 贯彻得淋漓尽致的典型,咱们一个个拆开揉碎了讲明白! ٩(ˊᗜˋ)و ✧(≖‿‿≖✿) ʕ•̀ω•́ʔ✧
------------ 可变参数模板 ------------
1. 什么是可变参数模板?
可变参数模板(Variadic Templates)
:是 C++11 引入的一项强大特性,允许模板(函数模板或类模板)接受数量不确定的参数。
- 这些参数可以是
任意类型
、任意数量
,极大地增强了模板的灵活性和表达能力- 这些参数被称为 "参数包",可以分为两大类
可变参数模板的核心是 "参数包" 的分类:
模板参数包
:表示零个或多个模板参数类型
- 例如 :
class... Args
函数参数包
:表示零个或多个函数调用参数
- 例如 :
Args... args
2. 怎么使用可变参数模板?
可变参数模板的基本语法 与声明形式:
1. 类模板的可变参数声明
cpp
//可变参数模板类:Tuple
// 1. 接收任意数量、任意类型的模板参数(如:Tuple<int, string, double>)
// 2. 底层使用 std::tuple 存储数据
template <class... Args> //注意:Args是模板参数包
class Tuple
{
private:
std::tuple<Args...> _data;
/* 数据成员:使用参数包实例化的 std::tuple
*
* Args... 会被展开为具体类型列表
* 如:Tuple<int, string> 会实例化为 std::tuple<int, string>
*/
public:
Tuple(Args... args) //注意:args是函数参数包
: _data(args...) //初始化列表:用参数包 args 初始化 std::tuple
{ }
/* 构造函数:接收与模板参数匹配的任意数量实参
* 注意:Args... args:函数参数包,对应模板参数包 Args... 的实例
*
* 参数包展开规则:
* 若模板实例化为 Tuple<int, string>,则构造函数等价于:
* Tuple(int arg1, string arg2)
* :_data(arg1, arg2)
* {}
*/
};

2. 函数模板的可变参数声明
cpp/*------------------值传递------------------*/ template <class... Args> void Func(Args... args) //注意:args是函数参数包 { // args 是函数参数包,可接收任意类型、任意数量的参数 } /*------------------左值引用------------------*/ template <class... Args> void Func(Args&... args) //注意:args是函数参数包 { // 每个参数都是左值引用,需绑定到左值(如:变量) } /*------------------万能引用------------------*/ template <class... Args> void Func(Args&&... args) //注意:args是函数参数包 { // 每个参数都是万能引用(转发引用),可绑定左值或右值 }
class... Args
:模板参数包,可接收任意数量的类型(如 :int
、double
、string
等)Args... args
:函数参数包,可接收与模板参数包类型匹配的任意数量实参Args&... args
:函数参数包的每个参数都是左值引用
,需绑定到左值(如:变量)Args&&... args
:函数参数包的每个参数都是万能引用
(转发引用),可绑定左值/右值,结合引用折叠规则适配实参类型
3. 参数包的关键特性有哪些?
1. 编译期类型推导
编译器会根据传入的实参 ,自动推导模板参数包的具体类型。
cppFunc(1, 2.3, "hello"); // 推导结果: // Args = int, double, const char* // args = 1 (int), 2.3 (double), "hello" (const char*)
2. 引用折叠规则(针对万能引用包)
对于
Args&&... args
形式的万能引用参数包:
- 传入左值(如:变量
a
)→Args
推导为int&
→ 折叠为int&
(左值引用)- 传入右值(如:
10
、std::move(a)
)→Args
推导为int
→ 折叠为int&&
(右值引用)
4. 参数包可以干什么?
1. 用 sizeof... 计算参数个数
- sizeof...(Args) 或 sizeof...(args) 可在编译期计算参数包的元素数量:
cpp
#include <iostream>
using namespace std;
template <class... Args>
void Func(Args... args)
{
cout << "参数个数:" << sizeof...(args) << endl;
}
int main()
{
// 调用:
Func(1, 2.3, "hello"); // 输出:参数个数:3
return 0;
}

2. 完美转发(std::forward + 可变参数包)
- 结合
std::forward + 可变参数包
,精准转发任意参数
cpp
#include <iostream>
#include <string>
#include <utility> // 包含 std::move 和 std::forward 的必要头文件
using namespace std;
/*-------------------定义Object类-------------------*/
class Object
{
private:
int m_value; //整型成员变量
std::string m_str; //字符串成员变量
Object* m_ptr = nullptr; //对象指针成员,初始化为nullptr
public:
//1.构造函数:接收多种类型参数
Object(int value, const string& str, Object&& other)
: m_value(value)
, m_str(str) //注:初始化m_str(调用string的拷贝构造函数)
{
//1.处理右值参数:转移 other 的资源
//1.1:接管other的资源
m_ptr = other.m_ptr;
//1.2:将other的资源置空,防止双重释放
other.m_ptr = nullptr;
cout << "Object 构造完成" << endl;
}
// 新增构造函数:支持用 nullptr 初始化 m_ptr
Object(int value, const string& str, nullptr_t)
: m_value(value)
, m_str(str)
, m_ptr(nullptr) //注:显式将指针成员初始化为 nullptr
{
cout << "Object 构造完成(nullptr 版本)" << endl;
}
//2.析构函数
~Object()
{
delete m_ptr; //注意:m_str是栈对象,会自动调用其析构函数
}
};
/*-------------------全局对象指针-------------------*/
Object* obj = nullptr; //实际应用中可能是 "类成员变量"
/*-------------------可变参数模板函数-------------------*/
template <class... Args>
void Emplace(Args&&... args)
{
obj = new Object(std::forward<Args>(args)...); //完美转发参数构造 Object 对象
/* 关键逻辑详解:
* 1. std::forward<Args>(args)...:
* - 这是一个参数包展开,对每个参数应用std::forward
* - 保持参数的原始值类别(左值/右值)
* - 展开后相当于:std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ...
*
* 2. new Object(...):
* - 直接在堆上构造Object对象
* - 避免了创建临时对象再拷贝的开销
* - 完美转发确保调用最匹配的构造函数
*/
}
int main()
{
//1.准备参数
Object tempObj(5, "temp", nullptr);
//2.调用 Emplace,参数会按原始属性转发:
Emplace(10, "hello", move(tempObj));
/* 参数转发分析:
* - 10: 右值(字面量)→ 转发为 int&&
* - "hello": 右值(const char[6]转换为临时string)→ 转发为 string&&
* - std::move(tempObj):显式转换为右值 → 转发为 Object&&
*
* 注意:调用后tempObj的资源已被转移,不应再使用
*/
//3.使用 obj...
//4.释放内存
delete obj; // 实际应用中建议使用unique_ptr或shared_ptr管理内存
return 0;
}
5. 什么是参数包展开?
对于 参数包 (模板参数包或函数参数包),最核心的操作是 "展开"。
除了用
sizeof...
计算参数个数外,参数包的主要价值体现在按规则分解为单个元素,并对每个元素应用相同逻辑。
参数包展开
:是 C++ 可变参数模板的核心操作,用于将参数包(Args...
或args...
)分解为独立的元素,并对每个元素应用相同的处理逻辑。在可变参数模板中,参数包 是一个未展开的参数集合 (类似 "黑盒"),必须通过特定语法显式展开才能使用。
参数包展开的本质是:
分解参数包
:将参数包(如:Args...
或args...
)拆分为独立的类型/值
- (如:
int, double, string
或1, 2.3, "hello"
)应用统一模式
:为每个分解出的元素定义同的处理逻辑
- (如:函数调用、运算符操作)
6. 怎么进行参数包的展开?
参数包扩展的过程可拆解为:
分解参数包:将参数包拆分为独立的元素。
- (如:
Args...
拆为arg1, arg2, ..., argN
)应用模式:为每个元素定义统一的 "处理模式"。
- (如:函数调用、运算符操作 )
触发扩展 :通过在模式右侧添加
...
(省略号),告知编译器对参数包逐个元素应用模式,生成扩展后的代码。
参数包扩展的方式:
- 递归展开(C++11 经典方式)
- 折叠表达式(C++17 简化方式)
6.1:递归展开(C++11 经典方式)
(1)递归展开(C++11 经典方式)
- 通过 递归调用 或逗号表达式,可逐个处理参数包的元素(C++17 后支持更简洁的折叠表达式):
cpp
#include <iostream>
using namespace std;
/*------------------递归终止函数------------------*/
template <class T>
void Print(T arg)
{
cout << arg << endl; //注:输出最后一个参数并换行
}
/* 注意事项:处理最后一个参数(仅剩一个参数时调用)
*
* 1. 模板参数 T:捕获最后一个参数的类型
* 2. 参数 arg:最后一个实参的值
*/
/*------------------递归展开函数------------------*/
template <class T, class... Args>
void Print(T arg, Args... args)
{
Print(arg); // 1. 处理当前第一个参数(递归调用终止函数)
Print(args...); // 2. 递归处理剩余参数包(展开为 Print(arg1, arg2, ...))
}
/*------------------主函数------------------*/
int main()
{
Print(1, 2.3, "hello");
return 0;
}
/* 注意事项:处理多个参数
*
* 1. 模板参数 T: 捕获第一个参数的类型
* 2. 模板参数包 Args...:捕获剩余参数的类型列表
*
* 3. 参数 arg: 第一个实参的值
* 4. 参数包 args...: 剩余实参的值列表
*/
// 调用示例:Print(1, 2.3, "hello");
// 编译器实例化过程:
// 1. 初始调用:Print<int, double, const char*>(1, 2.3, "hello")
// - 匹配多参数版本,实例化为:
// void Print(int arg, double, const char*)
// {
// Print(arg); // 调用 Print<int>(1)
// Print(2.3, "hello"); // 递归调用剩余参数
// }
//
// 2. 第一次递归:Print<double, const char*>(2.3, "hello")
// - 匹配多参数版本,实例化为:
// void Print(double arg, const char*)
// {
// Print(arg); // 调用 Print<double>(2.3)
// Print("hello"); // 递归调用剩余参数
// }
//
// 3. 第二次递归:Print<const char*>("hello")
// - 匹配终止函数,实例化为:
// void Print(const char* arg)
// {
// cout << arg << endl; // 输出 "hello"
// }

实例化流程 :以
Print(1, 2.3, "hello")
为例
递归层级 | 函数实例化 | 参数传递 | 执行动作 |
---|---|---|---|
1 | Print<int, double, const char*> |
arg=1 , args=2.3, "hello" |
输出 1 , 递归调用 Print(2.3, "hello") |
2 | Print<double, const char*> |
arg=2.3 , args="hello" |
输出 2.3 , 递归调用 Print("hello") |
3 | Print<const char*> |
arg="hello" |
输出 hello ,终止递归 |
代码案例 2:参数包的递归展开
cpp
/*----------------------------- 通过递归展开参数包实现任意参数打印 -----------------------------*/
#include <iostream>
#include <string>
using namespace std;
// ====================== 错误示例(说明参数包不能直接遍历) ======================
// 以下代码演示:参数包无法通过运行时循环遍历(编译时特性 vs 运行时逻辑)
/*
* template <class... args>
* void print(args... args)
* {
* // 错误:sizeof...(args) 是编译期常量,但 args 不是数组,无法用 [] 访问
* cout << sizeof...(args) << endl;
* for (size_t i = 0; i < sizeof...(args); ++i)
* {
* cout << args[i] << " "; // 编译报错:参数包不是容器,无 [] 运算符
* }
* }
*/
// ====================== 递归展开:ShowList 系列 ======================
//1. 递归终止条件:参数包为空时调用(匹配无参数的情况)
void ShowList()
{
cout << endl; // 换行,结束递归打印
}
//2. 递归展开函数:每次处理参数包的第一个元素,剩余参数继续递归
template <class T, class... Args>
void ShowList(T x, Args... args)
{
//1.打印当前第一个参数
cout << x << " ";
//2.递归调用:将剩余参数包 args... 继续传递给 ShowList,触发下一层展开
ShowList(args...);
}
// ====================== 封装 Print 接口 ======================
template <class... Args>
void Print(Args... args)
{
//1.调用 ShowList 展开参数包,实现任意参数打印
ShowList(args...);
}
// ====================== main 函数测试 ======================
int main()
{
// 测试不同参数数量的调用:
Print(); // 无参数 → 直接换行
Print(1); // 1个参数 → ShowList(1) → ShowList()
Print(1, string("xxxxx")); // 2个参数 → ShowList(1, "xxxxx") → ShowList("xxxxx") → ShowList()
Print(1, string("xxxxx"), 2.2);// 3个参数 → 逐层展开
return 0;
}
/*
// ====================== 编译器实例化过程(伪代码演示) ======================
// 当调用 Print(1, string("xxxxx"), 2.2) 时,编译器会自动生成以下展开:
//1. 最外层 Print 调用:
void Print(int, string, double)
{
ShowList(1, string("xxxxx"), 2.2);
}
//2. 第一次 ShowList 展开(处理 int):
void ShowList(int x, string, double)
{
cout << x << " "; // 输出: 1
ShowList(string("xxxxx"), 2.2);
}
//3. 第二次 ShowList 展开(处理 string):
void ShowList(string x, double)
{
cout << x << " "; // 输出: xxxxx
ShowList(2.2);
}
//4. 第三次 ShowList 展开(处理 double):
void ShowList(double x)
{
cout << x << " "; // 输出: 2.2
ShowList();
}
//5. 递归终止:
void ShowList()
{
cout << endl; // 输出换行
}
*/


6.2:折叠表达式(C++17 简化方式)
(2)折叠表达式(C++17 简化方式)
利用 ... 折叠表达式,无需递归即可展开参数包:
cpp#include <iostream> #include <string> using namespace std; /* 可变参数模板函数:使用折叠表达式展开参数包 * * 1. Args...:模板参数包,接收任意数量、任意类型的参数 * 2. args...:函数参数包,对应模板参数包的实例 */ template <class... Args> void Print(Args... args) { (cout << ... << args) << endl; /* 折叠表达式:展开参数包并通过 << 运算符连接 * 展开过程:(cout << arg1 << arg2 << ... << argN) << endl; * * 注意:需要确保所有参数类型都支持 << 运算符(如:内置类型、string 等) */ } int main() { /*----------------混合类型参数----------------*/ Print(1, " ", 2.3); // 输出:1 2.3 /* 调用示例:参数类型包括 int、const char*、double * * 展开结果:(cout << 1 << " " << 2.3) << endl; */ string name = "Alice"; Print("Name:", name, ", Age:", 20); // 输出:Name: Alice, Age: 20 /*----------------空参数调用----------------*/ Print(); // 输出空行 return 0; }
/---------- 折叠表达式(简单介绍)----------/
1. 折叠表达式语法:
折叠表达式是 C++17 的特性,用于简化可变参数模板的展开:
一元右折叠:
cpp(expr1 << ... << exprN)
- 展开顺序 :从左到右,等价于
((expr1 << expr2) << ... << exprN)
一元左折叠:
cpp(... << expr)
- 展开顺序 :从右到左,等价于
(expr1 << (expr2 << ... << exprN))
2. 参数包展开过程
以
Print(1, " ", 2.3)
为例:
模板参数包
Args
推导为int, const char*, double
函数参数包
args
对应为1, " ", 2.3
折叠表达式
cpp(cout << ... << args)
展开为:
cpp((cout << 1) << " ") << 2.3
最终输出:
1 2.3
3. 类型要求
折叠表达式要求所有参数类型都支持 << 运算符:
内置类型
(如:int
、double
、char*
)标准库类型
(如:std::string
、std::vector
等,需重载<<
)自定义类型
(需显式重载<<
运算符)
4. 与递归展开的对比
与传统递归展开方式(C++11/14)相比折叠表达式(C++11)的优势:
代码更简洁
---> 无需手动编写递归终止函数展开效率更高
---> 编译期一次性展开,而非递归调用
------------ empalce系列接口 ------------
1. 什么是emplace系列接口?
emplace系列接口
:是 C++11 为 STL 容器新增的高效插入操作接口
,用于直接在容器内部构造元素,而非先构造再拷贝或移动。
- 核心解决 "就地构造对象" 的问题,避免额外的 拷贝/移动 开销
- 这些接口提供了更高的效率,特别是在处理复杂对象时
emplace(原位构造)系列接口允许你:
- 直接在容器内存中构造对象
- 避免不必要的拷贝或移动操作
- 完美转发参数给元素的构造函数
2. emplace 系列接口是怎么实现的?
一、核心定义:可变参数模板接口
emplace
系列接口通过可变参数模板 实现,典型声明(以vector
为例):
cpp// 1. 在容器末尾构造元素(类似 push_back) template <class... Args> void emplace_back (Args&&... args); // 2. 在指定位置构造元素(类似 insert) template <class... Args> iterator emplace (const_iterator position, Args&&... args);
Args&&... args
:万能引用参数包,接收任意类型、任意数量的参数核心功能:直接用参数构造容器元素,而非先构造元素再插入。
3. emplace 系列接口的核心优势是什么?
二、emplace 系列的核心优势
emplace
系列接口通过可变参数模板 + 完美转发,实现两大关键特性:1. 兼容 push/insert 功能并且更高效
功能覆盖
:emplace_back
可替代push_back
,emplace
可替代insert
效率提升
:直接在容器内存中构造对象,跳过临时对象的 拷贝/移动
2. 支持 "参数直接构造"
对于容器
container<T>
,emplace
系列允许直接传入构造T
对象所需的参数 ,而非T
类型的对象本身。
cppstd::vector<std::string> vec; // push_back:需先构造临时 string 对象 vec.push_back(std::string("hello")); // emplace_back:直接传入构造参数,容器内直接构造 string vec.emplace_back("hello");
三、高效的本质:就地构造
传统 push/insert 流程:
构造临时对象 → 拷贝/移动到容器 → 销毁临时对象
cpp//传统 push_back/insert 流程: std::vector<std::string> vec; vec.push_back(std::string("hello")); /* 步骤: * 1. 构造临时 string 对象("hello") * 2. 拷贝/移动到容器内存 * 3. 销毁临时对象 */
emplace 系列流程:
直接在容器内存中构造对象(跳过临时对象的额外开销)
cpp//emplace_back 流程: vec.emplace_back("hello"); /* 步骤: * 直接在容器内存中构造 string 对象(用 "hello" 作为参数) */
差异 :
emplace
跳过 "临时对象构造 → 拷贝 → 销毁" 的额外开销,尤其适合构造复杂对象时提升效率。
4. emplace 系列接口为什么要实现完美转发?
四、完美转发的必要性:
emplace
系列通过Args&&... args
(万能引用参数包)接收参数。
emplace
系列通过std::forward<Args>(args)...
实现完美转发。确保:
- 左值参数保持左值属性(调用拷贝构造)
- 右值参数保持右值属性(调用移动构造)
emplace
系列需配合std::forward<Args>(args)...
实现完美转发,传递参数包时需用std::forward
保留值类别,确保右值属性不丢失。若不完美转发,右值引用参数会退化为左值,导致构造时调用拷贝构造(而非移动构造),失去性能优势。
cpptemplate <class... Args> void emplace_back (Args&&... args) { // 关键:用 forward 保留参数的左值/右值属性 construct_in_place(std::forward<Args>(args)...); }
5. emplace 系列接口在list容器种的使用
头文件:List.h
cpp
#include <iostream>
#include <utility> // 用于 std::move、std::forward
using namespace std;
namespace mySpace
{
/*------------------------"链表节点"模板类------------------------*/
template<class T>
struct ListNode
{
/*----------------成员变量----------------*/
//1.指向下一个节点的指针
//2.指向前一个节点的指针
//3.存储节点数据
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
/*----------------成员函数----------------*/
//1.实现:"拷贝构造节点的构造函数"(接收左值引用)
ListNode(T& data)
: _next(nullptr)
, _prev(nullptr)
, _data(data) // 直接拷贝数据
{ }
//2.实现:"移动构造节点的构造函数" (接收右值引用)
ListNode(T&& data)
: _next(nullptr)
, _prev(nullptr)
, _data(std::move(data)) // 移动语义,减少拷贝
{ }
//3.实现:"可变参数模板构造函数"(用于 emplace 系列接口,完美转发参数构造数据)
template <class... Args>
ListNode(Args&&... args)
: _next(nullptr)
, _prev(nullptr)
, _data(std::forward<Args>(args)...) //注意:完美转发参数包,直接在节点中构造数据对象
{ }
};
/*------------------------"链表迭代器"模板类------------------------*/
template<class T, class Ref, class Ptr>
struct ListIterator
{
/*----------------类型别名----------------*/
//1.重命名"链表的节点"的类型:ListNode<T> ---> Node
using Node = ListNode<T>;
//2.重命名"链表的迭代器"的类型:ListIterator<T,Ref,Ptr> ---> Self
using Self = ListIterator<T, Ref, Ptr>;
/*----------------成员变量----------------*/
//1.指向当前节点的指针
Node* _node;
/*----------------成员函数----------------*/
//1.实现:"迭代器的构造函数"
ListIterator(Node* node)
: _node(node)
{
}
//2.实现:"前置++运算符重载"
Self& operator++() //目的:用于迭代器向后移动
{
_node = _node->_next;
return *this;
}
//3.实现:"前置--运算符重载"
Self& operator--() //目的:用于迭代器向前移动
{
_node = _node->prev;
return *this;
}
//4.实现:"解引用运算符重载"
Ref operator*() //注意:返回节点数据的引用(支持 const/非const 场景)
{
return _node->_data;
}
//5.实现:"箭头运算符"
Ptr operator->() //目的:用于通过迭代器访问数据成员(常配合指针类型数据使用)
{
return &_node->_data;
}
//6.实现:"不等于比较"
bool operator!=(const Self& it) const //目的:判断迭代器是否指向不同节点
{
return _node != it._node;
}
};
/*------------------------"链表容器"模板类------------------------*/
template<class T>
class list
{
private:
/*------------------类型别名------------------*/
//1.重命名链表节点的类型:ListNode<T> ---> Node
using Node = ListNode<T>;
/*------------------成员变量------------------*/
Node* _head; //指向哨兵头节点的指针
public:
/*------------------类型别名------------------*/
//1.重命名链表的"普通迭代器"的类型:ListIterator<T, T&, T*> ---> iterator
using iterator = ListIterator<T, T&, T*>;
//2.重命名链表的"常量迭代器"的类型:ListIterator<T, const T&, const T*> ---> const_iterator
using const_iterator = ListIterator<T, const T&, const T*>;
/*------------------成员函数(公有)------------------*/
//1.实现:"获取起始位置的迭代器"
iterator begin()
{
return iterator(_head->_next);//哨兵头节点的 next 指向第一个有效节点
}
//2.实现:"获取末尾位置的迭代器"
iterator end()
{
return iterator(_head); //末尾迭代器指向哨兵头节点
}
// 3.实现:"初始化空链表"
void empty_init()
{
//核心:创建哨兵头节点,自己指向自己
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
//4.实现:"构造函数"
list() //初始化空链表(哨兵头节点)
{
empty_init();
}
//5.实现:"const T& 版本"的尾插
void push_back(const T& x) //适配左值,调用拷贝构造
{
insert(end(), x);
}
//6.实现:"T&& 版本"的尾插
void push_back(T&& x) //适配右值,调用移动构造
{
insert(end(), move(x)); //注意:显式将"右值"x转为"右值引用",触发移动逻辑
}
//7.实现:"const T& 版本"的插入
iterator insert(iterator pos, const T& x) //在 pos 位置插入"左值",拷贝构造新节点
{
//1.使用cur指针指向"插入的位置"
Node* cur = pos._node;
//2.使用newNode指针指向"新创建的节点"
Node* newNode = new Node(x); //用拷贝构造创建新节点
//3.使用prev指针指向"插入位置前面的节点"
Node* prev = cur->_prev;
//4.调整指针:将新节点接入链表
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
//5.返回指向新节点的迭代器
return iterator(newNode);
}
//8.实现:"T&& 版本"的插入
iterator insert(iterator pos, T&& x) //在 pos 位置插入"右值",移动构造新节点
{
//1.使用cur指针指向"插入的位置"
Node* cur = pos._node;
//2.使用newNode指针指向"新创建的节点"
Node* newNode = new Node(move(x)); //用移动构造创建新节点,转移 x 的资源
//3.使用prev指针指向"插入位置前面的节点
Node* prev = cur->_prev;
//4.调整指针:将新节点接入链表
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
//5.返回指向新节点的迭代器
return iterator(newNode);
}
//9.实现:"emplace_back接口函数"
template <class... Args>
void emplace_back(Args&&... args) //核心实现:"可变参数模板" + "完美转发参数直接构造节点"
{
//1.调用 insert,在末尾位置用完美转发的参数构造节点
insert(end(), forward<Args>(args)...);
}
//添加特化版本
template <class K, class V>
void emplace_back(K&& key, V&& value)
{
insert(end(), std::pair<K, V>(
std::forward<K>(key),
std::forward<V>(value)
));
}
//10.实现:"emplace接口函数"
//
template <class... Args>
iterator emplace(iterator pos, Args&&... args) //(简化版,完整 STL 还会有更多重载等,这里演示关键逻辑)
{
//1.调用 insert,在指定位置用完美转发的参数构造节点
return insert(pos, T(forward<Args>(args)...));
}
};
}
测试文件:Test.cpp
cpp
#include "List.h"
#include <string>
using namespace std;
int main()
{
// -------------------- 测试 string 类型链表 --------------------
cout << "-------------------- 测试 string 类型链表 --------------------" << endl;
mySpace::list<string> lt1;
// 1. 传左值,类似 push_back 的"拷贝构造"语义
string s1("1111111111");
lt1.emplace_back(s1);
cout << "********** 左值构造(类似 push_back 拷贝) **********" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl << endl;
// 2. 传右值,类似 push_back 的"移动构造"语义
lt1.emplace_back(move(s1));
cout << "********** 右值构造(类似 push_back 移动) **********" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl << endl;
// 3. 直接传构造 string 的参数包,这是 push_back 做不到的!
//注意:emplace_back 会直接用 "1111111111" 构造节点里的 string
lt1.emplace_back("1111111111");
cout << "********** 直接传参数包构造 string **********" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl << endl;
// -------------------- 测试 pair<string, int> 类型链表 --------------------
cout << "-------------------- 测试 pair<string, int> 类型链表 --------------------" << endl;
mySpace::list<pair<string, int>> lt2;
// 1. 传左值 pair,类似 push_back 的"拷贝构造"语义
pair<string, int> kv("苹果", 1);
lt2.emplace_back(kv);
cout << "********** 左值构造 pair(类似 push_back 拷贝) **********" << endl;
for (auto& e : lt2)
{
cout << e.first << " : " << e.second << " ";
}
cout << endl << endl;
// 2. 传右值 pair,类似 push_back 的"移动构造"语义
lt2.emplace_back(move(kv));
cout << "********** 右值构造 pair(类似 push_back 移动) **********" << endl;
for (auto& e : lt2)
{
cout << e.first << " : " << e.second << " ";
}
cout << endl << endl;
// 3. 直接传构造 pair 的参数包("苹果", 1),push_back 无法直接做到!
//注意:emplace_back 会直接用这两个参数构造节点里的 pair
lt2.emplace_back("苹果", 1);
cout << "********** 直接传参数包构造 pair **********" << endl;
for (auto& e : lt2)
{
cout << e.first << " : " << e.second << " ";
}
cout << endl << endl;
return 0;
}

6. 使用emplace系列接口有什么建议?
1.优先使用emplace 系列接口:
emplace_back
替代push_back
emplace
替代insert
2. 注意完美转发的使用 :传递参数包时需用
std::forward<Args>(args)...
3. 场景适配:
- 对已构造的对象(如 :
vec.push_back(obj)
),push
和emplace
效率差异小- 对直接传构造参数(如 :
vec.emplace_back(10, "a")
),emplace
优势显著
------------ 新的类功能 ------------
一、默认函数
在 C++ 类体系里,默认成员函数的规则可梳理如下:
一、默认成员函数的基础构成与演进
C++ 类原本包含 4 个默认成员函数 :
- 构造函数、析构函数
- 拷贝构造函数、拷贝赋值重载
到了 C++11 标准,新增
移动构造函数
和移动赋值运算符重载
,进一步丰富了类的资源转移能力。
二、默认移动构造函数的生成规则
编译器自动生成默认移动构造函数,需同时满足两个条件:
- 类中 没有手动实现
移动构造函数
- 类中 没有手动实现
析构函数
、拷贝构造函数
、拷贝赋值重载函数
(这三者只要有一个手动实现,就会抑制默认移动构造的生成 )
默认移动构造函数的行为:
- 对
内置类型成员
(如 :int
、double
、指针
等):直接按字节 "转移"(本质是浅拷贝,若涉及动态资源需注意风险 )- 对
自定义类型成员
:检查该成员是否实现了移动构造
- 若实现,则调用其
移动构造
- 若未实现,则退而调用其
拷贝构造
三、默认移动赋值运算符重载的生成规则
编译器自动生成默认移动赋值运算符重载,条件与移动构造类似:
- 类中 没有手动实现
移动赋值重载函数
- 类中 没有手动实现
析构函数
、拷贝构造函数
、拷贝赋值重载函数
(任一手动实现,都会抑制默认移动赋值的生成 )
默认移动赋值的行为:
对
内置类型成员
:逐成员按字节 "转移"(浅拷贝逻辑 )对
自定义类型成员
:检查该成员是否实现了移动赋值
- 若实现,则调用其
移动赋值
若未实现,则调用其
拷贝赋值
注 :整体逻辑与默认移动构造高度相似,只是操作对象从
"构造新对象"
变为"赋值给已有对象"
四、移动语义与拷贝语义的互斥性
若手动提供了 移动构造函数 或 移动赋值重载函数 ,编译器会认为你要自己控制资源的 "转移逻辑",因此 不会再自动生成默认的拷贝构造函数和拷贝赋值重载函数
(反之,若手动实现了拷贝语义相关函数,默认移动语义函数也不会自动生成,需开发者按需抉择 。)
代码案例:"移动构造"和"移动赋值"的生成条件
cpp
#include <iostream>
#include <utility> // 用于std::move
using namespace std;
namespace mySpace
{
class string
{
private:
// 存储字符串内容
const char* _str;
public:
// 1. 构造函数(带缺省参数)
string(const char* str = "")
: _str(str)
{
cout << "mySpace::string 构造函数" << endl;
}
// 2. 拷贝构造函数
string(const string& other)
: _str(other._str) // 浅拷贝指针
{
cout << "mySpace::string 拷贝构造" << endl;
}
// 3. 移动构造函数
string(string&& other) noexcept
: _str(move(other._str))
{
other._str = nullptr; // 避免悬挂指针
cout << "mySpace::string 移动构造" << endl;
}
// 4. 拷贝赋值运算符
string& operator=(const string& other)
{
if (this != &other)
{
_str = other._str;
cout << "mySpace::string 拷贝赋值" << endl;
}
return *this;
}
// 5. 移动赋值运算符
string& operator=(string&& other) noexcept
{
if (this != &other)
{
_str = move(other._str);
other._str = nullptr; // 避免悬挂指针
cout << "mySpace::string 移动赋值" << endl;
}
return *this;
}
// 6. 析构函数
~string()
{
cout << "mySpace::string 析构函数" << endl;
}
};
}
class Person
{
private:
mySpace::string _name; // 字符串类型的姓名
int _age; // 整型的年龄
public:
// 1. 构造函数(带缺省参数)
Person(const char* name = "", int age = 0)
: _name(name) // 调用mySpace::string的构造函数
, _age(age) // 直接初始化int成员
{
cout << "Person 构造函数" << endl;
}
// 2. 拷贝构造函数
//注意:根据规则,手动实现拷贝构造会抑制默认移动构造的生成
Person(const Person& p)
: _name(p._name) // 调用mySpace::string的拷贝构造
, _age(p._age) // 拷贝int成员
{
cout << "Person 拷贝构造" << endl;
}
// 3. 拷贝赋值运算符
//注意:根据规则,手动实现拷贝赋值会抑制默认移动赋值的生成
Person& operator=(const Person& p)
{
if (this != &p) // 防止自赋值
{
_name = p._name; // 调用mySpace::string的拷贝赋值
_age = p._age; // 赋值int成员
cout << "Person 拷贝赋值" << endl;
}
return *this;
}
// 4. 析构函数
// 注意:根据规则,手动实现析构函数会抑制默认移动构造和移动赋值的生成
~Person()
{
cout << "Person 析构函数" << endl;
}
/* 注意:
* 1. 由于我们手动实现了"拷贝构造、拷贝赋值和析构函数"
* 2. 根据C++规则,编译器不会自动生成默认的"移动构造"和"移动赋值"
* 3. 如果需要移动语义,必须手动实现
* 所以:如果你想看到默认生成的"移动构造"和"移动赋值",可以将person类中手动实现的"拷贝构造、拷贝赋值和析构函数"注释掉
*/
};
int main()
{
/*--------------------测试:默认构造函数--------------------*/
cout << "Person s1;" << endl;
Person s1; // 调用Person的构造函数,使用默认参数
/*--------------------测试:拷贝构造--------------------*/
cout << "\nPerson s2 = s1;" << endl;
Person s2 = s1; // 调用Person的拷贝构造函数
/*--------------------测试:移动构造--------------------*/
cout << "\nPerson s3 = move(s1);" << endl;
Person s3 = move(s1);
/* 说明:
* 1. 由于Person类手动实现了拷贝构造和析构函数
* 2. 编译器不会生成默认移动构造,这里实际会调用拷贝构造
*/
/*--------------------测试:拷贝赋值--------------------*/
cout << "\ns2 = s1;" << endl;
s2 = s1; // 调用Person的拷贝赋值运算符
/*--------------------测试:移动赋值--------------------*/
cout << "\ns3 = move(s1);" << endl;
s3 = move(s1);
/* 说明:
* 1. 由于Person类手动实现了拷贝赋值和析构函数
* 2. 编译器不会生成默认移动赋值,这里实际会调用拷贝赋值
*/
cout << "\n程序结束,对象开始析构" << endl;
return 0;
}

二、缺省值
成员变量声明时设置缺省值,作用是供构造函数的初始化列表使用。
若构造函数未在初始化列表里显式对该成员初始化,初始化列表就会用这个缺省值来初始化成员。
这个我们在《C++初阶之类和对象》部分已经讲过了,可以参考这篇博客:【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
三、defult和delete
一、用 =default 显式生成默认函数
=default
: 主动要求编译器生成默认版本的成员函数
- 解决因手动实现其他函数导致默认函数被抑制的问题
场景示例:
若手动实现了拷贝构造函数 ,编译器会默认抑制移动构造函数的生成。
此时若仍需要编译器生成默认移动构造,可显式声明:
cppclass MyClass { public: //1.手动实现拷贝构造,抑制了默认移动构造 MyClass(const MyClass& other) { /* 自定义逻辑 */ } //2.用 =default 显式要求编译器生成默认移动构造 MyClass(MyClass&& other) = default; };
核心价值:
让开发者在 "需要自定义部分函数(如:拷贝构造)" 的同时,保留编译器自动生成的其他默认函数(如:移动构造),避免手动实现所有函数的冗余。
二、用 =delete 显式删除默认函数
=delete
:主动禁止编译器生成默认版本的成员函数
- 替代 C++98 中 "将函数声明为
private
且不实现" 的蹩脚写法
C++98 旧写法(不推荐):
若要禁止拷贝构造,需将其声明为
private
且不实现,调用时才会报错(但报错在链接阶段,不直观):
cppclass NonCopyable { private: // 声明但不实现,外部调用会触发链接错误 NonCopyable(const NonCopyable&); };
C++11 新写法(推荐):
用
=delete
直接标记函数为 "删除",编译阶段即可报错,更高效且语义清晰:
cppclass NonCopyable { public: // 用 =delete 显式禁止编译器生成拷贝构造 NonCopyable(const NonCopyable&) = delete; }; int main() { NonCopyable a; // 编译报错:调用了被删除的函数(拷贝构造) NonCopyable b = a; }

典型应用场景:
- 禁止对象拷贝(如 :
std::unique_ptr 需独占资源
)- 禁止某些无意义的默认函数(如 :
禁止基本类型的隐式转换构造
)
三、=default 和 =delete 的对比总结
关键字 | 作用 | 优势 | 典型场景 |
---|---|---|---|
=default |
要求编译器生成默认函数 | 保留编译器优化 避免手动实现冗余 | 需自定义部分函数时保留默认函数 |
=delete |
禁止编译器生成默认函数 | 编译阶段报错 语义清晰,替代旧写法 | 禁止拷贝 禁止无意义默认函数 |
通过
=default
和=delete
,C++11 让开发者对 "默认成员函数的生成" 有了更精细的控制:
=default
用于 "主动保留默认行为"=delete
用于 "主动禁止默认行为"两者配合可大幅提升代码的可读性和安全性。
四、final与override
关于这两的关键字我们在《C++进阶之继承多态》中已经讲过了,所以就不再过多的赘述了,这里就再简单的介绍一下这两个关键字的作用。
final 关键字的作用:
final 关键字有两个主要用途:
阻止类的继承
和阻止虚函数的重写
1. 阻止类的继承:当在类名后面使用
final
修饰时,这个类就不能被其他类继承。
这在一些场景下非常有用,比如你设计了一个工具类,它的功能已经完整且不需要被扩展
或者你希望明确表示某个类是继承体系的最底层,不允许再有子类
cppclass FinalClass final { // 类的成员和实现 }; /* 以下代码会编译报错,因为FinalClass不能被继承 class SubFinalClass : public FinalClass { // 类的成员和实现 }; */
2. 阻止虚函数的重写:当在虚函数声明后面加上
final
时,该虚函数在派生类中不能被重写。
这有助于保证虚函数的行为在继承体系中有明确的定义,避免在后续派生类中意外地改变其行为
cppclass Base { public: virtual void func() final { // 函数实现 } }; class Derived : public Base { /* 以下代码会编译报错,因为Base中的func函数被声明为final,不能被重写 void func() override { // 函数实现 } */ };
override 关键字的作用:
override 关键字的主要用途:用于显式地表明派生类中的函数是对基类中虚函数的重写。
它主要有以下几个作用:
增强代码的可读性和可维护性:通过
override
关键字,代码的意图更加清晰,能够让阅读代码的人一眼看出这是对基类虚函数的重写。编译器检查:编译器会检查带有
override
关键字的函数是否真的重写了基类中的虚函数。
- 如果基类中
不存在对应的虚函数
,或者函数签名(返回类型、函数名、参数列表)与基类中的虚函数不匹配
,编译器会报错- 这样可以在编译阶段发现错误,而不是在运行时出现难以调试的多态问题
cppclass Base { public: virtual void virtualFunc() { // 函数实现 } }; class Derived : public Base { // 正确重写,编译器检查签名与基类虚函数一致 void virtualFunc() override { // 函数实现 } /* 1. 以下代码会编译报错,因为基类中不存在名为wrongFunc的虚函数 void wrongFunc() override { // 函数实现 } -------------------------------------------------------- 2. 以下代码会编译报错,因为参数列表与基类中virtualFunc函数不匹配 void virtualFunc(int num) override { // 函数实现 } */ };
总的来说 :
final
和override
关键字在 C++11 中为类的继承和多态提供了更好的控制和检查机制,有助于写出更健壮、更易理解和维护的代码。
五、STL新变化
注:下图是STL中的所有容器,其中用红色方框框起来的容器是C++11新添加的容器。

一、新容器
STL 新增了一些容器(如:图中圈出部分 ),其中最实用的是
unordered_set
和unordered_map
这两个容器我们之前已做过详细讲解,其他新增容器大家简单了解概念即可。
二、新接口
STL 容器新增了不少实用接口,核心聚焦在
C++11 移动语义
、初始化列表
等特性的支持上,关键改进包括:
移动语义相关接口:
push/insert/emplace 系列接口支持右值引用,可直接转移资源
- 如 :用
emplace
就地构造对象,避免拷贝容器的移动构造、移动赋值接口,让资源转移更高效
- 如 :
vector v2 = move(v1);
直接转移内存,而非深拷贝初始化列表支持:
支持用
initializer_list
直接初始化容器(如:vector<int> v = {1,2,3};
),语法更简洁次要接口补充:
像
cbegin
/cend
(返回 const 迭代器 )这类接口,实际使用频率低,需要时查文档即可
