【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


目录
相关文章
|
16小时前
|
C语言
C语言——函数递归
C语言——函数递归
4 0
|
16小时前
|
C语言
C语言—函数(大化小方式的心脏)
C语言—函数(大化小方式的心脏)
2 0
|
2天前
|
存储 编译器 C语言
C语言:字符函数 & 字符串函数 & 内存函数
C语言:字符函数 & 字符串函数 & 内存函数
15 2
|
2天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧
|
2天前
|
存储 C语言
C语言进阶---------作业复习
C语言进阶---------作业复习
|
2天前
|
存储 Linux C语言
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
2天前
|
自然语言处理 Linux 编译器
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
2天前
|
存储 编译器 C语言
C语言进阶第十课 --------文件的操作-1
C语言进阶第十课 --------文件的操作
|
2天前
|
存储 程序员 C语言
C语言进阶第九课 --------动态内存管理-2
C语言进阶第九课 --------动态内存管理
|
2天前
|
编译器 C语言
C语言进阶第九课 --------动态内存管理-1
C语言进阶第九课 --------动态内存管理