一.函数指针数组
1.概念
上一卷我们提到了指针数组和函数指针的概念,那么类比这两个概念,思考一下什么是函数指针数组。其实顾名思义函数指针数组就是用来存放函数指针的数组,它的本质还是一个数组,记作——
int(*pfarr[])(int, int),怎么理解呢,就是用来存放int(* )(int, int)即函数指针类型的数组,所以它的变量名pfarr要先与[]结合。
看一个代码示例
可以看到我定义了四个函数计算加减乘除,然后定义一个函数指针数组,将四个函数的地址存放在这个数组中。
2.转移表
那么函数指针数组有什么用呢?就不得不提到C语言中的转移表(在 C 语言中,转移表通常是一个数组,其中每个元素都包含一个跳转地址。在程序运行时,根据某种条件或索引值,程序可以直接跳转到数组中相应的跳转地址,从而避免了通过一系列条件判断和跳转指令来实现相同的功能。),它属于数据结构中的知识,但是今天讲的函数指针数组会用到它。
根据转移表概念可知,它本质是一个存放地址的数组,通过地址可以实现跳转调用。那我们想一下,函数指针数组本质是不是也是一个存放地址的数组呢,只不过它存放的是函数地址,可以实现对函数的跳转调用。
根据上文我定义了四个函数计算加减乘除,那我们可不可以利用函数指针数组和转移表的知识设计一个计算器呢。下面是代码演示:
#include <stdio.h> void Menu() { printf("**************\n"); printf("1.加法**2.减法\n"); printf("3.乘法**4.除法\n"); printf("****0.退出****\n"); printf("**************\n"); } void Add(int x, int y) { printf("%d\n", x + y); } void Sub(int x, int y) { printf("%d\n", x - y); } void Mul(int x, int y) { printf("%d\n", x * y); } void Div(int x, int y) { printf("%d\n", x / y); } int main() { Menu(); int x, y; int input; do { printf("请输入两个数:"); scanf("%d %d", &x, &y); void(*pfarr[])(int, int) = { NULL,Add,Sub,Mul,Div }; printf("\n请选择运算方式:"); scanf("%d", &input); if (input == 1) pfarr[1](x, y); else if (input == 2) pfarr[2](x, y); else if (input == 3) pfarr[3](x, y); else if (input == 4) pfarr[4](x, y); else if (input == 0) printf("退出计算器\n"); else printf("输入错误,请重新输入\n"); } while (input); return 0; }
输出展示:
这就是函数指针数组的一个实际应用。
二.函数回调
1.概念
函数回调(Function Callback)是指在程序运行期间,将一个函数指针作为参数传递给另一个函数,并在需要的时候调用该函数的一种技术。
函数回调允许一个函数在不同的时间或不同的位置调用另一个函数,而无需在代码中显式地指定要调用的函数。通过将函数指针作为参数传递给另一个函数,调用者可以在需要时灵活地调用被传递的函数。
而回调函数就是这个被调用的函数。
代码示例
这就是一个函数回调的过程,而函数指针pf所访问的函数被称为回调函数。
三.qsort函数(回调函数的应用)
为什么要把这个单独列出来呢,因为太important了
1.qsort函数的原理和作用
qsort函数底层原理是快速排序,作用是对一个任何数据类型的乱序数组,按照你自己的排序标准进行排序。
2.函数原型和参数类型
3.qsort函数的应用
(1).对整型数组排序
升序代码示例
降序代码示例
(2).对结构体型数组排序
假设有一个结构体包含姓名年龄
按年龄排序代码示例
按姓名首字母排序代码示例(注意字符串不能直接比较大小,而是用strcmp函数)
四.leecode算法题(qsort函数的应用)(题解+思路)
五.模拟qsort函数(底层原理冒泡排序)
先演示代码和运行结果,再进行讲解
#include <stdio.h> void Swap(char* p1, char* p2, size_t size) { for (int i = 0; i < size; i++) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } void bsort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*)) { for (int i = 0; i < num - 1; i++) { for (int j = 0; j < num - 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 cmp_arr(const void* p1, const void* p2) { return *(int*)p1 - *(int*)p2; } void print_arr(int* arr, size_t sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int main() { int arr[5] = { 2,3,4,9,6 }; int sz = sizeof(arr) / sizeof(arr[0]); bsort(arr, sz, sizeof(arr[0]), cmp_arr); print_arr(arr, sz); return 0; }
#include <stdio.h> #include <string.h> void Swap(char* p1, char* p2, size_t size) { for (int i = 0; i < size; i++) { char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } void bsort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*)) { for (int i = 0; i < num - 1; i++) { for (int j = 0; j < num - 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); } } } } struct Stu { char name[20]; int age; }; int cmp_arr_by_name(const void* p1, const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } void print_arr(struct Stu* arr, size_t sz) { for (int i = 0; i < sz; i++) { printf("%s,%d\n", arr[i].name, arr[i].age); } } int main() { struct Stu arr[] = { {"zhangsan",17},{"lisi",16},{"wangwu",18} }; int sz = sizeof(arr) / sizeof(arr[0]); bsort(arr, sz, sizeof(arr[0]), cmp_arr_by_name); print_arr(arr, sz); return 0; }
可以看到我自定义的bsort函数功能与C语言标准库函数qsort函数功能基本一致。
那么接下来就讲一下bsort函数的运行原理:
它是基于冒泡排序实现的,所以我就讲解一下它与冒泡排序不同的部分(我后面会单独出一章讲解C语言基本排序算法)
1.if条件语句中条件判断不一样,因为基本的冒泡排序算法大多情况是用来排序整型数据的,可以单纯的用大于号小于号去判断,但bsort函数不仅只用来排序整型数据,所以它的if判断条件要通过你传入的比较函数(也就是你比较两个数据的标准)的返回值去判断。而这个函数需要传入两个元素(来自你想要排序的数组)的地址,这是我们要注意,你的数组首元素地址使用void*来接受的,根据我们第一卷所讲,void*可以接受任何类型的指针(地址),但它不能用来进行指针前移后移来访问整个数组,所以我们需要把数组首元素地址强制转换为char*类型,每次移动一个字节,当你想访问整型数组中的元素,只需要乘上一个元素所占的字节即可,这就是为什么我们要传入每个元素所占的字节大小。
2.交换,因为数组类型不确定,导致数组中的元素所占大小不确定,所以不能直接通过下标和空变量的方式进行交换。我们在定义一个交换元素,把每个元素的首地址传入,然后再一个字节一个字节地进行交换即可。