C++ 回调函数详解
什么是回调函数?
回调函数(Callback Function)是一种作为参数传递给另一个函数的函数。换句话说,就是把一个函数当作参数传给另一个函数,让后者在适当的时机去"调用"这个函数。
简单比喻
想象你打电话给客服说"等会有空回我",客服说"好的"。这里的"回电"就是一个回调------你把要执行的操作(回电)留给对方在合适的时候执行。
在编程中,回调函数就是把自己的代码交给别人去调用。
回调函数的作用
- 异步执行 - 不阻塞主线程,让耗时的操作完成后自动执行后续代码
- 事件驱动 - 当某个事件发生时(如点击按钮),自动调用相应的处理函数
- 代码解耦 - 调用者不需要知道被调用者的具体实现,只需要知道接口
- 策略模式 - 可以在运行时动态改变行为
C++ 中的回调函数实现方式
C++ 中实现回调函数主要有几种方式:
- 函数指针
- std::function 和 std::bind
- Lambda 表达式
- 虚函数/接口
方式一:函数指针
基本语法
cpp
#include <iostream>
#include <vector>
// 定义函数指针类型
typedef int (*Operation)(int, int);
// 或者使用 using
using Operation = int(*)(int, int);
// 使用函数指针的函数
int calculate(int a, int b, Operation op) {
return op(a, b);
}
// 具体的回调函数
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
// 传递函数指针作为回调
std::cout << "加法: " << calculate(10, 5, add) << std::endl; // 15
std::cout << "乘法: " << calculate(10, 5, multiply) << std::endl; // 50
return 0;
}
示例:排序比较函数
cpp
#include <iostream>
#include <vector>
#include <algorithm>
// 比较函数的返回类型必须是 bool
bool ascending(int a, int b) {
return a < b; // 升序排列
}
bool descending(int a, int b) {
return a > b; // 降序排列
}
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9};
// 使用函数指针进行排序
std::sort(nums.begin(), nums.end(), ascending);
std::cout << "升序: ";
for (int n : nums) std::cout << n << " "; // 1 2 5 8 9
std::cout << std::endl;
// 重新排序为降序
std::sort(nums.begin(), nums.end(), descending);
std::cout << "降序: ";
for (int n : nums) std::cout << n << " "; // 9 8 5 2 1
std::cout << std::endl;
return 0;
}
方式二:std::function 和 std::bind
基本用法
cpp
#include <iostream>
#include <functional>
// 使用 std::function 作为参数
void processNumber(int num, std::function<void(int)> callback) {
std::cout << "处理数字: " << num << std::endl;
if (callback) {
callback(num * 2); // 执行回调
}
}
void myCallback(int result) {
std::cout << "回调结果: " << result << std::endl;
}
int main() {
// 传递普通函数
processNumber(10, myCallback);
// 传递 Lambda 表达式
processNumber(20, [](int result) {
std::cout << "Lambda 回调: " << result << std::endl;
});
return 0;
}
std::bind 绑定参数
cpp
#include <iostream>
#include <functional>
void greet(std::string name, std::string message) {
std::cout << message << ", " << name << "!" << std::endl;
}
int main() {
// 使用 bind 绑定部分参数
auto sayHello = std::bind(greet, std::placeholders::_1, "Hello");
auto sayBye = std::bind(greet, std::placeholders::_1, "Goodbye");
sayHello("Alice"); // Hello, Alice!
sayBye("Bob"); // Goodbye, Bob!
return 0;
}
方式三:Lambda 表达式(推荐)
Lambda 是 C++11 引入的特性,是现代 C++ 中最常用的回调方式。
基本语法
cpp
[capture list](parameters) -> return_type {
// function body
}
示例一:基础用法
cpp
#include <iostream>
#include <vector>
void forEach(std::vector<int>& nums, std::function<void(int)> func) {
for (int n : nums) {
func(n);
}
}
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 使用 Lambda 作为回调
forEach(nums, [](int n) {
std::cout << n << " ";
});
std::cout << std::endl;
return 0;
}
示例二:捕获外部变量
cpp
#include <iostream>
#include <vector>
int main() {
int multiplier = 2;
// Lambda 捕获外部变量
auto multiply = [multiplier](int x) {
return x * multiplier;
};
std::cout << multiply(5) << std::endl; // 10
// 修改捕获方式
int counter = 0;
auto increment = [&counter]() {
counter++;
};
increment();
increment();
std::cout << counter << std::endl; // 2
return 0;
}
捕获方式说明:
| 捕获方式 | 说明 |
|---|---|
[x] |
按值捕获 x |
[&x] |
按引用捕获 x |
[=] |
按值捕获所有外部变量 |
[&] |
按引用捕获所有外部变量 |
[=, &x] |
按值捕获所有,除 x 按引用 |
[&, x] |
按引用捕获所有,除 x 按值 |
示例三:作为排序回调
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20}
};
// 按年龄排序
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
});
std::cout << "按年龄排序:" << std::endl;
for (const auto& p : people) {
std::cout << p.name << ": " << p.age << std::endl;
}
return 0;
}
方式四:接口/虚函数(策略模式)
基础接口实现
cpp
#include <iostream>
#include <vector>
// 定义策略接口
class IStrategy {
public:
virtual ~IStrategy() = default;
virtual int execute(int a, int b) = 0;
};
// 具体策略 A
class AddStrategy : public IStrategy {
public:
int execute(int a, int b) override {
return a + b;
}
};
// 具体策略 B
class MultiplyStrategy : public IStrategy {
public:
int execute(int a, int b) override {
return a * b;
}
};
// 使用策略的上下文
class Calculator {
private:
IStrategy* strategy;
public:
void setStrategy(IStrategy* s) {
strategy = s;
}
int calculate(int a, int b) {
return strategy->execute(a, b);
}
};
int main() {
Calculator calc;
// 使用加法策略
calc.setStrategy(new AddStrategy());
std::cout << calc.calculate(10, 5) << std::endl; // 15
// 切换到乘法策略
calc.setStrategy(new MultiplyStrategy());
std::cout << calc.calculate(10, 5) << std::endl; // 50
return 0;
}
回调函数在实际项目中的应用
应用一:定时器回调
cpp
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <atomic>
class Timer {
private:
std::atomic<bool> running{false};
std::thread timerThread;
std::function<void()> callback;
public:
void start(int intervalMs, std::function<void()> cb) {
running = true;
callback = cb;
timerThread = std::thread([this, intervalMs]() {
while (running) {
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
if (callback && running) {
callback();
}
}
});
}
void stop() {
running = false;
if (timerThread.joinable()) {
timerThread.join();
}
}
~Timer() {
stop();
}
};
int main() {
Timer timer;
int counter = 3;
timer.start(1000, [&counter]() {
std::cout << "倒计时: " << counter << std::endl;
counter--;
});
// 等待倒计时结束
std::this_thread::sleep_for(std::chrono::seconds(4));
timer.stop();
std::cout << "时间到!" << std::endl;
return 0;
}
应用二:按钮事件回调
cpp
#include <iostream>
#include <functional>
#include <string>
#include <map>
// 简化的按钮类
class Button {
private:
std::string text;
std::function<void()> onClick;
public:
Button(const std::string& t) : text(t) {}
void setOnClick(std::function<void()> callback) {
onClick = callback;
}
void click() {
std::cout << "按钮 [" << text << "] 被点击" << std::endl;
if (onClick) {
onClick();
}
}
};
int main() {
Button saveBtn("保存");
Button cancelBtn("取消");
// 设置点击回调
saveBtn.setOnClick([]() {
std::cout << "-> 执行保存操作" << std::endl;
});
cancelBtn.setOnClick([]() {
std::cout << "-> 执行取消操作" << std::endl;
});
// 模拟点击
saveBtn.click();
cancelBtn.click();
return 0;
}
应用三:异步任务回调
cpp
#include <iostream>
#include <thread>
#include <functional>
#include <future>
// 模拟异步任务
void asyncTask(std::function<void(std::string)> callback) {
std::thread([callback]() {
std::this_thread::sleep_for(std::chrono::seconds(2));
callback("任务完成! 结果: 42");
}).detach();
}
int main() {
std::cout << "开始异步任务..." << std::endl;
// 注册回调函数
asyncTask([](std::string result) {
std::cout << "收到回调: " << result << std::endl;
});
std::cout << "主线程继续执行..." << std::endl;
// 等待异步任务完成
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
应用四:数据过滤回调
cpp
#include <iostream>
#include <vector>
#include <algorithm>
// 使用模板和函数指针进行过滤
template<typename T>
std::vector<T> filterData(const std::vector<T>& data, bool (*filterFunc)(const T&)) {
std::vector<T> result;
for (const auto& item : data) {
if (filterFunc(item)) {
result.push_back(item);
}
}
return result;
}
bool isEven(int n) {
return n % 2 == 0;
}
bool isPositive(int n) {
return n > 0;
}
int main() {
std::vector<int> numbers = {-3, -2, -1, 0, 1, 2, 3, 4, 5};
// 过滤偶数
auto evens = filterData(numbers, isEven);
std::cout << "偶数: ";
for (int n : evens) std::cout << n << " "; // -2 0 2 4
std::cout << std::endl;
// 过滤正数
auto positives = filterData(numbers, isPositive);
std::cout << "正数: ";
for (int n : positives) std::cout << n << " "; // 1 2 3 4 5
std::cout << std::endl;
// 使用 Lambda 过滤大于3的数
auto greaterThan3 = filterData(numbers, [](int n) { return n > 3; });
std::cout << "大于3: ";
for (int n : greaterThan3) std::cout << n << " "; // 4 5
std::cout << std::endl;
return 0;
}
应用五:遍历容器的回调
cpp
#include <iostream>
#include <vector>
#include <string>
// 通用遍历函数
template<typename T>
void forEach(const std::vector<T>& items, std::function<void(const T&)> callback) {
for (const auto& item : items) {
callback(item);
}
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// 遍历数字
std::cout << "数字: ";
forEach(numbers, [](int n) { std::cout << n * n << " "; });
std::cout << std::endl;
// 遍历字符串
std::cout << "名字: ";
forEach(names, [](const std::string& name) {
std::cout << "[" << name << "] ";
});
std::cout << std::endl;
return 0;
}
回调函数的注意事项
1. 空指针检查
cpp
#include <iostream>
#include <functional>
void process(std::function<void()> callback) {
// 必须检查回调是否为空
if (callback) {
callback();
}
// 或者使用以下方式(更安全)
// if (callback != nullptr) { ... }
}
int main() {
process(nullptr); // 不会崩溃
process([]() {
std::cout << "回调执行" << std::endl;
});
return 0;
}
2. Lambda 捕获生命周期
cpp
#include <iostream>
#include <functional>
// 错误示例:返回捕获局部变量的 Lambda
std::function<int()> createCallback() {
int local = 10;
// 警告:local 是局部变量,按值捕获后返回的 Lambda 引用了即将销毁的变量
return [local]() { return local; };
}
// 正确示例:按值捕获
std::function<int()> createCallbackCorrect() {
int local = 10;
return [local]() { return local; }; // local 的值被复制
}
// 正确示例:如果需要修改,使用 mutable
std::function<int()> createCounter() {
int count = 0;
return [&count]() mutable {
++count;
return count;
};
}
int main() {
auto counter = createCounter();
std::cout << counter() << std::endl; // 1
std::cout << counter() << std::endl; // 2
return 0;
}
3. 线程安全
cpp
#include <iostream>
#include <thread>
#include <atomic>
#include <functional>
// 线程安全的回调执行
void safeCall(std::function<void()>& callback) {
// 复制回调,在锁外执行,避免死锁
auto localCallback = callback; // 这里需要拷贝!
if (localCallback) {
localCallback();
}
}
int main() {
std::function<void()> callback;
std::thread t1([&callback]() {
callback = []() { std::cout << "Hello" << std::endl; };
});
std::thread t2([&callback]() {
if (callback) {
callback();
}
});
t1.join();
t2.join();
return 0;
}
总结
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 函数指针 | 简单、直接 | 不支持捕获外部变量 |
| std::function | 灵活、可存储 | 有轻微性能开销 |
| Lambda | 语法简洁、可捕获变量 | 需要 C++11 以上 |
| 虚函数/接口 | 真正的多态、安全 | 代码较多 |
推荐 :现代 C++ 开发中,优先使用 Lambda 表达式 配合 std::function,它们是最灵活、最易读的回调实现方式。