
前面我们已经分别征服了函数第十二篇和指针第十五、十六篇。当时我还卖了个关子——函数本身也能被指针指向而且这种“函数指针”能让你写出高度灵活、可插拔的代码。今天我们就来把这最后一块拼图补上。这篇文章会让你理解如何把函数当作参数传给另一个函数这正是回调的底层原理如何用函数指针实现策略模式以及那个让人头疼的“信号函数”声明到底怎么读。学完之后你看很多 C 库的源码比如qsort、信号处理就不会再一头雾水。一、函数也有地址指向函数的指针和数组一样函数在内存中也是一段连续的二进制指令自然也有起始地址。函数名在表达式中会被转换成这个地址——就像数组名被转换成首元素地址一样。#includestdio.hintadd(inta,intb){returnab;}intmain(void){printf(%p\n,(void*)add);// 打印函数地址return0;}既然函数有地址就可以用指针来存它。这就是函数指针。声明函数指针函数指针的声明格式就是把函数声明中的函数名换成(*指针名)// 普通函数声明intadd(inta,intb);// 对应的函数指针声明int(*func_ptr)(inta,intb);// func_ptr 是指向“返回int、接收两个int”函数的指针读法func_ptr是一个指针指向一个接收两个int、返回int的函数。让指针指向函数并调用int(*fp)(int,int);// 声明fpadd;// 赋值add 和 add 等价// 或 fp add;intresultfp(3,5);// 通过指针调用等价于 add(3, 5)printf(%d\n,result);// 8函数指针可以像普通函数一样直接加括号调用fp(3,5)和(*fp)(3,5)都可以。二、函数指针作为参数回调函数函数指针真正的威力在于把函数当作参数传给另一个函数。这样被调用的函数可以在适当的时候“回调”你传进去的函数。这在需要通用化操作时极其有用。回想第十篇我们写过冒泡排序但那个排序只能排int而且只能升序。如果我想让它能排double或者能由用户指定升序还是降序就要用函数指针。模拟qsort的冒泡排序#includestdio.h#includestdbool.htypedefbool(*compare_fn)(int,int);// 用 typedef 简化类型名boolascending(inta,intb){returnab;}// ab 说明需要交换booldescending(inta,intb){returnab;}voidbubble_sort(intarr[],intn,compare_fn cmp){for(inti0;in-1;i){bool swappedfalse;for(intj0;jn-1-i;j){if(cmp(arr[j],arr[j1])){// 调用回调函数决定是否交换inttemparr[j];arr[j]arr[j1];arr[j1]temp;swappedtrue;}}if(!swapped)break;}}intmain(void){intarr[]{5,2,8,1,9};intnsizeof(arr)/sizeof(arr[0]);bubble_sort(arr,n,ascending);printf(升序: );for(inti0;in;i)printf(%d ,arr[i]);printf(\n);bubble_sort(arr,n,descending);printf(降序: );for(inti0;in;i)printf(%d ,arr[i]);printf(\n);return0;}输出升序: 1 2 5 8 9 降序: 9 8 5 2 1同一个bubble_sort通过传入不同的比较函数就能改变排序行为。这就是策略模式在 C 语言里的实现——函数指针就是策略的载体。标准库的qsort正是这样做的voidqsort(void*base,size_tnmemb,size_tsize,int(*compar)(constvoid*,constvoid*));它使用void*实现通用类型再配合函数指针完成比较操作是函数指针最经典的应用。三、typedef简化函数指针类型函数指针的语法写起来有点啰嗦每次写int (*)(int, int)容易出错。typedef是解决这个问题的利器。typedefint(*operation_fn)(int,int);// operation_fn 是一个类型名operation_fn fpadd;// 简洁这比int (*fp)(int, int) add;好读得多。以后凡是见到typedef 返回类型 (*类型名)(参数列表);的写法就知道是在给函数指针类型取别名。再看一个更直观的例子——简单计算器#includestdio.htypedefint(*calc_fn)(int,int);intadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){return(b!0)?a/b:0;}intcalculate(intx,inty,calc_fn op){returnop(x,y);}intmain(void){printf(10 5 %d\n,calculate(10,5,add));printf(10 - 5 %d\n,calculate(10,5,sub));printf(10 * 5 %d\n,calculate(10,5,mul));printf(10 / 5 %d\n,calculate(10,5,div));return0;}calculate完全不知道具体运算是什么它只负责在合适的时机调用传入的函数指针。核心逻辑和具体操作彻底解耦。四、函数指针数组函数表的雏形如果有一组同类型的函数可以放进函数指针数组用下标来选择执行哪个。这在实现菜单驱动、状态机、命令解析时非常常见。#includestdio.htypedefvoid(*command_fn)(void);voidcmd_new(void){printf(新建文件\n);}voidcmd_open(void){printf(打开文件\n);}voidcmd_save(void){printf(保存文件\n);}voidcmd_quit(void){printf(退出程序\n);}intmain(void){command_fn menu[]{cmd_new,cmd_open,cmd_save,cmd_quit};intchoice;printf(0-新建 1-打开 2-保存 3-退出\n请输入选择: );scanf(%d,choice);if(choice0choice3){menu[choice]();// 通过下标调用对应的函数}else{printf(无效选择\n);}return0;}这种“函数表”的设计让添加新命令变得极为简单——只需写新函数再把它加入数组即可不用改任何if-else或switch逻辑。五、常见错误与陷阱1. 函数指针类型不匹配intadd(inta,intb){returnab;}double(*fp)(double,double)add;// 错误返回类型和参数类型都不匹配函数指针赋值时类型必须完全一致。如果确实需要转换必须显式强制转换且后果自负。2. 忘记写括号int*fp(int,int);// 这是函数声明返回 int*不是函数指针int(*fp)(int,int);// 这才是函数指针括号是函数指针声明的灵魂丢了它语义就彻底变了。3. 对函数指针使用sizeofsizeof(add);// 非法不能对函数名使用 sizeofsizeof只能用于对象类型不能用于函数。4. 解引用函数指针时的困惑int(*fp)(int,int)add;fp(3,4);// ✅ 可以直接调用(*fp)(3,4);// ✅ 也可以解引用后调用(**fp)(3,4);// ✅ 甚至这样也行因为函数名本身就是地址解引用后还是函数类型又会自动转回地址。所以怎么写都能跑。不用纠结形式直接用指针名加括号调用最简洁。六、小结与下篇预告今天我们把函数和指针结合了起来。核心收获函数在内存中有地址函数名就是地址。函数指针声明格式返回类型 (*指针名)(参数类型列表);用typedef简化函数指针类型告别复杂声明。函数指针作为参数实现回调是策略模式和解耦的利器。函数指针数组可以用来构建简单的函数表避免冗长的条件分支。理解函数指针你就推开了通往高阶 C 编程的一扇大门。标准库的qsort、bsearch、signal都依赖它大型项目中的插件架构、中断向量表也离不开它。到目前为止我们所有的数据——数组也好、字符串也罢——都是在编译时就定好大小的。但如果程序运行时才知道需要多少内存呢比如用户的输入长度不确定或者要根据数据库查询结果动态创建数据结构。这就是下一篇的主题动态内存分配。malloc、free、calloc、realloc以及堆与栈的根本区别。那是 C 语言又一块充满力量与危险的领域准备好你的指针知识我们将长驱直入。课后小练习写一个函数apply(int arr[], int n, int (*fn)(int))对数组的每个元素调用fn将返回值写回原位置。测试写一个square函数将数组元素变成它的平方。用函数指针数组实现一个简单的加减乘除计算器用户输入两个数和运算符、-、*、/程序通过查表调用对应函数输出结果。解释以下声明的含义int(*p1)(int,int);int*p2(int,int);int(*p3[4])(int,int);int(**p4)(int,int);提示用右左法则或从变量名向外读。小挑战自己实现一个简化版的qsort名字叫my_qsort支持对int数组排序接收一个比较函数指针作为参数。用你自己的排序算法冒泡或选择排序体验回调函数的实际运用。我们下期见获取本系列示例代码请访问 GitCode 仓库。