news 2026/2/17 4:42:50

C语言指针(六)——函数指针数组

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言指针(六)——函数指针数组

上节回顾:

C语言指针(五)进阶篇——函数指针

上一篇,我们吃透了函数指针的核心知识,知道了函数指针指向函数入口地址的指针,能通过它间接调用函数、实现回调机制,还用函数指针优化了计算器的代码,减少了重复逻辑。但当时的代码仍依赖switch语句匹配菜单选项和函数,有没有更简洁的方式?
当然!这就需要用到函数指针的 “加强版”——函数指针数组,它能批量管理多个函数指针,是实现 “菜单驱动” 功能的核心技术,让代码的耦合度更低、扩展性更强。

很多同学会觉得 “函数指针数组” 听起来复杂,其实它就是我们之前学过的指针数组函数指针的结合体:指针数组是 “存指针的数组”,而函数指针数组就是 “存函数指针的数组”。今天我们就用通俗的例子和代码,把这个知识点彻底搞懂~

函数指针数组

一、函数指针数组本质

存放函数指针的数组。

在学习函数指针数组之前,我们先复习两个之前学过的知识——指针数组函数指针

  1. 指针数组本质是数组,元素是指针(如:int* arr[5];元素的类型为 int*)
  2. 函数指针本质是指针,指向的是函数的入口地址。(如:int (*p)(int int);指向返回值类型为 int, 参数列表为(int, int)的函数)

那么函数指针数组的概念就很明确了:其本质就是数组,数组中存放的元素是相同类型的函数指针。

二、函数指针数组的定义格式(易错!)

返回值类型 (*数组名[数组长度])(函数参数类型列表);

int (*arr[5])(int, int);

函数指针数组的定义格式是在函数指针的基础上,增加了数组的标识[];

很多同学会把函数指针数组的定义写错,核心还是没掌握优先级:

  • 正确写法int (*arr[5])(int, int)(先arr[5]是数组,再*是函数指针);
  • 错误写法int* arr[5](int, int)(这是非法格式,编译器会直接报错,因为[]()的优先级混乱)。

记住一个口诀:先认数组,再认指针—— 先看到[]确定是数组,再看*确定数组元素是函数指针。

三、基础示例

我们先定义加减乘除四个函数,再用函数指针数组存储它们的地址:

#include <stdio.h> // 定义四个函数(参数、返回值类型完全一致) int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { if (y == 0) { printf("错误:除数不能为0!\n"); return 0; } return x / y; } int main() { // 定义并初始化函数指针数组:元素依次是Add、Sub、Mul、Div的函数指针 // 注意:数组下标可以和菜单选项对应(这里我们先从0开始) int (*funcArr[4])(int, int) = {Add, Sub, Mul, Div}; // 打印数组中每个元素(函数指针)的地址 for (int i = 0; i < 4; i++) { printf("funcArr[%d]的地址:%p\n", i, funcArr[i]); } return 0; }

运行结果:

funcArr[0]的地址:00401000
funcArr[1]的地址:00401020
funcArr[2]的地址:00401040
funcArr[3]的地址:00401060

这个例子中,funcArr 为函数指针数组的数组名,数组中元素是类型为 int(*)(int, int) 的函数指针。

如果上面这段代码你可以看懂并理解的话,那么,我们就可以来试着更进一步的优化一下上一节中简单计算器的代码

四、核心用法:简化菜单驱动程序

#include <stdio.h> // 菜单函数 void Menu() { printf("----------------------------------\n"); printf("--------- 1. add -----------\n"); printf("--------- 2. sub -----------\n"); printf("--------- 3. mul -----------\n"); printf("--------- 4. div -----------\n"); printf("--------- 0. exit ----------\n"); printf("----------------------------------\n"); } // 定义加减乘除函数(参数、返回值类型一致) int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { if (y == 0) { printf("错误:除数不能为0!\n"); return 0; } return x / y; } int main() { int input = 0; int x = 0, y = 0, ret = 0; // 定义函数指针数组:下标0留空(对应菜单的0退出),1-4对应加减乘除 // 这样可以让菜单选项和数组下标直接对应,无需额外匹配 int (*funcArr[5])(int, int) = {NULL, Add, Sub, Mul, Div}; do { Menu(); printf("请输入选择:\n"); scanf("%d", &input); // 核心逻辑:通过input判断是否退出,否则直接调用数组中的函数 if (input >= 1 && input <= 4) { printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); // 关键:input是菜单选项,直接作为数组下标,调用对应的函数 ret = funcArr[input](x, y); printf("结果:%d\n", ret); } else if (input == 0) { printf("退出计算器\n"); } else { printf("输入错误,请重新选择!\n"); } } while (input); return 0; }

运行结果示例:

---------------------------------- --------- 1. add ----------- --------- 2. sub ----------- --------- 3. mul ----------- --------- 4. div ----------- --------- 0. exit ---------- ---------------------------------- 请输入选择: 1 请输入两个操作数: 10 5 结果:15 ---------------------------------- --------- 1. add ----------- --------- 2. sub ----------- --------- 3. mul ----------- --------- 4. div ----------- --------- 0. exit ---------- ---------------------------------- 请输入选择: 4 请输入两个操作数: 10 0 错误:除数不能为0! 结果:0 ---------------------------------- --------- 1. add ----------- --------- 2. sub ----------- --------- 3. mul ----------- --------- 4. div ----------- --------- 0. exit ---------- ---------------------------------- 请输入选择: 0 退出计算器

这段代码的精髓就在于——菜单选项和函数指针数组的下标直接对应:

  1. 函数指针数组funcArr的下标 0 设为NULL(对应菜单的 0 退出),下标 1-4 分别对应 Add、Sub、Mul、Div(对应菜单的 1-4);
  2. 当用户输入input后,只需判断input的范围,直接用funcArr[input](x, y)调用对应的函数,彻底去掉了switch语句;
  3. 如果后续要增加功能(比如取模),只需添加一个取模函数,再把函数指针加入数组,修改菜单即可,无需改动核心逻辑 —— 这就是高扩展性

对比上一篇的代码,你会发现函数指针数组让菜单驱动程序的代码变得极其简洁,这也是它在实际开发中最常用的场景。

五、注意事项

  1. 数组中所有函数指针的类型必须一致:
    函数指针数组的每个元素都是同一类型的函数指针,这意味着它们指向的函数必须返回值类型相同、参数类型及顺序完全相同。比如不能把char Add(int x)的函数指针存入int (*funcArr[])(int, int)数组中,否则会导致编译错误。
  2. 数组下标与菜单选项的对应要合理:
    在菜单驱动程序中,通常会把数组下标 0 留空(对应退出选项),让菜单选项 1、2、3... 直接对应数组下标 1、2、3...,这样能避免下标越界或匹配错误。
  3. 避免越界访问数组:
    和普通数组一样,函数指针数组也不能越界访问。比如定义了长度为 5 的数组funcArr[5],就不能访问funcArr[5]funcArr[6],否则会访问到无效的内存地址(类似野指针),导致程序崩溃。
  4. 不要直接调用 NULL 的函数指针:
    如果数组中的某个元素是NULL(比如我们例子中的funcArr[0]),不要直接调用它,否则会访问无效内存。在调用前要先判断下标是否在合法范围内。

总结:

本节我们学习了函数指针数组的内容,并通过实例展示了函数指针数组的实际用途,了解了函数指针数组是如何简化代码的,并提高代码的扩展性。

希望同学们能亲手敲一遍实例代码,好好分析函数指针数组的应用逻辑,我相信,通过多思考,多动手,多练习,你一定能成为编写代码的高手!

下一节我们将学习更进阶的指针知识 ——指向函数指针数组的指针,这是指针的 “终极形态” 之一,虽然实际开发中用得不多,但能帮你彻底理解指针的层级关系。

希望讲解的内容能帮助到各位同学,如有错误或更好的建议还望指出 ~

谢谢大家!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/4 4:43:50

LobeChat能否显示用量统计?透明化消费展示

LobeChat 能否显示用量统计&#xff1f;透明化消费的工程实践解析 在如今 AI 应用快速落地的背景下&#xff0c;一个看似不起眼但极其关键的问题逐渐浮出水面&#xff1a;我们到底为每一次“你好&#xff0c;帮我写个邮件”花了多少钱&#xff1f; 这个问题在使用闭源大模型 AP…

作者头像 李华
网站建设 2026/2/12 23:33:31

解决langchain-chatchat缺少__init__.py问题

修复 Langchain-Chatchat 启动报错&#xff1a;module is not a callable object 的完整实践 在部署像 Langchain-Chatchat 这类基于 FastAPI 和模块化路由的本地知识库系统时&#xff0c;你可能遇到过这样的错误&#xff1a; <module server.chat.knowledge_base_chat fr…

作者头像 李华
网站建设 2026/2/12 13:10:16

Linly-Talker:能对答如流的AI数字人

Linly-Talker&#xff1a;能对答如流的AI数字人 你有没有想过&#xff0c;有一天只要上传一张照片&#xff0c;就能让那个“他”或“她”亲自为你讲解知识、陪你聊天&#xff0c;甚至在你难过时露出关切的表情&#xff1f;这不是电影《Her》的桥段&#xff0c;也不是遥远的元宇…

作者头像 李华
网站建设 2026/2/17 4:33:53

YOLOv5网络结构解析与代码实现

YOLOv5网络结构解析与代码实现 在目标检测领域&#xff0c;YOLO系列始终是工业落地的首选方案之一。尤其是YOLOv5&#xff0c;凭借其简洁的架构设计、高效的推理性能和极强的可部署性&#xff0c;迅速成为实际项目中的“标配”。但当我们真正想修改模型、定制结构或排查问题时&…

作者头像 李华
网站建设 2026/2/15 17:46:46

gpt-oss-20b微调与扩展全指南

GPT-OSS-20B 微调与扩展实战指南 你有没有遇到过这样的场景&#xff1a;手头有一个很棒的想法&#xff0c;想训练一个专属AI助手&#xff0c;却因为模型太大、显存不够、部署复杂而被迫放弃&#xff1f;现在&#xff0c;这种情况正在被改变。GPT-OSS-20B 的出现&#xff0c;让…

作者头像 李华