函数指针就是指向函数的指针变量。函数在内存里同样有地址,所以可以像变量一样被指针"指向",进而作为参数传递、存到数组里、运行时切换调用。

一、声明函数指针

语法:返回类型 (*指针名)(参数列表);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

double multiply(double a, double b) {
return a * b;
}

double add(double a, double b) {
return a + b;
}

int main() {
// 声明一个「指向返回 double、参数 (double, double) 函数」的指针
double (*ptr)(double a, double b) = multiply;
double res = ptr(4.2, 1.5); // 调用 multiply,res = 6.3

ptr = &add; // 换指向 add
double ans = (*ptr)(4.2, 1.5); // 等价于 ptr(4.2, 1.5),调用 add
cout << res << " " << ans; // 6.3 5.7

return 0;
}

调用时 ptr(...) 和 (*ptr)(...) 完全等价,编译器都把它当成"解引用后再调用"。

为什么要写 (*ptr) 不直接写 *ptr(...)?

因为小括号 () 优先级最高,*ptr(4.2, 1.5) 会被解读成 *(ptr(4.2, 1.5))——"对函数返回值解引用",显然不对。所以外层括号必须括起来。

二、用 typedef / using 给函数指针起别名

每次写 double (*FuncPtr)(double, double) 又臭又长,建议起个别名。

typedef(C 风格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

typedef double (*FuncPtr)(double, double);

double add(double a, double b) {
return a + b;
}

int main() {
FuncPtr ptr = add; // 定义函数指针
cout << ptr(0.4, 1.5); // 调用 add
return 0;
}

using(C++11 风格,更推荐)

1
2
3
4
using FuncPtr = double (*)(double, double);

FuncPtr ptr = add;
cout << ptr(0.4, 1.5);

using 写法更清晰,可读性也好。现代 C++ 优先用 using。

三、定义"函数类型"再取指针

也可以先定义函数类型(不带 *),再声明指针:

1
2
3
typedef double FuncType(double, double);     // FuncType 是函数类型
FuncType* ptr = add; // 指针变量 = 函数
cout << ptr(0.4, 1.5);

注意:FuncType 是函数类型,FuncType* 才是指针。这种写法较少用,了解即可。

四、用 auto 简化

函数指针声明往往很长,能用 auto 就用 auto:

1
2
auto ptr = add;
cout << ptr(0.4, 1.5);

限制:auto 只能在声明时初始化单个变量时用。声明函数指针数组就不能用 auto,必须用 typedef / using。

五、函数指针数组

1
2
3
4
5
6
7
8
9
10
typedef double (*FuncPtr)(double, double);

double multiply(double a, double b) { return a * b; }
double add(double a, double b) { return a + b; }

int main() {
FuncPtr ptrs[2] = { add, multiply };
cout << ptrs[0](0.4, 1.5) << " " << ptrs[1](0.4, 1.5); // 1.9 0.6
return 0;
}

实际工程里这种"分发表"用 std::function 或 std::map<std::string, fnPtr> 代替更灵活;函数指针数组更多出现在 C / 嵌入式代码里。

六、作为函数参数(回调函数)

函数指针最常见的用途——把"行为"作为参数传给另一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool compare(float a, float b) {
return a < b;
}

int main() {
vector<float> numbers{0.1, 2, 0.07, 3.14, 5.6, 4.7};
sort(numbers.begin(), numbers.end(), compare); // compare 作为 sort 的回调
for (auto i : numbers) cout << i << " ";
return 0;
}

输出:

1
0.07 0.1 1.9 2 3.14 4.7 5.6

sort 的第三个参数就是「比较函数指针 / 可调用对象」,这就是典型的回调函数场景。

七、类成员函数指针

类成员函数指针的语法跟普通函数指针不一样——前面要带类名限定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

class DemoClass {
public:
double multiply(double a, double b) { return a * b; }
double add(double a, double b) { return a + b; }
};

int main() {
// 声明一个「指向 DemoClass 成员函数」的指针
double (DemoClass::*ptrFunc)(double, double);
ptrFunc = &DemoClass::add;

DemoClass obj;
cout << (obj.*ptrFunc)(0.5, 1.2); // 通过对象调用:1.7

DemoClass* p = &obj;
cout << (p->*ptrFunc)(0.5, 1.2); // 通过对象指针调用:1.7

return 0;
}

调用语法

调用方式 语法
通过对象 (对象.*成员函数指针)(参数...)
通过对象指针 (对象指针->*成员函数指针)(参数...)

成员函数指针比普通函数指针复杂得多——实际工程里几乎都用 std::bind 或 lambda 捕获代替,可读性更好。

八、易错点清单

  1. 声明少写括号:double *ptr(double, double) 会被当成「返回 double* 的函数声明」,而不是「指向返回 double 的函数指针」。永远要把 *ptr 用括号包起来。
  2. 函数指针类型不匹配:参数列表或返回值类型对不上,编译直接报错。int (*)(int) 和 int (*)(double) 是完全不同的类型。
  3. 空指针解引用调用:double (*ptr)(double, double) = nullptr; ptr(1, 2); → 崩溃。调用前判断:if (ptr) ptr(...);。
  4. 成员函数指针忘了 & 和 .* / ->*:赋值要 &类名::函数名;调用要 (obj.*ptr)(...) 或 (p->*ptr)(...)。
  5. typedef 函数指针时漏掉 *:typedef double (FuncPtr)(double, double); 定义的是函数类型而不是指针,要写 typedef double (*FuncPtr)(double, double);。

九、极简速记

场景 推荐写法
普通函数指针 using FuncPtr = double (*)(double, double);
局部变量 auto ptr = func;
函数指针数组 FuncPtr arr[] = {f1, f2};
成员函数指针 double (Class::*ptr)(...) = &Class::method;
调用 ptr(...) / (*ptr)(...) / (obj.*ptr)(...)

十、小结

  • 函数指针 = 指向函数的指针变量,让"行为"也能像值一样传递;
  • 语法核心:返回类型 (*名字)(参数列表),* 一定要包进括号;
  • 别名优先用 using;局部变量优先用 auto;
  • 主要用途是回调函数(STL 算法、qsort、自定义 API 等);
  • 成员函数指针语法复杂、坑多,实际工程优先用 std::function 或 lambda 替代。