1. 回调函数
回调函数的基本思想是,将函数指针作为参数传递给另一个函数,并在需要时通过这个函数指针调用对应的函数。这种方式允许一个函数对执行的内容进行控制,而不需要知道具体的实现细节。
回调函数在以下场景中尤为有用:
- 事件驱动编程:例如处理按键事件或鼠标点击。
- 排序或过滤:可以将比较函数作为参数传递给排序函数。
- 异步操作:例如定时器到时后调用某个处理函数。
1.1 实现回调函数的步骤
实现回调函数主要有以下几个步骤:
- 定义一个回调函数:编写一个可以被回调的函数,这个函数的签名需要与回调机制所期望的签名一致。
- 定义函数指针:定义一个可以指向回调函数的函数指针。
- 调用函数并传递回调函数指针:在需要执行回调函数的地方,通过传递回调函数的地址来调用它。
1.2 回调函数实例
上一节我们讲解了转移表来改善我们的简易计算器,那我们能不能通过回调函数来实现呢?
1.2.1 改造前的代码
//使用回调函数改造前
#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;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add2:sub \n");
printf(" 3:mul4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
我们发现每一个case
中的代码有很大一部分都是重复的,那么这一部分可以用回调函数来改造一下
1.2.2 改造后的代码
//使用回到函数改造后
#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;
}
void calc(int(*pf)(int, int))
{
int ret = 0;
int x, y;
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf(" 1:add2:sub \n");
printf(" 3:mul4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
1.2.3 代码解析
这段代码的核心在于使用回调函数 calc
统一处理不同的运算操作,从而简化了 main
函数中的逻辑。
1.2.3.1 定义 calc
回调函数
calc
函数使用了函数指针 pf
,可以接收任意符合 int (int, int)
签名的函数。calc
函数的作用是通用地处理不同的运算逻辑,而不直接关心运算的具体实现,这使得代码更加灵活。
void calc(int (*pf)(int, int)) {
int ret = 0;
int x, y;
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y); // 调用传入的回调函数
printf("ret = %d\n", ret);
}
-
输入操作数:提示用户输入两个操作数
x
和y
。 -
调用回调函数:通过
pf(x, y)
调用传入的运算函数。 -
输出结果:将结果
ret
输出给用户。
1.2.3.2 主函数 main
main
函数的作用是提供用户交互界面,根据用户的选择调用对应的运算函数。
int main() {
int input = 1;
do {
printf("*************************\n");
printf(" 1:add2:sub \n");
printf(" 3:mul4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input) {
case 1:
calc(add); // 将加法函数指针传递给 calc
break;
case 2:
calc(sub); // 将减法函数指针传递给 calc
break;
case 3:
calc(mul); // 将乘法函数指针传递给 calc
break;
case 4:
calc(div); // 将除法函数指针传递给 calc
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
- 菜单显示:每次循环都会显示运算选项菜单,用户可以选择对应的运算。
-
用户输入:获取用户的选择并存储在
input
中。 -
调用
calc
函数:根据input
的值,通过calc
调用对应的运算函数。 -
退出程序:当
input
为0
时,程序输出退出提示并结束循环。
1.3 回调函数的优点
-
代码复用:通过传递不同的回调函数,
calc
函数可以实现不同的功能。 - 灵活性:可以在程序运行时动态地选择调用哪个函数。
- 模块化设计:使代码逻辑更加清晰、可维护。
1.4 回调函数的应用场景
-
排序算法:在
qsort
等函数中,用户可以自定义比较函数,实现各种排序逻辑。(下面会讲) - 事件驱动的GUI程序:响应用户的点击、输入等操作。
- 异步任务处理:在异步操作完成时,通过回调函数通知调用者任务完成。
2. qsort
使用
qsort
是C语言标准库<stdlib.h>
中的一个通用排序函数,它使用快速排序算法来对数组进行排序。qsort
的强大之处在于它可以用于各种数据类型的排序,包括基本数据类型和复杂的自定义数据结构。这里将探讨qsort
的使用方法,并通过详细的实例演示如何对不同类型的数据进行排序。
2.1 qsort
函数原型详解
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
参数说明:
-
base
:指向要排序的数组的第一个元素的指针。它是一个void指针,可以接受任何类型的数组。 -
nitems
:数组中元素的个数。通常使用sizeof(array) / sizeof(array[0])
来计算。 -
size
:数组中每个元素的大小,以字节为单位。通常使用sizeof(array[0])
来获取。 -
compar
:用于比较两个元素的函数指针。这个函数决定了排序的顺序和标准。
2.2 比较函数的设计
比较函数是qsort
的核心,它决定了排序的方式。比较函数的原型如下:
int compare(const void *a, const void *b)
比较函数应该遵循以下规则:
- 如果
*a < *b
,返回负数(通常是-1) - 如果
*a == *b
,返回0 - 如果
*a > *b
,返回正数(通常是1)
注意:比较函数接收的是void
指针,需要在函数内部进行适当的类型转换。
2.3 整数排序详解
下面是一个使用qsort
对整数数组进行排序的详细例子:
#include <stdio.h>
#include <stdlib.h>
// 升序排序的比较函数
int compare_ints_asc(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
// 降序排序的比较函数
int compare_ints_desc(const void* a, const void* b) {
return (*(int*)b - *(int*)a);
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
// 升序排序
qsort(arr, n, sizeof(int), compare_ints_asc);
printf("升序排序后的数组:\n");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
// 降序排序
qsort(arr, n, sizeof(int), compare_ints_desc);
printf("降序排序后的数组:\n");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
在这个例子中,我们定义了两个比较函数:一个用于升序排序,另一个用于降序排序。通过更改传递给qsort的比较函数,我们可以轻松地改变排序的顺序。
2.4 字符串排序详解
对字符串数组进行排序需要特别注意,因为我们处理的是指向字符串的指针数组。以下是一个详细的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 升序排序的比较函数
int compare_strings_asc(const void* a, const void* b) {
return strcmp(*(const char**)a, *(const char**)b);
}
// 降序排序的比较函数
int compare_strings_desc(const void* a, const void* b) {
return strcmp(*(const char**)b, *(const char**)a);
}
int main() {
const char* arr[] = {"banana", "apple", "cherry", "date", "elderberry"};
int n = sizeof(arr) / sizeof(arr[0]);
// 升序排序
qsort(arr, n, sizeof(const char*), compare_strings_asc);
printf("升序排序后的字符串数组:\n");
for (int i = 0; i < n; i++)
printf("%s\n", arr[i]);
// 降序排序
qsort(arr, n, sizeof(const char*), compare_strings_desc);
printf("\n降序排序后的字符串数组:\n");
for (int i = 0; i < n; i++)
printf("%s\n", arr[i]);
return 0;
}
在这个例子中,我们使用strcmp
函数来比较字符串。注意比较函数中的双重指针:*(const char**)a
用于获取实际的字符串指针。
2.5 结构体排序详解
对结构体数组进行排序是qsort最强大的应用之一。我们可以根据结构体的不同成员进行排序。以下是一个详细的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float height;
} Person;
// 按年龄升序排序
int compare_age_asc(const void* a, const void* b) {
Person* personA = (Person*)a;
Person* personB = (Person*)b;
return personA->age - personB->age;
}
// 按身高降序排序
int compare_height_desc(const void* a, const void* b) {
Person* personA = (Person*)a;
Person* personB = (Person*)b;
if (personA->height < personB->height) return 1;
if (personA->height > personB->height) return -1;
return 0;
}
// 按姓名字母顺序排序
int compare_name(const void* a, const void* b) {
Person* personA = (Person*)a;
Person* personB = (Person*)b;
return strcmp(personA->name, personB->name);
}
int main() {
Person people[] = {
{"Alice", 30, 165.5},
{"Bob", 25, 180.0},
{"Charlie", 35, 175.5},
{"David", 28, 170.0},
{"Eve", 22, 160.0}
};
int n = sizeof(people) / sizeof(people[0]);
// 按年龄升序排序
qsort(people, n, sizeof(Person), compare_age_asc);
printf("按年龄升序排序:\n");
for (int i = 0; i < n; i++)
printf("%s: %d岁, %.1fcm\n", people[i].name, people[i].age, people[i].height);
// 按身高降序排序
qsort(people, n, sizeof(Person), compare_height_desc);
printf("\n按身高降序排序:\n");
for (int i = 0; i < n; i++)
printf("%s: %d岁, %.1fcm\n", people[i].name, people[i].age, people[i].height);
// 按姓名字母顺序排序
qsort(people, n