“把函数当值用"的三种写法:函数指针、std::function、lambda。包含语法拆解、三者对比、捕获方式速记与常见坑。
核心概念速查
| 概念 | 说明 | 关键点 |
|---|---|---|
| 函数指针 | 存函数地址的变量 | C 风格,语法绕 |
| std::function | 通用函数包装器 | C++11,可存 lambda/仿函数 |
| lambda | 匿名函数对象 | C++11,现代首选 |
| 回调 | 函数指针的典型用途 | 事件、排序、遍历 |
| 捕获 | lambda 使用外部变量 | [ ] [&] [=] [x] |
为什么需要函数指针
把"行为"作为参数传递,让代码更灵活。
// 没有函数指针: 每有一种排序规则就要写一个函数
void sort_ascending(int* arr, int n);
void sort_descending(int* arr, int n);
// 有函数指针: 一个排序函数,规则由调用方决定
void sort(int* arr, int n, bool (*compare)(int, int));
函数指针基础语法
#include <iostream>
// 定义两个比较函数
bool ascending(int a, int b) { return a < b; }
bool descending(int a, int b) { return a > b; }
// 参数是函数指针: bool (*compare)(int, int)
void sort(int* arr, int n, bool (*compare)(int, int)) {
for (int i = 0; i < n - 1; ++i)
for (int j = i + 1; j < n; ++j)
if (compare(arr[j], arr[i])) // 调用比较函数
std::swap(arr[i], arr[j]);
}
int main() {
int arr[] = {3, 1, 4, 1, 5};
sort(arr, 5, ascending); // 传函数名(自动转成指针)
// sort(arr, 5, &ascending); // 显式取地址也可以
sort(arr, 5, descending);
for (int x : arr) std::cout << x << " ";
return 0;
}
语法记忆:bool (*compare)(int, int) —— *compare 是函数名,(int,int) 是参数,bool 是返回类型。不加括号 bool *compare(...) 就变成返回指针的函数了。
函数指针 vs std::function vs lambda
| 特性 | 函数指针 | std::function | lambda |
|---|---|---|---|
| C++ 版本 | 全版本 | C++11 | C++11 |
| 语法 | 繁琐 | 稍简 | 最简洁 |
| 捕获变量 | ❌ 不能 | ✅ 可以 | ✅ 可以 |
| 存成员函数 | ❌ 麻烦 | ✅ (bind) | ✅ (捕获this) |
| 开销 | 最小 | 有额外包装 | 可零开销 |
| 现代推荐 | 旧代码见 | 通用回调 | 首选 |
lambda 详解
#include <iostream>
int main() {
int base = 10;
// [捕获方式] (参数) -> 返回类型 { 函数体 }
auto add_base = [base](int x) -> int {
return x + base; // 捕获了 base 的拷贝
};
auto add_by_ref = [&base](int x) -> int {
base += x; // 引用捕获,修改外部变量
return base;
};
auto everything = [=](int x) { return x + base; }; // 按值捕获全部
auto modify_all = [&](int x) { base += x; return base; }; // 按引用捕获全部
// 空捕获 []: 只能访问参数和自身
auto pure = [](int x) { return x * 2; };
std::cout << add_base(5) << "\n"; // 15
std::cout << modify_all(5) << "\n"; // 15
return 0;
}
捕获方式速记:
| 写法 | 含义 | 用途 |
|---|---|---|
[] | 不捕获 | 纯函数,最安全 |
[x] | 按值捕 x | 只读外部单个变量 |
[&x] | 按引用捕 x | 要修改外部变量 |
[=] | 按值捕全部 | 简洁但可能有拷贝开销 |
[&] | 按引用捕全部 | 用全部外部变量 |
[=, &x] | 混合 | 默认值捕,特殊引用 |
std::function 通用回调
#include <iostream>
#include <functional>
// std::function 可以作为成员/参数,存任意"可调用对象"
void register_callback(std::function<void(int)> cb) {
cb(42); // 调用时不知道背后是函数/lambda/仿函数
}
void plain_func(int x) { std::cout << "func: " << x << "\n"; }
int main() {
std::function<void(int)> f1 = plain_func; // 存普通函数
std::function<void(int)> f2 = [](int x) { std::cout << "lambda: " << x << "\n"; };
register_callback(f1);
register_callback(f2);
return 0;
}
常见坑
| 问题 | 原因 | 解决 |
|---|---|---|
| lambda 按引用捕获生命期 | [&] 捕获了栈变量,回调时已析构 | 按值捕获 / 确保变量存活 |
| 野指针回调 | 悬空的 this / 已销毁对象 | 捕获前确认生命期,用 weak_ptr |
| 函数指针语法读不懂 | * 和括号优先级 | 多练、用 std::function 或 alias |
| 默认参数不适用 | 函数指针不携带默认参数 | 包一层 lambda |
| 递归 lambda | 匿名无法自引用 | 用 std::function 存自己 |
顿悟
- 函数指针的本质是"代码的地址”:把一段逻辑当数据传来传去,政策和策略分离。
- lambda 是现代 C++ 的回调主力:它其实是编译器帮你生成的匿名类 +
operator(),捕获的变量就是类成员。 - 从函数指针到 lambda 的演进:语法糖越来越多,但底层思想(回调、把行为当参数)始终没变。
- 优先级是最大障碍:
bool (*f)(int)中括号让*f先绑定成"函数名",是无数人栽跟头的地方。
自测清单
- 能写出并调用函数指针,看懂
bool (*)(int, int)语法 - 理解数组 + 函数指针 = 排序比较器用法
- 会用 lambda,区分
[] [=] [&] [x] - 会用
std::function存函数/lambda - 理解回调的应用场景(网络、事件、算法)
- 知道 lambda 按引用捕获的生命期陷阱
豫公网安备41108202000982号