C语言进阶指针(3) ——qsort的实现

news2025/1/13 13:44:36

大家好,我们今天来学习回调函数qsort的实现。

在这里插入图片描述
首先让我们打开cplusplus.com找到qsort函数。

在这里插入图片描述
我们看到这个函数就可以看到它的头文件和参数信息。

#include<stdlib.h>
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));

让我们看看它的具体参数信息:
void* base:待排序数组第一个元素的地址
size_t num:待排序数组的元素个数
size_t size:待排序数组中一个元素的大小单位是字节
int (compar)(const void,const void*)):这里是一个函数指针compar,指向的是一个比较的函数,是用来比较两个元素的,里面要放置的是要比较的两个元素的地址

我们要学习这个函数的话,我们首先就得来复习下冒泡排序法,这里大家可以参考我之前解析冒泡排序法的博客,我这里就不给大家一 一介绍了。https://editor.csdn.net/md/?articleId=132266676访问这个网址就可以看到我之前写的关于冒泡排序法的博客了。

我们为什么学会了冒泡排序法依然qsort排序函数呢,那是因为这个函数的优点,它既可以给数组排序也可以给结构体进行排序,但是排序时要注意:

  1. 排序整型数组, 两个整型可以直接使用>比较
  2. 排序结构体数组,两个结构体的数据可能不能直接使用>比较也就是不同类型的数据,比较出大小,方法是有差异的

我们看到代码—>:

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}

我们在这里定义了数组arr,里面我们要给它排序成升序,我们只要用引用qsort函数就可以了,首先我们给qsort函数传参,第一个传的就是数组中第一个元素的地址,第二个就是数组中的元素多少,我们用sizeof就可以计算出来sizeof(arr)计算的是整个数组的大小,而sizeof(arr[0])计算的是每个元素的大小,两个值相除那就是这个数组元素的个数,因为这是一个整形数组,所以我们第三个传参传每个元素的大小,只要传sizeof(int)就可以了,最后传的就是一个函数指针的地址了,而函数指针中第一个指针存放的地址当然就是数组首元素的地址,第二个指针存放的就是要和第一个相比较的元素的地址了,我们都了解了,那就来看看程序运行的结果吧。

在这里插入图片描述
我们现在已经学会使用了这个函数,那么我们该怎么进行模拟这个函数的使用呢,这就是我们今天要学习的重点了,我们今天就来分享如何用qsort函数对整型数组和结构体变量进行排序。

首先我们先来模拟实现整形数组升序的实现:

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
{
	//冒泡排序的趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//一趟冒泡排序
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			
			{
				//交换
				int tmp=arr[j];
				arr[j]=arr[j+1];
				arr[j+1]=tmp;
			}
		}
	}
}

void test1()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//升序
	//排序为降序
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);}
	
int main()
{
test();
return 0;
}	

这里我们要注意的是: int (cmp)(const void e1, const void* e2)
e1是一个指针,存放了一个要比较的元素的地址 e2是一个指针,存放了一个要比较的元素的地址 e1指向的元素>e2指向的元素,返回>0的数字
e1指向的元素==e2指向的元素,返回0 e1指向的元素<e2指向的元素,返回<0的数字

我们首先定义数组,调用函数test(),就会跳转到函数test中,在这个函数中我们用sz表示数组元素的个数,求解的方法还是sizeof的老方法,我们在将它调用到函数print_arr中对原数组进行打印,再调用函数bubble_sort,在里面我们用循环嵌套进行冒泡排序进行打印就行了。

那我们怎么来实现结构体的排序呢,因为我们int (cmp)(const void e1, const void*
e2)这个函数指针中所有的指针都是void* 型,这就相当于一个垃圾桶,可以储存任意类型变量的地址,这对我们实现结构体的排序密不可分。

我们先定义个结构体变量:

struct Stu
{
	char name[20];//20
	int age;//4
};
void test2()
{
	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
	int sz = sizeof(arr) / sizeof(arr[0]);//3
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
	//整型数据/字符数据/结构体数据...
	//可以使用qsort函数对数据进行排序
	//测试bubble_sort,排序整型数据
	//test1();

	//测试bubble_sort,排序结构体的数据
	test2();
	return 0;
}

看到代码块我们发现结构体里面定义了两个类型的变量,一个是名字char型,一个是年龄int型,那么我们对它进行排序的方法就有两种,一种是按照名字排序,一种按照年龄排序。

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


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

要注意的是,我们这里指针的类型是void* 类型要强制转换。这是我们两种方法的比较函数,那我们如何进行交换呢,我们就看到我们的交换函数

void swap(char* buf1, char* buf2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

swap((char*)base + j * size, (char*)base + (j + 1) * size, size)

我们看到这里的交换函数:

(char*)base + j * size :确定要交换的第一个数据的地址
(char*)base + (j + 1) * size :确定要交换的第二个数据的地址
size :确定这俩个数据直接隔了多少个字节,方便逐字节进行操作

因为我们的base是char*的指针里面存放的是结构体变量里首元素的地址,size表示的是每个元素的大小,j * size表示的就是跳过多少个元素,而两个元素之间的交换就是字节之间的交换,所以我们算出每个每个元素的大小,指针代表的char * 型的,是一个字节,所以两个元素进行一次交换是进行一个字节之间的交换,所以我们这里只需要用一个循环就可以实现两个元素的交换了。

接下来我们就来看看完整的代码:

#include <string.h>

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//int (*cmp)(const void* e1, const void* e2)
//e1是一个指针,存放了一个要比较的元素的地址
//e2是一个指针,存放了一个要比较的元素的地址
//e1指向的元素>e2指向的元素,返回>0的数字
//e1指向的元素==e2指向的元素,返回0
//e1指向的元素<e2指向的元素,返回<0的数字
//

void swap(char* buf1, char* buf2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


//泛型编程
void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
{
	//冒泡排序的趟数
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		//一趟冒泡排序
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//if (arr[j] > arr[j + 1])
			if(cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
			{
				//交换
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}


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

void test1()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//升序
	//排序为降序
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}	

struct Stu
{
	char name[20];//20
	int age;//4
};

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


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

void test2()
{
	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
	int sz = sizeof(arr) / sizeof(arr[0]);//3
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

int main()
{
	//整型数据/字符数据/结构体数据...
	//可以使用qsort函数对数据进行排序
	//测试bubble_sort,排序整型数据
	test1();

	//测试bubble_sort,排序结构体的数据
	test2();
	return 0;

这里我们就只需调用函数test2就可以访问了,在test2中我们将所需的参数上传到函数bubble_sort中,这里面我们对两个元素进行比较,调用到函数int cmp_stu_by_age和int cmp_stu_by_name中,在循环里进行比较,传参到函数int cmp_stu_by_age(const void* e1, const voide2)和int cmp_stu_by_name(const void e1, const void* e2)中,如果返回的值大于0就传参到交换函数中进行交换,如果小于0就无需交换,顺着循环和下一个元素进行比较。

我们知道年龄是整形的比较大小非常的简单,那我们的姓名是char型的,那这个怎么比较大小并且排序呢,因为我们的名字是字符串,所以比较名字之间的大小就是比较字符串之间的大小,那么怎么比较字符串的大小呢?那就是字符串对应位置的Ascll值的大小,如果相等的话就跳到下一个字符相比较,以此类推,直到比较出大小为止。

让我们看到下面的代码来验证字符串大小的比较:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[20] = "abc";
	char arr2[20] = "abe";
	if (strcmp(arr1, arr2) > 0)
		printf(">\n");
	else
		printf("<=\n");

	return 0;
}

在这里插入图片描述
我们看到图片第一个和第二个字符的Ascll值的大小相同,而第三个位置e>c,所以打印的结果就是<=。
在这里插入图片描述
那么大家看到都应该更加深刻的了解了qsort函数的实现吧,今天的分享就到这里,谢谢大家。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前后端跨域请求问题解决方法

如图&#xff1a; 1.在config配置包中创建一个CorsConfig配置类 2.将下面代码复制到这个类中即可 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfigurati…

[字符串和内存函数]strcmp和strncmp以及memcmp的区别

CPlus中对strcmp的介绍 #include <stdio.h> #include <string.h>int main () {char key[] "apple";char buffer[80];do {printf ("Guess my favorite fruit? ");fflush (stdout);scanf ("%79s",buffer);} while (strcmp (key,buf…

MySQL 约束与复杂查询

当涉及到数据库管理系统&#xff08;DBMS&#xff09;的高级主题时&#xff0c;包括数据库的约束、表的设计以及各种类型的查询&#xff0c;特别是聚合查询、联合查询和合并查询&#xff0c;是非常重要的。这些主题可以帮助我们更好地理解数据库的内部工作机制以及如何有效地操…

[maven] 创建 spring boot 项目及使用 Jenkins 运行 maven

[maven] 创建 spring boot 项目及使用 Jenkins 运行 maven 本篇笔记走一下用 maven 创建 spring boot 项目和利用 Jenkins 管理 maven 流程 使用 maven 创建 spring boot 项目 根据官方文档说&#xff0c;现在使用 boot 需要 java 17&#xff0c;Gradle 7.5/Maven 3.5 spri…

idea无法通过vpn 连接到数据库或者kafka

本地navicate 等sql客户端 &#xff0c;通过vpn&#xff0c;可以连接到数据库&#xff0c;&#xff0c;xshell也可以通过vpn连接到远程机器&#xff0c;但是&#xff0c;idea开发时&#xff0c;报连接超时错误&#xff0c;解决方法&#xff1a; 项目中 VM这一栏加上-Djava.net…

区块链:去中心化革命下的创新与发展!

区块链作为一项重要的技术实验&#xff0c;确实具有重大的影响力。它代表了一场去中心化的运动&#xff0c;吸引了许多研究人员、工程师、建设者和用户的参与&#xff0c;创造了我们目前所见到的一些最有趣的技术。 区块链的透明特性赋予了用户对于数据和交易的控制权&#xff…

来看看Python __all__变量的用法

​ 事实上&#xff0c;当我们向文件导入某个模块时&#xff0c;导入的是该模块中那些名称不以下划线&#xff08;单下划线“_”或者双下划线“__”&#xff09;开头的变量、函数和类。因此&#xff0c;如果我们不想模块文件中的某个成员被引入到其它文件中使用&#xff0c;可以…

展示日志log4.properties

log4.properties 1.log4.properties 此时文件主要用于展示日志的输出的级别的信息。 # Set root category priority to INFO and its only appender to CONSOLE. #log4j.rootCategoryINFO, CONSOLE debug info warn error fatal log4j.rootCategoryinfo, CONSO…

本地事务与分布式事务

目录 一、本地事务 1、事务的基本性质 2、事务的隔离级别 3、事务的传播行为 4、SpringBoot 事务关键点 二、分布式事务 1、为什么有分布式事务 2、CAP 定理与 BASE 理论 3、分布式事务几种方案 一、本地事务 1、事务的基本性质 数据库事务的几个特性&#xff1a;原…

PhpStorm 2023年下载、安装教程和好用插件,保姆级教程

PhpStorm 2023年下载、安装教程和好用插件&#xff0c;保姆级教程 文章目录 PhpStorm 2023年下载、安装教程和好用插件&#xff0c;保姆级教程前言一、安装PhpStorm二、好用的插件简体中文包Chinese(Simplified)Language Pack 三、卸载插件CTRLN 查找类CTRLSHIFTN 全局搜索文件…

Scala编程语言

Scala编程语言 一、Scala引入1、学习Scala的目的2、Scala的基本概念 二、Scala环境搭建1、安装步骤2、配置环境变量3、测试Scala4、Scala与idea的集成5、关联源码6、 class 和 object 说明 三、常用语法、变量和数据类型1、 注释2、变量和常量3 、标识符的命名规范4、字符串输出…

一阶低通滤波器滞后补偿算法

一阶低通滤波器的推导过程和双线性变换算法请查看下面文章链接: PLC算法系列之数字低通滤波器(离散化方法:双线性变换)_双线性离散化_RXXW_Dor的博客-CSDN博客PLC信号处理系列之一阶低通(RC)滤波器算法_RXXW_Dor的博客-CSDN博客_rc滤波电路的优缺点1、先看看RC滤波的优缺点…

matlab EL image绘制

利用光谱仪测试的结果可以得到image&#xff0c;输出为csv文件&#xff0c;包括640X512的矩阵&#xff0c;这个矩阵将会反映器件发光的位置和强度&#xff0c;算是一个灰度图。 在matlab中&#xff0c;可以用imagesc函数来进行绘图。 imagesc函数的用法 clear; clc; close a…

Linux从root账号切换到普通账号并执行shell脚本

背景&#xff1a;最近自己在用的中间件只能运行在 普通账号下&#xff0c;现在每次重启之后都要切换到 普通账号 然后去执行启动脚本。一次两次还能接受……反正现在接受不了了…… 写一个脚本让它帮我起吧 先贴目录 新建文件 start_es.sh, 用于root账号下调用 #!/bin/bash e…

计算机组成原理——基础入门总结(一)

本帖更新一些关于计算机组成原理的重点内容。由于博主考研时并不会考这门课&#xff0c;但是考虑到操作系统中又很多重要晦涩的概念涉及很多诸如内存、存储器、磁盘、cpu乃至各种寄存器的知识&#xff0c;此处挑选一些核心的内容总结复盘一遍——实现声明&#xff1a;本帖的内容…

【CCF】第30次csp认证——202305-1重复局面

202305-1重复局面&#xff1a; 问题描述 国际象棋每一个局面可以用大小为 88 的字符数组来表示&#xff0c;其中每一位对应棋盘上的一个格子。六种棋子王、后、车、象、马、兵分别用字母 k、q、r、b、n、p 表示&#xff0c;其中大写字母对应白方、小写字母对应黑方。棋盘上无…

[ESP32 IDF+Vscode]蓝牙配网后采用上传温湿度数据至阿里云(MQTT协议)

阿里云平台的设置 参考文章&#xff1a; http://t.csdn.cn/RzLGqhttp://t.csdn.cn/RzLGq Blufi配网 1.简介 BluFi 是一款基于蓝牙通道的 Wi-Fi 网络配置功能&#xff0c;适用于 ESP32。它通过安全协议将 Wi-Fi 配置和证书传输到 ESP32&#xff0c;然后 ESP32 可基于这些信…

OpenGL超级宝典(第五版)疑难点汇总解析

《OpenGL超级宝典(第五版&#xff09;》如下&#xff1a; 1. 在该书的第1章的Block例子中用到了平面阴影投射矩阵&#xff0c;关于该矩阵的推导&#xff0c;参见&#xff1a; OpenGL: 平面阴影投射矩阵的推导 2. 在该书的第8章的pix_buffs例子中用到了正交投影矩阵的推导过程…

腾讯mini项目-【指标监控服务重构】2023-08-23

今日已办 进度和问题汇总 请求合并 feature/venus tracefeature/venus metricfeature/profile-otel-baserunner-stylebugfix/profile-logger-Syncfeature/profile_otelclient_enable_config 完成otel 开关 trace-采样metrice-reader 已经都在各自服务器运行&#xff0c;并接入…

Datagrip 下载、安装教程,详细图文,亲测有效

文章目录 前言1. 下载2. 安装3 DataGrip 常用操作4 推荐阅读 前言 DataGrip 是 JetBrains 发布的多引擎数据库环境&#xff0c;支持 MySQL 和 PostgreSQL&#xff0c;Microsoft SQL Server 和 Oracle&#xff0c;Sybase&#xff0c;DB2&#xff0c;SQLite&#xff0c;还有 Hyp…