青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-
友链 搜索文章 >>

青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-

【C语言学习笔记】十五、函数指针


阅读数: 0次    2026-03-20
字数:3k字 | 预计阅读时长:12分钟

1. 函数指针的概念

之前学过的指针可以指向变量、数组、字符串、结构体、指针,函数指针就是指向函数的指针。

声明函数指针的方法:"返回类型" (*"指针变量名") ("参数类型");

例如有一个返回值为int,参数为两个int类型的函数为int function(int num1, int num2);,可以定义指向这个函数的函数指针为:int (*myFunctionPointer) (int, int);

将原函数的地址赋给函数指针后,直接使用函数指针myFunctionPointer即可调用原函数。

赋地址时,取地址符&可以省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>

// 加法函数
uint32_t add(uint32_t a, uint32_t b);

// 函数指针
int main() {
uint32_t(*func_ptr)(uint32_t, uint32_t) = add; // 定义函数指针并指向加法函数

uint32_t result = func_ptr(5, 10); // 使用函数指针调用加法函数

printf("The result of addition is: %" PRIu32 "\n", result); // 输出结果

return EXIT_SUCCESS;
}

// 加法函数
uint32_t add(uint32_t a, uint32_t b) {
return a + b;
}

函数指针的用途为实现回调函数,函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。简单讲:回调函数是由别人的函数执行时调用你实现的函数,它允许将一个函数作为参数传递给另一个函数,这样,当特定事件发生的时候,可以触发调用传递的参数。

以下是来自知乎作者常溪玲的解说:

你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

这种机制在事件驱动编程中非常常用。

2. 再探typedef

使用typedef可以为任意数据类型定义自定义的别名。

例如常用的uint32_t类型,在官方的定义中为typedef unsigned int uint32_t;。

甚至可以使用typedef为uint32_t类型自定义一个新的别名typedef uint32_t u32;,此时就可以使用别名类型u32直接声明变量,与原来的效果相同。

同样的,可以使用typedef为函数指针创建别名,例如typedef uint32_t (*func_ptr_t)(uint32_t, uint32_t);,定义后就可以直接使用别名func_ptr_t声明函数指针,代码更为简洁。

_t是type(类型)的缩写,是一个约定俗成的命名后缀,用于表示”这是一个自定义类型别名“,而非变量或函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>

// 定义函数指针类型别名
typedef uint32_t(*func_ptr_t)(uint32_t, uint32_t);

// 加法函数
uint32_t add(uint32_t a, uint32_t b);

// 函数指针
int main() {
func_ptr_t func_ptr = add; // 将函数指针指向加法函数

uint32_t result = func_ptr(5, 10); // 使用函数指针调用加法函数

printf("The result of addition is: %" PRIu32 "\n", result); // 输出结果

return EXIT_SUCCESS;
}

// 加法函数
uint32_t add(uint32_t a, uint32_t b) {
return a + b;
}

3. 练习:指针函数的用途

当多个函数的返回值与参数都相同时,可以创建一个函数指针数组来存放所有的函数,直接使用函数指针数组的下标即可调用对应函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>

// 定义函数指针类型别名:指向接收两个 int32_t 参数并返回 int32_t 的函数
typedef int32_t(*operations_t)(int32_t, int32_t);

// 四则运算函数前置声明
int32_t add(int32_t a, int32_t b);
int32_t subtract(int32_t a, int32_t b);
int32_t multiply(int32_t a, int32_t b);
int32_t divide(int32_t a, int32_t b);

// 函数指针数组:依次存放加、减、乘、除四个运算函数的地址
operations_t operations[] = { add, subtract, multiply, divide };

int main() {
int32_t a = 10, b = 5;

// 遍历函数指针数组,依次调用每个运算函数并输出结果
// sizeof(operations) / sizeof(operations[0]) 计算数组元素个数
for (size_t i = 0; i < sizeof(operations) / sizeof(operations[0]); ++i) {
printf("Result: %" PRId32 "\n", operations[i](a, b));
}

return EXIT_SUCCESS;
}

// 加法:返回 a + b
int32_t add(int32_t a, int32_t b) {
return a + b;
}

// 减法:返回 a - b
int32_t subtract(int32_t a, int32_t b) {
return a - b;
}

// 乘法:返回 a * b
int32_t multiply(int32_t a, int32_t b) {
return a * b;
}

// 除法:返回 a / b,若除数为零则输出错误信息并终止程序
int32_t divide(int32_t a, int32_t b) {
if (b == 0) {
fprintf(stderr, "Error: Division by zero\n");
exit(EXIT_FAILURE);
}
return a / b;
}

后续可以使用枚举为下标赋予有意义的名称,并实现数组的扩充。

4. 函数指针与callback回调函数的作用

回调函数是作为函数指针传递给另一个函数的函数,允许在后者中调用前者。

这种机制可以让程序在不同时间点或条件下,根据需要执行特定的功能。

例如写一个有遍历数组功能的函数,每次循环调用一次传入的函数指针,只要是返回值与参数类型相同的函数都可以传入,通过传入不同的函数,可以实现在遍历时执行不同的操作,相当于把遍历数组与具体要执行的操作模块化分开处理,提高代码的灵活性与可重用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>

// 定义回调函数类型:接受一个 int32_t 参数,无返回值
typedef void (*callback_t)(int32_t);

// 遍历数组,对每个元素调用回调函数
void traverseArray(int32_t* array, size_t size, callback_t callback);

// 回调函数:打印元素原值
void printElement(int32_t element);
// 回调函数:打印元素的两倍值
void doubleElement(int32_t element);
// 回调函数:判断元素的奇偶性
void checkEven(int32_t element);

// 函数指针演示:通过回调函数对同一数组执行不同操作,无需修改遍历逻辑
int main() {
int32_t array[] = { 1, 2, 3, 4, 5 };
size_t size = sizeof(array) / sizeof(array[0]); // 计算数组元素个数

// 传入不同的回调函数,复用同一套遍历逻辑
printf("打印每个元素:\n");
traverseArray(array, size, printElement);

printf("\n打印每个元素的两倍:\n");
traverseArray(array, size, doubleElement);

printf("\n判断每个元素的奇偶性:\n");
traverseArray(array, size, checkEven);

return EXIT_SUCCESS;
}

// 遍历数组,对每个元素依次调用 callback
// array:待遍历的数组指针,size:数组元素个数,callback:函数指针
void traverseArray(int32_t* array, size_t size, callback_t callback) {
for (size_t i = 0; i < size; ++i) {
callback(array[i]); // 通过函数指针调用回调函数
}
}

// 打印元素原值
void printElement(int32_t element) {
printf("Element: %" PRId32 "\n", element);
}

// 打印元素的两倍值
void doubleElement(int32_t element) {
printf("Doubled Element: %" PRId32 "\n", element * 2);
}

// 判断元素奇偶性并打印结果
void checkEven(int32_t element) {
if (element % 2 == 0) {
printf("Element %" PRId32 " is even.\n", element);
} else {
printf("Element %" PRId32 " is odd.\n", element);
}
}

5. 实际用途(函数指针与回调函数的妙用)

  1. 事件处理
  2. 异步编程
  3. 定时器函数
  4. 插件架构中的扩展点定义
  5. 排序算法中的自定义比较函数
  6. 多线程或并发编程中的线程启动函数
  7. 用户界面组件中的自定义渲染策略
  8. 网络请求处理中的协议解析器
  9. 状态机实现中的状态转换触发器
  10. 资源管理器中的资源释放策略回调

6. 事件处理框架(Event Handling Framework)

事件驱动的处理框架就是在某一个特定事件(Event)发生的时候执行特定的行动(Action)或功能(Function)。

例如组织一场派对,你是主人。

当有人敲门(一个事件)时,你要去开门(根据这个特定事件,你要做的行动);

当音乐停止,你要切换下一首……

7. 游戏架构事件设计:事件类型、事件处理函数、事件注册、事件分发机制

案例:一个游戏事件处理系统

  1. 定义事件类型和事件处理函数签名:首先定义游戏中可能发生的事件类型,以及处理这些事件的签名;
  2. 实现事件注册和分发机制:实现一个机制,允许为不同类型的事件注册处理函数,并且在相应的事件发生时调用这些函数;
  3. 定义事件处理函数:为游戏中的每一种事件类型定义具体的事件处理函数;
  4. 在游戏中使用事件处理系统:main中注册事件处理函数,并且模拟事件的发生来演示系统的工作流程。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>

// 定义游戏事件类型的枚举
typedef enum {
PLAYER_ATTACK = 0, // 玩家攻击事件
PLAYER_DEFEND, // 玩家防御事件
PLAYER_MOVE, // 玩家移动事件
EVENT_COUNT // 哨兵,表示事件类型的数量
} GameEventType;

// 定义事件处理函数指针类型
typedef void (*EventHandler)(const char* playerName);

// 事件处理函数数组
// 数组中的每个元素都是一个指向处理特定事件的函数的指针
// 这个函数符合EventHandler的签名,即接受一个const char*类型的参数(玩家名称)并返回void
EventHandler eventHandlers[EVENT_COUNT] = { 0 };

// 注册事件处理函数:将某个事件类型映射到对应处理逻辑
void registerEventHandler(GameEventType eventType, EventHandler handler);

// 分发事件:根据事件类型查表并调用已注册的处理函数
void dispatchEvent(GameEventType eventType, const char* playerName);

// 具体事件处理函数
void handlePlayerAttack(const char* playerName);
void handlePlayerDefend(const char* playerName);
void handlePlayerMove(const char* playerName);

// 函数指针、回调函数,事件处理框架
int main() {
// eventHandlers 是全局数组,未显式注册的位置保持为 NULL
// 分发时会检查 NULL,避免调用空函数指针

// 1. 注册事件与处理函数的对应关系
registerEventHandler(PLAYER_ATTACK, handlePlayerAttack);
registerEventHandler(PLAYER_DEFEND, handlePlayerDefend);
registerEventHandler(PLAYER_MOVE, handlePlayerMove);

// 2. 触发事件,框架会自动调用对应处理函数
dispatchEvent(PLAYER_ATTACK, "Alice");
dispatchEvent(PLAYER_DEFEND, "Bob");
dispatchEvent(PLAYER_MOVE, "Charlie");

return EXIT_SUCCESS;
}

void registerEventHandler(GameEventType eventType, EventHandler handler) {
// 卫语句:处理空处理函数输入
if (handler == NULL) {
fprintf(stderr, "Failed to register event handler: NULL handler.\n");
return;
}
// 边界检查:仅允许合法事件类型写入处理器表
if (eventType < PLAYER_ATTACK || eventType >= EVENT_COUNT) {
fprintf(stderr, "Failed to register event handler: invalid event type.\n");
return;
}

// 将事件类型作为索引,把处理函数存入查找表
eventHandlers[eventType] = handler;
}

void dispatchEvent(GameEventType eventType, const char* playerName) {
// 卫语句:玩家名不能为空
if (playerName == NULL) {
fprintf(stderr, "Failed to dispatch event: NULL player name.\n");
return;
}
// 卫语句:事件类型必须在合法枚举范围内
if (eventType < PLAYER_ATTACK || eventType >= EVENT_COUNT) {
fprintf(stderr, "Failed to dispatch event: invalid event type.\n");
return;
}
// 卫语句:事件必须先注册处理函数
if (eventHandlers[eventType] == NULL) {
fprintf(stderr, "Failed to dispatch event: unregistered handler.\n");
return;
}

// 按事件类型索引调用对应回调
eventHandlers[eventType](playerName);
}

// 具体事件处理函数
void handlePlayerAttack(const char* playerName) {
printf("%s performs an attack!\n", playerName);
}
void handlePlayerDefend(const char* playerName) {
printf("%s defends against an attack!\n", playerName);
}
void handlePlayerMove(const char* playerName) {
printf("%s moves to a new position!\n", playerName);
}

注册事件本质上就是将某一个特定功能的函数的指针写入函数指针数组。

分发事件本质上就是在某一个条件下调用特定功能的函数。

本文来源: 青江的个人站
本文链接: https://hanqingjiang.com/2026/03/20/20260320_C_functionPointer/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
知识共享许可协议
赏

谢谢你请我喝可乐~

支付宝
微信
  • Notes
  • C

扫一扫,分享到微信

微信分享二维码
【C语言学习笔记】十四、多级指针
  1. 1. 1. 函数指针的概念
  2. 2. 2. 再探typedef
  3. 3. 3. 练习:指针函数的用途
  4. 4. 4. 函数指针与callback回调函数的作用
  5. 5. 5. 实际用途(函数指针与回调函数的妙用)
  6. 6. 6. 事件处理框架(Event Handling Framework)
  7. 7. 7. 游戏架构事件设计:事件类型、事件处理函数、事件注册、事件分发机制
© 2021-2026 青江的个人站
晋ICP备2024051277号-1
powered by Hexo & Yilia
  • 友链
  • 搜索文章 >>

tag:

  • 生日快乐🎂
  • 新年快乐!
  • 小技巧
  • Linux
  • 命令
  • 语录
  • 复刻
  • Blog
  • Notes
  • Android
  • FPGA
  • C
  • Homework
  • MATLAB
  • Server
  • Vivado
  • Git

  • 引路人-稚晖
  • Bilibili-稚晖君
  • 超有趣讲师-Frank
  • Bilibili-Frank