第14讲:深入理解指针(4)——函数指针与“程序跳转术”
🧭 第14讲:深入理解指针(4)——函数指针与“程序跳转术” 🔄🎯
指针进阶:掌握函数指针、回调机制与转移表的魔法!
📚 目录
- 字符指针变量:字符串的“只读守护者” 🔐
- 数组指针变量:指向数组的“导航仪” 🧭
- 二维数组传参的本质:行地址的传递 🔁
- 函数指针变量:指向函数的“遥控器” 🎮
- 函数指针数组:函数的“集合仓库” 🏭
- 转移表:用函数指针实现“智能调度” 🚦
- 学习收获总结 ✅
字符指针变量:字符串的“只读守护者” 🔐
🧩 基本用法
字符指针 char*
用于指向字符或字符串。
int main() {char ch = 'w';char *pc = &ch;*pc = 'w'; // ✅ 修改单个字符return 0;
}
🔒 指向字符串常量
int main() {const char* pstr = "hello bit."; // ✅ 推荐写法printf("%s\n", pstr);return 0;
}
📌 关键理解:
"hello bit."
是一个字符串字面量,存储在只读内存区。
pstr
存放的是首字符'h'
的地址,即:pstr == &"hello bit."[0]
⚠️ 为什么用
const
?
防止通过指针修改只读内存,避免程序崩溃。
🧪 字符串常量的内存共享机制
#include <stdio.h>
int main() {char str1[] = "hello bit."; // 栈上创建副本char str2[] = "hello bit."; // 另一个副本const char *str3 = "hello bit."; // 指向常量区const char *str4 = "hello bit."; // 指向同一常量if(str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n"); // ✅ 输出if(str3 == str4)printf("str3 and str4 are same\n"); // ✅ 输出elseprintf("str3 and str4 are not same\n");return 0;
}
🌟 核心机制:
str1
,str2
:数组初始化 → 各自开辟独立空间str3
,str4
:指向字符串字面量 → 共享同一内存地址
✅ 结论:
字符串字面量在程序运行期间只存储一份,多个指针可共享。
数组指针变量:指向数组的“导航仪” 🧭
🧩 什么是数组指针?
指针类型 | 含义 |
---|---|
int* p | 指向 int 变量 |
float* pf | 指向 float 变量 |
int (*p)[10] | 指向 int[10] 数组 |
📌 数组指针是“指针”,不是数组!
它存放的是整个数组的地址。
🔍 如何区分指针数组与数组指针?
int *p1[10]; // ❌ 指针数组:数组,元素为 int*
int (*p2)[10]; // ✅ 数组指针:指针,指向 int[10]
🎯 记忆口诀:
[]
优先级高于*
,所以*p
必须加括号才能先结合(*p)
表示 p 是指针,[10]
表示它指向一个10个int的数组
🔧 数组指针的初始化
int arr[10] = {0};
int (*p)[10] = &arr; // ✅ 获取整个数组的地址
📌
&arr
类型为int(*)[10]
,与p
类型完全匹配。
🧭 数组指针的类型解析
int (*p)[10] = &arr;
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型(int)
二维数组传参的本质:行地址的传递 🔁
🔄 传统写法
void test(int a[3][5], int r, int c) {for(int i = 0; i < r; i++) {for(int j = 0; j < c; j++) {printf("%d ", a[i][j]);}printf("\n");}
}int main() {int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}};test(arr, 3, 5);return 0;
}
🔍 二维数组的本质
- 二维数组可视为“数组的数组”
arr[3][5]
是 3 个int[5]
类型一维数组的集合arr
是首元素地址 → 即第一行的地址- 第一行类型为
int[5]
→ 其地址类型为int(*)[5]
✅ 指针形式传参
void test(int (*p)[5], int r, int c) {for(int i = 0; i < r; i++) {for(int j = 0; j < c; j++) {printf("%d ", *(*(p + i) + j)); // 等价于 p[i][j]}printf("\n");}
}
📌
p
是数组指针,指向int[5]
类型的一维数组。
🎯 总结:二维数组传参
形参写法 | 本质 |
---|---|
int a[3][5] | 等价于 int (*a)[5] |
int (*p)[5] | 显式数组指针 |
✅ 结论:
二维数组传参,形参本质是指针,指向第一行(一维数组)。
函数指针变量:指向函数的“遥控器” 🎮
🧩 函数有地址吗?
#include <stdio.h>
void test() {printf("hehe\n");
}int main() {printf("test: %p\n", test); // 输出地址printf("&test: %p\n", &test); // 输出相同地址return 0;
}
✅ 结论:
函数名就是函数的地址,test == &test
🔧 函数指针的声明
void (*pf1)() = &test; // 指向无参无返回函数
void (*pf2)() = test; // 省略 & 也可int Add(int x, int y) {return x + y;
}int (*pf3)(int, int) = Add; // 指向 int(int,int) 函数
int (*pf4)(int x, int y) = &Add; // 参数名可省略
🧭 函数指针类型解析
int (*pf3) (int x, int y)
| | -------------
| | | |
| | pf3指向函数的参数类型和个数
| 函数指针变量名
pf3指向函数的返回类型(int)int (*) (int, int) // pf3 的完整类型
🔁 函数指针的使用
int main() {int (*pf)(int, int) = Add;printf("%d\n", (*pf)(2, 3)); // ✅ 解引用调用printf("%d\n", pf(3, 5)); // ✅ 直接调用(更常用)return 0;
}
📌 两种写法等价,
pf(3,5)
更简洁。
🤯 两段“天书”代码解析
🔡 代码1:(void(*)())0)()
(*(void (*)())0)();
📖 解析:
void (*)()
:返回void
、无参的函数指针类型(void(*)())0
:将地址0
强制转为该函数指针*(...)
:解引用该指针(...)
:调用该函数
🚨 含义:调用地址为0处的函数(常用于系统复位)
🔡 代码2:void (*signal(int, void(*)(int)))(int)
void (*signal(int num, void(*handler)(int)))(int);
📖 解析:
signal
是一个函数,它:
- 参数1:
int num
- 参数2:
void(*handler)(int)
,一个函数指针- 返回值:
void (*)(int)
,另一个函数指针
✅ 简化方法:使用
typedef
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t); // 清晰明了!
函数指针数组:函数的“集合仓库” 🏭
🧩 什么是函数指针数组?
- 指针数组:
int* arr[3]
→ 数组,元素为int*
- 函数指针数组:存放多个函数指针的数组
int (*parr[3])(); // ✅ 正确
📌 解析:
parr[3]
:parr 是数组int (*)()
:数组元素是函数指针,指向int(void)
函数
❌ 常见错误写法
int *parr2[3](); // ❌ 函数返回指针数组?
int (*)() parr3[3]; // ❌ 语法错误
转移表:用函数指针实现“智能调度” 🚦
🔄 传统计算器:switch
分支
// 使用 switch-case 实现加减乘除
// 代码冗长,不易扩展
🚀 转移表优化:函数指针数组
#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 div(int a, int b) { return a / b; }int main() {int x, y, input, ret;// 转移表:函数指针数组int (*p[5])(int, int) = {0, add, sub, mul, div};do {printf("1:add 2:sub 3:mul 4:div 0:exit\n");printf("请选择:");scanf("%d", &input);if (input >= 1 && input <= 4) {printf("输入操作数:");scanf("%d %d", &x, &y);ret = p[input](x, y); // 直接调用printf("ret = %d\n", ret);} else if (input == 0) {printf("退出计算器\n");} else {printf("输入有误\n");}} while (input);return 0;
}
🎯 转移表的优势
优势 | 说明 |
---|---|
✅ 代码简洁 | 替代冗长 switch |
✅ 易于扩展 | 新增功能只需添加函数和指针 |
✅ 高效调度 | O(1) 时间复杂度调用函数 |
✅ 回调基础 | 为 qsort 等函数提供回调机制 |
🌟 应用场景:
- 操作系统中断处理
- GUI 事件响应
- 状态机设计
- 高阶函数(如
qsort
)
✅ 学习收获总结
技能 | 掌握情况 |
---|---|
✅ 理解字符指针与字符串常量的关系 | ✔️ |
✅ 区分指针数组与数组指针 | ✔️ |
✅ 掌握二维数组传参的指针本质 | ✔️ |
✅ 能声明并使用函数指针 | ✔️ |
✅ 理解复杂函数指针声明(如 signal ) | ✔️ |
✅ 会使用 typedef 简化类型 | ✔️ |
✅ 掌握函数指针数组与转移表应用 | ✔️ |
🎯 函数指针是C语言实现“多态”与“高阶函数”的核心机制。
你已掌握这一强大工具,继续前行,解锁更多系统级编程奥秘!💪🔥
💬 需要本讲的 函数指针练习题 / 转移表示例扩展 /
qsort
预习资料?欢迎继续提问,我为你准备!