一、Lambda表达式:让代码更简洁
Lambda表达式,说白了就是匿名函数。你可以在需要函数的地方直接写一段逻辑,不用专门去定义一个函数。我个人觉得,这是C++11引入的最实用的特性之一。
1.1 基本语法
Lambda的语法结构其实很简单:
[捕获列表](参数列表) -> 返回类型 {
函数体
}
嗯,这里要注意,返回类型可以省略,编译器会自动推导。我刚开始用的时候,经常纠结要不要写返回类型,后来发现大部分场景下都可以不写。
来看个最简单的例子:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
// 用Lambda排序
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a < b; });
// 用Lambda打印
std::for_each(nums.begin(), nums.end(),
[](int n) { std::cout << n << " "; });
return 0;
}
你想想看,如果没有Lambda,你得单独写个比较函数,再传进去。代码分散不说,可读性也差。
1.2 捕获列表:Lambda的灵魂
捕获列表是Lambda最核心的部分。它决定了Lambda能访问外部哪些变量。
| 捕获方式 | 语法 | 说明 |
|---|---|---|
| 值捕获 | [x] | 捕获x的副本,Lambda内部修改不影响外部 |
| 引用捕获 | [&x] | 捕获x的引用,内部修改会影响外部 |
| 隐式值捕获 | [=] | 捕获所有用到的外部变量(按值) |
| 隐式引用捕获 | [&] | 捕获所有用到的外部变量(按引用) |
| 混合捕获 | [=, &x] | 除x按引用外,其余按值 |
避坑指南:我曾经在项目里吃过亏——用值捕获了一个大对象,结果Lambda被反复调用,每次都要拷贝这个对象,性能直接崩了。后来改成引用捕获,问题解决。记住:值捕获会拷贝,引用捕获不会。
来看个实际例子:
#include <iostream>
int main() {
int x = 10, y = 20;
// 值捕获
auto lambda1 = [x]() {
// x = 100; // 编译错误!值捕获的变量是const的
return x + y; // 错误!y没有被捕获
};
// 引用捕获
auto lambda2 = [&x, &y]() {
x = 100; // 可以修改
y = 200;
return x + y;
};
// 隐式捕获
auto lambda3 = [=]() { return x + y; }; // 全部按值
auto lambda4 = [&]() { x = 100; return x + y; }; // 全部按引用
lambda2();
std::cout << "x = " << x << ", y = " << y << std::endl;
return 0;
}
二、函数对象(仿函数):类的另一种用法
函数对象,也叫仿函数,就是重载了operator()的类对象。它看起来像函数,实际上是个对象。我个人的习惯是,当需要保存状态时,优先用函数对象而不是Lambda。
#include <iostream>
#include <vector>
#include <algorithm>
class GreaterThan {
private:
int threshold;
public:
GreaterThan(int t) : threshold(t) {}
bool operator()(int value) const {
return value > threshold;
}
};
int main() {
std::vector<int> data = {1, 5, 8, 3, 10, 2};
// 使用函数对象
int count = std::count_if(data.begin(), data.end(),
GreaterThan(5));
std::cout << "大于5的元素个数: " << count << std::endl;
// 用Lambda实现同样的功能
int threshold = 5;
count = std::count_if(data.begin(), data.end(),
[threshold](int v) { return v > threshold; });
return 0;
}
我的经验:函数对象比Lambda多一个优势——它可以有多个成员函数。比如你可以在类里加个reset()方法重置状态,Lambda做不到这一点。
三、std::function与std::bind:更灵活的函数封装
3.1 std::function:万能函数包装器
std::function可以存储任何可调用对象——普通函数、Lambda、函数对象,甚至成员函数。说白了,它就是个类型擦除的容器。
#include <iostream>
#include <functional>
// 普通函数
int add(int a, int b) {
return a + b;
}
// 函数对象
struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};
int main() {
// std::function可以存储各种可调用对象
std::function<int(int, int)> func;
func = add; // 存储普通函数
std::cout << "add: " << func(3, 4) << std::endl;
func = Multiply(); // 存储函数对象
std::cout << "multiply: " << func(3, 4) << std::endl;
func = [](int a, int b) { return a - b; }; // 存储Lambda
std::cout << "subtract: " << func(3, 4) << std::endl;
return 0;
}
3.2 std::bind:参数绑定利器
std::bind可以把函数的某些参数固定下来,生成一个新的可调用对象。我在项目中经常用它来适配接口。
#include <iostream>
#include <functional>
void print(int a, int b, int c) {
std::cout << "a=" << a << ", b=" << b << ", c=" << c << std::endl;
}
int main() {
using namespace std::placeholders;
// 绑定所有参数
auto f1 = std::bind(print, 1, 2, 3);
f1(); // 输出: a=1, b=2, c=3
// 绑定部分参数,用占位符留空
auto f2 = std::bind(print, _1, 100, _2);
f2(10, 20); // 输出: a=10, b=100, c=20
// 改变参数顺序
auto f3 = std::bind(print, _3, _1, _2);
f3(10, 20, 30); // 输出: a=30, b=10, c=20
return 0;
}
注意:std::bind在C++11之后用得少了,因为Lambda可以更清晰地完成同样的工作。我个人建议新项目优先用Lambda,除非你需要处理复杂的参数绑定场景。
四、回调函数实现:实战应用
回调函数是这些技术的典型应用场景。说白了,就是把函数作为参数传给另一个函数,让它在合适的时机调用。
#include <iostream>
#include <functional>
#include <vector>
// 事件处理器
class EventEmitter {
private:
std::vector<std::function<void(int)>> listeners;
public:
void on(std::function<void(int)> callback) {
listeners.push_back(callback);
}
void emit(int data) {
for (auto& listener : listeners) {
listener(data);
}
}
};
int main() {
EventEmitter emitter;
// 注册回调 - 用Lambda
emitter.on([](int data) {
std::cout << "回调1: 收到数据 " << data << std::endl;
});
// 注册回调 - 用函数对象
struct Logger {
void operator()(int data) const {
std::cout << "回调2: 记录数据 " << data << std::endl;
}
};
emitter.on(Logger());
// 触发事件
emitter.emit(42);
emitter.emit(100);
return 0;
}
核心要点:
- Lambda适合一次性、简单的回调逻辑
- 函数对象适合需要保存状态、可复用的回调
- std::function作为类型擦除的容器,可以统一存储各种回调
- std::bind在适配接口、固定参数时有用,但Lambda通常更清晰
五、知识体系总览
下面这张图总结了本章的核心内容,帮你理清思路:
我的建议:刚开始学的时候,先掌握Lambda的基本用法和捕获列表。函数对象和std::function可以在实际项目中慢慢体会。别想着一次全学会,编程这东西,用得多了自然就熟了。