文章目录
[1. 回调函数是什么?](#1. 回调函数是什么?)
[2. qsort 使用举例](#2. qsort 使用举例)
[2.1 qsort函数使用实例](#2.1 qsort函数使用实例)
[2.2 为什么不用冒泡函数](#2.2 为什么不用冒泡函数)
[2.3 使用qsort函数排序整型数据](#2.3 使用qsort函数排序整型数据)
[2.4 使用qsort排序结构数据](#2.4 使用qsort排序结构数据)
[3. qsort函数的模拟实现](#3. qsort函数的模拟实现)
1. 回调函数是什么?
回调函数就是⼀个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数
时,被调用的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的一方调用的,用于对该事件或条件进行响应。传递过去,使⽤函数指针接收,函数指针指向什么函数就调⽤什么函数,这⾥其实使⽤的就是回调函数的功能。
代码使用实例:
cpp
//回调函数:通过函数指针调用的函数
//使用函数指针可实现该效果
int add(int x, int y)
{
return x + y;
}
void test(int (*pf)(int, int))
{
printf("%d\n", pf(1, 2));
}
int main()
{
test(add);//add则为回调函数
return 0;
}
有了回调函数,再来观察上一篇博客中有关转移表的代码,
这是原代码:
//void menu()
//{
// printf("****************************\n");
// printf("****** 1. add 2. sub ******\n");
// printf("****** 3. mul 4. div ******\n");
// printf("****** 0. exit ******\n");
// printf("****************************\n");
//}
//
//int Add(int x, int y)
//{
// return x + y;
//}
//
//int Sub(int x, int y)
//{
// return x - y;
//}
//
//int Mul(int x, int y)
//{
// return x * y;
//}
//
//int Div(int x, int y)
//{
// return x / y;
//}
//
//int main()
//{
// int input = 0;
// int x = 0;
// int y = 0;
// int r = 0;
// do
// {
// menu();
// printf("请选择:");
// scanf("%d", &input);
// switch (input)
// {
// case 1:
// printf("请输入两个操作数:");
// scanf("%d %d", &x, &y);
// r = Add(x, y);
// printf("%d\n", r);
// break;
// case 2:
// printf("请输入两个操作数:");
// scanf("%d %d", &x, &y);
// r = Sub(x, y);
// printf("%d\n", r);
// break;
// case 3:
// printf("请输入两个操作数:");
// scanf("%d %d", &x, &y);
// r = Mul(x, y);
// printf("%d\n", r);
// break;
// case 4:
// printf("请输入两个操作数:");
// scanf("%d %d", &x, &y);
// r = Div(x, y);
// printf("%d\n", r);
// break;
// case 0:
// printf("退出计算器\n");
// break;
// default:
// printf("选择错误,重新选择\n");
// break;
// }
// } while (input);
//
// return 0;
//}
不难发现,没种情况下都有重复代码,且每个功能的函数类型大体上是一致的,可以用一个函数包含这四种函数,再只用回调这个包含函数即可:
改造后:
//
//void menu()
//{
// printf("****************************\n");
// printf("****** 1. add 2. sub ******\n");
// printf("****** 3. mul 4. div ******\n");
// printf("****** 0. exit ******\n");
// printf("****************************\n");
//}
//
//int Add(int x, int y)
//{
// return x + y;
//}
//
//int Sub(int x, int y)
//{
// return x - y;
//}
//
//int Mul(int x, int y)
//{
// return x * y;
//}
//
//int Div(int x, int y)
//{
// return x / y;
//}
//
////函数的参数是函数指针,可以接收不同函数的地址
////接收的地址不同,调用的函数就不同
////这个函数根据参数的不同就能完成不同的功能
//void Calc(int (*pf)(int, int))
//{
// int x = 0;
// int y = 0;
// int r = 0;
// printf("请输入两个操作数:");
// scanf("%d %d", &x, &y);
// //r = (*pf)(x, y);
// r = pf(x, y);
// printf("%d\n", r);
//}
//
//int main()
//{
// int input = 0;
//
// do
// {
// menu();
// 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;
2. qsort 使用举例
2.1 qsort函数使用实例
qsort ------ > quick sort
cpp
//包含stlib.h
//void qsort(void* base, 指针 指向被排序数组的第一个元素
// void* 我不知道是什么数组
// size_t num, 数组中元素个数 所以是size_t
// size_t size, 一个元素的字节大小
// int (*compar)(const void*, const void*) 排序函数的指针
// 因为普适性,要比较各种各样的数据,所 以用不同函数来规定排序规则
// 比如:比较结构体中数据
// );
这就是该函数规定的语法,其中下面这个比较函数需要自己去写,若第一个比较数大于第二个,则返回1;小于返回-1;等于返回0.
具体使用示例看下一个部分
2.2 为什么不用冒泡函数
//1 现成的,不用自己实现
//2 大多数情况下 效率较高
//3 通用
先看一个冒泡函数的例子:
cpp
void bubble_sort(int arr[], int sz)
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
}
}
void print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 3,1,5,8,7,9,2,4,6,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//冒泡排序
bubble_sort(arr,sz);
print(arr,sz);
return 0;
}
再用qsort进行排序:
cpp
int cmp_int(const void* p1, const void* p2)
{
if (*(int*)p1 > *(int*)p2)
{
return 1;
}
else if (*(int*)p1 < *(int*)p2) {
return -1;
}
else
{
return 0;
}
}
void test2()
{
int arr[] = { 3,1,5,8,7,9,2,4,6,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort
//包含stlib.h
void qsort(void* base, //指针 指向被排序数组的第一个元素
// void* 我不知道是什么数组
size_t num, //数组中元素个数 所以是size_t
size_t size, //一个元素的字节大小
int (*compar)(const void*, const void*) //排序函数的指针
// 因为普适性,要比较各种各样的数据,//所以用不同函数来规定排序规则
// 比如:比较结构体中数据
);
//自己写一个比较函数
qsort(arr,sz,4,cmp_int);
print(arr, sz);
}
再调用test2()即可。
2.3 使用qsort函数排序整型数据
上面其实已经介绍,这里再完整的介绍一遍:
cpp
#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
2.4 使用qsort排序结构数据
这⾥需要补充介绍结构指针和结构体成员访问操作符 -> :
cpp
//struct stu
//{
// char name[30];
// int age;
//};
//void test(struct stu* ps)
//{
// printf("%s\n", ps->name);
// printf("%d\n", ps->age);
//}
////-> 结构体成员访问操作符,针对结构体指针使用
//
//
//int main()
//{
// struct stu s = { "zhangsan",20 };
// test(&s);
// return 0;
//}
按照结构体中的年龄数据来进行比较:
cpp
struct stu
{
char name[30];
int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
return (*(struct stu*)p1).age - (*(struct stu*)p2).age;//也可用->
}
void print_stu(struct stu arr[],int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%s : %d\n", arr[i].name, arr[i].age);
}
}
void test3()
{
struct stu arr[] = { {"zhangsan",20},{"lisi",38},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);// 按照年龄
//qsort(arr, sz, sizeof(arr[0]), cmp_stu_name);
print_stu(arr, sz);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
如果按照结构体中的姓名字符串来拍序:
cpp
struct stu
{
char name[30];
int age;
};
void print_stu(struct stu arr[],int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%s : %d\n", arr[i].name, arr[i].age);
}
}
//两个字符串比较大小只能使用strcmp
#include <string.h>
int cmp_stu_name(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
void test3()
{
struct stu arr[] = { {"zhangsan",20},{"lisi",38},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort(arr, sz, sizeof(arr[0]), cmp_stu_age); 按照年龄
qsort(arr, sz, sizeof(arr[0]), cmp_stu_name);
print_stu(arr, sz);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
3. qsort函数的模拟实现
使⽤回调函数,模拟实现qsort(采⽤冒泡的⽅式)。
注意:这⾥第⼀次使⽤ void* 的指针,讲解 void* 的作⽤。
cpp
#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
void _swap(void *p1, void * p2, int size)
{
int i = 0;
for (i = 0; i< size; i++)
{
char tmp = *((char *)p1 + i);
*(( char *)p1 + i) = *((char *) p2 + i);
*(( char *)p2 + i) = tmp;
}
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++)
{
for (j = 0; j<count-i-1; j++)
{
if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) >
0)
{
_swap(( char *)base + j*size, (char *)base + (j + 1)*size,
size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
总结
-
回调函数的本质与应用
回调函数是通过函数指针调用的特殊函数,其核心是将函数地址作为参数传递给另一个函数,在特定条件下由后者调用以响应事件。
- 实际场景中,回调函数可有效简化冗余代码。例如计算器程序中,通过将
add
、sub
等运算函数的地址传递给calc
函数,避免了输入输出逻辑的重复编写,提升了代码复用性与可维护性。
- 实际场景中,回调函数可有效简化冗余代码。例如计算器程序中,通过将
-
qsort 函数的使用技巧
qsort
是 C 语言标准库中的快速排序函数,支持对任意类型数据排序,其关键在于用户需自定义比较函数(回调函数):- 排序整型数据时,比较函数需将
void*
指针转换为int*
,通过差值判断大小; - 排序结构体数据时,可根据成员(如年龄、名字)定制比较逻辑,例如用
strcmp
比较字符串类型的名字。
- 排序整型数据时,比较函数需将
-
qsort 的模拟实现思路
以冒泡排序为基础模拟
qsort
时,需结合void*
指针和回调函数:void*
指针可接收任意类型数据,通过强制转换与字节级操作(如char*
指针移动)实现通用访问;- 自定义比较函数决定排序规则,
_swap
函数通过字节交换实现任意类型数据的交换,最终实现通用排序功能。