C语言中关于库函数 qsort 的模拟实现过程

目录
  • 前言
  • 一、qsort函数
  • 二、qsort函数实现思路
    • 1. 底层原理
    • 2. 函数传参
      • 1). 第一个参数
      • 2). 第二个参数
      • 3). 第三个参数
      • 4). 第四个参数
  • 三、局部函数实现
  • 四、全部代码汇集
  • 五、总结

前言

我们在上一篇博客讲解了库函数qsort的使用,今天我为大家带来qsort的模拟实现。上一篇博客这个库函数的阅读链接:C语言中关于库函数 qsort 快排的用法

其实有人会问,我明明已经掌握了库函数qsort的使用方法,为何还要去写模拟实现,其实啊,学好一个东西,不仅仅只是会用就可以,如果我们能更深层次的去探索这个函数是怎么实现的,我相信,这其中的乐趣,不一般。。。

仅以此篇文章作为我学习的见证,希望能够各位带来一定的帮助,谢谢。文章若有不妥之处,欢迎指点。这是一个共同进步的平台!!!

一、qsort函数

我们先看看qsort函数的使用:

#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	//e1-e2,得到的是升序
	return *(int*)e1 - *(int*)e2;
}

int main()
{
	int arr[10] = { 2,3,1,4,5,6,7,9,8,10 };
	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]);
	return 0;
}

qsort函数的四个参数我们就不过多讲了。大家翻一翻上一篇博客。

二、qsort函数实现思路

1. 底层原理

其实啊,qsort函数,底层原理是 快速排序,只不过我们在使用的时候,被封装成了函数而已。我们写的是从冒泡排序的角度写。

2. 函数传参

qsort(arr, sz, sizeof(arr[0]), cmp_int);

1). 第一个参数

传入的是一个数组,可能大多数人的第一反应就是对应函数的形参用数组指针来接收,我当初自己在写的时候,也是这样想的,当我们越往后面写,发现这个形参用数组指针来接收,并不好用,至于为什么,往下看。

我们在上一篇博客提到过 void* 这个概念,它能接收来自所有类型的值,我们这第一个参数的形参就写成 void* base。

2). 第二个参数

传入的是元素个数,我们再提到一个概念:size_t 返回类型,它是一个无符号整形(unsigned int),我在《C primer plus》书找到了相关的解释,图放在下面:

因为我们传第二个参数时,传的其实就是sizeof的返回值,所有函数形参部分就写 size_t sz。

3). 第三个参数

这第三个参数啊,跟第二个参数是一样的,计算得到的也是sizeof的返回值,也直接写size_t width。(代表一个元素的大小)

4). 第四个参数

这第四个参数是一个函数啊,我们在传参的时候,传的其实是这个函数的地址啊,在my_sort(就是等会我们需要实现的qsort函数),在my_sort函数里面,我们通过传过来的函数地址(cmp_int)去调用这个(cmp_int)函数,我们就叫回调函数。这里有点绕,仔细品。

既然传过来的是一个函数的地址,对应的,我们就用函数指针去接收,具体的代码看下面:

void my_sort(void* base,
			size_t sz,
			size_t width,
			int (*cmp)(void*, void*));

有的小伙伴就会问函数指针是是什么?
就是用来接收函数的地址,用的指针,具体的原理,大家可以去CSDN查,这里,我们就不多讲了。

局部代码:

void my_sort(void* base, size_t sz, size_t width, int (*cmp)(void*, void*))
{
	//函数里面实则还是冒泡排序
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//进行判断和交换

		}
	}
}

这就是qsort函数大致的框架,还有一点点小细节问题,处理了就完成了,加油哦。

三、局部函数实现

现在放在我们面前的问题,怎么进行数值的判断和交换。

int arr[10]={2,1,4,5,6,3};

我们以这个数组为例,我们想想,如果我们要排一个升序的数组,只需要我们传递给(cmp_int)函数的两个参数相减为正数,上一篇博客提到了口诀“左减右为升序,反之则降”,即就是e1 - e2 大于0 ,也就是e1>e2了。 注:函数参数(const void* e1,const void* e2)

知道了其中原理,就好实现了呗,if语句判断,如果(cmp_int)函数的放回值大于0,我们就交换一下两个数。

void my_sort(void* base, size_t sz, size_t width, int (*cmp)(void*, void*))
{
	//函数里面实则还是冒泡排序
	int i = 0;
	for (i = 0; i < sz; 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函数,其实函数里面需要循环4次才行,因为是4个字节的数据嘛
				Exch((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

随着j的增加,我们比较的数据也一个个的往后移,特别注意(char*) base + j * width,我们在使用base时,必须先要进行强制类型转换,这样再进行加减操作才可以。因为 base的类型是 void * 类型,没有具体的大小。强制类型转换后,再加上j*width个字节,这样就能找到数组中一个个的元素。

接下来就是函数Exch的实现,这个函数就是交换两个数的位置用的,(exchange)。上面的if语句如果成立,我们就调用函数Exch,去交换两个数的位置,传参的话,跟if语句里面的参数一样的,毋庸置疑。只是我们在传参的时候,还需要传第三个参数width,因为我们调用函数Exch后,函数里面一次交换,只能交换1个字节的内容。有小伙伴就会问,我一次直接交换4个字节的内容,这不是简单的多了嘛。。。。
对于整形数组,一次直接交换4个字节的内容,当然简单了,但是我们需要交换其他数据类型的时候呢,难道还要重新写一遍my_sort吗??是吧,所以,1个字节慢慢交换,循环width次就行,这样写出来的函数,才是通用的。
Exch函数的实现

void Exch(char* cmp1, char* cmp2, size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *cmp1;
		*cmp1 = *cmp2;
		*cmp2 = tmp;
		cmp1++, cmp2++; //逗号表达式,从左到右,依次执行
	}
}

这里面就简单多了,就是创建一个临时变量 ,交换数据后,对应的地址加1即可,特别注意一下函数形参部分哦,为什么要写 char*。值得思考哦。

四、全部代码汇集

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}

void Exch(char* cmp1, char* cmp2, size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *cmp1;
		*cmp1 = *cmp2;
		*cmp2 = tmp;
		cmp1++, cmp2++; //逗号表达式,从左到右,依次执行
	}
}

void my_sort(void* base, size_t sz, size_t width, int (*cmp)(void*, void*))
{
	//函数里面实则还是冒泡排序
	int i = 0;
	for (i = 0; i < sz; 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函数,其实函数里面需要循环4次才行,因为是4个字节的数据嘛
				Exch((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	my_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	return 0;
}

五、总结

简单的给大家说了一下整体代码的思路,还有细节问题,没能给大家表达清楚,我感到非常遗憾。
特别注意一下冒泡排序里面的逻辑,又特别是里面的if语句,这是整个代码的关键,其他的就没多大的问题,捋清楚了,这些,应该就能懂了,还有就是指针哦,虽然有点难,但是始终相信功夫不负有心人!
如果还有什么不理解的,我们在评论区聊,共同进步。加油!!
下次见!!!

到此这篇关于C语言中关于库函数 qsort 的模拟实现过程的文章就介绍到这了,更多相关C语言 qsort内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言中常用的几个头文件及库函数

    不完全统计,C语言标准库中的头文件有15个之多,所以我主要介绍常用的这四个头文件stdio.h,string.h,math.h,stdlib.h,以后用到其他的再做补充.下面上干货: 1.<stdio.h>:定义了输入输出函数.类型以及宏,函数几乎占了标准库的1/3. (1)文件访问. FILE *fopen("filename","mode"): 以mode模式打开地址为'filename'的文件,并返回文件指针. 访问模式主要是"r&quo

  • 老生常谈C语言动态函数库的制作和使用(推荐)

    >>>>>>老生常谈C语言接静态函数库的制作和使用>>点击进入 2 动态函数库的制作和使用 动态函数库的制作步骤可以用下图来描述,具体包括 (1) 编写函数的.c文件(例如add.c.sub.c.mul.c和div.c) (2) 编写Makefile,然后make,实现函数的编译和归档入库 函数的编译:使用gcc –c add.c -fPIC只编译不链接函数.c文件,分别生成函数的目标文件(例如add.o.sub.o.mul.o和div.o). 函数的归档入

  • C++中的常用库

    1. cmath: 数学计算 #include <iostream> #include <cmath> using namespace std; int main () { // 数字定义 short s = 10; int i = -1000; long l = 100000; float f = 230.47; double d = 200.374; // 数学运算 cout << "sin(d) :" << sin(d) <&

  • 关于C语言多线程pthread库的相关函数说明

    线程相关操作说明 一 pthread_t pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符. 二 pthread_create 函数pthread_create用来创建一个线程,它的原型为: extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__at

  • C语言中关于库函数 qsort 快排的用法

    目录 前言 一.库函数(qsort)的含义 二.(qsort)函数的实现方式,话不多说,请看. 1. 第一个参数 2. 第二个参数 3. 第三个参数 4. 第四个参数 1). 函数的参数 2). 这第四个参数的重点 三.函数实现 四.总结 前言 我也只是一个奋斗的程序猿,仅以此篇文章,作为我学习的见证,可能我的文采不好,有时候讲的词不达意,但我尽力去做好我想做的这些事情,如果此篇文章能够给各位读者带来一定的认识,那自然是最好的.若文章中有鄙人讲错了的,欢迎评论区指点.谢谢!!! 一.库函数(qs

  • 一篇文章带你实现C语言中常用库函数的模拟

    目录 前言 函数介绍 strlen(求字符串长度) strcpy(字符串拷贝) strcat(字符串追加) strcmp(字符串比较) strstr(找子字符串) memcpy(内存拷贝) memmove(内存移动) 总结 前言 C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中. 字符串常量适用于那些对它不做修改的字符串函数. 函数介绍 strlen(求字符串长度) size_t strlen ( const char * str

  • 一篇文章教你自己动手实现C语言库函数

    目录 memmove 函数声明 函数作用 实现memmove memcpy 函数声明 函数作用 实现memcpy strstr 函数声明 函数作用 实现strstr strcat 函数声明 函数作用 实现strcat strcmp 函数声明 函数作用 实现strcmp strcpy 函数声明 函数作用 实现strcpy strlen 函数声明 函数作用 实现strlen 总结 memmove 函数声明 void * memmove ( void * destination, const void

  • C语言中关于库函数 qsort 的模拟实现过程

    目录 前言 一.qsort函数 二.qsort函数实现思路 1. 底层原理 2. 函数传参 1). 第一个参数 2). 第二个参数 3). 第三个参数 4). 第四个参数 三.局部函数实现 四.全部代码汇集 五.总结 前言 我们在上一篇博客讲解了库函数qsort的使用,今天我为大家带来qsort的模拟实现.上一篇博客这个库函数的阅读链接:C语言中关于库函数 qsort 快排的用法 其实有人会问,我明明已经掌握了库函数qsort的使用方法,为何还要去写模拟实现,其实啊,学好一个东西,不仅仅只是会用

  • C语言库函数qsort的使用详解

    目录 一.回调函数 二.库函数qsort 三.使用qsort排序整型数组 四.使用qsort排序结构体 1.使用qsort排序结构体中的字符成员 2.使用qsort排序结构体中的整型成员 五.基于冒泡排序的库函数qsort的模拟实现 1.使用改写函数排序整型数组 2.使用改写函数排序结构体中的字符成员 3.对库函数qsort的总结 六.力扣977#中库函数qsort的使用 一.回调函数 C语言库函数中的qsort的是一个回调函数,回调函数就是一个通过函数指针调用的函数.如果把函数的指针(地址)作

  • C语言中函数返回字符串的方法汇总

    在讨论着四种方法之前,首先要对函数有一个简单的认识,无论是在形实结合时,还是在return语句返回时,都有一个拷贝的过程.你传进来的参数是个值,自然函数在工作之前要把这个值拷贝一份供自己使用,你传进来的是个地址,函数也就会拷贝该地址供自己使用.同样return返回时,如果返回一个值,函数会将该值拷贝一份以提供给主调函数使用,返回的是一个指针(也就是地址),自然拷贝的就是一个地址,供主调函数使用. 先给出一个错误的例子: #include <stdio.h> #include <strin

  • C语言库函数qsort的使用及模拟实现

    目录 1.qsort函数的介绍 2.qsort实现不同类型数据排序 3.qsort的模拟实现 1.qsort函数的介绍  函数定义: 函数参数 : void* base 待排序的数据的起始位置 size_t num 待排序的数据元素的个数 size_t width 待排序的数据元素的大小,以字节为单位 int (*compar)(const void*,const void*) 是一个函数指针,函数功能是比较 因该排序算法要排序的数据的类型是不同的,比较方法也是有差异的,因此要给使用者提供一个自

  • C语言库函数qsort及bsearch快速排序算法使用解析

    目录 qsort 含义 实现 格局打开 bsearch qsort qsrot 就是C语言库函数中的快速排序函数,对数组,结构体都可以实现快速排序, 他在头文件<stdlib.h>中使用,声明格式为: void qsort(void* base, size_t nums, size_t size, int (*compare)(const void *, const void*)) 这么烦人一长串的参数各是什么意思呢,base 是指向要排序的数组的第一个元素的指针.nums是由 base 指向

  • C语言中回调函数和qsort函数的用法详解

    目录 回调函数 指向函数指针数组的指针 qsort(qulick sort)-库函数 回调函数 通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数. 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应. 举例: #include<stdio.h> void menu() { printf("*************************

  • 模拟实现C语言中的内存管理

    这里模拟了C语言中的内存管理,当我们要创建或者使用一个对象时,那么这个对象会调用retain方法,计数+1,当我们要释放对象,我们会调用free,这里注意要对计数记性判断,如果是0的话,那么就会销毁. #import <Foundation/Foundation.h> int cnt = 0; void fun (charchar * p) { printf("%c\n",p[0]); } charchar * retain1(charchar * p) { //retai

随机推荐