C语言指针(4):qsort函数

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;
}

拙作一篇,望诸位同道不吝斧正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值