本篇文章讲述了什么是回调函数,以及库函数qsort的用法。
目录
1.回调函数是什么?
回调函数就是一个通过函数指针调用的函数。
就是将一个函数地址作为参数传递给另一个函数,然后这个函数加以运用传递的函数。
上一章我们在编写一个转移表(计算器的一般实现)时,就采用了回调函数解决代码冗杂问题
#include <stdio.h>
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, y = 0, ret = 0;
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int x = 0, y = 0;
int input = 0;
int ret = 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");
default:
break;
}
} while (input);
return 0;
}
那个calc函数就是运用到了回调函数的思想,缩减了代码。
2.qsort() 函数
有了之前的知识铺垫,我们现在可以学习一个C语言中比较重要排序的库函数。如果还不了解函数指针变量,以及基本指针的,可以去翻看之前的文章。
2.1.qsort()函数的申明
这是官方文档的截图,上面给出了qsort的申明
void qsort(void* base,
size_t num,
size_t size,
int (*compar)(const void*, const void*))
下面对参数进行一一解释
- void* base, 指针,指向的是待排序的数组的第一个元素
- size_t num, 是base指向的待排序数组的元素个数
- size_t size base指向的待排序数组的元素的大小
- int (*compar)(const void*, const void*)) 函数指针 —— 指向的就是两个元素的比较函数
针对比较函数这里再做一个解释,因为我们不管是升序还是降序都需要进行数据比较,简单的 整型、浮点型、字符型可以直接通过 > < == 来进行比较,那如果是字符串或者结构体呢?
显然字符串和结构体是无法仅仅使用简单的比较运算符直接来比较,所以我们在函数中引入一个函数指针,这个函数指针用来接收用户编写的比较标准函数。
2.2.compar用法
这里采用简单的代码理解理解一下compar数值指针的用法。
样例一:简单的整型数组排序
#include <stdio.h>
void print_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void test()
{
int arr[] = { 3,1,2,5,7,8,4,0,6,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test();
return 0;
}
这里对函数cmp_int 进行解析
return *(int*)p1 - *(int*)p2;
- (int*) 强制类型转换 因为传入的参数为void*,而我们在 深入理解指针(1) 中已经讲过空指针void*不能被解引用,所以需要进行强制类型转换,才能解引用找到数据。
- 相减操作 相减只是一个简化代码的写法,因为如果p1指向的值 > p2 的话 返回正数,而相等 返回0,< 返回 负数,所以一个相减操作直接涵盖这三种情况。
样例代码只是提供一个升序的写法,如果我们要降序只需要将p1和p2对调就行了。
样例二:结构体数组排序
#include <stdio.h>
struct Stu
{
char name[20];
int age;
};
void print_stu(const void* p, int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%s, %d\n", (*((struct Stu*)p + i)).name, (*((struct Stu*)p + i)).age);
}
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
struct Stu arr[3] = { {"zhangsan", 20}, {"lisi", 35}, {"wangwu", 18} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
print_stu(arr, sz);
}
int main()
{
test2();
return 0;
}
- strcmp 是字符串中比较函数, p1 > p2 返回1, p1 = p2 返回0, p1 < p2 返回-1
- print_stu 打印输出结构体数组,其中(*((struct Stu*)p + i)).name就是打印各个结构体变量的name数据部分,可以结合数组加以理解。
这里是以结构体的name成员变量为标准进行排序,name为一个字符串所以按照字符串的比较进行升序排序。
3.qsort函数模拟实现
这里简单的用冒泡排序模拟实现qsort(原为快速排序)。因为我们要探讨的是qsort如何实现借用指针进行泛型编程。
#include <stdio.h>
#include <string.h>
struct Stu {
char name[20];
int age;
};
void Swap(char* buf1, char* buf2, size_t width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
int cmp_name(const void* p1, const void* p2)
{
return strcmp(p1, p2);
}
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
for (int i = 0; i < sz - 1; i++)
{
for (int j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void print_int(const void* base, size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", *((int*)base + i));
}
}
void print_Stu(const void* base, size_t sz)
{
for (int i = 0; i < sz; i++)
{
printf("%8s %5d\n", (*((struct Stu*)base + i)).name, (*((struct Stu*)base + i)).age);
}
}
void test1()
{
int arr[] = { 3, 1, 5, 6, 7, 2, 4, 0, 9, 8 };
size_t sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(int), cmp_int);
print_int(arr, sz);
}
void test2()
{
struct Stu arr[3] = {{"zhangsan", 20},{"lisi", 18},{"wangwu",30}};
size_t sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_name);
print_Stu(arr, sz);
}
int main()
{
//test1();
test2();
return 0;
}
为了达到泛型编程的目的,我们采用通用的空指针来接收地址,但是在 深入理解指针(1) 我们知道空指针是无法进行解引用操作的(因为空指针没有类型,编译器无法判断应该采用哪种权限进行解引用),所以我们得进行强制类型转换,因为不知道究竟转换成什么类型,就采用最小权限的char* 指针(+1 跳过一个字节),以此来逐个按一个字节的内容来进行操作,那么我们如何判断一个完整数据的内存呢,所以这时我们得传入一个处理数据的单个数据类型的大小即sizeof(arr[0])。这样就可以通过强制类型转换和简单的内存计算找到对应数据的区间完成比较。