深入理解回调函数qsort:从入门到模拟实现(下)

简介: 深入理解回调函数qsort:从入门到模拟实现(下)

四、模拟实现qsort函数



这里我是基于冒泡函数的思路来实现qsort函数的(实际上qsort函数的排序思路是快速排序)冒泡排序的设计在本篇文末


🧩冒泡排序


#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;
            }
        }
    }
}
int main()
{
    int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    printf("冒泡排序前:\n");
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    //冒泡排序
    bubble_sort(arr, sz);
    printf("\n冒泡排序后:\n");
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}


运行结果:

83c6d8781c954413af98a62cea520fbe.png


  1. 这里我们发现bubble_sort函数中用来接收待排序数组首元素地址的指针arr已经被写死了,是int*类型,这就表它只能对整型数组进行排序。
  2. 其次函数内部对数组元素的比较和交换只适用于int类型的数据。


现在将利用冒泡排序来实现qsort函数,让它能排序任意类型的数据,该怎么做呢?

  • 首先我们知道qsort函数的创作者,他并不知道我们将来需要排序什么类型的数组,但是呢?他却通过qsort函数实现了各种类型数组的排序,这是怎么做到的呢?这就得益于这个函数的4个参数了。
  • 因此,只要我们给qsort函数提供 待排序数组首元素的地址数组中元素的个数数组中每个元素所占内存空间的字节大小,以及一个 比较函数 就能实现对这个数组的排序。所以我们也可以通过这些参数来用冒泡排序的思想实现对任意类型数组的排序。


🧩bubble_sort函数(模拟实现的qsort函数)


值得注意的是,这里说的利用冒泡排序来实现qsort函数,仅仅是实现了qsort函数可以对任意类型的数组进行排序这一特点,并不是说实现了qsort函数的底层原理,qsort的底层其实是通过快速排序来实现的。


//利用冒泡排序实现qsort
void Swap(char* e1, char* e2, size_t width)
{
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *e1;
    *e1 = *e2;
    *e2 = tmp;
    e1++;
    e2++;
  }
}
//注意:这里的compar函数需要根据待排序的类型来书写
void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
//第一个参数 - 用来接收待排序数组的首元素地址,因为待排序的数组元素类型不确定,所以形参数组用void*来接收
//第二个参数 - 用来接收数组元素个数
//第三个参数 - 用来接收数组中每个元素的大小,单位是字节
//第四个参数 - 用来接收一个比较函数,根据待排序数组元素的类型来传递对应类型的比较函数
{
  int i = 0;
  //趟数
  for (i = 0; i < sz - 1; i++)
  {
    int flag = 1;//假设数组是排序好的
    //一趟冒泡排序的过程
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)
        //因为我们并不知道数组元素的类型,所以需要将元素转化为最小的char*类型,
        //即把arr强转为char*类型,arr就可以正常使用,且char*与width配合能访问到任意类型任意位置处的数组元素
        //char类型指针+1只会跳过一个字节,+ j*width表示跳过j个元素
      {
        //交换
        //由于这里的数组名已经被强转为char类型的指针
        //所以要交换数组中的元素,就只能一个字节一个字节进行交换
        Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
        //前两个参数是待交换元素的地址,第三个参数是待交换元素的所占字节的大小
        flag = 0;//如果数组元素进行交换了,说明数组还没有排好序
      } 
    }
    if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序
    {
      break;
    }
  }
}


🚩Swap函数剖析


Swap 函数用于交换两个元素的内容,它接受三个参数,这三个参数的作用如下:

void Swap(void* e1, void* e2, size_t width);


  1. void* e1: 指向第一个待交换元素的指针。由于数组的元素类型是未知的,所以使用 void* 类型来表示元素的指针。在函数内部,你需要将其转换为正确的类型,以便进行元素交换。
  2. void* e2: 指向第二个待交换元素的指针。同样,你需要在函数内部将其转换为正确的类型,以便进行交换操作。
  3. size_t width: 表示每个元素所占的字节数。由于元素类型未知,但在 bubble_sort 函数中有提供,所以通过这个参数确保在进行元素交换时能够正确地按字节进行操作。


  • Swap 函数内部,通过使用 width 参数,以字节为单位逐个交换两个元素的内容。这种设计使得 Swap 函数在不知道元素实际类型的情况下,仍能够正确地交换元素内容。
    虽然在实际代码中,可能会使用更高级的语言特性来进行元素交换(例如 C++ 中的模板函数或 C 中的宏),但是在这个示例中,通过使用 void* 指针和 字节级的操作,实现了一个通用的元素交换函数。


🧩利用bubble_sort函数排序整型数组


代码展示:

#include<stdio.h>
//利用bubble_sort函数排序整型数组
void Swap(char* e1, char* e2, size_t width)
{
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *e1;
    *e1 = *e2;
    *e2 = tmp;
    e1++;
    e2++;
  }
}
void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int flag = 1;//假设数组是排序好的
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序
      {
        Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
        flag = 0;//如果数组元素进行交换了,说明数组还没有排好序
      }
    }
    if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序
    {
      break;
    }
  }
}
//比较函数
int cmp_int(const void* e1, const void* e2)
{
  return *(int*)e1 - *(int*)e2;
}
//主函数
int main()
{
  int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  printf("排序前:\n");
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  //排序
  bubble_sort(arr, sz, sizeof(int), cmp_int);
  printf("\n排序后:\n");
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
  return 0;
}


运行结果:

f3030396ba564f1e944c6fe1a6e6d754.png


🧩利用bubble_sort函数排序结构体数组


1. 【按姓名来排序】

代码展示:

//按姓名来排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//利用bubble_sort函数排序结构体数组
void Swap(char* e1, char* e2, size_t width)
{
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *e1;
    *e1 = *e2;
    *e2 = tmp;
    e1++;
    e2++;
  }
}
void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int flag = 1;//假设数组是排序好的
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序
      {
        Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
        flag = 0;//如果数组元素进行交换了,说明数组还没有排好序
      }
    }
    if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序
    {
      break;
    }
  }
}
//声明一个结构体,并重命名为stu
typedef struct student
{
  char name[20];
  int age;
}stu;
//比较函数
int compare_name(const void* a, const void* b)
{
  return strcmp( ((stu*)a)->name, ((stu*)b)->name );//strcmp函数返回值与compare_name函数一致
}
int main()
{
  stu s[3] = { {"张三",20},{"李四",18},{"王五",25} };
  int sz = sizeof(s) / sizeof(s[0]);
  printf("排序前:");
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d", s[i].name, s[i].age);
    if (i < sz - 1)
      printf(" | ");
  }
  //排序
  bubble_sort(s, sz, sizeof(s[0]), compare_name);
  //打印
  printf("\n排序后:");
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d", s[i].name, s[i].age);
    if (i < sz - 1)
      printf(" | ");
  }
  printf("\n");
  return 0;
}


运行结果:

e03c3ec7750b4319802552b3300c9248.png


2. 【按年龄来排序】

代码展示:

//按年龄来排序
#include <stdio.h>
#include <stdlib.h>
//利用bubble_sort函数排序结构体数组
void Swap(char* e1, char* e2, size_t width)
{
  int i = 0;
  for (i = 0; i < width; i++)
  {
    char tmp = *e1;
    *e1 = *e2;
    *e2 = tmp;
    e1++;
    e2++;
  }
}
void bubble_sort(void* arr, int sz, size_t width, int(*compar)(const void* e1, const void* e2))
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int flag = 1;//假设数组是排序好的
    int j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (compar((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)//实现升序
      {
        Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
        flag = 0;//如果数组元素进行交换了,说明数组还没有排好序
      }
    }
    if (flag == 1)//如果没有再交换数组元素,就说明数组已经排好序
    {
      break;
    }
  }
}
//声明一个结构体,并重命名为stu
typedef struct student
{
  char name[20];
  int age;
}stu;
//比较函数
int compare_age(const void* a, const void* b)
{
  return (((stu*)a)->age - ((stu*)b)->age);
}
int main()
{
  stu s[3] = { {"张三",20},{"李四",18},{"王五",25} };
  int sz = sizeof(s) / sizeof(s[0]);
  printf("排序前:");
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d", s[i].name, s[i].age);
    if (i < sz - 1)
      printf(" | ");
  }
  //排序
  bubble_sort(s, sz, sizeof(s[0]), compare_age);
  //打印
  printf("\n排序后:");
  for (int i = 0; i < sz; i++)
  {
    printf("%s %d", s[i].name, s[i].age);
    if (i < sz - 1)
      printf(" | ");
  }
  printf("\n");
  return 0;
}

运行结果:

8b0f6ecd963945ef84c50a9862fcb490.png


总结



回调函数和 qsort 是 C 语言编程中重要的概念,能够提供强大的灵活性和功能。通过理解回调函数的概念,我们可以将特定行为作为参数传递给其他函数,实现代码的模块化和解耦合。qsort 则为数组排序提供了便利,允许我们自定义比较逻辑以满足不同的需求。


通过以上的介绍和模拟实现,希望初学者们能够更好地理解回调函数和 qsort 的核心概念,为日后的编程实践打下坚实的基础。无论是构建灵活的程序结构还是优化代码性能,这些概念都将成为你编程工具箱中不可或缺的工具。


🔥今天的分享就到这里, 如果觉得博主的文章还不错的话, 请👍三连支持一下博主哦🤞


image.png



目录
相关文章
|
7月前
|
搜索推荐 C语言
c语言从入门到实战——回调函数与qsort的讲解和模拟实现
回调函数是一个函数,它作为参数传递给另一个函数,并且能够在该函数内部被调用。在C语言中,回调函数通常被用于实现事件处理和排序算法中。 `qsort`是C标准库中的一个排序函数,它可以对任意类型的数组进行排序。`qsort`需要三个参数:要排序的数组、数组元素的个数和一个指向回调函数的指针。回调函数必须满足两个条件:能够比较数组中的元素,返回一个整数表示它们之间的大小关系;并且它应该能够被`qsort`函数调用。 回调函数是一种在编程中广泛使用的技术,它允许一个函数作为参数传递给另一个函数,并在需要时被调用。这种机制使得代码更加灵活和可重用。
69 0
|
7月前
|
C语言
c语言进阶部分详解(经典回调函数qsort()详解及模拟实现)
c语言进阶部分详解(经典回调函数qsort()详解及模拟实现)
75 0
|
2月前
|
C++
指针中的回调函数与qsort的深度理解与模拟
本文详细介绍了回调函数的概念及其在计算器简化中的应用,以及C++标准库函数qsort的原理和使用示例,包括冒泡排序的模拟实现。
22 1
|
7月前
|
编译器 C语言
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(下)
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。
44 0
|
7月前
|
存储 C语言
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(中)
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。
40 0
|
7月前
|
存储 C语言 C++
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(上)
C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。
28 0
|
7月前
回调函数,以qsort函数为例
回调函数,以qsort函数为例
30 0
|
算法 程序员 C语言
【进阶C语言】排序函数(qsort)与模拟实现(回调函数的实例)
回调函数为C语言重要知识点,以函数指针为主要知识;下面介绍回调函数的定义、回调函数的库函数举例即库函数模拟实现。
59 0
【进阶C语言】排序函数(qsort)与模拟实现(回调函数的实例)
|
存储 算法 测试技术
深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数
深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数
50 0
|
搜索推荐 C语言
深入理解回调函数qsort:从入门到模拟实现(上)
深入理解回调函数qsort:从入门到模拟实现(上)
87 0