前言
在 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 表达式是局部的匿名的函数对象,所以 sort 填仿函数那里可以写这个。底层其实是用的仿函数实现的。
该表达式不存在重载的说法。引申:auto f1 = []{}; 和 auto f2 = []{}; f1 和 f2 也不是同一类型,f1=f2 的话会报错。也就是 lambda 表达式之间不能相互赋值,即使看起来类型相同。
[捕捉列表](参数列表)(mutable)-> 返回值类型 { 函数体 };
捕获列表的话可以捕获这个表达式所在域的局部变量,函数也可以捕捉,全局域里的不用捕获也能用。这种的强行捕获可能会报错。参数列表里面不传参的话,() 也可以省了。这个 mutable 的话可以取消捕获列表里面东西默认的 const 性,但是在用该修饰符的时候,参数列表不能省略。如果捕获的东西本来是 const 的话,加了 mutable 也变不了。注意:捕获列表默认是 const 的。返回值类型一般可以不用写,编译器会推导。
捕获列表说明
[var]:表示值传递方式捕捉变量 var,var 的类型会跟传递过来的一模一样。[=]:表示值传递方式捕获所有父作用域中的变量 (包括 this)。[&var]:表示引用传递捕捉变量 var。[&]:表示引用传递捕捉所有父作用域中的变量 (包括 this)。
这里的 & 和 =:如果是 [&, x] 那就只有 x 是值传递,其他都是引用。注意:捕捉列表不允许变量重复传递,否则就会导致编译错误,例如 [=, a] 就不行,a 捕获了两次。
用法展示
引申:可调用对象的四个存储方法:函数指针、仿函数、lambda 表达式、用包装器搞到容器里面。
可变参数模板
就是让模板参数可以是不确定的数目。其实日常自己写的话很少用的。
template<class... Args>
void ShowList(Args... args) {}
Args 表示一个包含零个或多个类型的参数包,args 也是参数包,里面可以是 0 或多个参数。... 是展开参数包的操作(放在参数包后面,除了模板参数声明那里)。但是这个参数包想知道里面的参数是啥的话比较困难,不支持 eg: arg[1] 这样。
展开参数包的方法
- 递归展开参数包
- 逗号表达式展开参数包
应用
假设有个日期类 Date:
template<class... Args>
Date* Create(Args... args) {
Date* ret = new Date(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 必须要先把零散的参数搞成临时对象再传进去。
包装器
function 包装器
function 包装器也叫作适配器,它需要头文件 #include <functional>。
作用:并且包装器可以让可调用对象存储到容器中去了。
// 用法:function 的类模板原型
template<class T> function; // 这是模板的声明
template<class Ret, class... Args>
class function<Ret(Args...)>;
// Ret: 被调用函数的返回类型
// Args...:被调用函数的形参
// 使用示例
double f(double i) { return i / 2; }
struct Functor {
double operator()(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 是一个函数模板,它就像一个函数包装器 (适配器),接受一个可调用对象,生成一个新的可调用对象来'适应'原对象的参数列表。
这个的作用也就是把参数的顺序改了 + 可以固定一些参数的值。
这里固定参数的值跟直接给缺省值的比较:缺省值只能给固定的参数一个固定值,这里就不一样了。
// 用法:对于普通的函数
double Plus(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
// 如果想固定的值在中间的话
double Plus(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 这样的哈
// 用法:对于类里面的成员函数的话
// 如果函数是静态的,那到没啥区别 (但是记得加上类域)
// 如果函数不是静态的:函数后面要跟上这个对象或者对象的指针才行
// 函数是静态的话,可以加那个也可以不加
class SubType {
public:
int sub(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 编译器最终是将其转化为迭代器来进行处理的


