1 回调函数是什么?
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在深入理解指针(3)中我们写的计算机的实现的代码中,switch语句中的代码是重复出现的,虽然其中的执行计算的逻辑是有区别的,但是输入输出操作是冗余的,有没有办法简化一些呢?
因为switch语句2中的代码,只有调用函数的逻辑是有差异的,我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向沙漠函数就调用什么函数,这里其实使用的就是回调函数的功能。
使用回调函数改造前
cpp#include<stdio.h> 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 menu() { printf("-------------------------\n"); printf("------- 计算器 -------\n"); printf("-- 1.add 2.sub --\n"); printf("-- 3.mul 4.di ---\n"); printf("-------- 0.exit -------\n"); printf("-------------------------\n"); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Add(x, y); printf("%d\n", ret); break; case 2: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Sub(x, y); printf("%d\n", ret); break; case 3: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Mul(x, y); printf("%d\n", ret); break; case 4: printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = Div(x, y); printf("%d\n", ret); break; case 0: printf("退出计算器\n"); break; default: printf("输入错误,请重新输入!\n"); break; } } while (input); return 0; }
使用回调函数改造后
cpp#include<stdio.h> 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 ret = 0; printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = pf(x, y); printf("%d\n", ret); } void menu() { printf("-------------------------\n"); printf("------- 计算器 -------\n"); printf("-- 1.add 2.sub --\n"); printf("-- 3.mul 4.div --\n"); printf("-------- 0.exit -------\n"); printf("-------------------------\n"); } 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使用举例
qsort 是库函数,这个函数可以完成任意类型数据的排序。使用qsort 要包含头文件 stdlib.h。
cpp
void qsort (void* base, //base指向了要排序的数组的第一个元素
size_t num, //base指向的数组中的元素个数(待排序的数组的元素的个数)
size_t size, //base指向的数组中元素的大小(单位是字节)
int (*compar)(const void* p1,const void* p2));
//函数指针 - 指针指向的函数是用来比较数组中的2个元素的
//当p1指向的元素小于p2指向的元素,返回值<0
//当p1指向的元素等于p2指向的元素,返回值=0
//当p1指向的元素大于p2指向的元素,返回值>0
2.1 使用qsort函数排序整型数据
cpp
#include<stdio.h>
#include<stdlib.h>
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)
{
return *(int*)p1 - *(int*)p2; //升序
//return *(int*)p2 - *(int*)p1; //降序
//void*的指针是不能直接解引用的,我们知道要对比的数据类型是int型
//所以直接进行强制类型转换后再解引用
}
void test1()
{
int arr[10] = { 1,5,9,6,3,7,8,4,2 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
//将一个整型数组排序为升序
test1();
return 0;
}
2.2 使用qsort排序结构数据
2.2.1 按照年龄来比较
cpp
#include<stdlib.h>
struct Stu
{
char name[20]; //名字
int age; //年龄
};
//按年龄来排序
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; //正序
//return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age;//逆序
//void*的指针是不能直接解引用的
//我们知道要对比的数据类型是结构体类型
//所以直接进行强制类型转换后再通过->访问结构体成员
}
void test2()
{
struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main()
{
test2();
return 0;
}
2.2.2 按照名字来比较
两个字符串不能直接使用 < 、> 、== 等进行比较,而是需要用到库函数strcmp ,用于比较2个字符串,其实就是按照对应位置上字符的ASCII码值的大小来比较的。使用 strcmp 要包含头文件 string.h 。
cpp
abcdef
abz
//这2个字符串前两个字符相等,但是字符串2中的z的ASCII码值比字符串1中的c大
//所以判定字符串2比字符串1大
cpp
#include<stdlib.h>
#include<string.h>
struct Stu
{
char name[20]; //名字
int age; //年龄
};
//按名字来排序
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); //正序
//return strcmp(((struct Stu*)p2)->name, ((struct Stu*)p1)->name); //逆序
//void*的指针是不能直接解引用的
//我们知道要对比的数据类型是结构体类型
//所以直接进行强制类型转换后再通过->访问结构体成员
//再用strcmp比较字符串,strcmp的返回值逻辑与要求的返回值逻辑一致
}
void test3()
{
struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
test3();
return 0;
}
3 qsort函数的模拟实现
使用回调函数模拟实现qsort(在冒泡排序的基础上进行修改)。
冒泡排序如下:
cpp
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 - 1 - i; j++)
{
if (arr[j] > arr[j + 1]) //比较
{
int tmp = arr[j]; //
arr[j] = arr[j + 1]; //交换
arr[j + 1] = tmp; //
}
}
}
}
我们要对冒泡排序的3个地方进行改造:
- 数据比较的代码需要改造,需要用到回调函数
- 交换的代码需要改造,需要适配每一种数据类型的交换
- 函数参数需要改造,要排序的数据类型可能是任意的,还要加上一些排序时需要的参数。参数的修改可以参考qsort函数的参数
qsort函数:
cpp
void qsort (void* base, //base指向了要排序的数组的第一个元素
size_t num, //base指向的数组中的元素个数(待排序的数组的元素的个数)
size_t size, //base指向的数组中元素的大小(单位是字节)
int (*compar)(const void* p1,const void* p2));
//函数指针 - 指针指向的函数是用来比较数组中的2个元素的
//当p1指向的元素小于p2指向的元素,返回值<0
//当p1指向的元素等于p2指向的元素,返回值=0
//当p1指向的元素大于p2指向的元素,返回值>0
3.1 函数的参数
cpp
void Bubble_Sort2(void* base,
size_t sz,
size_t width,
int(*cmp)(const void* p1, const void* p2));
- base:因为qsort可能排序任意类型的数据,为了能够接收任意可能的指针类型,我们就设计成void*
- sz:数组的元素个数是未知的,元素个数不可能会是负数,所以使用size_t类型
- width:元素的类型是未知的,一个元素到底有多长是不清楚的,元素的长度不可能是负数,所以依然使用size_t类型
- cmp:针对不同类型的比较方法是有所差异的,所以需要提供一个比较函数,需要比较的数据的类型是未知的,并且我们不想要去修改数据,函数的参数就使用const void*,我们需要一个明确的比较结果,返回值使用int
3.2 对比的条件
cpp
if(cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
我们之前的《C语言》深入理解指针(1)-CSDN博客中的介绍到,char*类型的指针变量+1就是跳过1个字节,我们知道一个元素的宽度是width个字节,那么我们就可以使用char*指针+3*width来找到第3个元素,第四个元素就可以使用char*指针+4*width找到。比较出来的值大于0,我们就交换。
3.3 交换的过程
cpp
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++;
}
}
交换,我们需要知道需要交换的元素的地址,以及元素的宽度。
一个元素的宽度为width字节,那么我们就将这一个元素分为width份,每一份进行交换,交换width次,就可以实现交换的这一过程。假如说元素的类型是int型,那么它的宽度就是4个字节,就给它拆分成4份,对应的每一份进行交换,交换四次即可完成交换。
一次整型数据的交换
3.4 模拟实现的qsort
cpp
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void Bubble_Sort2(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 - 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);
}
}
}
}
3.5 模拟实现出的qsort排序整型数据
cpp
#include<stdio.h>
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void Bubble_Sort2(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 - 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_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test1()
{
int arr[] = { 3,5,7,9,6,8,4,1,2,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
Bubble_Sort2(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
return 0;
}

3.6 模拟实现出的qsort排序结构数据
3.6.1 按年龄排序
cpp
#include<stdio.h>
struct Stu
{
char name[20]; //名字
int age; //年龄
};
//按年龄来排序
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void Bubble_Sort2(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 - 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 test2()
{
struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_Sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
}
int main()
{
test2();
return 0;
}
输出结果:

3.6.2 按名字排序
cpp
#include<stdio.h>
struct Stu
{
char name[20]; //名字
int age; //年龄
};
//按名字来排序
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void Swap(char* buf1, char* buf2, size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void Bubble_Sort2(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 - 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 test3()
{
struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_Sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s %d\n", arr[i].name, arr[i].age);
}
}
int main()
{
test3();
return 0;
}
输出结果:
