当前位置: 首页 > news >正文

C++ 函数指针、回调与 Lambda 全解析

在学习 C++ 的过程中,函数指针常常让人觉得“古老又晦涩”。但其实它的思想非常简单:函数本身就是一块内存中的指令序列,我们完全可以把函数的地址当作变量传来传去。

这样,我们就可以在运行时决定到底调用哪个函数,从而让代码更灵活。本文会结合实例代码,从函数指针讲到 Lambda 和 std::function,最后总结适用场景。


1. 函数指针是什么?

我们平时调用函数是这样的:

void HelloWorld() {std::cout << "Hello World!" << std::endl;
}int main() {HelloWorld(); // 直接调用
}

但如果我们写成:

int main() {auto myHelloWorld = &HelloWorld; // 获取函数地址myHelloWorld();                  // 通过函数指针调用
}

依然会输出:

Hello World!

这里 auto 推导出来的类型是 void(*)(),意思是:
一个指向“无参数、返回 void 的函数”的指针。

 也就是说,函数名本身就能转化为函数指针。如果你写 HelloWorld(不带括号),那就是函数地址;如果写 HelloWorld(),那就是调用函数。

等价写法如下:

// 方法1:用 auto(推荐)
auto myHelloWorld = HelloWorld;// 方法2:显式声明
void (*myHelloWorld)() = HelloWorld;// (最佳)方法3
using myFunctionType = void(*)();
myFunctionType myHelloWorld = HelloWorld;// 方法4
typedef void(*myFunctionPtr)();
myFunctionPtr myHelloWorld = HelloWorld;

2. 带参数的函数指针

如果函数有参数,那函数指针的声明方式就是:

*返回类型 (变量名)(参数类型...)

void PrintValue(int value) {std::cout << "Value: " << value << std::endl;
}int main() {void (*func)(int) = PrintValue; // 声明函数指针func(42);                       // 调用
}

输出:

Value: 42

这说明函数指针和普通函数几乎一样,只是多了“把函数当变量”的能力。


3. 为什么要用函数指针?

如果只是一个函数调用另一个函数,我们完全没必要用函数指针。
它的价值在于:让函数能接收另一个函数作为参数,从而实现回调、算法选择、框架抽象

示例1:通用循环处理(回调)

假设我们希望对数组里的每个元素都执行某个操作:

void PrintValue(int value)
{std::cout << "Value: " << value <<std::endl;
}void ForEach(const std::vector<int>& values, void(*func)(int))
// 希望在这个函数里调用某个函数 本例中将会调用PrintValue
{for (int value : values){func(value);}
}int main()
{std::vector<int> values = { 1, 5, 2, 4, 3};ForEach(values, PrintValue);// 传入了名为values的vector// 然后对这个vector中的每一个元素 都执行PrintValue函数std::cin.get();
}

好处是:ForEach 本身不关心“怎么处理元素”,只管循环。
真正的处理逻辑交给调用者决定,这就是回调的思想。


示例2:运行时选择算法

如果我们需要在运行时决定“用哪种处理方式”,函数指针也能派上用场:

void ProcessMode1(int x) { std::cout << "Mode1: " << x << "\n"; }
void ProcessMode2(int x) { std::cout << "Mode2: " << x << "\n"; }/*void (*processor)(int) 声明了一个 函数指针,
它可以指向任何接收 int 参数、返回 void 的函数。初始值设为 nullptr,表示当前还没有选定处理函数。*/
void ProcessData(int mode, const std::vector<int>& data) {void (*processor)(int) = nullptr;if (mode == 1) processor = &ProcessMode1;else if (mode == 2) processor = &ProcessMode2;for (int v : data) processor(v);
}

这样就避免了在每个 if-else 分支里重复写循环代码。


4. 回调机制(事件驱动编程)

函数指针的一个经典用途是 事件回调:我们把一个函数交给某个对象,等特定事件发生时,它再调用这个函数。

#include <iostream>// 定义一个 Button 类,模拟按钮
class Button {private:// 成员变量,保存一个函数指针// 类型是 "void (*)()",即指向一个无参无返回值的函数void (*onClickHandler)() = nullptr; // 默认初始化为空指针(表示没有注册回调)public:// 注册点击事件回调函数// 参数是一个函数指针:指向 "void func()" 这种无参数、无返回值的函数void setOnClick(void (*callback)()) {onClickHandler = callback; // 保存用户传进来的函数指针}// 模拟按钮被点击void click() {if (onClickHandler)         // 判断是否已经注册了回调onClickHandler();       // 调用回调函数}};// 一个普通函数,符合回调要求:无参数、无返回值
void saveFile() {std::cout << "Saving...\n";
}int main() {Button saveBtn;                    // 创建一个按钮对象saveBtn.setOnClick(&saveFile);     // 注册回调,把 saveFile 绑定到按钮点击事件// 这里传递的是函数指针 "&saveFile",其实也可以直接写 saveFile(C++ 会自动转为指针)saveBtn.click();                   // 模拟用户点击按钮,触发回调,执行 saveFile()
}

这里 Button 类完全不知道“点击后做什么”,只管触发 onClickHandler
这样就达到了 解耦:按钮不依赖于具体的保存逻辑。


5. Lambda 与 std::function

在上一节里我们用过 传统函数指针void(*)(int)),它确实能传函数,但有几个缺点:

  1. 不能捕获外部变量
    比如想在回调里用外面的局部变量,函数指针就无能为力。

  2. 语法不直观
    声明函数指针的语法比较复杂,不如现代 C++ 的写法清晰。

  3. 只能绑定函数
    没办法直接传 lambda 或仿函数对象。

于是,C++11 引入了 Lambda 表达式std::function,让回调写法更灵活优雅。


Lambda 表达式

Lambda 就是一个匿名函数,写法是:

[capture](parameters) -> return_type {// 函数体
}
  • capture:捕获外部变量的方式(值捕获、引用捕获等)。

  • parameters:参数列表。

  • return_type:返回值类型(大多数情况可以省略)。

例子:

int a = 10;// 定义一个 Lambda:参数 v,返回 a+v
auto func = [&a](int v) { return a + v; };std::cout << func(5); // 输出 15

std::function

std::function 是一个通用的 函数包装器,它能存储:

  • 普通函数

  • 函数指针

  • Lambda 表达式

  • 仿函数对象(重载 operator() 的类)

#include <functional>void ForEach(const std::vector<int>& values, std::function<void(int)> func) {for (int v : values) func(v);
}int main() {std::vector<int> values = {1, 2, 3};// 直接写匿名函数ForEach(values, [](int v) { std::cout << v << "\n"; });// 捕获外部变量int a = 10;ForEach(values, [&a](int v) { std::cout << a + v << "\n"; });
}

 和 void(*)(int) 不同,std::function<void(int)> 可以接受:

  • 普通函数

  • Lambda(捕获变量)

  • 仿函数对象

这让它更适合现代 C++ 编程。


6. 小结

  • 函数指针:就是保存函数地址的变量,能把函数当作参数传递。

  • 主要用途:动态选择算法、事件回调、插件系统、通用算法框架。

  • 现代替代方案std::function + Lambda,更灵活,也能捕获外部变量。

  • 核心思想:抽取变化部分(用函数指针/回调),统一框架部分,避免重复代码。

http://www.dtcms.com/a/423271.html

相关文章:

  • UNIX下C语言编程与实践4-UNIX 编程环境搭建:三种安装方式(本机、虚拟机、网络终端)对比与实操
  • 辽宁平台网站建设公司万维网站注册
  • 网站建设div ass抖音代运营合作方案ppt
  • uni-app 开发H5软键盘会顶起底部内容的解决方案
  • Syslog日志集成搭建
  • 基于AI辅助工具的原创音乐创作实践研究——以Tunee首届音乐挑战赛作品《断掉的铜线》为例[特殊字符]
  • mysql数据库学习之用户权限管理(四)
  • 如何做网站网页流程粤icp备案号查询网官网
  • AI使用 Node.js modbus-serial 搭建一个可交互的 Modbus TCP 主站与从站 Demo
  • Websocket+cpolar:如何轻松实现服务远程访问?
  • 嵌入式Linux BootLoader全景图:主流选择与核心对比
  • 基于Springboot的DDD实战(不依赖框架)
  • 网站设计流程步骤网站网络资源建立
  • 不用宝塔用linux操作mysql
  • Nginx 服务器
  • 网站开发浏览器企业网站建设定位注意的问题
  • AI视频生成进入多镜头叙事时代!字节发布 Waver 1.:一句话生成 10 秒 1080p 多风格视频,创作轻松“一键”达!
  • 怎样创建网站吉洋大鼓免费广告发布平台
  • 【Python3教程】Python3高级篇之集成MongoDB
  • MongoDB源码分析慢日志:从配置到实现的完整解析
  • Bootloader核心原理与简单实现:从零写一个bootloader
  • MongoDB到关系型数据库:JSON字段如何高效转换?
  • 网站排名优化原理一个公司能备案多个网站吗
  • 苏大团队联合阿丘科技发表异常生成新方法:创新双分支训练法,同步攻克异常图像生成、分割及下游模型性能提升难题。
  • wordpress如何使用百度主动推送seo短视频网页入口引流下载
  • Docker 镜像加速安装MySQL操作步骤
  • 量子计算技术全景:从硬件路线到AI融合
  • 人工智能-机器学习day1
  • 济南网站制作企业建设部标准定额网站
  • 微服务组件-Eureka 技术详解