C语言之指针(4)使用并模拟实现qsort

冒泡排序有局限性,实现时间长而且只能进行整型数据的排序,接下来介绍模拟实现qsort来方便实现各种数据的排序。

函数基本形式:

可以看到该函数有四个参数,第四个参数是一个函数指针,这个指针指向的函数第一个参数和第二个参数都是const void*,所以之后要调用这个函数的时候,第四个参数应该传一个函数的地址过去。base是一个指针,指向的是待排序数组的第一个元素,num是base指向的元素的个数,size是base指向待排序元素数组的大小。在冒泡排序中,由于类似与结构体这样的数据不能直接比较大小,所以冒泡排序只能比较整型数据,那么现在就可以统一写一个函数,把两个元素的比较方法封装成函数,然后把函数的地址传给qsort函数中的函数指针,也就是第四个变量。如果传过来的是整型就按照整型的方式来比较,如果是字符串就按照字符串的方式来比较,如果是结构体就按照结构体的方式来比较。qsort函数也是用这个思想,所以这第四个参数就是指向这个比较函数的指针。

使用方式:

 现在就要写这个比较函数。我已经明确知道自己要排序整型,所以我就写一个比较整型大小的函数就行了,而且这个函数的两个参数要和qsort函数里面那个函数指针所指向的函数的参数类型一致,也就是const void*。这个函数就大概长这样:

然后这个函数的指针就应该是待比较两个元素的地址。然后qsort还有要求,这两个指针指向的元素所比较出来的结果,如果p1>p2,返回一个大于0的数字,如果p1<p2,返回一个小于0的数字,如果p1=p2,返回0。另外,这个比较函数在编译比较代码时并不能直接去解引用然后比较,因为void*类型的指针是无具体类型的指针,不能直接解引用,也不能进行加减乘除的运算。但是我们在写这个函数时是非常肯定指针指向的类型是整型,所以我希望从p1、p2这两个位置向后找到整型数据,那么直接强制类型转换就好了。比较函数大概就这样:

综上,代码完成再打印一下:

#include <stdio.h>
#include <stdlib.h>

void Print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int com_int(const void* p1, const void* p2)
{
	if (*(int*)p1 > *(int*)p2)
	{
		return 1;
	}
	if (*(int*)p1 < *(int*)p2)
	{
		return -1;
	}
	if (*(int*)p1 = *(int*)p2)
	{
		return 0;
	}
}
int main()
{
	int arr[] = { 10,2,3,6,7,4,8,9,5,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), com_int);

	Print_arr(arr, sz);

	return 0; 
}

接下来使用qsort排序一下结构体数据:首先创建一个结构体变量并创建一个结构体数组用来存放结构体元素:

struct Stu
{
	char name[20];
	int age;
};
void test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
}

然后就是利用qsort来排序,前面三个正常传参,关键是第四个参数,第四个参数。此时用qsort函数排序的是结构体数据,那么就是传结构体元素比较的函数。问题来了,怎么比较两个结构体的大小?这个结构体有两种数据,一种是名字,一种是年龄,所以可以按照名字来排序,也可以按照年龄大小来排序。首先按照名字来排序的话:

现在又是和上面一样的问题,不能直接比较大小,而且p1和p2接收的结构体类型的地址,所以它是个结构体类型的指针,所以要强制类型转换为结构体类型的指针,然后字符串的比较用strcmp函数来比较 ,而且这个函数的返回值也是>0、<0、=0的,所以可以直接返回。最后把这个比较函数的函数名传给qsort就好了。补充一点:strcmp比较的是字符串每个字符对应位置的ASCII码值的大小,例如abcdef和abd,前面两个a对应a,b对应b一样大,所以就接下去比较,d的ASCII码值比c的大,所以abd>abcdef。最后代码的样子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
	char name[20];
	int age;
};

int com_stu_by_name(void* p1, void* p2)
{
	return  strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), com_stu_by_name);
}
int main()
{
	//test1();
	test2();
	return 0; 
}

最后调试一下看看结果:

可以说非常的完美。 

接下来按照年龄来排序,那么上面这个函数就用不了了,就得重写一个新的函数,那么就只需要把结构体里面的年龄元素做差即可:

 

完美。以上就是qsort的使用。

接下来就是模仿qsort函数来实现一个利用冒泡排序的方式排序任意数据的函数。回顾一下冒泡排序的写法:

void bubble_sort(int arr[], int sz)
{
	int i = 0, j = 0;
	for (i = 0; i < sz; i++)
	{
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

首先如果想要排序其他数据,那么函数的第一个参数,这个数组就不可以是整型变量。对比一下qsort函数,它的参数是void*类型的,是无具体类型的指针,可以接收任意类型的地址,这个地址是排序元素的起始地址。那么可以模仿qsort也写一个void*的指针。在排序时还得知道元素的个数,元素个数也不会是负数,所以用size_t去定义。有了元素个数,但是并不知道元素的大小,就是多少个字节,所以又需要第三个变量来得知元素大小。最后,不同的数据比较方法也不一样,那么可以抽象成一个函数指针,这个指针指向的函数有两个参数,指向待比较的两个元素的地址,返回类型是int。修改一下就是:

接下来就是冒泡排序函数的内部。首先就是比较两个元素之间大小关系时,数据不同比较方式不同,但是有了第四个参数的函数指针,就可以利用这个指针来进行大小的判断。此时这个指针指向那个比较函数,那么使用这个指针相当于调用这个函数,那么就需要把需要排序的两个值传个这个函数,如果返回值是>0的,交换,以此类推,接下来就是要把两个元素的地址传给这个函数,这一步非常关键。

假设有个数组叫arr[ ],在比较时,第一次可能比较arr[1]和arr[2],第二次可能比较arr[3]和arr[4],但是对于这个冒泡排序函数来说,没有所谓的arr[0]或[1]的概念,只有起始位置void* base,那么就还需要进行指针的运算以此到达下一个元素,此时第三个参数就起作用了,假设数据是整型的,第三参数是4,我此时想跳过四个字节从arr[0]到arr[1],那么应该是base+4,为了保证它确实跳过四个字节,可以把base强制类型转换为char*类型的指针,如果要跳到arr[2],就应该加8,也就是4*2,抽象一下就是要跳过j个字节,就是j*width。所以原先冒泡排序内部if语句就可以写成:

接下来就是交换元素:交换的元素是上面传过去的地址指向的元素,可以考虑再写一个Swap函数来完成交换,就直接把那两个地址传过去最简单。由于传过去的值已经被强制类型转换为char*的指针了,所以Swap的两个形参就可以直接写成char*类型的。但是仅仅把这两个地址传过去貌似不够,因为是要交换这两块空间的内容的,但是没有说明交换空间的长度,这样可能交换不完全或者换错了。然后就写个循环,把整块空间的地址给换了,整型就四个字节算整块,字符型就一个字节算整块。所以循环的次数要小于width就是字节宽度。

总结一下,代码就是这样:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Swap(char* buf1, char* buf2,size_t,width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		buf2 = tmp;
		buf1++;
		buf2++;
	}

}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
	int i = 0, j = 0;
	for (i = 0; i < sz; i++)
	{
		for (j = 0; j < sz - i - 1; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				
			}
		}
	}
}

 很难这个,吸收消化,多点耐心。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/518518.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据分析——数据规范化

数据规范化是数据分析中的一个重要步骤&#xff0c;其目的在于确保数据的一致性和可比性&#xff0c;提高数据质量和分析结果的准确性。以下是一些数据规范化的常见方法和技术&#xff1a; 数据清洗&#xff1a;此步骤主要清除数据中的重复项、空格、格式错误等&#xff0c;确…

【Oracle】oracle、mysql、sql server三者区别

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Oracle》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识…

Waifu2x:使用深度卷积神经网络的动漫风格艺术的图像超分辨率

Github网址&#xff1a;nagadomi/waifu2x&#xff1a;动漫风格艺术的图像超分辨率 (github.com) 该项目主要讲述的是如何利用预训练的深度学习模型来达到无损扩大收缩和去噪&#xff0c;对于一般训练图像的小伙伴应该很清晰图像经常要通过resize操作固定大小&#xff0c;然后c…

操作系统① —— 进程管理

1. 进程、线程、协程 进程&#xff1a; 是系统进行资源分配的基本单位私有地址空间&#xff0c;私有栈、堆上下⽂切换需要切换虚拟地址空间 线程&#xff1a; 是资源调度的基本单位公有同⼀地址空间&#xff0c;公有堆、私有栈上下⽂切换只需要切换少量寄存器 进程和线程的对比…

Oracle APEX 23.2版本 使用应用程序工作副本进行协作开发

现状描述&#xff1a; 当前APEX协作开发都是在同一应用程序下进行的&#xff0c;这样做有可能因同一时间对同一数据进行操作造成锁表或其他问题&#xff0c;Oracle APEX23.2版本迭代后新增了部分功能&#xff0c;可以创建应用程序的工作副本来修复错误、添加功能&#xff0c;然…

趣学前端 | 综合一波CSS选择器的用法

背景 最近睡前习惯翻会书&#xff0c;重温了《HTML5与CSS 3权威指南》。这本书&#xff0c;分上下两册&#xff0c;之前读完了上册&#xff0c;下册基本没翻过。为了对得起花过的每一分钱&#xff0c;决定拾起来近期读一读。 CSS 选择器 在CSS3中&#xff0c;提倡使用选择器…

大模型生成RAG评估数据集并计算hit_rate 和 mrr

文章目录 背景简介代码实现公开参考资料 背景 最近在做RAG评估的实验&#xff0c;需要一个RAG问答对的评估数据集。在网上没有找到好用的&#xff0c;于是便打算自己构建一个数据集。 简介 本文使用大模型自动生成RAG 问答数据集。使用BM25关键词作为检索器&#xff0c;然后…

AI图片智能选区抠像解决方案

高质量的图片处理往往依赖于繁琐的手动操作&#xff0c;耗费大量时间与精力。美摄科技推出了一款革命性的AI图片智能选区抠像解决方案&#xff0c;旨在帮助企业轻松实现图片的高效处理&#xff0c;提升内容创作效率与质量。 美摄科技的AI图片智能选区抠像解决方案&#xff0c;…

An Aspect-Based Engine

GPU Pro 译&#xff1a; By 王钰涵 2024 4.14 10.1 Introduction&#xff08;简介&#xff09; 引擎的定义在整个行业中有所不同。在最基本的层面上&#xff0c;该术语描述了一个代码库&#xff0c;它在多个项目中提供共同的功能。其目的是分享开发这些功能所需的资源成本…

知网参考文献引用格式转latex中BibTex-Python操作

处理思路 参考 处理步骤&#xff1a; &#xff08;单条处理&#xff1a;&#xff09; 1、选知网NoteExpress格式的2-7行复制信息 2、新建一个文本文件&#xff0c;命名为cite.txt&#xff0c;把知网所复制信息粘贴进来 &#xff08;txt文件保存编码ANSI可行&#xff09; 3、…

GD32F470_TTP224 4路 电容式 触摸开关 数字触摸传感器模块移植

2.8 TTP224触摸传感器 该模块是一个基于触摸检测IC(TTP223B)的电容式点动型触摸开关模块。常态下&#xff0c;模块输出低电平&#xff0c;模式为低功耗模式&#xff1b;当用手指触摸相应位置时&#xff0c;模块会输出高电平&#xff0c;模式切换为快速模式;当持续12秒没有触摸时…

C#智慧手麻系统源码 医院手术麻醉系统源码 支持三甲医院评级需求 可提供演示

C#智慧手麻系统源码 医院手术麻醉系统源码 支持三甲医院评级需求 可提供演示 手术麻醉管理系统是应用于医院手术室、麻醉科室的计算机软件系统。该系统针对整个围术期&#xff0c;对病人进行全程跟踪与信息管理&#xff0c;自动集成病人HIS、LIS、RIS、PACS信息&#xff0c;采…

吃豆豆 经典的区间DP 好题典题

这里很巧妙的注意一点是&#xff0c;你最后要把所有的豆子都吃掉&#xff0c;所以你只要看你多增加的尽量的少就好了 然后维护一段区间&#xff0c;表示的是吃掉这段区间里面的所有豆子的最小代价&#xff0c;然后发现最后一个是左端点或者右端点 你吃一段新的区间的同时会把…

c++的学习之路:11、string(3)

昨天写string的时候没有说全&#xff0c;这里就开始接着讲。 目录 一、resize 二、insert 三、erase 一、resize 昨天说这个的时候没有考虑到缩小范围时咋处理&#xff0c;然后发现报错了&#xff0c;接着我调试发现缩小就不能正常执行了&#xff0c;因为用的是strcap所以…

有关字符串算法

例题一 解法&#xff1a; 算法思路&#xff08;两两⽐较&#xff09;&#xff1a; 我们可以先找出前两个的最⻓公共前缀&#xff0c;然后拿这个最⻓公共前缀依次与后⾯的字符串⽐较&#xff0c;这样就可以找出所有字符串的最⻓公共前缀。 例题二 解法&#xff08;中⼼扩散&am…

UNIAPP(小程序)每十个文章中间一个广告

三十秒刷新一次广告 ad-intervals"30" <template><view style"margin: 30rpx;"><view class"" v-for"(item,index) in 100"><!-- 广告 --><view style"margin-bottom: 20rpx;" v-if"(inde…

win10电脑无线网卡优化

近期win10会频繁断网&#xff0c;无任何规律。目前整理搜索后使用以下两种方法优化网卡&#xff0c;更改配置后断网问题得到有效改善。 方法一&#xff1a;在【电源管理】中取消勾选【允许计算机关闭此设备以节约电源】 方法二&#xff1a;【Preferred enable】修改为prefer 5…

R语言数据操纵:常用函数

这篇文章主要介绍R语言中处理循环&#xff0c;排序&#xff0c;总结重要信息的常用函数。 处理循环的函数 lapply函数 这个函数就是俗称的一句话循环函数&#xff0c;不同于while循环或者for循环&#xff0c;这个函数可以实现一句话就是一个循环的效果。 具体格式为lapply(…

C语言数据结构专题--顺序表(1基础)

前言 我们在对C语言有一定的了解之后&#xff0c;我们就可以开始数据结构的学习了&#xff0c;数据结构多用指针、结构体、动态内存开辟等知识&#xff0c;若对这些知识还不太了解的朋友&#xff0c;就需要加深其理解了&#xff0c;那么废话不多说&#xff0c;我们正式开始本节…

36.基于CAS实现的java类

JUC, java.util.concurrent并发工具包下。 1.原子整数 AtomicInteger AtomicLong AtomicBoolean 底层用的CAS来实现。 AtomicInteger类的incrementAndGet方法&#xff0c;addAndGet方法 public static void main(String[] args) {AtomicInteger atomicInteger new Atom…