目录
前言
Part1. 万能引用的不足
Part2. 可变模板参数 编程
Part2.1. 语法定义
Part2.2. 参数包递归展开原理
Part2.3. 递归终止函数的匹配优先级
Part2.4. 递归展开的另一种形式
Part3. emplace_back 原理
Part3.1. 基础对比
Part3.2. 左右值场景区分
Part3.3. 右值的注意事项
Part4. lambda表达式
Part4.1. 基本格式
Part4.2. 核心特点
Part4.3. 捕获规则
Part5. 结语
前言
C++11中可变模板参数和lambda表达式是非常重要的知识,在项目的实现和竞赛中都有所体现,我们来了解一下吧。
let's go!!!!!!!!
Part1. 万能引用的不足
在上篇文章中我们讲了,我们可以用万能引用来减少代码的冗余,但是这样是错误的,为什么?我们来看:
vector<pair<int,int>> aaa;
aaa.push_back({1,2});//隐式类型转换
用码道免费领 1 个月 Token
cpp
运行
这里的这个代码,{1,2}转化为pair<int,int>是走的隐式类型转换,但是我们的万能引用是单独一个模板参数x,他依靠我们传入的类型来做推导,但是我们现在传入的类型他无法推导,因此我们必须区分左右值版本来实现push_back,而不是单单一个万能引用。 语言资源
Part2. 可变模板参数
C++11引入模板可变参数包 Args...,在编译期完成参数推导。
Part2.1. 语法定义
// Args 代表一包类型,Args...代表展开所有类型
template<class ...Args>
void printf(Args&& ...args)
{
// ...
}
//class ...Args:模板参数包
//Args&& ...args:函数参数包
用码道免费领 1 个月 Token
cpp
运行
编译器根据传入实参数量、类型,实例化对应函数。
Part2.2. 参数包递归展开原理
可变参数包无法直接遍历,标准实现方式:递归拆解 + 递归终止条件
// 递归终止:参数包为空
void showlist()
{
cout << endl;
}
// 递归拆解:取出第一个参数,剩余参数继续递归
template<class T, class ...Args>
void showlist(T x, Args&& ...args)
{
cout << x << " ";
showlist(std::forward<Args>(args)...);
}
用码道免费领 1 个月 Token
cpp
运行
调用流程示例 showlist(1, 3.14, "hello")
1. T=int,剩余参数3.14,"hello" → 递归 编程
2. T=double,剩余参数"hello" → 递归
3. T=const char*,无剩余参数 → 触发无参终止函数
!易错点:
不能使用 sizeof...(Args)==0 判断作为return条件,
模板的实例化在编译期,而if这些判断这些是在运行时,其无法阻止模板的实例化。
模板实例化阶段仍然会完整解析所有分支代码,依然会触发递归。
必须依靠重载函数实现编译期分支选择。
Part2.3. 递归终止函数的匹配优先级
重载规则:固定参数版本 > 参数包版本
// 终止函数
void showlist(double b) { cout << b; }
// 递归函数
template<class T, class ...Args>
void showlist(T x, Args&& ...args)
用码道免费领 1 个月 Token
cpp
运行
调用showlist(1, 3.14)
1. 第一层:T=int,剩余3.14
2. 第二层:传入参数仅3.14,优先匹配普通函数showlist(double),不再进入模板递归。
原理:普通非模板函数优先级高于模板函数。
也就是说,我们可以显式的把每一个类型写出来,来作为包展开的另一种形式。
Part2.4. 递归展开的另一种形式
template<class T>
const T& GetArg(const T& x) { return x; }
template<class ...Args>
void print(Args&& ...args)
{
A(GetArg(std::forward<Args>(args))...);
}
用码道免费领 1 个月 Token
cpp
运行
原理:先逐个处理参数包内每个元素,全部处理完成后,一次性转发给函数A。
注意:展开表达式最终返回类型不能是void,编译器要求表达式具备合法类型。 编程
Part3. emplace_back 原理
Part3.1. 基础对比
<1> push_back(value):
先构造临时对象,再将临时对象拷贝/移动进容器;
必须存在完整对象,再传入函数。
<2> emplace_back(参数列表):
直接在容器内存原地构造对象,不会产生临时对象。
Part3.2. 左右值场景区分
<1> 传入右值:emplace_back 和 push_back 性能差距很小;
<2> 原地构造场景优势巨大:
// 直接传入构造所需参数,不需要手动创建对象
vec.emplace_back("111", 1);
// 等价于容器内部执行 T("111",1),直接构造
push_back写法:
vec.push_back(T("111",1));
// 产生临时对象,再移动进入容器
用码道免费领 1 个月 Token
cpp
运行
Part3.3. 右值的注意事项
针对参数x,不要无脑使用std::move(x)
如果x后续还需要使用,移动之后对象资源转移。 编程
Part4. lambda表达式
Part4.1. 基本格式
[捕获列表](参数列表) -> 返回类型 { 函数体 };
<1> [ ]:捕获外部变量,规定外部变量如何传入lambda
<2> ( ):形参列表
<3> ->:显式指定返回值;返回类型可省略,编译器自动推导
<4> { }:函数体
Part4.2. 核心特点
Lambda本质是编译器自动生成的匿名仿函数类;
函数调用层面没有类型名称,因此变量只能使用auto接收。
Part4.3. 捕获规则
<1> [=]:值捕获,所有外部变量拷贝一份
<2> [&]:引用捕获,直接引用外部变量
<3> [x]:仅值捕获x
Part5. 结语
这篇文章我们认识到了可变模板参数和lambda表达式相关知识,接下来,小编还会带来更多关于C++11的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!
最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~