【C++】C++11(可变参数模板、lambda表达式、包装器)
文章目录
- 1. 可变参数模板
- 1.1 介绍
- 1.2 emplace系列接口实现
- 2. lambda表达式
- 2.1 语法介绍
- 2.2 原理
- 3. 包装器
- 4. bind

1. 可变参数模板
1.1 介绍
可变参数我们在C语言阶段已经了解过了,C语言中叫做可变参数列表,其中使用 ... 代表可变参数。
C语言中的可变参数列表链接
由于C++中增加了模板,因此C++11中就引进了可变参数模板,其能够创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
C++中的可变参数模板延续了C语言中可变参数列表中 ... 的使用。
下面就是一个基础的可变参数的函数模板

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。
注意使用方式:
- template<class
...Args> ,作为模板参数包时,...在前, - Args
...args,作为函数形参参数包时,...在后 - 函数参数args进行传递时,应这样传递,args
...,…在args的后面
我们可以使用sizeof...(args)检查参数的个数

那我们能不能显示参数包中的数据呢?

注意:解析模板参数包是在编译时做的,上述代码是在运行时,所以是不可能显示出来的。
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。
由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些其它方式获取参数包的值。
递归函数方式展开参数包

这样就可以一一进行打印了。
逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接就地展开的, PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。
这种就地展开参数包的方式实现的关键是逗号表达式。
- 逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。
- 同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组。
- {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]
- 由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
1.2 emplace系列接口实现
学习完可变参数模板后,我们就可以试着来实现以下STL中容器的emplace系列接口。
这里我们就以list为例,我们看到它不仅使用了可变参数模板,而且使用了万能引用。


注意:尽管emplace_back是可变参数模板,但是它不支持插入多个值

那它支持可变模板参数是干什么的呢?
对于多参数的,例如一个pair类型,无论是使用push_back还是emplace_back插入一个pair对象,二者没有区别,都是构造pair+拷贝/移动构造。
但是对于没有pair对象的第三种情况而言,它把构造pair的参数作为可变参数包直接往下传,不断地传;直到传到链表节点那个地方使用pair的参数直接去构造节点,没有了“中间商”,因此只有一个构造。

因此我们emplace_back可以按照下面的方式写,对于未使用对象操作,仅使用构造对象的参数进行传参的,都直接构造。
参数传递时注意右值的退化,合理使用完美转发!


emplace_back高效原因总结:
原地构造:emplace_back 允许在容器内部直接构造元素,而不需要先创建一个临时对象,然后再将其复制或移动到容器中。这避免了额外的复制或移动操作,从而提高了效率。优化构造函数调用:使用 emplace_back 时,可以直接传递构造函数的参数给容器,而不是先构造一个完整的对象。这允许编译器优化构造函数的调用,例如通过完美转发来减少不必要的拷贝和移动操作。避免拷贝/移动构造函数和析构函数的调用:对于需要在容器中添加大量复杂对象时,emplace_back 通过直接构造对象,避免了临时对象的拷贝或移动构造函数的调用,同时也避免了在容器销毁时这些临时对象的析构函数调用。这可以显著提高性能,尤其是在对象构造、析构或拷贝/移动操作开销较大时。与 std::move 相比的优势:虽然 std::move 可以与 push_back 结合使用以减少拷贝开销,但它仍然需要构造一个临时对象(尽管是一个右值引用),然后再将其移动到容器中。相比之下,emplace_back 直接在容器内部构造对象,完全避免了临时对象的创建。
综上所述,emplace_back 通过在容器内部直接构造元素,减少了拷贝/移动操作,优化了内存使用,并避免了不必要的构造函数和析构函数调用,从而提供了比 push_back 更高效的元素添加方式。在可能的情况下,优先使用 emplace_back 可以显著提升性能。
2. lambda表达式
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。
int main()
{int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较,排出来结果是升序size_t sz = sizeof(array) / sizeof(array[0]);sort(array, array + sz );// 如果需要降序,需要改变元素的比较规则sort(array, array + sz, greater<int>());return 0;
}
对于自定义类型的数据,需要自己定义比较规则。

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要去写一个类并在类中实现仿函数,如果每次比较的逻辑不一样,还要去实现多个类和仿函数,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。
2.1 语法介绍
lambda表达式书写格式:[捕捉列表] (参数列表) mutable -> 返回值类型 { 函数体 }。它的结构类似于函数,唯独没有函数名。
捕捉列表:该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。参数列表:与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。返回值类型:用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。函数体:在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
- 简单的lambda表达式
对于捕捉列表和mutable我们稍后在描述,先简单写一个lambda看一看。

尽管返回值类型可以省略,但是还是推荐写上。
现在我们就可以使用lambda改造上面排序的比较规则

这样直接在当前位置写是不是就清爽很多,不必再像以前一样先写个类,然后重载operator()了

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量
- 捕捉列表
mutable可以取消其常量性

- 传值捕捉,我们发现其实a,b在外部并未被交换

所以我们必须要了解捕获列表说明:
捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式传值还是传引用。
- [var]:表示值传递方式捕捉变量var
- [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
- 父作用域指包含lambda函数的语句块
- [&var]:表示引用传递捕捉变量var
- [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
- [this]:表示值传递方式捕捉当前的this指针
- 传引用捕捉

- 所有值都传值捕捉

- 所有值都传引用捕捉

- 混合使用(捕捉列表可由多个捕捉项组成,并以逗号分割)


注意:
- 捕捉列表不允许变量重复传递,否则就会导致编译错误
- 在块作用域中的lambda函数仅能捕捉父作用域中局部变量或全局变量,捕捉任何非此作用域或者非局部变量都会导致编译报错
- lambda表达式之间不能相互赋值,即使看起来类型相同
2.2 原理
我们先使用重载operator()和lambda写同一个功能,观察底层实现
class Rate
{
public:Rate(const double& rate):_rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};int main()
{//使用类对象重载的operator()double rate = 0.015;Rate r1(rate);cout << r1(10000, 2) << endl;//使用lambdaauto func = [=](double money, int year)->double {return money * rate * year; };cout << func(10000, 2) << endl;return 0;
}
从使用方式上来看,函数对象与lambda表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到,然后通过传参传进去。

我们查看汇编代码可以发现,实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。
3. 包装器
- function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?
到目前位置,我们知道的可调用对象有:函数指针、仿函数、lambda表达式三种,下面我们就用这三种来实现三个不同的函数。
template<class F, class T>
T Transfer(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
//函数指针
double f(double i)
{return i / 2;
}
//仿函数
struct Functor
{double operator()(double d){return d / 3;}
};int main()
{// 函数名cout << Transfer(f, 66.6) << endl << endl;// 函数对象(仿函数)cout << Transfer(Functor(), 66.6) << endl << endl;// lamber表达式cout << Transfer([](double d)->double { return d / 4; }, 66.6) << endl << endl;return 0;
}
我们都知道,静态变量在一个语句块中是同一个,但是下面的代码中却不是。显然它们调用的不是同一个Transfer,由于这里模板的原因,编译器在底层会根据不同的函数模板实例化出不同的Transfer函数,导致效率低下。

同时,如果我们要把可调用对象存储到一个容器中,那容器元素的类型是什么呢?函数指针、仿函数还是lambda表达式?很显然都不行。
为了解决这个问题,C++11引入了std::function包装器,它可以将这些不同类型的可调用对象(参数、返回值相同)封装成统一的类型,使得函数模板和容器等可以更加灵活地处理这些对象。
包装器的语法比较怪,使用它需要包含对应的头文件functional;其次它是一个类,类的模板参数和平常的不一样。


还可以使用包装器包装类的成员函数:

使用包装器包装后,就无需再实例化多个Transfer函数,此时不同类型的可调用参数对象都是同一种类型了。

包装器的应用
题目链接:逆波兰表达式
右边的代码使用包装器使代码更加的灵活

4. bind
bind函数定义在头文件functional中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M一般小于N)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。
一句话总结:bind可以调整一个可调用对象的参数的个数和顺序。
原型如下:

该函数的参数
- Fn:可调用对象
- 可变参数模板Args:要么是值,要么是placeholder(一个命名空间)

- 调整参数的顺序(不常用)

- 调整参数个数(常用)
绑定某些参数,其余参数通过调用时传递

对于调整参数的个数而言,更加适合下面这种场景,可以简化调用


相关文章:
【C++】C++11(可变参数模板、lambda表达式、包装器)
文章目录 1. 可变参数模板1.1 介绍1.2 emplace系列接口实现 2. lambda表达式2.1 语法介绍2.2 原理 3. 包装器4. bind 1. 可变参数模板 1.1 介绍 可变参数我们在C语言阶段已经了解过了,C语言中叫做可变参数列表,其中使用 ... 代表可变参数。 C语言中的可…...
矩阵获客时代,云微客让你一个人成就一支队伍
短视频利用大家碎片化的时间让自身得到广泛的应用和发展,因此很多公司纷纷布局短视频赛道。但是一个账号的曝光量有限,并且能够出的爆款视频更是少之又少,这个时候就需要增加账号的数量,布局形成账号矩阵。 做账号矩阵,…...
浅谈基础的图算法——Tarjan求强联通分量算法(c++)
文章目录 强联通分量SCC概念例子有向图的DFS树代码例题讲解[POI2008] BLO-Blockade题面翻译题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 思路AC代码 【模板】割点(割顶)题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示…...
【Godot4自学手册】第四十四节用着色器(shader)实现溶解效果
本小节,我将自学用用着色器(shader)实现溶解效果,最终效果如下: 一、进行shader初始设置 首先我们进入Player场景,选择AnimatedSprite2D节点,在检查器中找到CanvasItem属性,并在M…...
【画流程图工具】
画流程图工具 draw.io draw.io(现称为 diagrams.net)是一款在线图表绘制工具,可以用于创建各种类型的图表,如流程图、网络图、组织结构图、UML图、思维导图等。以下是关于它的一些优点、应用场景及使用方法: 优点&a…...
Revit二次开发选择过滤器,SelectionFilter
过滤器分为选择过滤器与规则过滤器 规则过滤器可以看我之前写的这一篇文章: Revit二次开发在项目中给链接模型附加过滤器 选择过滤器顾名思义就是可以将选择的构件ID集合传入并加入到视图过滤器中,有一些场景需要对某些构件进行过滤选择,但是没有共同的逻辑规则进行筛选的情况…...
【Linux】进程概念—环境变量
目录 一、冯诺依曼体系结构 二、操作系统(Operator System) 1 .概念 2 .设计OS的目的 3 . 定位 4 . 系统调用和库函数概念 三、进程 1 .基本概念 2 .描述进程-PCB(process control block)进程控制块 3 . 组织进程 4 . 查看进程 5 .通过系统调用获取进程…...
第十二章 Spring MVC 框架扩展和SSM框架整合(2023版本IDEA)
学习目标 12.1 Spring MVC 框架处理JSON数据12.1.1 JSON数据的传递处理12.1.2 JSON数据传递过程中的中文乱码和日期问题12.1.3 多视图解析器 12.2 Spring MVC 框架中的数据格式转换12.2.1 Spring MVC 框架数据转换流程12.2.2 编写自定义转换器12.2.3 使用InitBinder装配自定义编…...
js中的全局函数有这些
js中的全局函数有这些,记忆规则 6个编译 escape、unescape、decodeURI、decodeURIComponent、encodeURI、encodeURIComponent 2个数据处理 Number()、String() 4个数字处理 isFinite、isNaN、parseFloat、parseInt 1个特殊情况 eval()...
Android SurfaceFlinger——重绘闪烁处理(四十六)
在帧数据准备完成后,下一步是调用 devOptRepaintFlash() 函数处理显示输出设备的可选重绘闪烁问题,这里我们就来看一下重绘闪屏问题的处理方案。 1.更新输出设备的色彩配置文件2.更新与合成相关的状态3.计划合成帧图层4.写入合成状态5.设置颜色矩阵6.开始帧7.准备帧数据以进行…...
罗马数字转整数 C++
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#x…...
Day20_2--介绍同步加载和异步加载
同步加载和异步加载是处理程序或数据的两种不同方式,它们在处理任务的方式、效率和用户体验上有显著差异。下面是对这两种加载机制的详细介绍。 1. 同步加载(Synchronous Loading) 定义: 同步加载是一种加载数据或资源的方式&am…...
sftp做成一个池子
前言:开发中的需求要去对方的 ftp 服务器下载文件,这里下载文件采用 ftp 方式,下载之后程序再去解析文件的内容,然后再存数据库。下载过来的文件默认是 zip 格式,需要解压 unzip 一下,然后里面有一个 csv 文…...
全网最全-Netty从入门到精通
XiaoYongCai/2024/8/6 一:Netty入门 1.Netty概述 A.Netty的定义 Netty是一个提供异步事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。在Java领域,Netty被认为是除了Java原生NIO之外的最佳网络…...
C#知识|文件与目录操作:对象的创建、保存、读取
哈喽,你好啊,我是雷工! 面向对象编程的特点就是一切皆对象,操作的也是对象,本节学习文件与目录操作中,对象的保存; 以下为学习笔记。 01 对象的特点 ①:对象运行在内存中ÿ…...
自定义 SwiftUI 中符号图像的外观
文章目录 前言大小颜色渲染模式单色分层调色板多色 可变值设计变体示例代码结论 前言 符号图像是来自 Apple的SF Symbols 库的矢量图标,设计用于在 Apple 平台上使用。这些可缩放的图像适应不同的大小和重量,确保在我们的应用程序中具有一致的高质量图标…...
循环神经网络和自然语言处理一
目录 一.分词 1.分词工具 2.分词的方法 3.N-gram表示方法 二.向量化 1.one-hot编码 2.word embedding 3.word embedding API 4.数据形状改变 既然是自然语言,那么就有字,词,句了 一.分词 1.分词工具 tokenization,jie…...
CSS技巧专栏:一日一例 20-纯CSS实现点击会凹陷的按钮
本例图片 案例分析 其实这个按钮非常的简单啊,主要就是利用了box-shadow的inset。 布局代码 <button class="base">凹下的按钮</button> 基础样式 :root{--main-bg-color: #dcdcdc; /* 将页面背景色调整为浅灰色 */--color:#000;--hover-color:#99…...
20240807 每日AI必读资讯
👨💼马斯克再发难、OpenAI 高层巨变:两大核心人物离职,总裁休长假到年底 - OpenAI 联合创始人 John Schulman 官宣离职,加入原是竞品公司的 Anthropic - 陪伴 OpenAI 共同成长 9 年的总裁兼联合创始人 Greg Brockm…...
海外社媒账号如何让防关联?账号隔离的5大要点
在跨境电商迅速发展和全球化营销的背景下,海外社交媒体平台成为外贸人拓展市场的关键阵地。因此,为了保障账号安全,实现高效推广,账号隔离以及安全防关联对外贸人来说至关重要。本文将盘点引起海外社媒账号关联的原因及其五大解决…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...



