【c语言】深入理解指针3——回调函数
一、回调函数
回调函数:通过函数指针调用的函数.
当把一个函数的地址传递给另一个函数,通过该地址去调用其指向的函数,那么这个被调用的函数就是回调函数.
示例:
在【深入理解指针2】中结尾写了用函数指针实现计算器的功能,如下:
void menu()
{printf("1.add 2. sub \n");printf("3.mul 4. div \n");printf("0.exit \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 a, b;printf("请输入两个参数:");scanf_s("%d %d", &a, &b);int ret = pf(a, b);printf("%d \n", ret);
}
int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;do {menu();printf("请选择:");scanf_s("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);case 4:calc(Div);break;case 0:printf("退出\n");break;default:printf("请重新输入:");break;}} while (input);return 0;
}
这里加入想实现加法函数的功能,并没有直接在主函数中直接调用加法函数Add,而是通过另一个函数calc中函数指针变量来接受Add函数的地址,然后通过函数指针变量再去调用Add函数,因此,这里用函数指针去调用的那个函数就是回调函数,即Add、Sub、Mul、Div是回调函数.
二、qsort函数使用
官网对qsort函数的说明:
void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));
qsort函数有四个参数:
- void* base : base是一个指针,指向待排序数组的首元素地址
- size_t num : num是指待排序数组中元素的个数
- size_t size :size是指待排序数组中每个元素的大小
- compar : 函数指针,指向两个元素的比较函数,此函数需要自己实现
假设两元素为p1,p2,若p1<p2,则返回小于0的值;若p1=p2,则返回等于0的值;若p1>p2,则返回大于0的值.
排序整型数据
#include <stdlib.h>
#include<stdio.h>
int cmp(void* e1, void* e2)//比较函数
{return *(int*)e1 - *(int*)e2;//void* 不能解引用,要强制类型转换//此方式默认升序排列,若想实现降序,可用e2-e1,将逻辑改反
}
void test()
{int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]),cmp );int i = 0;for (i = 0;i < sz;i++){printf("%d ", arr[i]);}}
int main()
{test();return 0;
}
排序结构体数据
#include <string.h>
struct stu
{char name[20];int age;
};
int cmp_name(const void* e1,const void* e2)
{return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void test()
{struct stu arr[3] = { {"zhangsan",18},{"lishi",25},{"wangwu",22} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_name);}
int main()
{test();return 0;
}
其中,strcmp函数比较时,比较的是对应字符的ASCII码值,并不比较字符串长度,比如:
- “abc"和"abd” : a和a的ASCII码值相等,比较b和b,也相等,再比较c和d,d的ASCII码值大,则"abc"小于"abd"
- “aef"和"abcde” : a的ASCII码值相同,e的ASCII码值大于b,则不再进行比较,认为"aef"大于"abcde".
可以看出 ’ l ’ < ’ w ’ < ’ z ',因此 " lishi " 排第一
三、qsort函数模拟实现
用冒泡排序实现qsort函数
冒泡排序算法:
void bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0;i < sz-1;i++)//趟数{int j = 0;for (j = 0;j < sz - i - 1;j++)//交换次数{if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = arr[j];}}}
}
我们想排序的数据类型如果不是整型,是结构体类型、字符类型等,在bubble_sort函数参数部分就不可以用整型数组来接收,因此,参考qsort函数的定义:
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2));
- 冒泡排序首先要知道要比较的数据的首元素地址,但是不知道具体是什么类型,因此用void* base来接收
- 需要知道数据的个数,类型为无符号的整型,用sz接收
- 在我们拿到首元素地址之后,向后找元素进行比较,需要知道每个元素的内存大小,不同类型元素的大小不同,但都是整型,因此用width接收,与qsort函数的size相似
- 最后需要传入对元素进行比较的函数的地址,类型为函数指针,因为并不知道要比较的元素类型,因此用void* 类型的指针进行接收,进行比较时根据不同的类型进行强制类型转换,然后用返回值来判断他们的大小关系,因此比较函数返回类型为 int
然后分析冒泡排序函数内部:其中排序的趟数和元素交换次数不需要变
- 在元素进行比较时,如果元素类型不是整型,如结构体类型、字符类型等就不可用大于小于这种方式来比较,因此应该用函数指针cmp去调用 用来比较的函数进行元素比大小
- 在元素交换部分,交换的元素类型不能确定,肯定不能使用 int 类型,因此将其封装成函数,在其内部进行操作,具体思路如下:
拿到元素地址的目的是为了进行元素的交换. 首先,base是无符号指针,应该先对其进行强制类型转换成char*类型,那么base+4就拿到下一个元素的地址,base+8拿到第三个元素的地址,以此类推…
每个元素的内存大小为width,那么第一个元素地址为(char*) base + j * width
, 第二个元素地址为(char*) base + (j+1) * width
,拿到两个元素地址后进行交换,交换完成后 j++,这样继续取得第二个和第三个元素的地址,继续进行操作
有人会问,为什么base要强转成char*
类型,转换成int*
不行吗?
我们不知道要排序的元素类型是什么,可能是整型,可能是结构体类型,可能是字符类型等等,每个元素的大小可能是1、4、7、9等,那么在解引用,进行交换元素的时候,由于整型一次只能交换4个字节的内存,在交换7、9字节的内存时候就不行了,char*类型进行解引用是char类型,每次只交换一个字节,这样无论元素是什么类型都可以交换完成
因此,由于排序元素的类型不确定性,选择一个字节一个字节进行交换的方式完成排序,进而选择(char*)base
具体实现代码如下:
//比较函数由自己编写,只有使用者知道要排序什么类型的元素
int cmp_int(const void* e1, const void* e2)
{return strcmp((char*)e1 , (char*)e2);
}
void Swap(char* buf1, char* buf2,size_t width)
{char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;
}
void bubble_sort(void* base, size_t sz, size_t 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 - i - 1;j++){if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}int main()
{char arr[] = "aksjdu";int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for (i = 0;i < sz;i++){printf("%c ", arr[i]);}return 0;
}