在学习 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,更灵活,也能捕获外部变量。

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

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐