qsort函数的使用,以及如何用冒泡排序模拟qsort函数

本文探讨了回调函数的概念,介绍了冒泡排序及其在数组排序中的应用,重点讲解了C语言中的qsort函数,以及如何利用冒泡排序思想模拟qsort对不同数据类型的排序,包括结构体数组的排序方法。
摘要由CSDN通过智能技术生成

1.回调函数是什么?

回调函数就是通过一个函数指针调用函数。

void Add_Sub(int (* p)(int,int))
{
    int x,y;
    scanf("%d%d",&x,&y);

    return p(x,y);//也可以写成*p(int,int)

}
int Add(int x,int y)
{
   return x+y;
}

int Sub(int x,inty)
{
    return x-y;

}

int main() 
{
    
    Add_Sub(Add);

    Add_Sub(Sub);//因为Add和Sub两个函数类型与Add_Sub指向的函数类型相同,所以可以回调。

}

把函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其指向的函数,被调用的函数就叫做回调函数。

回调函数不是直接掉用函数如Add,Sub;而是通过把函数地址传给函数指针,再通过指针在Add_Sub里间接调用。

2.冒泡排序(从小到大排序)

所谓冒泡就是一串泡泡,越往上越大。将一串数据看作泡泡大的往上走,慢慢的直到所有泡泡都从上往下,大小减少。

如果有这样一个整形数组int arr[10]={3,5,2,1,9,0,6,8,7,4};我们如何利用冒泡排序,让他从小到大排列呢?

首先我们要找到最大的泡泡,从最下面开始一个个找,直到让最大的泡泡冒到最上面。

然后依次从下面重新开始开始找第二大的泡泡,让它冒到第二大的地方,直到把所有泡泡找完。 

如果是一串整形数字,我们要如何实现呢?
首先我们从第一个数字开始比较,它如果比后面的数字大,那就上去,让后面的数字下来,它如果比后面的数字小,那就不用动。当第一个数字比完,就开始比第二个数字,同样的操作,直到没有数字可比。 

上面数字1,3,2,4;首先从第一个开始,1小于3,所以不用动,然后开始比第二个数,第二个数是大于第三个数,所以交换位置,这样不停操作,始终让你比较的数比它左边的数都大。这样最后一个数就是最大的。找到最大的数后,就不用管最大的数,然后在其他数里,找最大的数,直到所有数找完。

如利用冒泡排序实现int arr[10]={2,1,0,3,7,6,4,5,9,8}的排序; 

int main()
{
     int arr[10]={2,1,0,3,7,6,4,5,9,8},swp;
   
     for(int i=o;i<9;i++)//因为有10个数,我们只需找到最大的9个数,剩下一个就是最小的,所以只需要找九次。
        {
           for(int j=0;j<9-i;j++)//当第一次找的时有10个数,需要比较9次,第二次,只剩9个数,只需比较8次,往后依次减少。
              {
                    
                   if(arr[j>arr[j+1]])//我们要找到大的数,因此将大的往后排。
                        {
                             swp=arr[j];
                             arr[j]=arr[j+1];
                             arr[j+1]=swp;
                            
                        }
              }

        }

     return 0;
}

3.qsort函数 

3.1qsort函数

冒泡排序只能排整形数组,而qsort可以排任意数据。

void qsort(void*base,size_t num,size_t size,int(*compare)(const void*p1,const void*p2));
使用qsort排序需要传4个参数:

1.void*base,//要排序的数组第一个元素地址。
2.size_t num,//指向数组中的元素个数。
3.size_t size,//指向数组中元素大小
4.int(*compare)(const void*p1,const void*p2),//函数指针,指向的函数是用来比较数组中的2个元素的。

 int(*compare)(const void*p1,const void*p2)使用方法:自己写一个函数传给它。

int cmp_int(const void*p1,const void*p2)
{

   return *(int*)p1-*(int*)p2;//因为p1和p2都是无类型指针,不能直接解引用,因此先强制转换类型。

}//这个函数返回一个整形,如果返回值大于0,则p1指向的数据大于p2指向的数据;等于0,相等;小于0,则p1指向的数据小于p2指向的数据。
int arr[6]={3,5,2,7,5,3};
qsort(arr,6,sizeof(arr[0]),cmp_int)//如果cmp_int的返回值大于0,则交换p1和p2指向的数据。等于和小于不交换。

如果cmp_int的返回值大于0,则交换p1和p2指向的数据;等于和小于不交换。因此cmp_int返回值是*(int*)p1-*(int*)p2,时排序是从小到大排序。如果返回值是*(int*)p2-*(int*)p1,排序为从小到大。

为什么返回值是*(int*)p2-*(int*)p1,排序为从小到大?
如果返回值大于0,此时说明p2是大于p1的,则交换数据,交换后p2在p1前,此时大的数据在前面,排序就是从大到小排。

3.2qsort函数的几种使用。
 
#include <stdio.h>
#include <string.h>
struct student
{
    char name[20];
    int age;
};

int cmp_struct_age(const void* p1, const void* p2)
{

    return ((struct student*)p1)->age - ((struct student*)p2)->age;
}//根据返回值,判断年龄大小
void test1(struct student* arr[])//根据年龄排序,这里的形参类型本质是一个结构体指针,用来接收结构体数组的首元素地址,也可以写成struct student*arr
{
    qsort(arr, 3, sizeof(arr[0]), cmp_struct_age);

}


int cmp_struct_name(const void* p1, const void* p2)//根据返回值,判断姓名字母顺序。
{

    return strcmp(((struct student*)p1)->name, ((struct student*)p2)->name);//strcmp函数从字符串首元素开始比较,直到找出字母ASCII不同的字母,如果前面字母ASCII大于后面字母,则函数返回一个大于0的整形,小于的话,返回一个小于0的整形,如果返回值等于0,则两个字符串相同。
}

void test2(struct student* arr)//根据姓名字母顺序排,原理字母的ASCII大小排序。
{
    qsort(arr,3,sizeof(arr[0]),cmp_struct_name);

}
int main()
{
    struct student arr[3] = { {"zhang",17},{"wang",16},{"li",19} };
    test2(arr);
    return 0;
}

4.利用冒泡排序模拟qsort函数

我们知道冒泡排序可以,排序整形,那么我们能否利用冒泡排序实现qsort的功能?

首先,整形数组可以直接比较,但是结构体无法直接比较。所以我们要实现一个函数可以比较各种类型数据。

#include <stdio.h>
#include <string.h>
struct student
{
    char name[20];
    int age;
};
int cmp_struct_name(const void* p1, const void* p2)//根据返回值,判断姓名字母顺序。
{

    return strcmp(((struct student*)p1)->name, ((struct student*)p2)->name);//strcmp函数从字符串首元素开始比较,直到找出字母ASCII不同的字母,如果前面字母ASCII大于后面字母,则函数返回一个大于0的整形,小于的话,返回一个小于0的整形,如果返回值等于0,则两个字符串相同。
}

void swp_(char* p1, char* p2, size_t size)
{
    char swp;
    for (int i = 0;(size_t) i < size; i++)//size为元素大小,从第一个字节开始交换size次,刚好可以完成两个数据的交换。
    {
        swp = p1[i];
        p1[i] = p2[i];
        p2[i] = swp;
    }

}
void My_qsort(void* base, size_t num, size_t size, int(*compare)(const void* p1, const void* p2))
{
    
    for (int j = 0; (size_t)j <num - 1; j++)
    {
        
        for (int i = 0; (size_t)i <num-1-j; i++)
        {
            if (compare((char*)base+i*size, (char*)base + size*(i+1)) > 0)
                swp_((char*)base+i*size, (char*)base +(i+1)*size, size);
            
        }
    }
}


int cmp_struct_age(const void* p1, const void* p2)
{

    return ((struct student*)p1)->age - ((struct student*)p2)->age;
}//根据返回值,判断年龄大小

int main()
{
    struct student arr[4] = { {"zhang",14},{"wang",13},{"liu",16},{"cheng",19} };
    My_qsort(arr, 4, sizeof(arr[0]), cmp_struct_name);
    return 0;
}

1.void My_qsort(void*base,size_t num,size_t size,int(*compare)(const void*p1,const void*p2))的形参设计原理
//void*base用来接收排序数组的首元素,因为需要可以排序任意类型,所以指针是void*型。元素数量num和元素大小size类型为size_t,他们都是整数。而最后一个函数指针用来接收比较函数,根据返回值正负,确定元素大小比较,因此比较函数类型为整形,它的形参为两个需要比较的数据的指针,因为不确定数据类型,所以用void*型,而*前加const是为了防止指针指向的数据被修改,它们只需要比较大小,不用进行修改。

2.冒泡算法程序原理:
   
 int arr[5]={2,3,1,7,6};
     num=5;
     int swp;
     for(int j=o;j<num-1;j++)
      { 
         for(int i=0;i<num-1-i;i++)
            {
                if(arr[i]>arr[i+1])
                   {
                        swp=arr[i];
                        arr[i]=arr[i+1];
                        arr[i+1]=swp;
                   } 
            }
      }

3.将冒泡排序改为可以排序各种类型的形式:我们形参指针是void*,无法解引用,因此我们首先要强制类型转换。为了可以使交换有通用型,我们应该将指针强制转换成char*。

如指针接收的是整形,

0123

一个格子代表一个字节,数字为数字下标。

为了使两个整形交换,我们可以使整形的每个字节依次交换。如下标为0和1的元素交换,分别让第1个和第5个格子内容交换,第2个和第6个交换,往后交换4次,两个整形就完成交换。

算法实现两个数据的交换:

void swp_(char*p1,char*p2,size_t size}p1和p2为两个交换数据的第一个字节的地址。
{
      char swp;
      for(int i=0;i<size;i++)//size为元素大小,从第一个字节开始交换size次,刚好可以完成两个数据的交换。
         {
              swp =p1[i];
              p1[i]=p2[i];
              p2[i]=swp;   
         }

}

同样我们有了各种数据交换的方法,我们可以把各种数据看成整体(比如我们要以结构体名字排序): 

#include <stdio.h>
#include <string.h>
struct student
{
    char name[20];
    int age;
};

int cmp_struct_name(const void* p1, const void* p2)//根据返回值,判断姓名字母顺序。
{

    return strcmp(((struct student*)p1)->name, ((struct student*)p2)->name);
    //strcmp函数从字符串首元素开始比较,直到找出字母ASCII不同的字母,如果前面字母ASCII大于后面字母,则函数返回一个大于0的整形,小于的话,返回一个小于0的整形,如果返回值等于0,则两个字符串相同。
}


void swp_(char* p1, char* p2, size_t size)//size为数据字节数量,数据有多少字节就交换多少次。
{
    char swp;
    for (int i = 0;(size_t) i < size; i++)
    //size为元素大小,从第一个字节开始交换size次,刚好可以完成两个数据的交换。
    {
        swp = p1[i];
        p1[i] = p2[i];
        p2[i] = swp;
    }

}


void My_qsort(void* base, size_t num, size_t size, int(*compare)(const void* p1, const void* p2))
{
    
    for (int j = 0; (size_t)j <num - 1; j++)
    {
        
        for (int i = 0; (size_t)i <num-1-j; i++)
        {
            if (compare((char*)base+i*size, (char*)base + size*(i+1)) > 0)
            //这里的比较函数根据返回值判断前面数据和后面数据的大小。i=0,时,(char*)base就是第一个元素地址;当i=1,(char*)base+i*size就是base地址往后走size*i个字节的地址,刚好把第一个元素跳过了,此时(char*)base+i*size就是第二个元素地址,同理i=2时,就是第3个元素地址。
                swp_((char*)base+i*size, (char*)base +(i+1)*size, size);
                //这是我们的交换函数,可以交换各种类型数据。
            
        }
    }
}



int main()
{
    struct student arr[4] = { {"zhang",14},{"wang",13},{"liu",16},{"cheng",19} };
    My_qsort(arr, 4, sizeof(arr[0]), cmp_struct_name);
    return 0;
}

这里我们之所以可以交换各种类型是因为,各种数据都是又字节构成的,我们利用char*指针每次调用一个字节空间的原理,可以将各种数据,一个一个字节的交换。 

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小张正在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值