13、函数指针:函数指针的定义与赋值、通过函数指针调用函数、函数指针数组
各位同学,今天我们来聊一个让很多C语言学习者又爱又恨的话题——函数指针。说实话,我当年刚接触这个概念时,也觉得它有点绕。明明函数就是函数,为什么还要搞个指针指向它?这不是多此一举吗?
直到我在一个嵌入式项目中,需要根据不同的传感器类型调用不同的数据处理函数。那时候我才恍然大悟:函数指针,说白了就是让代码变得灵活、可配置的利器。你想想看,如果不用函数指针,我可能得写一堆if-else或者switch-case,代码又臭又长。用了函数指针,整个架构瞬间清爽了。
13.1 函数指针的定义与赋值
先说说函数指针到底是什么。函数指针,顾名思义,就是一个指向函数的指针变量。它存储的是函数的入口地址。在C语言中,函数名本身就是一个地址——函数的首地址。
定义函数指针的语法,说实话有点反人类。我刚开始学的时候也经常搞混。它的基本形式是:
返回类型 (*指针变量名)(参数类型列表);
注意那个括号!(*指针变量名) 必须用括号括起来。如果不加括号,就变成了返回指针的函数声明,那完全是两码事了。嗯,这里要特别小心。
来看个具体的例子:
#include <stdio.h>
// 定义一个普通函数
int add(int a, int b) {
return a + b;
}
int main() {
// 定义一个函数指针,指向返回值为int、参数为两个int的函数
int (*pFunc)(int, int);
// 赋值:将函数名赋给指针
pFunc = add;
// 或者写成 &add 也可以,但函数名本身就是地址
// pFunc = &add;
printf("函数指针的地址: %p\n", pFunc);
printf("函数本身的地址: %p\n", add);
return 0;
}
我在项目中遇到过一个问题:有人把函数指针定义成了 int *pFunc(int, int),结果编译器报了一堆错。这就是没加括号的后果——它变成了一个返回int指针的函数声明,而不是函数指针。
核心要点:
- 函数指针的类型必须与指向的函数签名完全一致(返回值类型和参数类型都要匹配)
- 函数名就是函数的地址,可以直接赋值给函数指针
- 定义时括号不能省略:
(*指针名)
13.2 通过函数指针调用函数
函数指针赋值之后,怎么用它来调用函数呢?有两种方式:
// 方式一:直接通过指针名调用
result = pFunc(3, 5);
// 方式二:显式解引用调用
result = (*pFunc)(3, 5);
我个人习惯用第一种方式,简洁明了。第二种方式虽然更直观地体现了"指针解引用"的概念,但写起来多了一层括号,看着有点啰嗦。
来看一个完整的例子:
#include <stdio.h>
int multiply(int a, int b) {
return a * b;
}
int main() {
int (*calc)(int, int);
calc = multiply;
// 通过函数指针调用
int result = calc(4, 6);
printf("4 * 6 = %d\n", result);
// 也可以这样
result = (*calc)(4, 6);
printf("4 * 6 = %d\n", result);
return 0;
}
这里有个小细节:如果你用 calc(4, 6) 这种方式,编译器会自动把 calc 当作函数指针解引用。所以两种写法效果完全一样。
我的经验:在嵌入式系统中,函数指针经常被用来实现回调机制。比如定时器超时后,通过函数指针调用用户注册的处理函数。这样框架代码不需要知道具体要做什么,只负责在合适的时候调用指针指向的函数即可。
13.3 函数指针数组
函数指针数组,就是把多个函数指针放在一个数组里。这个在实现菜单系统、状态机或者命令分发器时特别有用。
定义函数指针数组的语法:
返回类型 (*数组名[数组大小])(参数类型列表);
举个例子,假设我们有三个数学运算函数:
#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int main() {
// 定义函数指针数组,包含3个元素
int (*ops[3])(int, int) = {add, sub, mul};
// 也可以先定义再赋值
// int (*ops[3])(int, int);
// ops[0] = add;
// ops[1] = sub;
// ops[2] = mul;
int a = 10, b = 5;
// 通过数组下标调用不同的函数
printf("add(10, 5) = %d\n", ops[0](a, b));
printf("sub(10, 5) = %d\n", ops[1](a, b));
printf("mul(10, 5) = %d\n", ops[2](a, b));
return 0;
}
我曾经在一个工控项目中,用函数指针数组实现了命令解析器。上位机发来不同的命令码,程序直接通过命令码作为数组下标,调用对应的处理函数。代码简洁、执行效率高,而且添加新命令只需要在数组里加一个函数指针就行,完全不用改主逻辑。
注意:函数指针数组虽然好用,但要注意数组越界问题。如果下标超出了数组范围,程序会访问到未知的内存地址,轻则崩溃,重则产生难以排查的bug。我建议在使用前做边界检查。
13.4 知识体系总览
下面这张图总结了函数指针的核心知识点和它们之间的关系:
13.5 避坑指南
最后,分享几个我踩过的坑:
- 类型不匹配:函数指针的类型必须与函数完全一致。我曾经把返回
int的函数指针赋值给了返回void的函数,编译没报错,但运行时数据全乱了。 - 空指针调用:函数指针在使用前一定要检查是否为NULL。我曾经在系统初始化时忘记注册回调函数,结果程序一运行就崩溃了。
- 作用域问题:如果函数指针指向的是局部函数(比如在另一个文件中的static函数),跨文件使用时要注意链接问题。
我的建议:在嵌入式开发中,函数指针经常用于实现"分层设计"。比如驱动层提供一组函数指针,应用层通过注册的方式把具体实现挂上去。这样驱动层和应用层解耦,换硬件时只需要换驱动实现,应用代码完全不用动。
好了,函数指针的内容就讲到这里。这东西刚开始可能觉得别扭,但用多了就会发现它真的很强大。下次写代码时,不妨想想:这里能不能用函数指针让代码更灵活一些?
公众号:蓝海资料掘金营,微信deep3321