指针与函数
看清函数指针与数据指针的本质差异,掌握声明、调用与回调
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
指针与函数
看清函数指针与数据指针的本质差异,掌握声明、调用与回调
指针能指向什么
上一页我们让指针指向函数——这背后其实藏着一个更基本的事实:函数也有地址。今天我们顺着这条线往回追问一次:指针这个东西,到底能指向哪些东西?
书签不是书的内容,只记一页的位置,插在哪本就指向哪本
为什么要指向函数
上一页说指针可以指向函数,但「能」不等于「值得」。真正的问题是:什么场景下必须把函数当数据来传?三个核心用途:回调、动态分发、抽象接口。
按键是固定入口,触发哪个函数可以随时重新配置
函数指针的类型长什么样
上一页我们谈了为什么要让指针指向函数——回调、跳转表、动态行为。光有动机还不够,编译器必须先知道:我要指向的函数长啥样。
返回值=输出电压,参数列表=插孔形状,决定什么电器能插进来
声明语法详解
一段 C 代码逐符号拆解 `int (*pf)(int, int)` 的声明,并展示调用方式。
L7 是核心:`int` 返回值、(*pf) 强调 pf 是指针、(int,int) 是参数列表。L10-L12 展示赋值与两种等价调用写法。
函数指针 vs 函数原型
写法相似但身份完全不同:原型声明函数,指针声明装着函数地址的变量。
- 声明的是一个函数本身
- 编译期签名契约,不占运行内存
- 作用是让编译器允许你调用
- 声明的是一个变量(装函数地址)
- 运行时对象,占内存存地址
- 作用是间接调用、传递、切换函数
函数名就是地址
前面几页我们写声明、画括号,把类型对齐了。现在直接问:调用处写 func,编译器拿到的是函数本身,还是它的地址?
名字本身就指明地点,不必额外「取地址」
赋值与调用示例
从定义函数到通过指针调用,一段最小可运行代码完整跑通四个步骤。
L8 声明一个空指针 fp;L9 把函数名 add 这个地址塞进去,指针"有内容";L10 用 fp(3,4) 即可调用,效果和 add(3,4) 完全一样——这是赋值与调用的完整闭环。
两种调用方式等价性
运行结果说明:(*pf)(3,4) 和 pf(3,4) 输出完全相同。
函数调用运算符 () 本就自带解引用,所以 * 是冗余但合法的——编译器把两者当成一回事。
回调函数机制图解
左侧用户代码,右侧库代码。重点看 l4→u3 那根"回"字箭头——库通过存贮的指针,把控制权反向交还用户。
qsort中的回调实战
用 qsort 把整型数组降序排:自定义比较器作为函数指针传入。
qsort 第四参期待特定函数指针类型,cmp_desc 签名匹配后函数名自动 decay 成指针传入——这正是回调的本质:把比较规则从算法骨架里"挖"出来交给调用方。
函数指针数组
用状态编号索引函数指针数组,依次执行三个状态处理函数,展示状态机的分发表。
table[state] 先按当前状态选中函数,再调用并取得新状态;数组下标同时负责选择处理函数和驱动状态转移。
返回指针的函数声明
前面学了用指针调用函数。现在反过来:函数本身能不能返回一个指针?很多场景下函数不直接给出值,而是返回一个『地址』让调用者自己取——比如 malloc、字符串查找函数。
管理员不递书,只给写着书架号的纸条——书被下架纸条就失效
正确返回指针的场景
三个返回指针的合法姿势:静态常量、堆分配、调用者传入的缓冲区。
返回值有合法归宿——静态区永生、堆上 malloc/free 配对、或者就是调用者自己那块地皮。共同点:函数返回时那块内存还活着。
返回局部变量的危险
用一段经典错误代码看:返回栈上变量地址后会发生什么。
函数结束后栈帧立即回收,&local 成了悬空地址;继续解引用 *p 即触发未定义行为。
返回指针 vs 返回值
函数可以返回「值」也可以返回「指针」,对大对象而言差异显著——性能、所有权、失败表达都不一样。
- 在栈上拷贝一份,调用者独占
- 函数退出即确定,无需关心释放
- 失败靠返回特定值(如 0、-1),需文档约定
- 只把地址交出去,零拷贝
- 谁负责释放、何时失效要约定清楚
- 返回 nullptr 即失败,类型自洽
综合示例:计算器
把函数指针数组与回调合在一起:一个循环跑完四则运算。
typedef 让声明变干净、数组把函数捆成一捆、回调参数把『何时调用』交给被调方——三者组合即可替代一连串 if/else。
自测题
函数返回其内部局部变量的地址,调用方用这个指针访问时会怎样?
课后思考
三个开放问题,探讨函数指针的深层边界