函数指针必须严格匹配目标函数签名(返回类型、参数个数与类型),声明如int (*fp)(int, int),初始化用函数名fp = add;;调用可写为fp(1, 2)或(*fp)(1, 2),不支持捕获、成员函数或lambda(除非空捕获且隐式转换)。

怎么声明和初始化函数指针
函数指针不是“指向函数的地址”这种模糊说法能搞定的,它必须严格匹配目标函数的签名——返回类型、参数个数和类型一个都不能错。比如你有个函数 int add(int a, int b),对应的函数指针类型就得是 int (*)(int, int),不能漏掉 int 返回值,也不能把 int 参数写成 const int 或省略。
初始化时直接用函数名(不加括号),因为函数名在多数上下文中自动退化为地址:
int add(int a, int b) { return a + b; }
int (*fp)(int, int) = add; // 正确
// int (*fp)(int, int) = &add; // 也对,但 & 是冗余的
常见错误包括:
- 把
fp声明成int *fp(int, int)—— 这其实是函数声明,返回int *,不是函数指针 - 用
fp = add(1, 2)赋值 —— 这是在调用函数,得到的是int值,类型不匹配 - 参数类型用
long代替int,哪怕值能隐式转换,类型也不兼容
怎么通过函数指针调用函数
调用方式有两种:显式解引用 (*fp)(1, 2),或更常见的直接 fp(1, 2)。两者等价,C++ 允许省略 *,但语义上仍是“通过指针调用”,不是直接调用原函数。
注意:函数指针本身不携带上下文,不能捕获局部变量,也不支持重载解析——它只绑定到某个具体函数地址。如果想传参更灵活,得靠额外封装(比如用 std::function)。
典型使用场景:
- 回调机制,比如排序时传比较函数:
std::sort(arr, arr + n, cmp_fn_ptr) - 状态机中根据状态切换处理逻辑:
state_handler = states[current_state]; state_handler(); - 插件系统里加载外部模块的入口点
函数指针和 std::function 有什么区别
std::function 是类型擦除容器,能装普通函数、lambda、成员函数(需绑定)、甚至可调用对象;而原生函数指针只能指向符合签名的自由函数或静态成员函数。
关键差异:
- 函数指针体积小(通常一个指针大小),
std::function有额外开销(可能堆分配、虚函数调用) - 函数指针不能存储 lambda(除非是无捕获的,且编译器允许隐式转换为函数指针)
- 成员函数指针语法完全不同:
void (MyClass::*)(),不能直接赋给void (*)() - 模板推导时,函数指针类型明确,
std::function需显式指定模板参数或依赖 auto 推导
所以,如果你只需要调用一个已知签名的 C 风格函数,用函数指针更轻量;如果要统一管理多种可调用体,选 std::function。
容易忽略的 const 和 noexcept 问题
C++17 起,函数类型可以带 noexcept 说明符,而函数指针类型是否带 noexcept 是类型的一部分。这意味着:
void f() noexcept {}
void g() {}
void (*fp1)() noexcept = f; // OK
void (*fp2)() = g; // OK
// void (*fp3)() noexcept = g; // 错误:g 不是 noexcept
同理,const 修饰的是函数本身(C++ 中极少用),不是指针所指对象(函数不能被修改)。真正影响兼容性的,是参数和返回类型的 cv 限定——比如 int(*)(const int&, int) 和 int(*)(int&, int) 是不同类型,不能互赋。
实际项目里最容易栽在这里:头文件里声明了 noexcept,实现文件忘了加,链接时报错或运行时崩溃,但编译器不一定立刻报类型不匹配。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











