C语言指针(4):qsort函数
1.回调函数
2.qsort函数
3.模拟qsort函数
1.回调函数
回调函数是指通过函数指针调用的函数。回调函数的功能:
- 把重复的代码实现为函数。
- 同时,这个函数又能完成不同任务。
上期模拟计算器的代码也可以用回调函数来写。
#include<stdio.h>
void menu()
{
printf("**************************\n");
printf("***** 1.加法 2.减法*****\n");
printf("***** 3.乘法 4.除法*****\n");
printf("***** 0. 退出 *****\n");
printf("**************************\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 (*p)(int,int))
{
int x=0,y=0,z=0;
printf("请输入两个操作数:");
scanf("%d%d",&x,&y);
z=p(int x,int y);
printf("%d\n",z);
}
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;
}
定义一个返回类型为void类型的函数calc,其参数是一个函数指针。当我们想在switch语句中调用Add、Sub、Mul、Div函数时,只要把函数名作为参数输入calc函数中,即可完成调用。
2.qsort函数
qsort函数,又叫快速排序函数,它的底层逻辑是冒泡排序,需包含头文件 stdlib.h,函数原型如下:
void qsort(void* base,size_t num,size_t size,int (*compar)(const void*,const void*));
- void* base
参数base是void* 类型的指针,指向待排序数组的首元素。 - size_t num
size_t 是一种类型,num 表示待排序数组的元素个数。 - size_t size
size表示待排序数组中每个元素的大小,即多少字节。 - int (* compar)(const void*,const void*)
这是一个函数指针,函数指针变量名是 compar,指向的函数返回类型是 int ,有两个参数const void* 。这个函数指针指向的是两个元素的比较函数,我们通过这个函数指针来确定排序规则,即是升序还是降序 。
两个元素的比较函数
int compar(const void* p1,const void* p2)
{
if( *(type*)p1 < *(type*)p2 )
return -1;
if( *(type*)p1 = *(type*)p2 )
return 0;
if( *(type*)p1 > *(type*)p2 )
return 1;
}
- 指针p1指向一个元素,p2指向p1指向的元素的下一个元素。由于p1、p2都是void* 类型的指针,所以需要强转为需要的类型。
- 返回值是 -1、0、1,也可以是大于返回正值,等于返回0,小于返回负值。
- 默认是升序,要改成降序就把参数p1、p2调换位置。
函数也可以简写为:
int compar(const void* p1,const void* p2)
{
return *(type*)p1 - *(type*)p2 ; //作差
}
qsort函数使用演示
将数组arr[ ]={3,1,7,8,5,2,4,9,0}升序排列。
#include<stdio.h>
#include<stdlib.h>
void print_arr(int arr[],int sz) //打印排序后的数组
{
for(int i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
int cmp_int(const void* p1,const void* p2)
{
return *(int*)p1 - *(int*)p2 ;
}
void test()
{
int arr[]={3,1,7,8,5,2,4,9,0};
int sz=sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
print_arr(arr,sz);
}
int main()
{
test();
return 0;
}
排序成功,将p1、p2位置对调则是降序。
结构体qsort排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_name(const void* p1,const void* p2) //用姓名排序
{
return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
void test()
{
struct Stu arr[3]={{"zhangsan",20},{"lisi",35},{"wangwu",18}};
int sz=sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_name);
for(int i=0;i<sz;i++)
{
printf("%s,%d\n",arr[i].name,arr[i].age);
}
}
int main()
{
test();
return 0;
}
3.模拟qsort函数
qsort函数的排序功能强大且方便,但在一些考试和竞赛中,为了提升难度,库函数往往会被禁用,qsort就无法使用了,所以,我们学习模拟qsort函数以应对上述情况,并进一步理解qsort函数。
qsort函数的底层逻辑是冒泡排序,我们只要将冒泡排序的参数、交换条件、交换方法进行改变。现在开始模仿qsort函数来进行模拟。
void bubble_sort(void* base,size_t sz,size_t width,int (*cmp)(const void* p1,const void*p2))
{
for(int i=0;i<sz-1;i++)
{
for(int j=0;j<sz-1-i;j++)
{
if(交换条件)
{
交换方法;
}
}
}
}
bubble_sort函数的参数与qsort函数一样,其中的 width 表示待排序数组中的每个元素的宽度,即是所占字节的多少。
接下来要解决交换条件、交换方法,就要在不知道待排序数组的元素类型的情况下,用指针表示arr[0]、arr[1]、arr[2]、arr[3] …
我们已经知道了指向首元素的指针base,但base是void* 类型,不能对base进行解引用等操作,而且base加减整数的时候不知道跳过几个字节,所以我们把base强转为char* 类型,这样base +/- n 时就会跳过n个字节,那么base就可以如下表示整型数组arr中的元素。
我们可以看出规律:
我们先默认升序,参数中的函数指针指向的函数返回值是int类型,所以交换条件应该是:
if(cmp((char*)base+j*width,(char*)base+(j+1)*width) > 0)
我们写一个Swap函数来作为交换方法。在冒泡排序中我们是用中间变量tmp,来交换arr[j]和arr[j+i],所以,在Swap函数中需要两个参数,即指向arr[j]和arr[j+1]的指针,这时候就应该是char*类型的指针。因为不知道待排序数组的元素是什么类型,所以还需要参数width,表示每个元素占width个字节。
Swap(char* buf1,char* buf2,size_t width)
为了方便理解,我们假设待排序数组为整型数组,buf1、buf2的类型是char*,待排序数组的元素的类型是int,交换arr[i]和arr[j+1]的内容时,是每个字节每个字节地交换,直到交换完4个字节就交换完了arr[j]和arr[j+1]。
Swap(char* buf1,char* buf2,size_t width)
{
for(int i=0;i<width;i++)
{
char tmp=*buf1;
*buf1=*buf2;
*buf2=tmp;
buf1++;
buf2++;
}
}
所以,以整型数组arr[]={5,1,8,7,3,6,0,9,2,1}为例,模拟qsort函数的代码为:
#include<stdio.h>
Swap(char* buf1,char* buf2,size_t width) //交换方法
{
for(int i=0;i<width;i++)
{
char tmp=*buf1;
*buf1=*buf2;
*buf2=tmp;
buf1++;
buf2++;
}
}
int cmp(const void* p1,const void* p2) //排序规则
{
return *(int*)p1 - *(int*)p2;
}
void bubble_sort(void* base,size_t sz,size_t width,int (*cmp)(const void* p1,const void*p2))
{
for(int i=0;i<sz-1;i++) //模拟qsort
{
for(int 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) //打印排序后的数组
{
for(int i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
void test()
{
int arr[]={5,1,8,7,3,6,0,9,2,1};
int sz=sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp);
print_arr(arr,sz);
}
int main()
{
test();
return 0;
}
拙作一篇,望诸位同道不吝斧正。