
🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
-
- [前景回顾:前四篇指针核心速记 📝](#前景回顾:前四篇指针核心速记 📝)
- [一、回调函数:通过函数指针调用的函数 📞](#一、回调函数:通过函数指针调用的函数 📞)
-
- [1. 回调函数的定义与理解](#1. 回调函数的定义与理解)
- [2. 代码示例:计算器中的回调函数](#2. 代码示例:计算器中的回调函数)
- [3. 生活类比理解回调函数](#3. 生活类比理解回调函数)
- [二、qsort函数:通用排序的利器 ⚡](#二、qsort函数:通用排序的利器 ⚡)
-
- [1. qsort函数的原型](#1. qsort函数的原型)
- [2. 比较函数的规则](#2. 比较函数的规则)
- [3. qsort的使用案例](#3. qsort的使用案例)
- [三、模拟实现qsort:基于冒泡排序的通用改造 🔧](#三、模拟实现qsort:基于冒泡排序的通用改造 🔧)
-
- [1. 核心改造思路](#1. 核心改造思路)
- [2. 完整模拟实现代码](#2. 完整模拟实现代码)
- [写在最后 📝](#写在最后 📝)
指针系列的倒数第二篇来啦!这一篇我们将聚焦指针的终极实战用法------回调函数,同时深度解析库函数qsort的使用方法和模拟实现,帮你彻底掌握指针在通用算法中的灵活应用,为下一篇的笔试面试题精讲做好充分准备!
前景回顾:前四篇指针核心速记 📝
指针第一讲:从内存到运算,吃透指针核心逻辑
指针第二讲:const 修饰、野指针规避与传址调用
指针第三讲:数组与指针深度绑定 + 二级指针 + 指针数组全解析
指针第四讲:字符指针、数组指针、函数指针及转移表应用
想要吃透本篇的实战内容,先回顾前四篇的关键知识点:
- 指针本质是地址,不同类型的指针指向不同的目标对象,包括变量、数组、函数。
- 数组与指针深度绑定,数组传参本质传递首元素地址;函数指针可存储函数地址,实现对函数的间接调用。
- 函数指针数组可以构建转移表,简化多分支逻辑;
typedef可重命名复杂的指针类型,提升代码可读性。
一、回调函数:通过函数指针调用的函数 📞
回调函数是C语言中一种重要的编程思想,它的核心是把函数的地址作为参数传递给另一个函数,在合适的时机通过函数指针调用这个函数。这一知识点也是笔面试中的高频考点。
1. 回调函数的定义与理解
- 回调函数不是由函数的实现者直接调用,而是由其他函数通过函数指针间接调用。
- 回调函数的出现,让程序的逻辑分层更清晰,也让功能的拓展更灵活。
2. 代码示例:计算器中的回调函数
以简易计算器为例,Add、Sub等功能函数就是回调函数,它们的地址被传递给calc函数,在calc函数中被调用。
c
#include <stdio.h>
// 功能函数------回调函数
int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }
// 中间层函数:接收函数指针,调用回调函数
void calc(int (*p)(int, int))
{
int x = 0, y = 0, r = 0;
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
r = p(x, y); // 通过函数指针调用回调函数
printf("计算结果:%d\n", r);
}
// 菜单函数
void menu()
{
printf("*************************\n");
printf("*** 1.add 2.sub ***\n");
printf("*** 0.exit ***\n");
printf("*************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch(input)
{
case 1:
calc(Add); // 传递Add函数地址
break;
case 2:
calc(Sub); // 传递Sub函数地址
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while(input);
return 0;
}
3. 生活类比理解回调函数
回调函数的逻辑就像酒店的叫醒服务:
- 用户(主函数)告诉前台(中间层函数)叫醒的时间和方式(传递回调函数地址)。
- 到了指定时间,前台(中间层函数)按照用户要求的方式(调用回调函数)叫醒用户。
- 用户不需要自己定闹钟,只需要提供"叫醒方式",前台负责执行。
二、qsort函数:通用排序的利器 ⚡
qsort是C语言标准库中的排序函数,基于快速排序算法实现,可以排序任意类型的数组 ,包括整型、字符型、结构体等,其核心就是借助回调函数实现通用比较逻辑。qsort的使用与模拟实现是笔面试的重点考察内容。
1. qsort函数的原型
使用qsort需要包含头文件<stdlib.h>,函数原型如下:
c
void qsort(
void* base, // 待排序数组的首元素地址
size_t num, // 待排序数组的元素个数
size_t size, // 数组中每个元素的大小(单位:字节)
int (*compar)(const void*, const void*) // 比较两个元素的回调函数
);
2. 比较函数的规则
qsort的第四个参数是一个函数指针,指向的比较函数需要遵循固定规则:
- 若
p1指向的元素 >p2指向的元素,返回 大于0 的数。 - 若
p1指向的元素 ==p2指向的元素,返回 0。 - 若
p1指向的元素 <p2指向的元素,返回 小于0 的数。 - 函数参数是
const void*类型,可接收任意类型的地址,使用时需强制类型转换。
3. qsort的使用案例
案例1:排序整型数组
c
#include <stdio.h>
#include <stdlib.h>
// 整型比较函数(升序)
int cmp_int(const void* p1, const void* p2)
{
// void* 不能直接解引用,需强制转换为int*
return *(int*)p1 - *(int*)p2;
}
// 整型比较函数(降序)
// int cmp_int(const void* p1, const void* p2)
// {
// return *(int*)p2 - *(int*)p1;
// }
// 打印数组
void print_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test1()
{
int arr[] = {9,7,5,3,1,2,4,6,8,0};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
printf("排序后:");
print_arr(arr, sz);
}
int main()
{
test1();
return 0;
}
案例2:排序结构体数组
按姓名或年龄排序结构体数组,只需编写对应的比较函数。
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
};
// 按姓名比较(字典序)
int cmp_stu_by_name(const void* p1, const void* p2)
{
// 结构体指针访问成员用->
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
// 按年龄比较(升序)
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
// 打印结构体数组
void print_stu(struct Stu arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("姓名:%s 年龄:%d\n", arr[i].name, arr[i].age);
}
}
void test2()
{
struct Stu arr[] = {{"zhangsan",20},{"lisi",25},{"wangwu",18}};
int sz = sizeof(arr)/sizeof(arr[0]);
// 按姓名排序
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
printf("按姓名排序后:\n");
print_stu(arr, sz);
// 按年龄排序
// qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
// printf("按年龄排序后:\n");
// print_stu(arr, sz);
}
int main()
{
test2();
return 0;
}
三、模拟实现qsort:基于冒泡排序的通用改造 🔧
我们可以基于冒泡排序的思想,结合回调函数,模拟实现一个通用的qsort函数,理解其底层逻辑。这一实现思路在笔面试中极容易被考察。
1. 核心改造思路
- 参数设计 :参考库函数
qsort,设计void* base、元素个数、元素大小、比较函数指针四个参数。 - 元素比较:借助比较函数指针,调用用户提供的比较逻辑,判断两个元素的大小。
- 元素交换 :因为元素类型不确定,需按字节交换,设计一个通用的
Swap函数。
2. 完整模拟实现代码
c
#include <stdio.h>
#include <string.h>
// 通用交换函数:按字节交换两个元素
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for(i=0; i<width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
// 模拟实现qsort(基于冒泡排序)
void bubble_sort(
void* base,
int sz,
int width,
int (*cmp)(const void* p1, const void* p2)
)
{
int i = 0;
// 控制冒泡排序的趟数
for(i=0; i<sz-1; i++)
{
int flag = 1; // 标记是否已有序
int j = 0;
// 控制每一趟的比较次数
for(j=0; j<sz-1-i; j++)
{
// 计算第j个和第j+1个元素的地址
// 强转为char*,+width就是跳过一个元素的字节数
if(cmp((char*)base + j*width, (char*)base + (j+1)*width) > 0)
{
// 交换两个元素
Swap((char*)base + j*width, (char*)base + (j+1)*width, width);
flag = 0;
}
}
if(flag == 1)
{
break; // 已有序,提前结束
}
}
}
// 整型比较函数
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
// 打印数组
void print_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test()
{
int arr[] = {9,7,5,3,1,2,4,6,8,0};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
printf("排序后:");
print_arr(arr, sz);
}
int main()
{
test();
return 0;
}
写在最后 📝
本篇内容聚焦指针在实战和算法中的核心应用,回调函数与qsort的知识点紧密关联笔面试考点。掌握这些内容,你就拥有了应对指针类编程题的重要基础。
下一篇,我们将直击指针经典笔试面试题,从易到难拆解各类高频考题,帮你理清解题思路,轻松应对求职和考试中的指针难关!