回调函数(Callback)详解
一、什么是回调函数?
一句话理解 :你把一个函数"交给"别人,让别人在合适的时候帮你调用它。
就像你在餐厅点餐:
- 你告诉服务员:"菜好了叫我"(注册回调)
- 你去忙别的事(异步等待)
- 菜好了,服务员喊你(触发回调)
你不需要一直站在厨房门口等,服务员会在合适的时候通知你。
二、为什么需要回调函数?
没有回调时(同步阻塞)
cpp
// 你必须一直等,什么都干不了
double result = measure(); // 阻塞 5 秒才返回
qDebug() << result; // 5 秒后才能执行这行
有回调时(异步非阻塞)
cpp
// 发完指令立即返回,可以继续干别的
measure(TAG, [](const QVariant& opt, std::optional<double> data) {
// 数据到了才执行这里(可能 5 秒后)
qDebug() << data.value();
});
qDebug() << "我可以先做其他事情"; // 立即执行
三、C++ 中回调的三种实现方式
方式 1:函数指针(最原始)
cpp
// 定义回调类型
typedef void (*CallbackFunc)(int result);
// 接受回调的函数
void doSomething(int input, CallbackFunc callback) {
int result = input * 2;
callback(result); // 调用回调
}
// 调用方
void onResult(int result) {
qDebug() << "结果是:" << result;
}
int main() {
doSomething(5, onResult); // 输出: 结果是: 10
}
缺点:不能捕获外部变量,不够灵活。
方式 2:std::function + Lambda(现代 C++,推荐)✅
cpp
#include <functional>
#include <QDebug>
// 接受回调的函数
void doSomething(int input, std::function<void(int)> callback) {
int result = input * 2;
callback(result); // 调用回调
}
int main() {
// Lambda 表达式作为回调
doSomething(5, [](int result) {
qDebug() << "结果是:" << result;
});
// 捕获外部变量([=] 捕获所有值,[&] 捕获所有引用)
int factor = 10;
doSomething(5, [factor](int result) {
qDebug() << "结果是:" << result << ",倍数:" << factor;
});
}
方式 3:仿函数(Functor)
cpp
struct MyCallback {
void operator()(int result) {
qDebug() << "仿函数收到:" << result;
}
};
int main() {
MyCallback cb;
doSomething(5, cb);
}
四、Lambda 表达式语法速查
cpp
[捕获列表](参数列表) -> 返回类型 { 函数体 }
cpp
int a = 10;
int b = 20;
// [a] --- 按值捕获 a(拷贝一份,只读)
// [&a] --- 按引用捕获 a(可以修改原变量)
// [=] --- 按值捕获所有外部变量
// [&] --- 按引用捕获所有外部变量
// [=, &a] --- 默认按值捕获,但 a 按引用捕获
// [] --- 不捕获任何变量
auto lambda1 = [a]() { qDebug() << a; }; // 值
auto lambda2 = [&a]() { a += 1; }; // 引用
auto lambda3 = [=]() { qDebug() << a << b; }; // 全部值
auto lambda4 = [&]() { a += 1; b += 1; }; // 全部引用
五、与你项目的实际关联
你的代码中的回调
cpp
// SeiverPortTest.cpp --- 调用方
LCW->measure(TAG,
[this](const QVariant& option, std::optional<double> data) {
if (data.has_value()) {
qDebug() << "11111" << option.toString() << data.value();
}
}
);
这段代码可以拆解理解:
| 部分 | 含义 |
|---|---|
LCW->measure(TAG, ...) |
调用 measure,传入标识符和回调 |
[this] |
Lambda 捕获 this 指针,这样回调里能访问成员变量 |
(const QVariant& option, std::optional<double> data) |
回调参数:标识符 + 解析结果 |
{ if (data.has_value()) ... } |
回调体:数据有效时打印 |
完整调用链路图
你调用 LCW->measure(TAG, 回调Lambda)
│
▼
CommunicationWrap::measure()
├─ 保存 option 和 callback 到成员变量
└─ 调用 this->measure() (纯虚函数)
│
▼
LevelCommuationWrap::measure()
└─ write(_measure) 发送串口指令 "aaa"
│
▼
等待串口回复...(不阻塞,程序继续运行)
│
▼
串口收到数据 "3.14\r\n"
│
▼
readyRead 信号触发 → Lambda 槽函数
├─ readLine() 读取一行
├─ parse("3.14") → 返回 optional<double>(3.14)
└─ _callback(_option, 3.14) ← 触发你的回调!
│
▼
你的 Lambda 执行:
"11111 SeiverPortTest 3.14"
六、更多生活化例子
例子 1:快递到了通知你
cpp
void orderPackage(const QString& address, std::function<void(const QString&)> onDelivered) {
// 模拟:发货 → 运输 → 送达
qDebug() << "包裹已发出,地址:" << address;
// 模拟延迟后调用回调
QTimer::singleShot(3000, [onDelivered]() {
onDelivered("已签收");
});
}
// 调用
orderPackage("北京市海淀区", [](const QString& status) {
qDebug() << "快递通知:" << status; // 3秒后输出
});
例子 2:排序时传入自定义比较规则
cpp
QList<int> numbers = {3, 1, 4, 1, 5, 9};
// 升序排序 --- 传入一个"比较回调"
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) { return a < b; } // 回调:告诉 sort 怎么比
);
// 结果: 1, 1, 3, 4, 5, 9
// 降序排序 --- 换一个回调
std::sort(numbers.begin(), numbers.end(),
[](int a, int b) { return a > b; } // 回调:反过来比
);
// 结果: 9, 5, 4, 3, 1, 1
例子 3:按钮点击(Qt 信号槽本质就是回调)
cpp
// 你写的代码
connect(ui.pushButton, &QPushButton::clicked, this, [this]() {
qDebug() << "按钮被点击了";
});
// 等价理解:
// "clicked 信号" = 按钮内部说"我被点了"
// "Lambda" = 你给按钮的回调:"点的时候帮我做这件事"
七、常见陷阱
陷阱 1:悬垂引用
cpp
// ❌ 错误:&num 是引用,lambda 执行时 num 已销毁
std::function<void()> getCallback() {
int num = 42;
return [&num]() { qDebug() << num; }; // 危险!
}
// ✅ 正确:按值拷贝
std::function<void()> getCallback() {
int num = 42;
return [num]() { qDebug() << num; }; // 安全,拷贝了一份
}
陷阱 2:this 指针失效
cpp
// ❌ 如果对象先被销毁,回调中的 this 就是野指针
auto* obj = new MyClass();
someAsyncTask([obj]() {
obj->doSomething(); // 如果 obj 已 delete,崩溃!
});
delete obj;
陷阱 3:回调地狱(嵌套过深)
cpp
// ❌ 丑陋的"回调地狱"
step1([](auto r1) {
step2(r1, [](auto r2) {
step3(r2, [](auto r3) {
step4(r3, [](auto r4) {
// 越来越深...
});
});
});
});
// ✅ 现代 C++ 用 std::async / std::future 或协程解决
八、总结
| 概念 | 说明 |
|---|---|
| 回调函数 | 作为参数传递的函数,在特定时机被调用 |
| 用途 | 异步通知、事件处理、自定义行为 |
| C++ 实现 | 函数指针 / std::function + Lambda / 仿函数 |
| Lambda 捕获 | [=] 值拷贝、[&] 引用、[this] 当前对象 |
| 核心思想 | 把"做什么"和"什么时候做"分离 |