C++11新特性(下)----《Hello C++ Wrold!》(26)--(C/C++)
文章目录
前言
在 C++11 标准带来的诸多革命性特性中,“简化代码编写” 与 “统一可调用对象管理” 是两大核心目标。lambda 表达式解决了传统仿函数 “定义繁琐、复用性低” 的痛点,让局部场景下的自定义逻辑(如排序规则、回调函数)能以更简洁的匿名函数形式实现;可变参数模板则打破了模板参数数量固定的限制,为 STL 容器(如emplace_back)和通用函数设计提供了灵活的参数处理能力;而 function 包装器与 bind 函数,则进一步整合了函数指针、仿函数、lambda 等不同类型的可调用对象,实现了统一管理与参数适配,甚至让可调用对象存储到容器中成为可能。
这些特性并非孤立存在 ——lambda 的底层依赖仿函数实现,可变参数模板为emplace系列接口提供了技术支撑,function 与 bind 则基于前两者的特性,解决了 “不同可调用对象类型不统一” 的问题。本文将从实际开发需求出发,先讲解 lambda 表达式的语法规则与捕获逻辑,再深入可变参数模板的展开方法与应用场景,最后通过 function 包装器与 bind 函数的实例,展示如何统一管理各类可调用对象、灵活调整参数顺序与固定参数值。
文中不仅会拆解易混淆的概念(如 lambda 的 mutable 修饰符作用、placeholders 占位符的使用规则),还会结合具体代码示例(如用 vector 存储 function 对象、用 bind 适配类成员函数),帮助读者理解这些特性的设计逻辑与实际价值。同时,文末的作业解析也将围绕 C++11 常见考点(如范围 for 的适用场景)展开,进一步巩固对标准特性的理解,为后续高效使用 C++11 进行开发打下基础。
lambda表达式
比如:一个类里面有很多成员变量,想对他们都进行排序,但是每次比较的逻辑不一样,而且还要每次都写一个类去比较,太麻烦了,所以就引入了lambda表达式lambda表达式是局部的匿名的函数对象–所以sort填仿函数那里可以写这个
底层其实是用的仿函数实现的
这个表达式不存在重载这个说法哈
引申:auto f1 = (){}; 和auto f2 = (){}; f1和f2也不是同一类型 f1=f2的话会报错
也就是. lambda表达式之间不能相互赋值,即使看起来类型相同
这个表达式的格式:[捕捉列表](参数列表)(mutable)-> 返回值类型 { 函数体 }; 捕获列表的话可以捕获这个表达式所在域的局部变量--函数也可以捕捉哈 --全局域里的不用捕获也能用 --这种的强行捕获可能会报错 参数列表里面不传参的话,()也可以省了 这个mutable的话可以取消捕获列表里面东西默认的const性 但是在用该修饰符的时候,参数列表不能省略 --如果捕获的东西本来是const的话,加了mutable也变不了 注意:捕获列表默认是const的 ->返回值类型:这个一般可以不用写,编译器会推导 关于捕获列表:(没用mutable的话,捕获的东西都不能被修改)[var]:表示值传递方式捕捉变量var–var的类型会跟传递过来的一模一样[=]:表示值传递方式捕获所有父作用域中的变量(包括this)[&var]:表示引用传递捕捉变量vareg:[&x,&y]--这个跟取地址不要混了:表示引用传递捕捉所有父作用域中的变量(包括this)
[&]
这里面的&和=:如果是[&,x]那就只有x是值传递,其他都是引用
注意:捕捉列表不允许变量重复传递,否则就会导致编译错误[=,a]就不行,a捕获了两次
用法展示:
引申:可调用对象的四个存储方法:函数指针,仿函数,lambda表达式,用包装器搞到容器里面可变参数模板
就是让模板参数可以是不确定的数目
其实日常自己写的话很少用的
template<class... Args>voidShowList(Args... args){} Args表示一个包含零个或多个类型的参数包 args也是参数包,里面可以是0或多个参数 ...是展开参数包的操作(放在参数包后面--除了模板参数声明那里) 但是这个参数包想知道里面的参数是啥的话比较困难--不支持eg:arg[1]这样 展开参数包的方法
1.递归展开参数包
2.逗号表达式展开参数包
应用
假设有个日期类Date template<class... Args> Date*Create(Args... args){ Date* ret =newDate(args...);return ret;} 可以Date* p1 =Create(2025,9,7);这样来用 或者 Date d(2025,9,7); Date* p1 =Create(d);也行 emplace_back有可变参数模板 push_back没用 emplace_back可以直接传零散的参数进去 但是push_back必须要先把零散的参数搞成临时对象再传进去 包装器
fiction包装器
function包装器也叫作适配器 他需要头文件#include <functional>
作用:比如:
并且包装器可以让可调用对象存储到容器中去了
用法: function的类模板原型:template<classT> function;//这是模板的声明template<classRet,class... Args>classfunction<Ret(Args...)>; Ret: 被调用函数的返回类型 Args…:被调用函数的形参 使用:doublef(double i){return i /2;}structFunctor{doubleoperator()(double d){return d /3;}}; vector<function<double(double)>> v ={ f,[](double d)->double{return d /4;},Functor()}; 第一个double是返回值类型 第二个double是形参类型--两个double类型的形参的话就写(double,double)用法还有eg: map<string,function<int(int)>> map1 ={{"a",[](int x){return x;}},{"y",[](int x){return x+1;}}}; 用的话就:int x =0;int a = map1["a"](x);//注意理解map1["a"]取出包装器.....bind函数
这个的头文件也是#include <functional>是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表
bind
这个的作用也就是把参数的顺序改了+可以固定一些参数的值
这里固定参数的值跟直接给缺省值的比较:
缺省值只能给固定的参数一个固定值
这里就不一样了
用法: 对于普通的函数:doublePlus(int a,int b,double rate){return(a + b)* rate;} function<double(int,int)> Plus1 =bind(Plus, placeholders::_2, placeholders::_1,4.0); 这个的话就是rate固定是4.0 然后Plus1(9,8) 这里的placeholders::_1表示的是(9,8)里面的9然后会传给b这样 如果想固定的值在中间的话 eg:doublePlus(int a,double rate,int b){return(a + b)* rate;} function<double(int,int)> Plus1 =bind(Plus, placeholders::_2,4.0,placeholders::_1);Plus1(9,8);--8最终给了a 这个依旧是_1,_2这样的哈 用法: 对于类里面的成员函数的话: 如果函数是静态的,那到没啥区别(但是记得加上类域) 如果函数不是静态的: 函数后面要跟上这个对象或者对象的指针才行 --函数是静态的话,可以加那个也可以不加 eg:classSubType{public:intsub(int a,int b){return a - b;}}; SubType st; function<double(int,int)> Sub2 =bind(&SubType::sub,&st, placeholders::_1, placeholders::_2,3); function<double(int,int)> Sub2 =bind(&SubType::sub, st, placeholders::_1, placeholders::_2,3); function<double(int,int)> Sub3 =bind(&SubType::sub,SubType(), placeholders::_1, placeholders::_2,3);//这里用的是匿名对象 引申:类里面的函数在外面用的时候必须要到类域里面去找才行 作业部分

下面关于范围for说法错误的是(C) A.范围for可以直接应用在数组上 B.对于STL提供的所有容器,均可以使用范围for依次访问器元素 C.使用范围for操作stack,可以简化代码 //上面说的是容器,但是stack是容器适配器 D.对于自定义类型,想要支持范围for,必须提供begin和end迭代器 E.范围for编译器最终是将其转化为迭代器来进行处理的 