你真的理解qsort函数吗?带你深度剖析qsort函数

这篇具有很好参考价值的文章主要介绍了你真的理解qsort函数吗?带你深度剖析qsort函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

你真的理解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函数的介绍

你真的理解qsort函数吗?带你深度剖析qsort函数

 

  • 图片1为整体描述,下面的图片2、3、4、5、6为图片1的解释。
  1. 首先我们可以看出qsort函数不需要返回类型,有四个参数,分别为空指针、无符号型、无符号型、函数指针。
  2. 图二说明该函数无返回类型。
  3. 图三说明我们base为要排序中的起始地址,是一个空指针类型,所以我们传首元素地址。
  4. 图四说明num为元素个数,是一个无符号整型,所以我们传一个无符号整型。
  5. 图五说明width为每个元素的宽度,单位为字节,是一个无符号整型,所以我们传一个无符号整型。
  6. 图六说明这个函数指针指向一个含有两个空指针作为参数并且返回类型为int的(比较)函数。这个是qsort的最后一个参数,作用是让我们提供比较大小的规则,所以我们传的时候需要传一个函数地址让库函数中qsort的函数指针来接收。
  7. 补充: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]);
}

你真的理解qsort函数吗?带你深度剖析qsort函数

  • 这样就完成了排序。

疑问:
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;
}

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

五、总结

这是第一篇博客,自己以后的博客也一定好好去写,希望大佬可以给个点赞关注加收藏,长期更新,每周至少三篇。

你真的理解qsort函数吗?带你深度剖析qsort函数大魔王(作者)
你真的理解qsort函数吗?带你深度剖析qsort函数大佬(读者)!
你真的理解qsort函数吗?带你深度剖析qsort函数
💚💚💚被你宠坏的表情💚💚💚

🔥🔥🔥🔥🔥🔥🔥🔥大魔王不哭🔥🔥🔥🔥🔥🔥🔥
 
求你了,大魔王真的需要你的关注!!!文章来源地址https://www.toymoban.com/news/detail-423220.html

到了这里,关于你真的理解qsort函数吗?带你深度剖析qsort函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • TypeScript深度剖析:TypeScript 中命名空间与模块的理解?区别?

    TypeScript  与 ECMAScript  2015 一样,任何包含顶级  import  或者  export  的文件都被当成一个模块 相反地,如果一个文件不带有顶级的 import 或者 export 声明,那么它的内容被视为全局可见的 例如我们在在一个  TypeScript  工程下建立一个文件  1.ts ,声明一个变量 a ,如下: 然

    2024年02月07日
    浏览(31)
  • JavaScript 深度剖析-函数式编程(一)

    为什么要学习函数编程以及什么是函数式编程 函数式编程的特性(纯函数、柯里化、函数组合等) 函数式编程的应用场景 函数式编程库 Lodash 函数式编程是非常古老的一个概念,早于第一台计算机的诞生,函数式编程的历史。 那我们为什么现在还要学函数式编程? 函数式编程

    2024年02月15日
    浏览(47)
  • 【C语言深度剖析——第三节(关键字3)】《C语言深度解剖》+蛋哥分析+个人理解

    本文由@睡觉待开机原创,未经允许不得转载。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言,共同进步! 前言: 本期我们继续探讨关于C深度解剖这本书相关内容,继上一篇博客,本篇博客来围绕“数据类型”、“原反补”有关话题进行探

    2024年01月21日
    浏览(43)
  • 【C语言深度剖析——第四节(关键字4)】《C语言深度解剖》+蛋哥分析+个人理解

    追求本质,不断进步 本文由@睡觉待开机原创,转载请注明出处。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言,共同进步! 前言: 本节博客继续前篇内容进行续写,我们着重探求有符号与无符号数的问题,探求 整形在内存中的存储 这一课

    2024年01月20日
    浏览(45)
  • 多态 虚函数表深度剖析 纯干货讲解(2)

    💯 博客内容:多态 😀 作  者:陈大大陈 🚀 个人简介:一个正在努力学技术的准C++后端工程师,专注基础和实战分享 ,欢迎私信! 💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘 目录 虚函数存在哪里?  虚函数的地

    2024年02月05日
    浏览(40)
  • 一文带你深入理解probe函数

    驱动注册的probe函数 probe函数在设备驱动注册最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用 platform设备的probe函数完成驱动注册最后工作。资源、 中断调用函数以及其他相关工作。下面是probe被调用的一些程序流程。 从driver_register看起:

    2024年02月13日
    浏览(70)
  • 深入剖析 深度学习中 __init()__函数和forward()函数

    再看代码时,发现init函数和forward函数都有参数,具体是怎么传参的呢? 为了更方便的讲解,会举简单的代码例子结合讲解。 forward() 和 __init__() 是神经网络模型类中的两个重要函数。它们通常是通过类的实例化和调用来执行的。 __init__() 函数是Python类中的构造函数,用于初

    2024年02月07日
    浏览(50)
  • 【数据结构】3个例题带你理解图的遍历:深度优先搜索

    1、定义 2、性能分析 3)算法实现 💟作者简介:大家好呀!我是 路遥叶子 ,大家可以叫我 叶子 哦!❣️     📝个人主页:【叶子博客】 🏆博主信息: 四季轮换叶,一路招摇胜! 🐋希望大家多多支持😘一起进步呀#

    2024年02月08日
    浏览(44)
  • 深度剖析qt cmake 的qt_add_qml_module函数

    qt_add_qml_module 函数是一个高层次的 CMake 函数,用于创建和管理 QML 模块。它简化了将 QML 代码与 C++ 代码集成以及与其他资源文件集成的过程。这个函数旨在用于 Qt 6 和更高版本的项目。 在调用 qt_add_qml_module 时,它会执行以下操作: 创建一个动态链接库——作为QML模块的插件

    2024年02月02日
    浏览(40)
  • 【图文结合c++】一篇文章解析c++默认函数规则,带你深度学习构造函数

    前言 : 类和对象 是面向对象语言的重要概念。 c++身为一门 既面向过程,又面向对象 的语言。 想要学习c++, 首先同样要先了解类和对象。 本节就类和对象的几种构造函数相关内容进行深入的解析。 目录 类和对象的基本概念 封装 类域和类体 访问限定符 private public protec

    2024年03月14日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包