C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(中):https://developer.aliyun.com/article/1513046
6. 函数指针数组
函数指针数组就是存放函数指针的数组。
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,
比如:
int *arr[10]; //数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])(); int *parr2[10](); int (*)() parr3[10];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int main() { int (*pf)(int, int) = Add; int (*pf2)(int, int) = Sub; int (*pfArr[2])(int, int) = { Add, Sub };//pfArr 就是函数指针数组 return 0; }
函数指针数组的用途:转移表
6.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 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 = 0, y = 0; int input = 1; int ret = 0; do { menu(); 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; }
发现代码很多重复的,而且计算器要加入其它运算时很麻烦
这时就要使用函数指针数组,使用函数指针数组的实现:
#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 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 = 0, y = 0; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { NULL, add, sub, mul, div }; //转移表 《C和指针》 do { menu(); printf("请选择:"); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf("输入操作数:"); scanf("%d %d", &x, &y); ret = (*p[input])(x, y); printf("ret = %d\n", ret); } else if (input == 0) { printf("退出程序"); } else { printf("选择错误,重新选择\n"); } } while (input); return 0; }
解析:这就是函数指针数组的应用。接收一个下标,通过下标找到数组里的某个元素,这个元素如果恰好是一个函数的地址,然后去调用那个函数。它做到了一个 "跳板" 的作用,所以通常称这种数组叫做 转移表(转移表在《C和指针》这本书中有所提及)。
7. 指向函数指针数组的指针
指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?
int Add(int x, int y) { return x + y; } int main() { int arr[10] = { 0 }; int(*p)[10] = &arr; // 取出数组的地址 int (*pfArr[4])(int, int); // pfArr是一个数组 - 函数指针的数组 // ppfArr是一个指向[函数指针数组]的指针 int (*(*ppfArr)[4])(int, int) = &pfArr; // ppfArr 是一个数组指针,指针指向的数组有4个元素 // 指向的数组的每个元素的类型是一个函数指针 int(*)(int, int) return 0; }
7.1指针的总结
8. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,
用于对该事件或条件进行响应。
下面使用回调函数模拟实现qsort。
8.1 qsort函数介绍:
引入:我们以前自己实现的冒泡排序函数只能排序整型顺序,如果我们要排序字符串或者一个结构体,我们是不是要单独重新实现这个函数呢?而 qsort 函数可以帮我们排任意想排的数据类型。
说明:qsort 函数是C语言编译器函数库自带的排序函数( 需引入头文件 stdlib.h ),能够排序任意数据类型的数组其中包括整形,浮点型,字符串甚至还有自定义的结构体类型。
qsort函数定义:
注意:这里第一次使用 void* 的指针,void* 可以指向任意类型的地址,但是void * 类型的指针不能进行加减操作,也就无法移动
1.首元素地址base
我们要排序一组数据,首先我们需要找到这组数据在哪,因此我们直接将首元素的地址传给qsort函数来确定从哪开始排序。
2.元素个数num
我们知道了从哪开始,也要知道在哪结束才能确定一组需要排序的数据,但是我们不方便直接将结尾元素的地址传入函数,因此我们将需要排序的元素的个数传给qsort函数来确定一组数据。
3.元素大小width
我们知道qsort函数能排序任意数据类型的一组数据,因此我们用void * 类型的指针来接收元素,但是我们知道void * 类型的指针不能进行加减操作,也就无法移动,那么在函数内部我们究竟用什么类型的指针来操作变量呢?我们可以将void * 类型的指针强制类型转换成char * 类型的指针后来操作元素,因为char * 类型的指针移动的单位字节长度是1个字节,我们只需要再知道我们需要操作的数据是几个字节就可以操作指针从一个元素移动到下一个元素,因此我们需要将元素大小传入qsort函数
4.自定义比较函数compar
第四个参数是一个函数指针
需要告诉qsort函数我们希望数据按照怎么的方式进行比较,比如对于几个字符串,可以比较字符串的大小(strcmp),也可以比较字符串的长度(strlen),因此要告诉qsort函数我们希望的比较方式,就需要传入一个比较函数compar,简写为cmp。
演示一下qsort函数的使用:
排序int 类型:
#include <stdio.h> #include<stdlib.h>//qsort的头文件 //qsort函数的使用者得实现一个比较函数 int int_cmp(const void* p1, const void* p2)//类型要和qsort第四个参数一致 { 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; }
排序结构体类型:写结构体的cmp函数(升序):
( 需求:结构体内容为 " 姓名 + 年龄 ",使用qsort,实现按年龄排序和按姓名排序 )
#include <stdio.h> #include <stdlib.h> #include <string.h> struct Stu { char name[20]; int age; }; int cmp_struct_age(const void* p1, const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } int cmp_struct_name(const void* p1, const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } void struct_sort() { // 使用qsort函数排序结构体数据 struct Stu s[3] = { {"zhangsan", 20}, {"lisi", 40}, {"wangwu", 30} }; int sz = sizeof(s) / sizeof(s[0]); // 按照年龄排序 //qsort(s, sz, sizeof(s[0]), cmp_struct_age); // 按照名字来排序 qsort(s, sz, sizeof(s[0]), cmp_struct_name); } int main() { struct_sort(); return 0; }
现在是升序,如果我想实现降序呢?
很简单,只需要把 p1 - p2 换为 p2 - p1 即可:
8.2回调函数模拟实现qsort(采用冒泡的方式):
#include <stdio.h> #include <string.h> struct Stu { char name[20]; char age; }; // 模仿qsort实现一个冒泡排序的通用算法 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++; } } void bubble_sort_q( 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 j = 0; for (j = 0; j < sz - 1 - i; j++) { // 两个元素比较 arr[i] arr[j+i] if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { //传参给用户传进来的cmp函数,但不知道比较的数据的类型 //把base强制类型转化为char*,一个字节一个字节交换 //把两个元素的地址传给cmp函数,升序,>0就交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_struct_age(const void* p1, const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } int cmp_struct_name(const void* p1, const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } void struct_sort() { //使用qsort排序结构体数据 struct Stu s[3] = { {"zhangsan", 20}, {"lisi", 40}, {"wangwu", 30} }; int sz = sizeof(s) / sizeof(s[0]); // 按照年龄排序 bubble_sort_q(s, sz, sizeof(s[0]), cmp_struct_age); // 按照名字排序 bubble_sort_q(s, sz, sizeof(s[0]), cmp_struct_name); } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int cmp_int(const void* p1, const void* p2) { // 升序: p1 - p2 return *(int*)p1 - *(int*)p2; } void int_sort() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); // 排序 bubble_sort_q(arr, sz, sizeof(arr[0]), cmp_int); // 打印 print_arr(arr, sz); } int main() { int_sort(); // struct_sort(); return 0; }