草庐IT

模拟实现qsort函数(C指针进阶)

派小星233 2023-04-11 原文

目录

一:指针函数

(1)什么是函数指针?

(2)函数名的意义。

二:什么是qsort()函数

(1)qsort()函数的函数原型

(2):qsort()函数的参数意义

三:冒泡排序

(1):什么是冒泡排序?

(2)图解:

四:回调函数

(1):什么是回调函数?

(2)回调函数的意义

五:创建源文件和头文件

六:函数的具体实现

(1):函数bubblesort( )

(2):比较方法(函数)

(3)怎么找到函数元素的首地址?

(4):怎么进行交换?

七:全部代码

(1)cmp.h

(2)cmp.c

(3)bubble_sort.c

八:结语


一:指针函数

(1)什么是函数指针?

函数指针:首先它是一个指针,一个指向函数的指针,在内存空间中存放的是函数的地址。

(2)函数名的意义。

我们都知道数组名在大部分情况下代表的是数组的首元素地址。&数组名代表的是数组的地址,尽管数值上一致,但代表的含义完全不同。

那我们可不可以认为函数名是函数所在空间的首地址,&函数名(函数指针)代表的是函数的地址呢?我们看下面这个例子。

我们可以看到二者的数值是相同的,那它们的意义是不是也如同数组名和&数组名一样呢?我们看下面对函数指针的引用。

 

按照我们原来对指针的理解,好像对p加*进行解引用才能找到函数,但我们可以看到函数名前面不管加几个*,结果都是一致的,由此我们得到结论:函数名与&函数名完全等价,不管函数名前面加多少个*都没有影响。 

做个小结:函数名=&函数名
数组名!=&数组名

二:什么是qsort()函数

(1)qsort()函数的函数原型

void qsort(void *base, int nelem, int width, int (*fcmp)(const void *,const void *))

qsort(即,quicksort)主要根据你给的比较条件给一个快速排序,主要是通过指针移动实现排序功能。排序之后的结果仍然放在原来数组中。

qsort函数包含在头文件stdlib.h中。

(2):qsort()函数的参数意义

在讲参数意义之前,我们先说说,void类型的妙用,我们早期学习数组的时候传入的函数参数一般为数组对应类型的指针,这样只能接收一种数据类型,如果要修改会比较复杂,但是void类型可以接收任何类型,使代码更加的泛用。

void类型是一个没有实际意义的类型,我们不能直接用void类型参与运算,也不能对void类型的指针解引用,除非进行强制类型转换

第一个参数:void*用来接收待排序的目标数组的首元素地址

第二个参数:num是待排序的目标数组的元素个数

第三个参数:width是单个数组元素的大小(即字节数)

第四个参数:compare是一个函数指针 ,即用户自己定义的比较方法(函数)

三:冒泡排序

(1):什么是冒泡排序?

冒泡排序是一种比较简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换

(2)图解:

四:回调函数

(1):什么是回调函数?

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

简单的讲,回调函数就是在特定条件下通过函数指针在另外一个函数的内部调用函数。

(2)回调函数的意义

我们可以看下面这个例子:

如果我们有四个函数,用来进行整形的加减乘除,我们在另外一个函数中需要这些功能,如果我们不使用函数指针,就需要写多个判断语句来进行选择,这样不够简洁,也不利于后续代码的维护。如果我们将函数指针作为参数传入,功能函数就可以通过修改参数来调用对应的函数,而它本身不用做任何的修改。

五:创建源文件和头文件

头文件:cmp.h用来包含一些必要的头文件,声明函数或者定义结构体。

源文件:cmp.c用来定义一些比较方法(函数)

bubble_sort.c用来定义主体函数bubbleSort(),进行相关的测试。

六:函数的具体实现

(1):函数bubblesort( )

void bubbleSort(void* s, int sz,int size,int(*cmp)(const void*e1,const void*e2))

bubblesort( )函数参数意义和qsort()函数一致

函数定义:

(2):比较方法(函数)

比较函数是由使用者自己定义的,也就是说使用者知道要比较的数据类型,我们需要接收数组两个元素的首地址,这个时候地址的类型是空类型,我们不能直接解引用,需要进行强制类型转换,如果第一个元素大于第二个元素,我们返回大于0的数,否则返回小于0的数。

int类型的比较方法:

char类型的比较方法:

结构体类型的比较方法(包含字符串类型的比较方法,注意包含头文件string.h):

(3)怎么找到函数元素的首地址?

函数内部已经传入了数组单个元素类型的大小,我们可以将传人的地址强制类型转换成(char*),这样我们就可以通过((char*)s + size*j)((char*)s + size*(j+1))找到第j个和第j+1个元素的首地址,然后进行比较。


图解:

(4):怎么进行交换?

我们前面已经提到了char*强制转换,我们可以将元素的一个个字节进行交换,循环的条件由sz决定。

图解:

 

函数定义:  

七:全部代码

(1)cmp.h

#include <stdio.h>
#include <string.h>
//字符比较
int cmp_char(const void* e1, const void* e2);
//整形比较
int cmp_int(const void* e1, const void* e2);
//结构体定义
struct stu {
	char name[10];
	int age;
};
//结构体内部成员比较
int cmp_stu_name(const void* e1, const void* e2);
//结构体内部成员比较(字符串比较)
int cmp_stu_age(const void* e1, const void* e2);

(2)cmp.c

#include "cmp.h"

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

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

int cmp_stu_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}

int cmp_stu_age(const void* e1, const void* e2)
{
	return (((struct stu*)e1)->age - ((struct stu*)e2)->age);
}


(3)bubble_sort.c

#include "cmp.h"
void swap(void* s1, void* s2,int size)
{
	for (int i = 0; i < size; i++)
	{
		char c = 0;
		c = *((char*)s1+i);
		*((char*)s1+i) = *((char*)s2+i);
		*((char*)s2+i) = c;
	}
}

void bubbleSort(void* s, int sz,int size,int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			//如果比较方法(函数)返回的值大于0,就进行交换
			if (cmp(((char*)s + size*j), ((char*)s + size*(j+1)))>0)
			{
				//进行交换
				swap(((char*)s + size * j), ((char*)s + size * (j + 1)),size);
			}
		}
	}
}

//进行测试
void text1()
{
	int a[7] = { 0,1,8,4,6,3,5 };
	int sz = sizeof(a) / sizeof(a[0]);
	bubbleSort(a, sz, sizeof(a[0]), cmp_int);
	for (int i = 0; i < sz; i++)
		printf("%d ", a[i]);

	char str[4] = "aBDA";
	int sz = sizeof(str) / sizeof(str[0]);
	bubbleSort(str, sz, sizeof(str[0]), cmp_char);
	for (int i = 0; i < sz; i++)
		printf("%c ", str[i]);

	struct stu s1[] = {{"zhangsan",30},{"lisi",16} ,{"wangwu",26}};
	int sz = sizeof(s1) / sizeof(s1[0]);
	bubbleSort(s1, sz, sizeof(s1[0]), cmp_stu_name);
	for (int i = 0; i < sz; i++)
		printf("%s %d", s1[i].name, s1[i].age);
}

int main()
{
	text1();
	return 0;
}

八:结语

到此我们就完成了qsort()函数的模拟实现,大家也可以根据自己的需求设计更多比较方法(函数),相信各位对指针函数和回调函数有了更加清晰的认识,也希望各位能够评论指出我的不足,让我们共同进步,end!

有关模拟实现qsort函数(C指针进阶)的更多相关文章

  1. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  2. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  3. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  6. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  7. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐