前言:上篇文章的末尾我们使用了转移表来解决代码冗余的问题,那我们还有没有什么办法解决代码冗余呢?有的这就是接下来要说的回调函数。
文章目录
一,回调函数
先来回顾一下上篇文章末尾的内容,写一个模拟计算的代码:
c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do{
printf("*************************\n");
printf("*******1:add 2:sub ******\n");
printf("******* 0:exit ******\n");
printf("*************************\n");
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 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
我们可以看到代码是非常冗余的,想要解决冗余的问题方法一在我们上一篇文章指针4(转移表)。方法二我们使用回调函数来实现。
那什么是回调函数呢?
回调函数说的是,如果你把函数的指针(地址)作为参数传递给另外一个函数时,当这个指针被用来调用其所指向的函数时,被掉用的函数就称为回调函数。
我们给出具体的代码:
c
#include<stdio.h>
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
void clac(int (*pf)(int, int))
{
int x = 0, y = 0;
printf("请输入两个数:");
scanf("%d %d", &x, &y);
int ret = (*pf)(x, y);
printf("计算结果为:%d\n", ret);
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do {
printf("*************************\n");
printf("*******1:add 2:sub ******\n");
printf("******* 0:exit ******\n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
clac(add);
break;
case 2:
clac(sub);
break;
case 0:
printf("退出程序\n");
}
} while (input);
}
通过上面的两段代码对比我们有两个改进之处:
一,将case语句中那些重复性的语句封装到了一个新建的函数中。
二,函数调用发生了变化,从原来的直接调用add函数sub函数变成了先调用clac函数再去调用add函数sub函数。这种函数称之为回调函数。
看明白这两点相信已经不难理解回调函数了,但是还有一点需要注意:回调函数不是由该函数的实现方直接调用,而是在特定的事件或条 件发生时由另外的一方调用的,用于对该事件或条件进行响应。
下面画一张图让你更好的理解:
二,qsort实现快速排序
在前面的文章中我们介绍了冒泡排序,这次我们来介绍一些qsort。q即quick快速的,sort是排序的意思。所以qsort就是快速排序俗称快排。
那怎么使用qsort来实现快速排序呢?首先我们先得了解一些qsort:
![
c
void qsort(
void *base,
size_t num,
size_t width,
int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
通过专业文献对qsort的描述我们知道qsort
共有4个参数第一个参数
void *base
是代表要传入的目标数组名即所需要排序的数组名。size_t num
是代表要传入的数组内有多少个元素。size_t width
是代表要传入的数组内元素的大小。int (*compare )(const void *elem1, const void *elem2 )
是一个函数指针,函数指针的两个参数 类型都为void*
也就是说在第四个参数中我们要传入一个函数且这个函数具有比较elem1
和elem2
这两个元素大小的功能。
我们写一个排序整型数组的代码来给大家举例,让大家能更好的了解qsort的使用方法:
c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
/*
void qsort
(void* base, //第一个参数为需要比较的数组的首地址
size_t num, //第二个参数为该数组的元素个数
size_t width, //第三个参数为该数组每个元素的大小
int(__cdecl* compare)(const void* elem1, const void* elem2));//第四个参数为函数指针,及传入一个能比较数组内部元素大小的函数的地址
*/
//实现比较两个函数大小的函数
int comp_int(const void* elem1, const void* elem2)
{
return *(int*)elem1 - *(int*)elem2;//void*的指针不能直接使用 要强转后再解引用才能使用
//elem1>elem2 返回大于零的数
//elem==elem2 返回0
//elem1<elem2 返回小于零的数
}
//打印整型数组
void print(int* P_arr, int sz)//一维数组传参传过去的是数组的首地址
{
int i = 0;
for (i = 0;i < sz;i++)
{
printf("%d ", *(P_arr + i));
}
}
//排整型的数组
int test1()
{
int arr1[10] = { 1,4,3,2,6,5,8,7,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
qsort(arr1, sz, sizeof(arr1[0]), comp_int);//
print(arr1, sz);
}
int main()
{
//写一个test1来测试qsort排序整型数组
test1();
return 0;
}
我们来分析代码的含义:

1,void*指针
上面诸多地方提到了void*指针,下面我们给出解释:
在指针类型中有⼀种特殊的类型是
void*
类型的,可以理解为无具体类型的指针(或者叫泛型指 针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void*
类型的指针不能直接进行指针的±整数和解引用的运算。
c
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* pc = &a;
return 0;
}
在上⾯的代码中,将⼀个int类型的变量的地址赋值给⼀个char
*类型的指针变量。编译器会警告,是因为类型不兼容。而使用void*
类型就不会有这样的问题。
我们再看看void*
的指针接收别的类型的指针:
c
#include <stdio.h>
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
*pa = 10;
*pc = 0;
return 0;
}

这里我们可以看到,
void*
类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。 那么void*
类型的指针到底有什么用呢? ⼀般void*
类型的指针是使⽤在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。
理解了void*
类型,以及qsort如何使用了以后,我们就可以试着去排序一些其他类型的数据了。代码内容在下载文件处取噢。
熟练了使用qosrt
来排序各种类型的数据后接下来我们就来模仿着造一个qosrt
函数。
三,qsort的模拟实现
我们之前学过了冒泡排序并且知道冒泡排序有一个缺点就是只能排序固定类型的数据,而qsortt
能排序任意类型的数据那我们能不能使用冒泡排序的方式来模拟实现qsort
快速排序呢?答案是肯定的。
我们先写一个冒泡排序,然后再看看哪些地方需要修改:
c
#include<stdio.h>
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]=tmp;
}
}
}
}
void print_arr(int arr[],int sz)
{
int i=0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void test()
{
int arr[10]={1,3,5,7,9,2,4,6,8,0};
int sz=sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz);
print_arr(arr,sz);
}
int main()
{
test();
return 0;
}
那如何知道那些地方需要修改呢?通过与qsort的对比我们可以得出以下几个地方需要改造:
那我们就先来改造参数部分:void bubble_sort(void*base,int sz,int width,int (*cmp)(const void*e1,const void*e2))
改造之后我们发现多了两个参数,一个是 width
代表单个元素大小;一个是 int (*cmp)(const void*e1,const void*e2)
这个函数指针。
其中一个修改的地方是从原来接受
int类型
的数组改为了void*类型
原因是方便接受任意类型的数组。
其次我们来修改比较部分,上面分析了使用一个函数指针指向一个函数然后通过函数的返回值来得到e1和e2这两个元素的大小关系,这也是为什么函数指针的返回类型是int的原因。
c
int comp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
比较函数我们依然可以这样写但是要注意一个问题,现在是我们模仿qsort的逻辑,所以在bubble_sort这个函数里边就会涉及一个传参的问题,下面我们着重来探究传参问题:
c
if(arr[j]>arr[j+1])------------------>if(comp((char*)base+j*width,(chr*)base+(j+1)*width)>0)
//注意base就是arr
解决了参数问题,解决了传参问题接下来就是交换数据的问题了,如果不满足我们的升序要求则需要交换那怎么交换呢?通过上面的比较得出需要交换那我们既然已经得到了要交换的两个元素的地址,就不妨再封装一个函数来专门去交换元素的内容。
c
#include<stdio.h>
#include<stdlib.h>
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++;
}
}
解决完交换的问题我们就可以给出所有的代码啦:
c
int bubble_cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
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++;
}
}
int bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0;i < sz-1;i++)
{
int j = 0;
for (j = 0;j < sz - 1 - i;j++)
{
if (bubble_cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
{
swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
void test2()
{
int arr[10] = { 1,3,5,7,9,2,4,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), bubble_cmp);
print(arr, sz);
}
int main()
{
test2();//使用冒泡排序来模拟qsort
return 0;
}
好了以上就是本章的全部内容啦!
感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!