回调函数学习

回调函数(Callback)详解

一、什么是回调函数?

一句话理解 :你把一个函数"交给"别人,让别人在合适的时候帮你调用它。

就像你在餐厅点餐:

  1. 你告诉服务员:"菜好了叫我"(注册回调)
  2. 你去忙别的事(异步等待)
  3. 菜好了,服务员喊你(触发回调)

你不需要一直站在厨房门口等,服务员会在合适的时候通知你。


二、为什么需要回调函数?

没有回调时(同步阻塞)

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] 当前对象
核心思想 把"做什么"和"什么时候做"分离
相关推荐
码上上班1 小时前
iptables学习; firewalld学习;ufw学习
学习
泡海椒1 小时前
动态代理核心原理:JQuick-Java接口规则自动生成机制解析
java·开发语言·python
RisunJan1 小时前
丝连族谱 0452 — 功能拆解与使用手册
学习
网硕互联的小客服1 小时前
怎样在CMD里用chkntfs /x C: 命令来取消磁盘自检?——原理与实践全解
运维·服务器·网络
步行cgn1 小时前
Spring 启动报错:BeanFactory not initialized 的原因与解决
java·python·spring
步行cgn2 小时前
Spring 报错:No bean class specified on bean definition 的原因与解决
java·后端·spring
老王爱玩车2 小时前
深入理解指针2
c语言·开发语言·学习
zhangjw342 小时前
第47篇:Java综合实战:电商秒杀系统(高并发场景)
java·开发语言
土司大王2 小时前
LeetCode hot100 回溯专题总结:Java 通用模板、决策树模型
java·算法·leetcode·决策树