【C语言进阶】qsort函数详解以及它的模拟实现(一)

简介: 【C语言进阶】qsort函数详解以及它的模拟实现(一)

一、qsort函数介绍

 qsort是一个库函数,可以对任意数据类型的数组进行排序。它的底层是通过快速排序来实现的

cplusplus网站中对qsort函数的解释如下:

2133881c04d04404bc36c7fd82454d72.png

qsort的函数声明:

 void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));

qsort函数的参数:

  • void* base
  • size_t num
  • size_t size
  • int(*compar)(const void*,const void*)

qsort函数的返回值:

 qsort函数的返回值void型

二、qsort函数参数介绍

2.1:void* base

 参数base的类型是void*(空指针),说明base是一个指针,它指向待排序数组的第一个元素,换言之:base存放的是待排序数组的首元素地址

补充:void*类型介绍:

 void*是无具体指向的指针类型,任何类型变量的地址都可以存放到void型指针变量里面,这可以说是void*变量的一个优点。当然它也有一个致命的缺点:void*指针不能解引用。

为什么base的类型是void*呢?

回到base的用途,base是用来存放待排序数组首元素的地址,既然存放的是地址,那首先base一定是一个指针类型,那为什么是void型的指针呢?这就要回到qsort函数设计的初衷,qsort函数希望实现对任何类型的数组都可以排序,这就包括:整型数组、字符数组、结构体数组等等,既然面对的排序对象是各种类型的数组,那数组首元素的类型一定也是各种各样的,比如:对整型数组进行排序,数组首元素就是整型;对字符数组排序,数组首元素就是字符型。既然数组首元素的类型可能有多种,那数组首元素的地址一定也是各种各样的,可能是整型的地址,可能是字符的地址,等等。此时就要求base能够存放各种类型的地址,因此就让base是void*型。void*变量就可以存放各种类型的地址。假如:让base是int*型,当待排序的是字符数组,字符数组的首元素是一个字符,字符的地址就不能存到int*的变量里。

2.2:size_t num

 num中存的是待排序数组的元素个数,他的类型是size_t,size_t其实就是把unsigned int重命名的得到的。

2.3:size_t size

 size中存的是数组中每个元素的大小(以字节为单位)。

2.4:int(* compar)(const void *,const void *)

 compar是一个函数指针类型,所谓函数指针就是可以指向一个函数,里面存放的是函数的地址

compar到底指向什么函数呢?

 compar是英文单词compare(比较)的缩写,所以顾名思义,compar是比较的意思,说明它指向一个比较函数。

什么是比较函数?

要实现对数组元素的排序,那一定要对数组里面的元素进行逐一比较,而对于不同类型的数组来说,它们的比较方法也有所不同。比如:整型数组可以比较它们元素之间的大小关系,而字符数组则有专门的字符串比较函数strcmp,如果是一个结构体数组,一个结构体里面有不同的数据,我们就可以按照不同的标准进行排序。就像对一群人进行排序,可以按照年龄排,也可以按照身高排等等。此时就需要用户自己确定一个排序的标准,用函数封装起来,然后把这个函数的地址传给qsort函数用函数指针compar来接收。

对比较函数的要求:

形参compar已经规定了它所指向的函数类型是:int (const void*,const void).即:所指向的比较函数有两个void*类型的参数,函数的返回值是int型。

比较函数的参数、返回值的意义:

int compar (const void* p1, const void* p2);

 其中两个void*类型的参数 p1 和 p2 用来存放数组中待比较的两个元素的地址。如果compar函数的返回值小于0,会把p1指向的元素排到p2指向的元素前面;如果返回值等于0,不会改变p1和p2指向的元素位置;如果返回值大于0,会把p1指向的元素排到p2指向的元素后面。

三、实际应用

3.1:利用qsort函数对整型数组排序

int comper(const void* e1, const void* e2)
//comper函数是用户自己写的,我们自己当然知道要排序的元素类型是什么
//我们就可以利用强制类型转化来实现对e1和e2的比较
{
  return *(int*)e1 - *(int*)e2;//void*类型不能直接解引用
}
//*(int*)e1 - *(int*)e2<0
//说明e1指向的元素比e2指向的元素小,此时刚好返回一个小于0的数,qsort就会把e1指向的元素排在e2指向的元素前面
//*(int*)e1 - *(int*)e2>0
//说明e1指向的元素比e2指向的元素大,此时刚好返回一个大于0的数,qsort就会把e2指向的元素排到e1指向的元素前面
int main()
{
  int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  qsort(arr, sz, sizeof(arr[0]), comper);//利用库函数qsort来排序
  int i = 0;
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  printf("\n");
  return 0;
}
//结果:
0 1 2 3 4 5 6 7 8 9

3.2:利用qsort函数对结构体数组排序

按照年龄排序:

struct Stu
{
  char name[20];
  int age;
};
//根据名字比较
int comper_age(const void* e1, const void* e2)
{
  return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
    struct Stu arr[3] = { {"zhangsan",100},{"lisi",20},{"wangwu",3} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  //排序前打印
  for (i = 0; i < sz; i++)
  {
    printf("%s %d   ", arr[i].name, arr[i].age);
  }
  printf("\n");
  //根据年龄进行排序
  qsort(arr, sz, sizeof(arr[0]), comper_age);
  //排序后打印
  for (i = 0; i < sz; i++)
  {
    printf("%s %d   ", arr[i].name, arr[i].age);
  }
  printf("\n");
  return 0;
}
//结果:
zhangsan 100   lisi 20   wangwu 3
wangwu 3   lisi 20   zhangsan 100

按照名字进行排序:

struct Stu
{
  char name[20];
  int age;
};
//按照名字比较
int comper_name(const void* e1, const void* e2)
{
  return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e1)->name);//名字是字符串,所以名字的比较要用到字符串比较 函数strcmp
}
int main()
{
    struct Stu arr[3] = { {"zhangsan",100},{"lisi",20},{"wangwu",3} };
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  //排序前打印
  for (i = 0; i < sz; i++)
  {
    printf("%s %d   ", arr[i].name, arr[i].age);
  }
  printf("\n");
  //按照年龄进行排序
  qsort(arr, sz, sizeof(arr[0]), comper_name);
  //排序后打印
  for (i = 0; i < sz; i++)
  {
    printf("%s %d   ", arr[i].name, arr[i].age);
  }
  printf("\n");
  return 0;
}
//结果:
zhangsan 100   lisi 20   wangwu 3
lisi 20   wangwu 3   zhangsan 100


目录
相关文章
|
3月前
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
985 0
|
5月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
318 15
|
10月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
384 1
一文彻底搞清楚C语言的函数
|
11月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
567 16
|
11月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
444 3
|
11月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
356 2
|
11月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
197 1
|
存储 C语言
C 语言函数完全指南:创建、调用、参数传递、返回值解析
函数是一段代码块,只有在被调用时才会运行。 您可以将数据(称为参数)传递给函数。 函数用于执行某些操作,它们对于重用代码很重要:定义一次代码,并多次使用。
418 3
|
C语言
C语言函数返回值详解
本文详细解析了C语言中函数返回值的概念与应用。从函数的基本定义入手,深入探讨了不同类型返回值的作用及意义,并提供了实用的编程示例,帮助读者更好地理解和使用函数返回值。通过本文,你将掌握如何有效利用返回值优化代码结构与功能实现。
1200 2
|
C语言
C语言---函数---知识点总结(三)------函数的返回值类型
C语言---函数---知识点总结(三)------函数的返回值类型