冒泡排序模拟C库排序函数qsort的实现

前言

这篇文章来分享一下一个C语言库函数里面的一个函数,叫qsort,它是一个用来进行排序的函数,

它的功能强大在于能对不同类型的数据进行排序,那么它究竟是怎么用?怎么实现呢?

本文将用简单的逻辑帮助你理解,它为什么能实现不同类型数据的排序。

qsort介绍

首先来看一下这个函数,他长这样:

来简单介绍一下它的一些基本信息:

函数名字是qsort,其实就是快速排序(quick sort)的简写

它包含在<stdlib.h>中

介绍一下参数

void* base

size_t num

size_t width

int(__cdecl*compare)(constvoid*elem1,constvoid*elem2)

这是个参数是一个指针,它接受数组的起始地址

这个参数是数组元素个数,size_t是unsigned int 的重命名

这个参数是数组每一个元素的大小,单位是字节

这个参数的名字是 compare,它是一个函数指针


void *指针

(举例介绍void *指针的作用)

我们知道,指针有类型,指针的类型决定指针指向变量的类型,决定了指针移动的步长。

int main()
{
    int a = 10;
    int* p = &a;//整型指针接收整型变量的地址
    char b= 'A';
    char* pp = &b;//字符型指针接收字符变量的地址
    *p = 200;//解引用,修改变量的值
    *pp = 'C';//解引用,修改变量的值
    int arr[10] = { 0 };
    int* ppa = arr;
    ppa++;//指针可以进行移动
    return 0;
}

上面列举了一般的指针,指针的操作有 解引用移动

但是,很遗憾,对于void *的指针,他不能进行这两个操作,但是它能存放不同类型的地址

明显,进行解引用移动错误的,但我们关键使用的是它能接受不同类型的地址


函数指针

刚刚在qsort的参数里面也提到了函数指针,这个也要说明一下。

平时,我们知道,变量有地址,数组有地址,那么函数是否也有地址函数名是什么

答案是肯定的,函数有地址函数名就是函数的地址。

int Add(int x, int y)
{
    return x + y;
}
int main()
{
    int (*pf)(int, int) = Add;//函数指针的定义
    int ret = pf(3, 5);//函数指针的使用
    printf("%d ", ret);
}
 int (*pf)(int, int) = Add;//函数指针的定义

分析

这里pf是指针的名字,后面括号里面是指向函数的参数类型,前面的int 是指向函数的返回值

这样写是很合理的,因为你想,函数的关键就是:参数列表、返回值、函数名,这种定义的方法把这三个全都描述出来了!!

补充

  • Add和&Add得到的都是函数的地址,二者没有区别

  • 指针调用函数,可以加上解引用运算符,也可以不加,所以这里函数名等价于指针名。

即,我们可以写出这样的代码:

int Add(int x, int y)
{
    return x + y;
}
int main()
{
    //int (*pf)(int, int) = Add;//函数指针的定义
    int (*pf)(int, int) = &Add;
    //int ret = pf(3, 5);//函数指针的使用
    int ret = (*pf)(3, 5);
    printf("%d ", ret);
}

我们再看回来刚刚参数列表的参数:

int( __cdecl* compare)(const void*elem1,const void*elem2);

compare是这个指针的名字,指向的函数的参数是两个const void *的指针,返回值是int

至于__cdecl,这个不用管,这是函数调用约定,不影响我们分析代码。


上面是我们补充的一些知识点,我们主要的任务是模拟qsort能实现不同数据类型排序的功能,我们采用的是一种常见的排序,冒泡排序来模拟这个功能。

冒泡排序

接下来回忆一下冒泡排序

  • 冒泡排序我们分析的关键是趟数和次数的关系

  • 两两进行比较,每一趟确定一个数据的正确位置

上面分析了冒泡排序的过程,代码如下:

void Swap(int* a, int* b)
{
    int tmep = *a;
    *a = *b;
    *b = tmep;
}
void Bubble_sort(int* arr, int sz)
{
    for (int i = 0; i < sz - 1; i++)
    {
        
        for (int j = 0; j < sz - 1 - i; j++)
        {
            
            if (arr[j] > arr[j + 1])
            {
                Swap(&arr[j], &arr[j + 1]);
            }
        }
    }
}

qsort简单使用

*void qsort(void* base, size_t num, size_t width,
            int (__cdecl *compare )(const void *elem1, const void *elem2 ));

案例

  • 整型数据的排序

void Print(int* arr, int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int comp(const void* p1, const void* p2)
{
    return *(int*)p1 - *(int*)p2;
}
void test01()
{
    int arr[] = { 1,4,5,6,6,8,2,4,8,9,0 };
    int sz = sizeof(arr) / sizeof(int);
    qsort(arr, sz, sizeof(int), comp);
    printf("整形数组排序:>\n");
    Print(arr, sz);
}

前面四个参数都好说:

  • 第一个是数组名字,也就是其实第一个元素的地址。

  • 第二个是数组元素的个数。

  • 第三个是数组每个元素的大小,单位是字节。


分析

!!这里是核心!!!

关键是第四个,为什么要写一个函数指针??

这里我们就要讨论一下,为什么qsort要求我们写这么一个函数?

我们想,qsort能实现不同类型数据的排序,这个函数的实现者知道要对什么样的数据进行排序吗?

显然是不知道的,那么谁知道?

函数的调用者知道,那么我们就要提供一个这样的函数,能比较我们传入的数据的函数。

排序最核心的操作就是比较,然后调整相对位置。

那么我们是不是就可以认为,只要我们能给出两个数据的大小关系,这个状态给给这个qsort函数,它的

内部就可以对我们传入的数据进行排序了?

没错就是这个样子。


那么我们就要讨论一下,这个我们写的比较函数的返回值了。

  • 元素1大于元素2,返回大于0的值

  • 元素1小于元素2,返回小于0的值

  • 元素1等于元素2,返回0的值

qsort默认的排序是升序,来看一下结果。

来看,我们写的比较函数里面的两个参数也是void *的指针,但是我们是知道我们排序数据的类

型,这里是int *,那么就把他强制转化为int *,那么这里的p1 p2,就是整型指针,一次能访问4个

字节,解引用后,能实现两个相邻数据的大小比较。

  • 结构体数据的排序

结构体类型是类似的,来看代码

typedef  struct student
{
    int age;
    char name[20];
} student;
void print_s( student* p1, int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("姓名:>%5s\t年龄:>%d", p1[i].name, p1[i].age);
        printf("\n");
    }
    printf("\n");
}
int comp_stu_age(const void* p1, const void* p2)//按年龄比较
{
    return (( student*)p1)->age- (( student*)p2)->age;
}
int comp_stu_name(const void* p1, const void* p2)//按姓名比较
{
    return  strcmp((( student*)p1)->name, (( student*)p2)->name);
    /*strcmp()字符串比较函数的返回值,刚好满足我们刚刚说的比较函数的返回值,所以这里直接
             返回它的值就行了。*/
}
void test02()
{
    student arr2[] = { 20,"zhangsan" ,25,"lisi",39,"wangwu" };
    int sz = sizeof(arr2) / sizeof(student);
    qsort(arr2, sz, sizeof(student), comp_stu_age);
    printf("结构体数组按年龄排序:>\n");
    print_s(arr2, sz);
    qsort(arr2, sz, sizeof(student), comp_stu_name);
    printf("结构体数组按姓名排序排序:>\n");
    print_s(arr2, sz);
}

效果


以上是我们的铺垫,能耐心看到这里,你真棒!!

接下来就是重点,有了前面的铺垫,我们就能更好的理解这个函数。

qsort模拟实现

既然是模拟实现,那么我们就从参数开始,很简单,跟qsort的参数是一样的。

void Bubble_Qsort(void* base, size_t num, size_t width,
                int(*compare)(const void *p1,const void *p2))

参数这里,我们就不多说了,前面有介绍,这个和qsort的是一样的。

来看框架:

void Bubble_Qsort(void* base, size_t num, size_t width,
    int(*compare)(const void *p1,const void *p2))
{
    unsigned i = 0;
    unsigned j = 0;
    for (i = 0; i < num-1; i++)
    {
        
        for (j = 0; j < num - 1 - i; j++)
        {
            if()
        }
        
    }
}

这就是冒泡排序的框架,我们今天的主题就是为了实现不同类型数据的排序,但仅仅是这个框架是无法实现的。

那我们思考,如何确地出来使用者传进来的数据类型呢?

我们有一个关键的参数,就是每个数据占据的字节数,它就是每个数据的宽度

那么现在,我们抛开数据类型,来想一想:

数据在数据存储是以字节为单位的,而且数组中的数据是在内存中连续存放,那么这样我们是不是可

以把数据通过指针按字节进行交换,只要确地好指针的位置,依次按字节把数据进行交换,最终实现的效

果不也是排序了吗?


好,重新回到我们的排序代码中,按照冒泡排序的想法,首先要确定两个数据的大小,根据已有参数

是可以确定两个相邻数据的。

if(compare((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                //交换两个数据
                }
compare((char*)base + j * width, (char*)base + (j + 1) * width

我们看if中的这个代码,它是一个函数调用,我们关键看参数,先把 base指针强转为char *类型的指

针,再让它移动宽度乘以 j 个字节,这是为了让它能遍历整组数据

为什么要转化为char *呢?

因为指针的类型决定了指针能移动的步长,而char类型一次是移动一个字节的,这个特点,能让我们把

它和传进来宽度联系起来,也能实现在相邻两个数据之间的移动,我们关键是要找到起始地址,传给比较函数,根据它返回值来确定是否要交换。

看图:

接下来就是交换相邻两个元素了,前面提到,我是按字节依次交换数据,那么代码应该这样写:

void Swap_qsort(char* p1, char* p2, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}


相邻两个元素都是占宽度个字节,那么依次按字节交换,那就要交换宽度次,用char *指针来解引用,因为它一次能移动一个字节。

看图:

按照这样的规则,我们把数据按照这样的方式进行交换,最终是能够完成数据的排序的。

来看一下整体代码:

void Swap_qsort(char* p1, char* p2, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}
void Bubble_Qsort(void* base, size_t num, size_t width,
    int(*compare)(const void *p1,const void *p2))
{
    unsigned i = 0;
    unsigned j = 0;
    for (i = 0; i < num-1; i++)
    {
        
        for (j = 0; j < num - 1 - i; j++)
        {
            if(compare((char*)base + j * width,
                     (char*)base + (j + 1) * width) > 0)
            {
                //交换 两个数据,
                Swap_qsort((char*)base + j * width,
                         (char*)base + (j + 1) * width, width);
            }
        }  
    }
}


测试

#include<stdio.h>
#include<string.h>
void Print(int* arr, int sz)//打印整型数组
{
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
typedef  struct Student
{
    int age;
    char name[20];
} Student;
int comp_stu_age(const void* p1, const void* p2)//结构体按年龄排序
{
    return (( Student*)p1)->age- (( Student*)p2)->age;
}
int comp_stu_name(const void* p1, const void* p2)//结构体按姓名排序
{
    return  strcmp(((Student*)p1)->name, (( Student*)p2)->name);
}
int comp(const void* p1, const void* p2)//整型数据排序
{
    return *(int*)p1 - *(int*)p2;
}
void Print_s( Student* p1, int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("姓名:>%s\t年龄:>%d", p1[i].name, p1[i].age);
        printf("\n");
    }
    printf("\n");
}
void Swap_qsort(char* p1, char* p2, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *p1;
        *p1 = *p2;
        *p2 = tmp;
        p1++;
        p2++;
    }
}
void Bubble_Qsort(void* base, size_t num, size_t width,
    int(*compare)(const void *p1,const void *p2))
{
    unsigned i = 0;
    unsigned j = 0;
    for (i = 0; i < num-1; i++)
    {
        
        for (j = 0; j < num - 1 - i; j++)
        {
            if(compare((char*)base + j * width,
                 (char*)base + (j + 1) * width) > 0)
            {
                //交换 两个数据,
                Swap_qsort((char*)base + j * width,
                             (char*)base + (j + 1) * width, width);
            }
        }
        
    }
}
int main()
{
    int arr[] = { 1,4,5,6,7,8,3,3,5,7,8 };
    int sz = sizeof(arr) / sizeof(int);
    Bubble_Qsort(arr, sz, sizeof(int), comp);
    printf("整形数组排序:>\n");
    Print(arr, sz);
    printf("\n===============================\n");
    Student arr2[] = { 10,"lisi" ,45,"zhangsan",29,"wangwu" };
     sz = sizeof(arr2) / sizeof(Student);
     Bubble_Qsort(arr2, sz, sizeof(Student), comp_stu_age);
    printf("结构体数组按年龄排序:>\n");
    Print_s(arr2, sz);
    printf("\n===============================\n");
    Bubble_Qsort(arr2, sz, sizeof(Student), comp_stu_name);
    printf("结构体数组按姓名排序排序:>\n");
    Print_s(arr2, sz);
    return 0;
}

运行结果


总结

以上就是本次分享的全部内容,总结一下:

关于这个模拟实现,我们要清楚以下几点:

  • 函数指针的理解

  • void *指针的理解

  • 如何按字节对数据进行操作


~~如果这片文章对你有帮助,希望各位大佬,一键三连走一波~~~

~~我们下一篇再见~~

需要源代码和演草的话,来博主的仓库哦~

test_3_3 · 琦琦爱敲代码/C语言练习 - 码云 - 开源中国 (gitee.com)

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
C语言数组排序函数qsort是标准函数,用于对数组进行排序。它的使用方法是通过传入一个比较函数来指定排序的规则。在给出的代码中,我们可以看到使用qsort函数对一个整型数组进行升序排序的例子。\[1\] 为了实现qsort函数的功能,我们可以使用一种较为简单的排序算法,比如冒泡排序算法来模拟实现一个具有排序数组、字符串、结构体等功能的bubble_sort函数。\[2\]这个函数的参数可以仿照qsort函数的参数,包括要排序的数组的起始地址、元素个数、每个元素的大小以及一个比较函数。\[3\] 具体实现bubble_sort函数函数体可以根据冒泡排序算法来编写,通过比较相邻的元素并交换位置来实现排序排序的规则可以通过比较函数来指定,根据需要可以实现升序或降序排序。 总结起来,qsort是C语言标准中的数组排序函数,可以通过传入比较函数来指定排序规则。如果想要模拟实现类似功能的排序函数,可以使用一种简单的排序算法,比如冒泡排序,并根据需要实现相应的比较函数。 #### 引用[.reference_title] - *1* *2* *3* [【C语言】qsort()函数详解:能给万物排序的神奇函数](https://blog.csdn.net/weixin_72357342/article/details/130628874)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值