(C语言)理解 回调函数 和 qsort函数
一. 回调函数
1. 什么是回调函数?
回调函数(Callback Function)是通过 函数指针 调用的函数。其本质是:
将函数作为参数传递给另一个函数,并在特定条件下被调用,实现 反向控制。
2. 回调函数的使用
回调函数的主要用途包括事件处理和排序算法。通过传递函数指针作为参数,可以实现代码的灵活性和可重用性。例如,在一个简单的计算器程序中,可以通过传递不同的计算函数(如加法、减法等)来简化代码。
3. 示例代码
以下是一个简单的回调函数示例,展示了如何使用回调函数进行加法和减法操作:
#include <stdio.h>
// 定义回调函数类型
typedef int (*Operation)(int, int);
// 定义加法函数
int add(int a, int b)
{
return a + b;
}
// 定义减法函数
int jian(int a, int b)
{
return a - b;
}
// 使用回调函数进行计算
int calculate(int a, int b, Operation op)
{
return op(a, b);
}
int main()
{
int x = 10, y = 5;
printf(" %d\n", calculate(x, y, add));
printf(" %d\n", calculate(x, y, jian));
return 0;
}
二. qsort函数
1 qsort 函数的定义
qsort
函数是C语言标准库中的一个排序函数,用于对数组进行快速排序。它的完整声明如下:
#include <stdlib.h>
void qsort(void *base,
size_t nmemb,
size_t size,
int (*compar)(const void * e1, const void * e2));
base
:指向要排序的数组的第一个元素的指针。nmemb
:数组中元素的个数。size
:数组中每个元素的大小,以字节为单位。compar
:用来比较两个元素的函数,即函数指针(回调函数)。
2.compar参数
- compar参数指向一个比较两个元素的函数。比较函数的原型应该像下面这样。注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型。
- int compar(const void *e1, const void *e2);
- 如果compar返回值小于0(< 0),那么e1所指向元素会被排在e2所指向元素的左面;
- 如果compar返回值等于0(= 0),那么e1所指向元素与e2所指向元素的顺序不确定;
- 如果compar返回值大于0(> 0),那么e1所指向元素会被排在e2所指向元素的右面。
这里再强调一下
参数类型:必须为 const void*
参考代码:
以下是一个使用 qsort
函数对整型数组进行升序排序的示例:
qsort 一般默认是升序
#include <stdio.h>
#include <stdlib.h>
int comp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
int main()
{
int arr[6] = { 44,0,520,1314,888,444};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), comp);
/*for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);*/
return 0;
}
运行结果:
以下是一个使用 qsort
函数对结构体数组进行排序的示例,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct student
{
int id;
char name[10];
int grade;
}student;
int cmp1(const void *a, const void *b)//一级排序
{
student *s1 = (student*)a;
student *s2 = (student*)b;
return s1->id - s2->id;
}
int cmp2(const void *a,const void *b)//二级排序
{
student *s1 = (student*)a;
student *s2 = (student*)b;
if(strcmp(s1->name , s2->name) != 0)
return strcmp(s1->name , s2->name);
else
return s1->id - s2->id;
}
int cmp3(const void *a,const void *b)//三级排序
{
student *s1 = (student*)a;
student *s2 = (student*)b;
if(s1->grade != s2->grade)
return s1->grade - s2->grade;
else
{
if(strcmp(s1->name , s2->name) != 0)
return strcmp(s1->name , s2->name);
else
return s1->id - s1->id;
}
}
int main()
{
int i,N,C;
scanf("%d %d",&N,&C);
student *stu;
stu=(student*)malloc(N*sizeof(student));
for(i = 0 ; i < N ; i++)
scanf("%d %s %d" , &stu[i].id , stu[i].name , &stu[i].grade);
switch(C)
{
case 1: qsort(stu, N, sizeof(student), cmp1);break;//一级排序
case 2: qsort(stu, N, sizeof(student), cmp2);break;//二级排序
case 3: qsort(stu, N, sizeof(student), cmp3);break;//三级排序
}
printf("排序结果:\n");
for(i = 0 ; i < N ; i++)
printf("%03d %s %d\n" , stu[i].id , stu[i].name , stu[i].grade);
return 0;
}
三 .调试技巧与常见陷阱
1. 调试建议
-
在比较函数中添加打印语句:
-
int compare_debug(const void *a, const void *b) { int x = *(int*)a; int y = *(int*)b; printf("Comparing %d vs %d\n", x, y); return x - y; }
2. 常见错误
掌握回调函数与qsort的配合使用,不仅能写出更通用的代码,更能深入理解C语言"函数即数据"的哲学思想。这种设计模式在文件操作、事件处理、算法策略等场景中广泛应用,是提升编程能力的重要阶梯。
-
错误1:忘记类型转换
// 错误写法: return *a - *b; // a和b是void指针! // 正确: return *(int*)a - *(int*)b;
-
错误2:整数溢出
-
int compare_unsafe(const void *a, const void *b) { return *(int*)a - *(void*)b; // 可能溢出! } // 改进版: int compare_safe(const void *a, const void *b) { int x = *(int*)a; int y = *(int*)b; return (x > y) ? 1 : ((x < y) ? -1 : 0); }
-
错误3:修改const数据
int compare_wrong(const void *a, const void *b) { *(int*)a = 10; // 尝试修改原始数据! return ...; }
四.最佳实践总结
-
严格遵循比较函数规范
确保返回正确的-1/0/1,而不仅仅是差值 -
优先使用const修饰
比较函数参数应声明为const void*
-
复杂结构预先处理
对频繁排序的大型结构,可建立索引数组 -
跨平台注意字节序
处理网络传输数据时考虑大小端问题 -
性能关键处慎用
高频调用场景可考虑特定优化