在C++中,函数指针保存的是某个函数的入口地址,它本身的类型由返回值和参数列表共同决定。当我们希望一个函数接收“某种行为”而不是固定逻辑时,就可以把函数指针作为参数;当工厂或调度器需要根据条件交出不同处理逻辑时,让函数返回函数指针也很自然。理解这两点,关键在于先写出正确的指针类型,再谈如何使用。

一、函数指针的基础声明
函数指针的声明容易让人迷惑,是因为星号和中括号混在一起。假设有一个接收两个int、返回int的函数,指向它的指针类型应写成int(*)(int,int)。括号不能省,否则会变成返回int指针的函数声明。
下面用最原始的方式定义一个函数以及指向它的指针,并直接调用:
#include <iostream>
int add(int a, int b) {
return a + b;
}
int main() {
// 声明函数指针并绑定
int (*pFunc)(int, int) = &add;
std::cout << pFunc(3, 4) << std::endl; // 输出7
return 0;
}
这里pFunc就是变量名,类型是int(*)(int,int)。取地址符&可写可不写,因为函数名在多数表达式里会退化为指针。掌握这种原始写法,才能看懂后面用别名简化后的代码。
二、作为函数参数传递
把函数指针当参数,最常见于回调和通用算法。比如我们写一个计算器,它不关心具体做加减还是乘除,只负责拿到两个数和一种运算。
使用typedef可以让参数表干净很多:
#include <iostream>
typedef int (*CalcOp)(int, int);
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
// 第三个参数就是函数指针
int compute(int x, int y, CalcOp op) {
return op(x, y);
}
int main() {
std::cout << compute(2, 5, add) << std::endl; // 7
std::cout << compute(2, 5, mul) << std::endl; // 10
return 0;
}
上面的CalcOp把又长又易错的类型封装起来。调用方传入add或mul,compute内部完全不需要知道细节。这种写法在C语言风格的接口里非常普遍,比如标准库qsort的比较函数就是典型例子。
现代C++更推荐用using替代typedef,语义更清晰,也支持模板别名:
#include <iostream>
using CalcOp = int(*)(int, int);
int sub(int a, int b) { return a - b; }
int compute(int x, int y, CalcOp op) {
return op(x, y);
}
int main() {
std::cout << compute(9, 4, sub) << std::endl; // 5
return 0;
}
这两种别名方式在作为传参时完全等价。如果项目已经用C++11及以上,统一用using能减少阅读负担,也方便后面改成std::function做更灵活的可调用对象封装。
三、作为函数返回值
返回函数指针意味着“根据输入给我一个能用的函数”。声明返回类型时,由于运算符优先级,必须把指针类型用括号包在返回值位置。
下面示例根据字符返回不同的运算函数:
#include <iostream>
using CalcOp = int(*)(int, int);
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
// 返回函数指针,注意括号
CalcOp getOp(char kind) {
if (kind == '+') return add;
else return mul;
}
int main() {
CalcOp op = getOp('+');
std::cout << op(3, 3) << std::endl; // 6
return 0;
}
这里getOp的返回类型是CalcOp,也就是函数指针别名。它返回的是全局函数地址,不存在生命周期问题。千万不要返回指向局部函数的指针,因为局部函数随栈帧销毁而无效,这点后面会强调。
如果不使用别名,直接写返回类型会非常晦涩:
int (*getOpRaw(char k))(int, int) {
// 省略实现,仅展示语法
return nullptr;
}
这种“返回指向函数的指针,该函数接收int,int返回int”的写法,连很多熟手都要看两遍。所以返回函数指针时,类型别名不是可选项,而是可读性必备。
四、常见误区与注意点
第一个误区是返回局部定义函数的指针。在C++中不能在函数内定义普通函数,但有些人会用lambda误以为能当函数指针返回。无捕获的lambda可转函数指针,但捕获了的就不行,而且返回局部static函数虽合法但易引发耦合。
第二个误区是数组参数退化。函数指针参数里如果写int(*)(int[]),实际等价于int(*)(int*),数组维度被丢弃。传参时别指望靠指针类型检查出数组大小。
#include <iostream>
using Printer = void(*)(int*);
void show(int* arr) {
std::cout << arr[0] << std::endl;
}
void call(Printer p, int* data) {
p(data);
}
int main() {
int a[3] = {10, 20, 30};
call(show, a);
return 0;
}
这段代码里show接收的是退化为指针的数组,调用方传a自动转成int*。如果业务逻辑依赖数组长度,必须在指针外单独传长度参数,而不是寄望于函数指针签名。
五、与std::function的对比
函数指针只能指向全局或静态函数以及无捕获lambda,而std::function可以包装任意可调用对象,包括有状态的lambda、仿函数。在需要返回或传递带上下文的逻辑时,优先考虑std::function。
但函数指针零开销、类型简单,在底层库或C兼容接口里仍不可替代。下面展示用std::function改写前面的compute:
#include <iostream>
#include <functional>
int compute(int x, int y, std::function<int(int,int)> op) {
return op(x, y);
}
int main() {
auto op = [](int a, int b) { return a - b; };
std::cout << compute(8, 2, op) << std::endl; // 6
return 0;
}
可以看到std::function的模板参数直接写调用签名,不必处理星号括号。但它有微小的堆或栈分配成本,在极端性能敏感路径上,纯函数指针仍是更轻的选择。理解了传参和返回的本质,两者可以按需互换。