你真的理解C语言qsort函数吗 带你深度剖析qsort函数

目录
  • 一、前言
  • 二、简单冒泡排序法
  • 三、qsort函数的使用
    • 1、qsort函数的介绍
    • 2、qsort函数的运用
      • 2.1、qsort函数排序整型数组
      • 2.2、qsort函数排序结构体
  • 四、利用冒泡排序模拟实现qsort函数
  • 五、总结

一、前言

我们初识C语言时,会做过让一个整型数组按照从小到大来排序的问题,我们使用的是冒泡排序法,但是如果我们想要比较其他类型怎么办呢,显然我们当时的代码只适用于简单的整形排序,对于结构体等没办法排序,本篇将引入一个库函数来实现我们希望的顺序。

二、简单冒泡排序法

#include <stdio.h>
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int i = 0;
	int sz = sizeof(arr);
	for (i = 0; i < sz-1; i++)
	{
		int j = 0;
		for (j = 0; j < sz-1-i; j++)
		{
			int tep = 0;
			if (arr[j] > arr[j + 1])
			{
				tep = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tep;
			}
		}
	}
	return 0;
}

这是我们最初学习的冒泡排序法,我们如果把其修改一下,能不能比较其它类型的数据呢,结果是肯定的。

三、qsort函数的使用

1、qsort函数的介绍

图片1为整体描述,下面的图片2、3、4、5、6为图片1的解释。

首先我们可以看出qsort函数不需要返回类型,有四个参数,分别为空指针、无符号型、无符号型、函数指针。图二说明该函数无返回类型。图三说明我们base为要排序中的起始地址,是一个空指针类型,所以我们传首元素地址。图四说明num为元素个数,是一个无符号整型,所以我们传一个无符号整型。图五说明width为每个元素的宽度,单位为字节,是一个无符号整型,所以我们传一个无符号整型。图六说明这个函数指针指向一个含有两个空指针作为参数并且返回类型为int的(比较)函数。这个是qsort的最后一个参数,作用是让我们提供比较大小的规则,所以我们传的时候需要传一个函数地址让库函数中qsort的函数指针来接收。补充:qsort对应的头文件为<stdlib.h>。(比较)函数中的两个参数如果第一个大返回一个大于0的数,如果第二个大返回一个小于0的数,如果相等则返回0。

看完这些,相信你对qsort函数有了初步了解。

2、qsort函数的运用

在这里我们举出两个例子来直观看出qsort的应用方法,分别为整型数组排序和结构体排序。

2.1、qsort函数排序整型数组

#include<stdio.h>
#include <stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
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]), cmp_int);
	int i = 0;
	for(i = 0;i<sz;i++)
	printf("%d ", arr[i]);
}

这样就完成了排序。

疑问:
1.qsort第四个参数(函数地址)为什么不需要传参?
首先:我们要知道,我们第四个参数传的是一个函数地址(函数名加不加取地址符号都可以表示函数的地址),而不是一个函数,所以就没有传参这个说法。其次:等到库函数qsort用函数指针接收后会进行传参等工作(这是程序内部进行的,我们不需要管)。
2.为什么函数定义里面的参数是空指针类型,而不是特定类型?
因为qsort函数并不知道你要传的是什么类型,也就是它使用这个函数时不知道传什么类型的实参,所以统一先传一个空类型地址然后函数用空类型指针接收。
3.空指针是什么意思,怎么用?
空指针就像一个垃圾桶,我们可以对其赋上任意类型的指针,这很方便,但是它不能直接使用,就像我们赋的时候一样,用的时候它并不知道你是什么类型,它相当于只储存了一个字节的地址,没有明确的步长(指针+1跳过的字节)和访问权限大小(解引用指针访问的字节个数)。所以当我们用空指针时,需要进行强制类型转换,转换成我们需要的类型。
4.为什么com_int函数在用的时候要把参数强转为int?
因为当我们使用qsort函数时,我们肯定知道要进行排序的类型,所以我们需要根据自己的需要的类型来写出对应的函数让qsort函数调用。

2.2、qsort函数排序结构体

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stu
{
	char name[10];
	int age;
};
//排序结构体年龄的回调函数
int cmp_struct_stu_age(const void*e1,const void*e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
//排序结构体姓名的回调函数
int cmp_struct_stu_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e1)->name);
}
//运用qsort函数排序结构体年龄
void test1()
{
	struct stu s[3] = { {"xiaoming",30},{"lihua",60},{"wangli",40} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_struct_stu_age);
}
//运用qsort函数排序结构体名字
void test2()
{
	struct stu s[3] = { {"xiaoming",30},{"lihua",60},{"wangli",40} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_struct_stu_name);
}
int main()
{
	test1();
	//test2();
	return 0;
}

补充:
字符串比较大小使用strcmp函数,对应的头文件为<string.h>,依次从左到右比较字母的ASCII值直到不相等,左边大返回大于0的数,右边大返回小于0的数,直到最后都相等返回0,如果你仔细看了最前面关于qsort的介绍,你会发现他们很相似,qsort中用到的回调函数也是需要返回这样的形式。

四、利用冒泡排序模拟实现qsort函数

你对于qsort内部如何执行操作的是否感兴趣呢?你对于qsort中传的那些参数是否存在疑问,不知道他们有什么作用呢,接着看下面的内容,让我们更进一步理解qsort的工作原理,同时让你的冒泡排序变得具有普遍性。

首先我们先试着用my_qsort函数模拟出qsort函数来对一个整型数组排序。

#include <stdio.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++, buf1++, buf2++)
	{
		char tep = 0;
		tep = *buf1;
		*buf1 = *buf2;
		*buf2 = tep;
	}
}
void my_qsort(const void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
		}
	}
}
void test3()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
		printf("%d ", arr[i]);
}
int main()
{
	test3();
	return 0;
}

以上代码就是我们模拟出的qsort函数,相信看完这个你会对qsort理解更深刻,不过有一点需要注意,我们模拟时使用的是冒泡排序,而qsort其实内部使用的是快排,所以其实稍有差距。但大体是一致的。

补充:
1.在my_qsort函数中,我们在比较函数(cmp)中传入的是内容解释:
☃️(char*)base+jwidth 这个实际上就是原始的base是空指针类型,所以我们无法直接使用,但我们也不确定传来的具体是什么类型,因此我们先将其强制类型转换为char,然后根据指针加几就跳过几个步长个字节来确定每次跳过一个元素。
2.在my_qsort函数中,我们的swap函数也是一个重要部分,它是如何实现的呢?
☃️首先和1刚开始一样,我们传的实参的形式就是为了保证每次可以跳过一个元素,然后当传到swap函数里后,我们不像cmp函数一样有提前准备好的强转类型,因此我们需要用一种特别的方法保证可以互换元素,那么我们会想到元素是怎样储存的呢,是以二进制以字节为单位储存的,那么我们如果需要交换元素,是不是只需要把每个字节都交换一次就好了呢?答案是肯定的,于是我们也将传来的地址强制类型转换为char*,保证每次交换的是一个字节,然后又有一个问题就是我们需要交换几次呢,还记得之前传的那个参数width吗,它是每个元素的宽度,单位是字节,因此我们可以通过一个for循环,修女换width次来使得一个元素所占的字节全部交换。

我们这一部分虽然是用模拟的qsort来排序整型数组,但其实如果这个学会了的话,结构体和前面说的是一模一样的,因为模拟的qsort不用变,只需要改一下cmp函数的类型即可。

完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//排序整型数组顺序时的回调函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//声明结构体类型
struct stu
{
	char name[10];
	int age;
};
//排序结构体年龄顺序时的回调函数
int cmp_struct_stu_age(const void*e1,const void*e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
//排序结构体名字顺序时的回调函数
int cmp_struct_stu_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e1)->name);
}
//运用qsort函数排序整型数组
void test1()
{
	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]), cmp_int);
}
//运用qsort函数排序结构体
void test2()
{
	struct stu s[3] = { {"xiaoming",30},{"lihua",60},{"wangli",40} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_struct_stu_name);
	//qsort(s, sz, sizeof(s[0]), cmp_struct_stu_age);
}
//模拟qsort中的交换函数
void swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++, buf1++, buf2++)
	{
		char tep = 0;
		tep = *buf1;
		*buf1 = *buf2;
		*buf2 = tep;
	}
}
//模拟qsort
void my_qsort(const void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
		}
	}
}
//运用模拟qsort函数(my_qsort)排序整型数组
void test3()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
		printf("%d ", arr[i]);
}
//运用模拟qsort函数(my_qsort)排序结构体
void test4()
{
	struct stu s[3] = { {"xiaoming",30},{"lihua",60},{"wangli",40} };
	int sz = sizeof(s) / sizeof(s[0]);
	my_qsort(s, sz, sizeof(s[0]), cmp_struct_stu_name);
	//qsort(s, sz, sizeof(s[0]), cmp_struct_stu_age);
}
int main()
{
	//test1();  //用qsort实现整型数组排序
	//test2();  //用qsort实现结构体排序
	test3();    //用模拟qsort函数实现整型数组排序
	//test(4)   //用模拟qsort函数实现结构体排序
	return 0;
}

回调函数:回调函数就是一个通过函数指针调用的函数。如果你把函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。
通常来讲就是:把这个函数地址作为参数传过去,另一个函数用函数指针接收然后再调用这个函数,那么这个被调用的函数就叫回调函数。(通过函数指针调用的这个函数,叫做回调函数)

五、总结

到此这篇关于你真的理解C语言qsort函数吗带你深度剖析qsort函数的文章就介绍到这了,更多相关c语言qsort函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言中使用qsort函数对自定义结构体数组进行排序

    目录 使用qsort函数对自定义结构体数组进行排序 结构体 排序函数 总体代码 C语言 qsort()函数详解 1.qsort概念介绍 2.qsort()函数实现(循序渐进式讲解) 3.小结 使用qsort函数对自定义结构体数组进行排序 qsort进行排序的数组存储的不能是结构体的指针,需要是结构体本身. 结构体 struct student{     char* id;     int mark; }arr[4], test0={"0001",80}, test1={"00

  • 一文带你学会C语言中的qsort函数

    目录 铺垫知识 使用qsort函数进行整型数组的排序 使用qsort函数进行浮点型数组的排序 使用qsort函数进行结构体数组的排序 铺垫知识 qsort函数 参数类型 void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); 参数类型解释 参数1 待排序数组首元素的地址 参数2 数组内元素个数 参数3 数组内每个元素大小,单位是字节 参数4 函数指针,由自己实现,内容是两个元

  • C语言详细i讲解qsort函数的使用

    目录 qsort 1.int型 2.float型 3.struct型 qsort 功能:Performs a quick sort.(快速排序) 参数:void qsort( void *base, size_t num, size_t width, int (*cmp )(const void *e1, const void *e2 ) ); 头文件:#include <stdlib.h> 用法: 第一个参数待排数组的首元素地址 第二个参数待排数据个数 第三个待排数据中每个参数的大小——单位

  • C语言之qsort函数详解

    目录 一.qsort函数原型 二.qsort常见的几种比较函数 1.int类型的排序 2.double类型的排序 3.char类型的排序 4.字符串的排序: 1.按首字母排序 2.按字符串长度排序: 总结 一.qsort函数原型 qsort 功 能: 使用快速排序例程进行排序,这个函数是根据二分法写的,其时间复杂度为n*log(n) #include<stdlib.h> void qsort(void *base, int nelem, int width, int (*fcmp)(const

  • C语言 使用qsort函数来进行快速排序

    目录 前言 qsort的简单介绍 用qsort实现一个整形类型的排序 用qsort函数实现结构体的排序 qsort函数的实现 前言 今天分享一个库函数 介绍qsort的使用及实现方法 他可以实现不限于整形.浮点型.字符型.自定义等类型的排序 qsort的简单介绍   qsort 头文件 #include <stdlib.h> 格式 void qsort(void* base,size_t num,size_t width,int(__cdecl*compare(const void*,cons

  • C语言中qsort函数的用法实例详解

    C语言中qsort函数的用法实例详解 快速排序是一种用的最多的排序算法,在C语言的标准库中也有快速排序的函数,下面说一下详细用法. qsort函数包含在<stdlib.h>中 qsort函数声明如下: void qsort(void * base,size_t nmemb,size_t size ,int(*compar)(const void *,const void *)); 参数说明: base,要排序的数组 nmemb,数组中元素的数目 size,每个数组元素占用的内存空间,可使用si

  • 关于C语言qsort函数详解

    目录 C语言qsort函数详解 一.qsort函数是什么 二.使用qsort排序-以升序为例 1.整形数组排序 2.字符数组排序 3.字符指针数组排序 4.结构体数组排序 5.浮点型数组排序 三.使用冒泡排序思想模拟实现qsort函数 1.什么是冒泡排序 2.冒泡排序代码 3. 使用冒泡排序思想模拟实现qsort函数 C语言qsort函数详解 一.qsort函数是什么 我们可以使用  搜索库函数网址或者MSDN软件进行查找. qsort()函数:快速排序的函数  -引用stdlib.h头文件 参

  • 你真的理解C语言qsort函数吗 带你深度剖析qsort函数

    目录 一.前言 二.简单冒泡排序法 三.qsort函数的使用 1.qsort函数的介绍 2.qsort函数的运用 2.1.qsort函数排序整型数组 2.2.qsort函数排序结构体 四.利用冒泡排序模拟实现qsort函数 五.总结 一.前言 我们初识C语言时,会做过让一个整型数组按照从小到大来排序的问题,我们使用的是冒泡排序法,但是如果我们想要比较其他类型怎么办呢,显然我们当时的代码只适用于简单的整形排序,对于结构体等没办法排序,本篇将引入一个库函数来实现我们希望的顺序. 二.简单冒泡排序法

  • C++中的函数你真的理解了吗

    目录 1 概述 2 函数的定义及调用 3 值传递 4 函数的常见形式 5 函数的声明 6 函数的分文件编写作用:让代码结构更加清晰 1. 2. 3. 4. 总结 1 概述 作用:将一段经常使用的代码进行封装起来,减少重复代码. 一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能. 2 函数的定义及调用 函数的定义一般主要有5个步骤: 1.返回值类型:一个函数可以返回一个值,需要知道这个值的类型. 2.函数名:给函数起个名字. 3.参数表列:使用该函数时,传入的数据. 4.函数体语句:花

  • 深入理解 Go 语言中的 Context

    Hi,大家好,我是明哥. 在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 <Go编程时光>,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长. 我的在线博客:http://golang.iswbm.com 我的 Github:github.com/iswbm/GolangCodingTime 1. 什么是 Context? 在 Go 1.7 版本之前,context 还

  • 带你理解C语言中的汉诺塔公式

    目录 汉诺塔公式 汉诺塔问题在数学层面的公式: C语言递归公式 两层汉诺塔 三层汉诺塔 总结 汉诺塔公式 汉诺塔问题在数学层面的公式: 不用说,你看到这个公式一定一脸懵逼,我现在来讲解这个公式的作用. 先来回想一下大象放冰箱要几步,三步吧,打开冰箱,放进去,关上门就行了,我们先不要去思考一些细碎的步骤,将一个复杂的问题先简单化,再慢慢去分析. 那汉诺塔问题也是同样的简单三步:(假设有n个盘子) 一.把最大的盘子留在A柱,然后将其他的盘子全放在B柱. 二.把最大的盘子放到C柱. 三.然后将B柱上的

  • 深入浅出理解C语言初识结构体

    目录 1.定义和使用结构体变量 结构体的基础知识 自己建立结构体类型 struct 结构体名 类型名 成员名: 声明结构体的形式 结构体的初始化 2. 结构体成员的访问 3.结构体传参 1.定义和使用结构体变量 结构体的基础知识 结构是一些值的集合,这些值称为成员变量.结构的每个成员可以是不同类型的变量. 自己建立结构体类型 结构的成员可以是标量.数组.指针,甚至是其他结构体. struct 结构体名 {成员表列}:↓ 注意:结构体类型的名字由一个关键字 struct 和结构体名组合而成的(例如

  • 深入理解Swift语言中的闭包机制

    在 Swift 中的闭包类似于结构块,并可以在任何地方调用,它就像 C 和 Objective C 语言内置的函数. 函数内部定义的常数和变量引用可被捕获并存储在闭包.函数被视为封闭的特殊情况,它有 3 种形式. 在 Swift 语言闭合表达式,如下优化,重量轻语法风格,其中包括: 推导参数并从上下文菜单返回值的类型 从单封表达的隐性返回 简略参数名称 尾部闭包语法 语法 下面是一个通用的语法定义用于闭包,它接受参数并返回数据的类型: 复制代码 代码如下: {(parameters) -> re

  • 详解C语言gets()函数与它的替代者fgets()函数

    在c语言中读取字符串有多种方法,比如scanf() 配合%s使用,但是这种方法只能获取一个单词,即遇到空格等空字符就会返回.如果要读取一行字符串,比如: I love BIT 这种情况,scanf()就无能为力了.这时我们最先想到的是用gets()读取. gets()函数从标准输入(键盘)读入一行数据,所谓读取一行,就是遇到换行符就返回.gets()函数并不读取换行符'\n',它会吧换行符替换成空字符'\0',作为c语言字符串结束的标志. gets()函数经常和puts()函数配对使用,puts

  • C语言编程中从密码文件获取数据的函数总结

    C语言getpw()函数:取得指定用户的密码文件数据 头文件: #include <pwd.h> #include <sys/types.h> 定义函数: int getpw(uid_t uid, char *buf); 函数说明:getpw()会从/etc/passwd中查找符合参数uid所指定的用户账号数据, 找不到相关数据就返回-1. 所返回的buf 字符串格式如下: 账号:密码:用户识别码(uid):组识别码(gid):全名:根目录:shell 返回值:返回 0 表示成功,

  • 深入理解C语言指针

    指针是一种数据类型 指针也是一种变量,占有内存空间,用来保存内存地址 指针就是告诉编译器,开辟4个字节的存储空间(32位系统),无论是几级指针都是一样的 *p操作内存 在指针声明时,* 号表示所声明的变量为指针 在指针使用时,* 号表示操作指针所指向的内存空间中的值 *p相当于通过地址(p变量的值)找到一块内存:然后操作内存 *p放在等号的左边赋值(给内存赋值) *p放在等号的右边取值(从内存获取值) 指针变量和它指向的内存块是两个不同的概念 给p赋值p = 0x1111;只会改变指针变量值,不

  • 理解Vue2.x和Vue3.x自定义指令用法及钩子函数原理

    目录 Vue2.x用法 全局注册 局部注册 使用 钩子函数 钩子函数的参数 Vue3.x用法 全局注册 局部注册 使用 钩子函数 较 Vue2.x 相比, 钩子函数有变化 Vue2.x用法 全局注册 Vue.directive( 指令名, { 自定义指令生命周期 } ) 局部注册 directives: { 指令名, { 自定义指令生命周期 } } 使用 v-指令名: 属性名.修饰符="value值" 钩子函数 bind - 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只

随机推荐