跳到主要内容
🔍
1.3.4已发布intermediate · 约 3 课时 · P0

函数指针与回调

掌握函数指针、typedef 简化与回调框架,理解嵌入式事件驱动思想

  • 来源
  • 补充

标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原

完成标准(本章)

  • 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180
  • ✏️ 已练习:小练习正确率 ≥ 60%
  • 📝 已通过测验:分数 ≥ 60
  • 🛠️ 已掌握还需完成实践任务
学习状态:未开始

1.3.4 函数指针与回调

本章来源:函数指针定义/赋值/调用、typedef 简化、回调函数与 qsort 来自《第一阶段讲义》20.6/19.7 节【来源】,经重新组织表述;中断回调类比与全部示例为新编【补充】并本机实测(gcc 15.2.0)。平台适用性 universal。

① 学习目标

  1. 写出函数指针的完整声明(如 int (*p)(int, int)),说明 &funcfunc(*p)(args)p(args) 的等价关系;
  2. 用 typedef 简化函数指针类型,写出 typedef int (*op_fn)(int, int) 及其用法;
  3. 实现"注册回调 → 统一触发"的迷你框架,说清回调函数的两要素(被调用方提供、由框架在恰当时机调用);
  4. 用 qsort 排序数组并写出正确的比较函数(返回负数/零/正数);
  5. 能解释"为什么驱动开发大量使用回调"(中断回调类比)。

② 前置知识

  • 必选:1.3.3 函数与递归(函数声明/定义/调用,值传递);1.3.1 指针基础(指针变量与解引用——函数指针只是"指向代码段"的指针)。

③ 核心概念【来源】

概念说明
函数指针声明int (*p)(int, int):p 是"指向返回 int、接收两个 int 的函数的指针";括号不能省int *p(int,int) 是"返回 int* 的函数")
赋值p = &addp = add 等价——函数名在表达式中会转换为指向自己的指针
调用p(3, 4)(*p)(3, 4) 等价
typedef 简化typedef int (*op_fn)(int, int); 之后 op_fn f = sub; 即声明一个函数指针变量——多参数/多函数时大幅提升可读性
回调(callback)把函数指针作为参数传给别人,由"别人"在某个时机调用:被调用方提供、调用方触发;qsort 的比较函数是最经典的 C 标准库回调
中断回调类比嵌入式驱动中,中断发生时硬件跳到注册好的服务函数(向量表里存的正是函数地址)——"你先注册,事件发生时我来调你"正是回调思想
qsortqsort(数组, 元素数, 元素大小, 比较函数);比较函数返回负值/零/正值分别表示 a<b / a==b / a>b

④ 通俗解释【补充】

  • 函数指针是"留给客人的电话号码":你要排序,就把"比大小"这个活儿外包——把自己的比大小规则写成函数,把它的"电话号码"(指针)交给 qsort。qsort 排序时遇到"谁大谁小",就拨这个号码问你的规则;
  • 回调是"我给你留个坑":框架(比如 qsort、比如操作系统)不知道你的具体业务,就规定好接口"遇到 XX 情况我会调用你留下的函数"。你注册一个符合接口的函数,事件发生时框架触发它——这就是回调两要素:注册 + 触发;
  • 中断回调是"门铃直通":门铃(中断)一响,硬件直接跑去执行你在门口留的纸条上写的那个房间(函数地址)。所以驱动开发大量使用回调:把"中断来了做什么"写成函数、把地址注册进向量表,剩下的交给硬件和内核。

⑤ 示例代码

代码示例与验证记录

  • examples/ex1-function-pointer.c函数指针基础与 typedef 递进✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16
    编译:gcc ex1-function-pointer.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    op(3, 4) = 7
    (*op)(3, 4) = 7
    f(10, 4) = 6
    

    差异说明:本机实测 0 警告;第二行证明 p(args) 与 (*p)(args) 调用等价

    完整源码见 /code 代码示例页

  • examples/ex2-callback.c回调框架:注册两个操作 → 统一触发✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16
    编译:gcc ex2-callback.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    fn[0](8, 3) = 11
    fn[1](8, 3) = 5
    fn[2](8, 3) = 24
    

    差异说明:本机实测 0 警告;run_all 只认接口 calc_fn,不关心具体是哪个操作——注册与触发的分离

    完整源码见 /code 代码示例页

  • examples/ex3-qsort.cqsort 与比较函数(标准库回调)✓ 已实测(gcc 15.2.0 / MinGW-w64 x86_64 / Windows 11, 2026-08-16
    编译:gcc ex3-qsort.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic
    适用环境:LinuxWindows(MinGW)
    展开预期输出(实测)
    3 7 15 42 99
    

    差异说明:本机实测 0 警告;比较函数返回 (a > b) - (a < b) 避免 a - b 溢出(INT_MAX - (-1) 会溢出)

    完整源码见 /code 代码示例页

⑥ 编译与运行方法

gcc ex1-function-pointer.c -o ex1 -std=c11 -Wall -Wextra -Wpedantic
gcc ex2-callback.c -o ex2 -std=c11 -Wall -Wextra -Wpedantic
gcc ex3-qsort.c -o ex3 -std=c11 -Wall -Wextra -Wpedantic

三个示例本机编译 0 警告,运行输出与示例卡折叠区一致。要点:qsort 的比较函数参数类型固定为 const void *,函数体内先转换回真实类型再比较。

⑦ 常见错误

症状原因解决
声明写错:int *p(int, int)漏了括号——这声明的是"返回 int* 的函数"而非函数指针int (*p)(int, int);拿不准就用 typedef
比较函数返回 a - b 造成排序错误两数相减可能溢出(INT_MAX - (-1) 溢出)(a > b) - (a < b) 或显式 if/else 返回 -1/0/1
回调类型不匹配(如比较函数参数写成 int*qsort 固定要求 int (*)(const void*, const void*)参数类型严格按接口写 const void *,函数体内再转换
把函数指针与函数调用混写(p = add()add() 是调用并取得返回值,不是地址赋值用 p = add(或 &add),调用才用 p(3,4)
回调函数忘了声明在注册之前未声明标识符回调实现放前面,或先在文件顶部写函数原型

⑧ 小练习

小练习

学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)
  • ex-1-3-4-1.下面哪个声明是正确的"指向返回 int、接收两个 int 的函数的指针"?(单选)

    ◌ 未作答
  • ex-1-3-4-2.qsort 的比较函数参数类型是什么?(单选)

    ◌ 未作答
  • ex-1-3-4-3.代码审查:cmp 已声明为 int cmp(int, int),框架注册处写成 register(cmp, 10) 且 register 原型为 void register(int (*)(double, double), int)。这段代码的问题是?(单选)

    ◌ 未作答

⑨ 章节测验

章节测验

5 题题库 · 随机抽 5 题 · 及格线 60 分 · 前端判分(学习自测)
开始测验 →

⑩ 实战任务

实践任务

写一个"注册回调 → 事件触发"的迷你框架

用 typedef 定义回调类型,实现一个迷你框架:注册至少两个回调函数(如"收到消息"与"定时到期"两个处理函数), 由框架的事件触发循环统一调用;运行输出能证明"注册顺序 ≠ 触发时机由框架决定",并写一句 "为什么驱动开发大量使用回调"的解释。

输入与输出

无程序输入输出。交付物:① 可运行程序(注册 ≥2 个回调,统一触发);② 运行输出;③ 一句话解释(驱动/中断视角)。

功能要求

  • 用 typedef 声明回调类型(如 typedef void (*event_fn)(int);)
  • 实现注册函数(把回调存入数组)与触发函数(遍历调用)
  • 注册至少 2 个不同的回调并触发,输出顺序与注册顺序可验证
  • 写出"驱动开发为什么大量使用回调"的一句话解释(提到中断/注册/触发中至少两点)

限制条件

  • 实验在专用目录进行,编译口径 -std=c11 -Wall -Wextra -Wpedantic
  • 回调签名必须与框架接口完全一致

验收步骤(自检清单 0/4

验收标准

  • 编译 0 警告、运行输出与回调注册一致(验收步骤 3)
  • 解释提到中断/注册/触发中至少两点(验收步骤 4)

常见失败原因

  • 回调签名与接口不一致(编译警告/报错)
  • 比较函数式错误:把回调写成直接调用而非交给框架触发
  • 函数指针声明漏括号

可选扩展

  • 用 struct 把回调与参数打包成事件(事件队列雏形)
  • 模仿 qsort 给自己写一个 apply_all(int *arr, size_t n, int (*op)(int)) 的通用遍历框架

完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)

⑪ 面试问题

面试问题

  • 写出"指向接收两个 int、返回 int 的函数的指针"的 typedef,并说明函数指针最常见的用途。高频C/C++ · medium

    要点:typedef int (*op_fn)(int, int); 最常用于回调:把函数作为参数交给框架/库,由其在恰当时机调用(如 qsort 比较函数、事件处理、中断服务)。

    typedef int (*op_fn)(int, int); 之后 op_fn 即该函数指针类型。用途核心是回调(callback): 调用方不知道、也不该知道被调用方的具体业务,只约定接口(签名),被调用方注册一个符合接口的函数, 框架在事件发生时触发。标准库例子:qsort 的比较函数、bsearch;工程例子:事件循环、插件机制; 嵌入式例子:中断向量表存放服务函数地址、驱动"操作集"结构体(struct of function pointers)。

    追问:
    • 追问:qsort 比较函数为什么参数是 const void * 而不是 int *?(通用性:qsort 不知道元素类型,void* 可接受任意类型指针)
    评分要点:
    • typedef 写法正确
    • 回调两要素(注册/触发)
    • 至少一个具体场景
  • 为什么说"驱动开发大量使用回调"?请结合中断解释。高频嵌入式 · medium

    要点:硬件事件何时发生是异步不可知的,驱动把处理函数地址注册进中断向量表,事件发生时硬件/内核直接跳转调用——这就是回调:你注册、事件触发、框架调用。

    中断的本质是异步事件:CPU 不知道按键/定时器/串口数据什么时候来。驱动开发的做法是把"中断来了做什么" 写成服务函数,把函数地址注册到中断向量表(注册),事件发生时硬件按表跳转执行(触发)。内核的 驱动模型同样如此:file_operations 结构体里注册 open/read/write 等函数指针,内核在合适时机调用—— 驱动作者只实现业务,调用时机交给框架。这正是回调思想在系统层的体现。

    追问:
    • 追问:中断回调函数与普通函数在编写上有什么特殊约束?(必须短小、不可阻塞、注意重入)
    评分要点:
    • 异步事件与注册-触发
    • 中断向量表/操作集例子
  • 用 qsort 对一个 int 数组升序排序,比较函数怎么写?为什么不能直接返回 a - b?高频C/C++ · medium

    要点:返回 (a > b) - (a < b);a - b 在极端值下会溢出(如 INT_MAX 与 -1 相减),溢出后符号错误导致排序结果错误。

    比较函数签名固定 int cmp(const void *pa, const void *pb),函数体内先转换: const int a = *(const int*)pa; const int b = *(const int*)pb; 然后 return (a > b) - (a < b); 该写法保证只返回 -1/0/1 且不会溢出。a - b 的隐患:INT_MAX - (-1) 在数学上是 2147483648, 超出 int 范围发生有符号溢出(未定义行为),实际可能得到负数,比较结果符号反转 → 排序错误。 面试加分:顺带说 bsearch 与 qsort 共用同一套比较函数约定。

    追问:
    • 追问:如果想降序排序怎么办?(交换 a、b 的角色:return (b > a) - (b < a))
    评分要点:
    • 签名与转换
    • 溢出风险与安全写法

⑫ 延伸阅读

  • 《C 程序设计语言(K&R)》5.11 节 函数指针——只引章节名;
  • 本地手册:man qsort(Linux)或 MSYS 环境下 qsort 文档——比较函数签名与返回值约定;
  • 下一章预告:1.3.5 作用域与 static/extern——回调框架里的"模块内私有函数"就用 static 限制可见性。

迁移训练(migration training)

函数指针与回调三平台语法完全一致;嵌入式两个视角:

环节通用 C(本机实测)嵌入式
回调载体qsort 比较函数、框架注册中断向量表(存函数地址)、驱动操作集(struct of function pointers)
触发方库函数/框架硬件中断、内核调度
差异中断上下文内回调必须短小、不可阻塞(7.x/支线 A 展开)

不变的:声明语法、typedef、注册-触发模型;要变的是"谁在什么时候调用你"。

内容来源映射

内容部分资料位置标记说明
函数指针定义/赋值/调用、typedef 简化、回调函数、qsort 与比较函数第一阶段讲义20.6/19.7 节来源正文在原资料基础上重新组织表述,未大段复制原文
中断回调类比(嵌入式视角)、示例代码与实验补充原资料无嵌入式事件驱动类比;本章示例全部重写并本机实测
小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读补充原资料该章无成体系练习,全部新编