【C语言进阶篇】回调函数都学了吧!那么用冒泡排序实现qsort函数你会嘛?

简介: 【C语言进阶篇】回调函数都学了吧!那么用冒泡排序实现qsort函数你会嘛?

📋 前言

  🌈hello! 各位宝子们大家好啊,前面一章讲解了qsor快排函数的使用那么我们是否可以自己实现一下他呢?

  ⛳️冒泡排序我们都知道只能排序整形,但是回调函数学完了之后就可以完美解决这个问题,下面就来看看吧!

  📚本期文章收录在《C语言进阶篇》,大家有兴趣可以看看呐

  ⛺️ 欢迎铁汁们 ✔️ 点赞 👍 收藏 ⭐留言 📝!

🔥 注:VS2022 等C语言学习工具都在《学习工具专栏》, 还有各种实用调试技巧有兴趣可以去看看呐!

💬 qsort 和 冒泡排序的区别

📑 qsort 的特点

🔥 注:快排函数qsort的使用博主在《qsort的使用详解》详细讲解过哦,不会可以去看看。

qsort的特点是:

  • 可以排序任意类型的数据
  • 使用快速排序的思想 quick

📑 冒泡排序 的特点

冒泡排序 的特点:

  • 只能排序整形数据

冒泡排序 思想:

  • 俩俩相邻的元素进行比较,满足条件就交换

好了这俩种排序的思想和区别我们都明白了!冒泡排序我相信大家都不陌生,那么我们今天的任务就是使用冒泡排序的思想去模拟实现库函数qsort 函数的功能!

  • 而这需要解决冒泡排序3个缺陷
  • 一、只能排序整形
  • 二、不同类型的数据比较方法不一样
  • 三、不同类型数据如何交换方法也不一样

💭 如何解决只能排序整形

这个是冒泡排序最主要的问题,那么改如何解决呢?既然是模拟实现qsort 函数那么我们就可以借鉴一下 qsort 函数的方法!

  • qsort 函数里面直接用 通用类型指针 接收的数据
  • 通用类型指针 是不是刚好能解决冒泡排序只能接收整数的问题

📖(void *)指针讲解

void我们都知道是一个空类型的意思,void 就是无类型的指针 :*

  • 无具体类型的指针,可以说他为通用类型指针
  • 但是这种类型的指针是不能够直接进行解引用操作的
  • 由于类型是空类型所以也不能进行指针运算
  • 因为既然他是个空类型那么我们 + - 是该跳过多少字节呢?

📚示例一:

  ⛳️这里就就可以看出一旦指针类型不同是不可以接收不同类型的地址的!

  • 而用 void* 类型的指针就不会出现这种情况

📚示例二:

📖(void* )类型的指针该如何使用

  ⛳️前面说了这种指针既不能直接解引用,又不能进行指针运算那么我们该怎么使用void*类型的指针呢?

  • 🌱 其实void*类型的指针在使用的时候需要强制转换一下就好了!
  • 🌱 这样这个空指针类型不就有类型了(我们强制转换的类型)
  • 🌱 那么指针的运算不也解决了?因为有类型了就可以知道
  • 🌱 加一步我们可以跳过多少字节

📑图片展示:

✅ 解决方法

现在我们知道了 qsort 快排函数的参数 和 通用类型指针 void* 如何使用那么解决冒泡排序只能排序整形还不简单嘛?

  • 既然是模拟实现 qsort 那么就先仿着 qsort 的参数写
  • 来实现我们的冒泡排序 bubble_sort

📚 代码演示:

//模拟实现 qsort 
void bubble_sort(void* base, //第一个参数的地址
         size_t num,//要比较元素的个数
         size_t size, //比较元素的大小
        int (*cmp)(const void* , const void*) )
        //比较函数的地址

这里我们就把要模拟实现的函数 bubble_sort 的参数给写好了,由于我们也要排序不同类型的参数所以,肯定是需要元素类型大小

  • 从哪里排序的第一个参数地址
  • 以及要排序多少个元素
  • 和每个元素怎么进行比较

💭 如何解决只能排序整形

大家都知道冒泡排序在比较整数的时候字需要简单的进行比个大小就好了。但是我们这里需要对不同类型进行比较就不能进行以前那种简单的比较方法了!

  • 那么该怎么解决呢?这个其实也很简单 qsort
  • 库函数里面需要我们自己写一个比较函数来进行判断如何比较
  • 那么我们也可以使用这种方法,对于不同的数据由使用者来决定如何比较
  • 我们只需要调用就好了。

📖 冒泡排序需要改进的地方

✅ 改进方法

📚 代码演示:

这里我们可以怎么改进呢?前面说了对于不同的数据由使用者来决定如何比较!我们只需要写一个回调函数就好了!

  • 使用回调函数就可以在这里解决问题
  • 一个函数可以调用多种不同的函数

🔥 注: 回调函数的详细讲解和使用实例在这里《回调函数的实战技巧》

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* , const void*) )
{
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      if ( cmp((char*)base+j*size, (char*)base +( j +1)* size)>0)
      {
        int tmp = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = tmp;
      }
    }
  }
}

✅ 参数讲解

cmp((char*)base+jsize, (char)base +( j +1)* size)>0

这个函数调用是如何写出来的呢? 虽然我们的比较函数是由使用者来实现的!但是我们只是可以调用函数,而函数的参数还是需要我们在 bubble_sort 里面传出去的。

  • 既然要比较就需要 第一个 第二个 俩个相邻的元素
  • void* 类型的指针又不能直接使用,我们还要排序不同类型的元素所以类型转换就不能写死
  • 把它强转为 (char*) 是最合理的,一个字节!

而我们又知道每个元素的类型大小是多少,这不就和巧妙嘛!(char*)base+j*size char* 指针是每次访问一个字节,那么乘上我们的元素类型大小就刚好可以访问不同类型的元素!

  • 假设我们参数是整形数组
  • 那么 (char*)base+j*size 就是访问4个字节(char*)base+j*4
  • 刚好是一个整形的大小。

💭 如何解决不同类型的交换问题

而冒泡排序以前的交换算法也肯定不可取了,这就需要我们自己构建一种可以交换任意类型的数据了!

✅ swap交换函数的实现

既然是交换那么肯定需要俩个参数,所以 (char*)base+j*size(char*)base +( j +1)* size) 这俩个参数肯定是需要的

  • 而我们传过来的参数是强转成 char* 的
  • 那么我们是不是可以这样交换

一个字节一个字节的进行交换,诶是不是很神奇。这样是不是也能把俩个元素个相互交换并且内容都不发生改变。

  • 因为交换的本质还是一样,只不是以前是一步完成的!
  • 我们现在交换了4次,只是次数变多了但结果是一样的!
  • 都是把不同字节的内容给交换了!
  • 只需要知道要交换元素类型大小是多少,所以我们还需要一个类型大小 size 传过来!

📚 代码演示:

//交换函数
void swap(char* p1, char* p2,int size)
{
  int i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
     p1++;
     p2++;
  }
}

💬 bubble_sort实现完全体

好了这下我们冒泡排序的所有缺点都解决了,折现就可以验证一下 bubble_sort 冒泡排序模拟实现的 qsort 在功能上是不是一样的!

💭 bubble_sort完整代码

📚 代码演示:

//交换函数
void swap(char* p1, char* p2,int size)
{
  int i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
     p1++;
     p2++;
  }
}
//测试 bubble_sort 整数排序
//void qsort(void* base, size_t num, size_t size,
  //int (*compar)(const void*, const void*))
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* , const void*) )
{
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      if ( cmp((char*)base+j*size, (char*)base +( j +1)* size)>0)
      {
        swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
      }
    }
  }
}

🌈 测试排序整形数组

📚 代码演示:

#include <stdio.h>
#include <string.h>
//交换函数
void swap(char* p1, char* p2,int size)
{
  int i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
     p1++;
     p2++;
  }
}
//测试 bubble_sort 整数排序
//void qsort(void* base, size_t num, size_t size,
  //int (*compar)(const void*, const void*))
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* , const void*) )
{
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      if ( cmp((char*)base+j*size, (char*)base +( j +1)* size)>0)
      {
        swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
      }
    }
  }
}
//整形比较函数
int int_cmp(const void* p1, const void* p2)
{
  return (*(int*)p1) - (*(int*)p2);
}
test1()
{
  int arr[10] = { 1,9,5,3,4,2,10,8,7,6 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);
  //打印函数
}
int main()
{
  test1();
  return 0;
}

🌈 测试排序结构体

📚 代码演示:

#include <stdio.h>
#include <string.h>
struct stu
{
  char name[20];
  int   age;
};
//交换函数
void swap(char* p1, char* p2,int size)
{
  int i = 0;
  for (i = 0; i < size; i++)
  {
    char tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
     p1++;
     p2++;
  }
}
//测试 bubble_sort 整数排序
//void qsort(void* base, size_t num, size_t size,
  //int (*compar)(const void*, const void*))
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* , const void*) )
{
  int i = 0;
  for (i = 0; i < num - 1; i++)
  {
    int j = 0;
    for (j = 0; j < num - 1 - i; j++)
    {
      if ( cmp((char*)base+j*size, (char*)base +( j +1)* size)>0)
      {
        swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
      }
    }
  }
}
//结构体比较函数
int struct_cmp(const void* p1, const void* p2)
{
  return strcmp( ((struct stu*)p1)->name,((struct stu*)p2)->name);
}
//测试排序结构体
void test2()
{
  struct stu arr[3] = { {"zhangsan",20},{"lisi",40},{"wangwu",33} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble_sort(arr, sz, sizeof(arr[0]), struct_cmp);
}
int main()
{
  test2();
  return 0;
}

📝全篇总结

✅ 归纳:

好了以上就是关于模拟实现qsort 函数的全部讲解了,学会这些你就可以完美的使用回调函数!

  qsort函数和冒泡排序的区别

  只能排序整形的改进方法

  判断部分的改进

  交换函数的实现

  测试数据

☁️ 以上就全部内容了,本章是对回调函数的应用和提高大家学会了嘛!

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注

💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖

拜托拜托这个真的很重要!

你们的点赞就是博主更新最大的动力!

有问题可以评论或者私信呢秒回哦。


目录
相关文章
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
60 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
63 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
36 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
19 2
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
51 1
|
搜索推荐 C语言
【C语言】使用回调函数通过冒泡排序模拟实现qsort函数
【C语言】使用回调函数通过冒泡排序模拟实现qsort函数
【C语言】使用回调函数通过冒泡排序模拟实现qsort函数
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
62 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
66 15
|
2月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
92 10
|
2月前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
68 9

热门文章

最新文章