qsort函数使用方法总结

目录

一、qsort函数原型

二、compar参数

三、各种类型的qsort排序

1. int 数组排序 

2. 结构体排序 

3. 字符串指针数组排序 

4. 字符串二维数组排序

四、回调函数

1. 什么是回调函数

2. 为什么要用回调函数?

3. 怎么使用回调函数?

4.下面是一个四则运算的简单回调函数例子:

五、qsort函数的模拟实现

一、qsort函数原型

void qsort( void *base,
            size_t nmemb,
            size_t size,
            int (*compar)(const void *, const void *)
          );

头文件:<stdlib.h> 函数功能:qsort()函数的功能是对数组进行排序,数组有nmemb个元素,每个元素大小为size。 

参数base - base指向数组的起始地址,通常该位置传入的是一个数组名 

参数nmemb - nmemb表示该数组的元素个数 

参数size - size表示该数组中每个元素的大小(字节数) 

参数(*compar)(const void *, const void *) - 此为指向比较函数的函数指针,决定了排序的顺序。 

函数返回值:无 

注意:如果两个元素的值是相同的,那么它们的前后顺序是不确定的。也就是说qsort()是一个不稳定的排序算法。

二、compar参数

compar参数是qsort函数排序的核心内容,它指向一个比较两个元素的函数,注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型,见下文。

int compar(const void *p1, const void *p2);

如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的前面,如果compar返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序不确定,如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的后面。因此,如果想让qsort()进行从小到大(升序)排序,那么一个通用的compar函数可以写成这样: 

int compare (const void * a, const void * b)
 {
   if ( *(MyType*)a <  *(MyType*)b ) return -1;
   if ( *(MyType*)a == *(MyType*)b ) return 0;
   if ( *(MyType*)a >  *(MyType*)b ) return 1;
 }

注意:你要将MyType换成实际数组元素的类型。 或者

//升序排序
int compare(const void* a, const void* b)
{
	return (*(int*)a - *(int*)b);
}
//降序排列
int compare(const void* a, const void* b)
{
	return (*(int*)b - *(int*)a);
}

三、各种类型的qsort排序

1. int 数组排序 

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

int values[] = { 40, 10, 100, 90, 20, 25 };

int compare(const void* a, const void* b)
{
    return (*(int*)a - *(int*)b);
}

int main()
{
    int n;
    qsort(values, sizeof(values) / sizeof(values[0]), sizeof(int), compare);
    for (n = 0; n < sizeof(values) / sizeof(values[0]); n++)
        printf("%d ", values[n]);
    return 0;
}

2. 结构体排序 

#include <stdio.h>
#include<stdlib.h>
// void qsort(void* base, size_t num, size_t size, int(*compare)(const void*, const void*))

typedef struct
{
    char name[30];   // 学生姓名
    int Chinese;    // 语文成绩
    int Math;        // 数学成绩  
    int English;     // 英语成绩
}st;
int cmp(const void* a, const void* b)
{
    st* pa = (st*)a;
    st* pb = (st*)b;
    int num1 = pa->Chinese + pa->English + pa->Math;
    int num2 = pb->Chinese + pb->English + pb->Math;

    //return (int)num1 - num2;   // 从小到大,
    return (int)num2 - num1;   //  从大到小
}
int main(void)
{
    st students[7] = {
        {"周",97,68,45},
        {"吴",100,32,88},
        {"郑",78,88,78},
        {"王",87,90,89},
        {"赵",87,77,66},
        {"钱",59,68,98},
        {"孙",62,73,89}
    };
    qsort(students, 7, sizeof(st), cmp);   // 注意区别 students 与 st

    for (int i = 0; i < 7; i++)
    {
        printf("%s%4d%4d%4d\t", students[i].name, students[i].Chinese, students[i].Math, students[i].English);
        printf("总分:%d\n", students[i].Chinese + students[i].English + students[i].Math);
    }

    system("pause");
    return 0;
}

3. 字符串指针数组排序 

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

int compare(const void* arg1, const void* arg2);

int main(int argc, char** argv)
{
    int i;

    char* arr[5] = { "i", "love", "c", "programming", "language" };

    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(char*), compare);

    for (i = 0; i < 5; i++) {
        printf("%s ", arr[i]);
    }
    printf("\n");

}

int compare(const void* arg1, const void* arg2) {
    char* a = *(char**)arg1;
    char* b = *(char**)arg2;
    int result = strcmp(a, b);
    if (result > 0) {
        return 1;
    }
    else if (result < 0) {
        return -1;
    }
    else {
        return 0;
    }
}

那么我们向qsort传入arr之后,qsort将arr理解为指向数组中第一个元素的指针,所以形参表中,arg1和arg2其实是指向“指向常量字符串的指针”的指针,是char**。而我们需要传给strcmp这个字符串比较函数的,是“指向字符串的指针”,是char*,所以我们将void*转换为char**,然后解引用,得到char*,赋予a和b。接下来使用strcmp对a和b进行比较。(数组名本身算一层指针,而里面的内容又是一层指针,数组存放的是指向字符串的地址) 

4. 字符串二维数组排序

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

int compare(const void* arg1, const void* arg2);

int main(int argc, char** argv)
{
    int i;

    char arr[5][16] = { "i", "love", "c", "programming", "language" };

    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare);
    printf("%s\n", arr[0]);
    for (i = 0; i < 5; i++) {
        printf("%s ", arr[i]);
    }
    printf("\n");
}

int compare(const void* arg1, const void* arg2) 
{
    char* a = (char*)arg1;
    char* b = (char*)arg2;
    int result = strcmp(a, b);
    if (result > 0) {
        return 1;
    }
    else if (result < 0) {
        return -1;
    }
    else {
        return 0;
    }
}

这里对二维数组进行排序,其实是对二维数组的第二维中存放的字符串进行排序。所以qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]), compare);对qsort函数的调用中,第二个参数是待排元素的个数(5个),第三个参数是待排元素的大小(16)。

我们将arr传入qsort函数,qsort函数将arr理解为指向数组第一个元素的指针,arr的第一个元素是arr[0][0],所以参数arg1和arg2指的是指向"a[i][0]"的指针,我们知道,a[i][0]是字符,就是char,所以arg1和arg2指的是char *。我们将void*转换为char*,赋予a和b,调用strcmp函数对a和b进行比较。

四、回调函数

1. 什么是回调函数

我们先来看看百度百科是如何定义回调函数的:

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

这段话比较长,也比较绕口。下面我通过一幅图来说明什么是回调:

假设我们要使用一个排序函数来对数组进行排序,那么在主程序(Main program)中,我们先通过库,选择一个库排序函数(Library function)。但排序算法有很多,有冒泡排序,选择排序,快速排序,归并排序。同时,我们也可能需要对特殊的对象进行排序,比如特定的结构体等。库函数会根据我们的需要选择一种排序算法,然后调用实现该算法的函数来完成排序工作。这个被调用的排序函数就是回调函数(Callback function)。

结合这幅图和上面对回调函数的解释,我们可以发现,要实现回调函数,最关键的一点就是要将函数的指针传递给一个函数(上图中是库函数),然后这个函数就可以通过这个指针来调用回调函数了。注意,回调函数并不是C语言特有的,几乎任何语言都有回调函数。在C语言中,我们通过使用函数指针来实现回调函数。

我的理解是:把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调

如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2. 为什么要用回调函数?

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。

简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

int Callback()    ///< 回调函数
{
    // TODO
    return 0;
}
int main()     ///<  主函数
{
    // TODO
    Library(Callback);  ///< 库函数通过函数指针进行回调
    // TODO
    return 0;
}

回调似乎只是函数间的调用,和普通函数调用没啥区别。

但仔细看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。

这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。

3. 怎么使用回调函数?

#include <stdio.h>
int Callback_1(int a)   ///< 回调函数1
{
    printf("Hello, this is Callback_1: a = %d \n", a);
    return 0;
}

int Callback_2(int b)  ///< 回调函数2
{
    printf("Hello, this is Callback_2: b = %d \n", b);
    return 0;
}

int Callback_3(int c)   ///< 回调函数3
{
    printf("Hello, this is Callback_3: c = %d \n", c);
    return 0;
}

int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{
    Callback(x);
}

int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

看看结果:

 

如上述代码:可以看到,Handle()函数里面的参数是一个指针,在main()函数里调用Handle()函数的时候,给它传入了函数Callback_1()  /  Callback_2()  /  Callback_3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。

4.下面是一个四则运算的简单回调函数例子:

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

/****************************************
 * 函数指针结构体
 ***************************************/
typedef struct _OP {
    float (*p_add)(float, float);
    float (*p_sub)(float, float);
    float (*p_mul)(float, float);
    float (*p_div)(float, float);
} OP;

/****************************************
 * 加减乘除函数
 ***************************************/
float ADD(float a, float b)
{
    return a + b;
}

float SUB(float a, float b)
{
    return a - b;
}

float MUL(float a, float b)
{
    return a * b;
}

float DIV(float a, float b)
{
    return a / b;
}

/****************************************
 * 初始化函数指针
 ***************************************/
void init_op(OP* op)
{
    op->p_add = ADD;
    op->p_sub = SUB;
    op->p_mul = &MUL;
    op->p_div = &DIV;
}

/****************************************
 * 库函数
 ***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
    return (*op_func)(a, b);
}

int main(int argc, char* argv[])
{
    OP* op = (OP*)malloc(sizeof(OP));
    init_op(op);

    /* 直接使用函数指针调用函数 */
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2),
        (op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));

    /* 调用回调函数 */
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n",
        add_sub_mul_div(1.3, 2.2, ADD),
        add_sub_mul_div(1.3, 2.2, SUB),
        add_sub_mul_div(1.3, 2.2, MUL),
        add_sub_mul_div(1.3, 2.2, DIV));

    return 0;
}

五、qsort函数的模拟实现

使⽤回调函数,模拟实现qsort(采⽤冒泡的⽅式)。

注意:这里第⼀次使用 void* 的指针,讲解 void* 的作⽤。

#include <stdio.h>

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

void 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;
	//趟
	for (i = 0; i < sz - 1; i++)
	{
		//每一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//if (arr[j] > arr[j + 1])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				/*int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;*/
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

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

void test()
{
	//设计和实现bubble_sort2(),这个函数能够排序任意类型的数据
	int arr[] = { 3,1,5,7,9,2,4,0,8,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

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

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

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

相关文章

皮肤性病科专家谭巍主任提出HPV转阴后饮食七点建议

HPV转阴是每一位感染者都期盼的&#xff0c;因为转阴所以健康&#xff0c;只有转为阴性才意味着不具备传染性&#xff0c;从此也不必再害怕将病毒传染给家人的风险&#xff0c;也不必再担忧持续感染而引发的健康风险。总之&#xff0c;HPV转阴是预示感染者恢复健康与否的主要标…

不知道如何制作产品图册的,赶紧收藏住!

产品图册是展示产品外观、功能和特点的重要工具&#xff0c;对于销售和推广产品至关重要。然而&#xff0c;制作一本高质量的产品图册并不是一件容易的事情。如果你没有经验或者不确定如何着手&#xff0c;那么这篇文章将为你提供一些实用的建议和技巧&#xff0c;帮助你轻松制…

AtCoder Beginner Contest 329 题解A~F

A - Spread 输入字符串&#xff0c;字符之间加上空格输出 B - Next 输出数组当中第二大的数 C - Count xxx 统计每个字符出现过的最长长度&#xff0c;再累加即可 #include<bits/stdc.h> #pragma GCC optimize("Ofast") #define INF 0x3f3f3f3f #define I…

Flutter 中数据存储的四种方式

在 Flutter 中&#xff0c;存储是指用于本地和远程存储和管理数据的机制。以下是 Flutter 中不同存储选项的概述和示例。 Shared Preferences&#xff08;本地键值存储&#xff09; Shared Preferences 是一种在本地存储少量数据&#xff08;例如用户首选项或设置&#xff09…

漂亮的pyqt6皮肤 PyOneDark_Qt_Widgets_Modern_GUIPublic

大家先看看界面图&#xff0c;真的很漂亮&#xff1a; github地址&#xff1a;GitHub - Wanderson-Magalhaes/PyOneDark_Qt_Widgets_Modern_GUI 作者还录了教程&#xff1a; TUTORIALS: Tutorial 01: https://youtu.be/QQGlTGYCMg0 Tutorial 02: https://youtu.be/LwKre2proDk…

GMEL:基于地理上下文嵌入的OD流预测

1 文章信息 文章题为“Learning Geo-Contextual Embeddings for Commuting Flow Prediction”&#xff0c;是一篇发表于The Thirty-Seventh AAAI Conference on Artificial Intelligence (AAAI-20)的一篇论文。该论文主要针对交通中OD流预测任务&#xff0c;从地理上下文信息中…

【机器学习基础】正则化

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ ⭐特别提醒&#xff1a;针对机器学习&#xff0c;特别开始专栏&#xff1a;机器学习python实战 欢迎订阅&am…

鸿蒙:实现两个Page页面跳转

效果展示 这篇博文在《鸿蒙&#xff1a;从0到“Hello Harmony”》基础上实现两个Page页面跳转 1.构建第一个页面 第一个页面就是“Hello Harmony”&#xff0c;把文件名和显示内容都改一下&#xff0c;改成“FirstPage”&#xff0c;再添加一个“Next”按钮。 Entry Compone…

⑩⑥ 【MySQL】详解 触发器TRIGGER,协助 确保数据的完整性,日志记录,数据校验等操作。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 触发器 ⑩⑥ 【MySQL】触发器详解1. 什么是触发…

在前端开发中,什么是CDN(Content Delivery Network)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

基于金鹰算法优化概率神经网络PNN的分类预测 - 附代码

基于金鹰算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于金鹰算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于金鹰优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

牛客网刷题笔记三 寻找第K大+两数之和+合并两个排序的链表+用两个栈实现队列

算法题牛客网NC88 寻找第K大 题目&#xff1a; 思路就是做个排序&#xff0c;要求时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)&#xff0c;因此选用快排。代码&#xff1a; class Solution:def quickSort(self, a, start, end):if start > end:returnval a[start]…

测试老鸟总结,Web/APP与接口测试测试流程总结,避背黑锅...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、web测试流程 …

集合框架面试题

一、集合容器的概述 1. 什么是集合 集合框架&#xff1a;用于存储数据的容器。 集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。 任何集合框架都包含三大块内容&#xff1a; 对外的接口、接口的实现和对集合运算的算 法。 接口&#xff1a;表示集合的抽象数据…

量化交易:借助talib使用技术分析指标

什么是技术分析&#xff1f; 所谓股票的技术分析&#xff0c;是相对于基本面分析而言的。基本分析法着重于对一般经济情况以及各个公司的经营管理状况、行业动态等因素进行分析&#xff0c;以此来研究股票的价值&#xff0c;衡量股价的高低。而技术分析则是透过图表或技术指标…

低代码在ERP中的理解与应用:提升开发效率与业务灵活性

企业资源规划&#xff08;ERP&#xff09;指通过融合不同部门的信息和流程&#xff0c;提升企业效率、融洽运营的管理体系。ERP系统通过提供一套集成化应用程序&#xff0c;助力企业管理工作流程&#xff0c;包含选购、库存、销售、生产规划等。 低代码&#xff08;Low-Code&a…

网页视频下载工具 iTubeGo mac中文版软件特色

iTubeGo YouTube Downloader mac是一款功能强大的YouTube视频下载工具。 iTubeGo YouTube Downloader mac软件特色 多种格式支持&#xff1a;iTubeGo YouTube Downloader可以将YouTube视频下载为多种常见的视频和音频格式&#xff0c;包括MP4、MP3、AVI、FLV、MOV、WMV等&…

基于猕猴Spike运动解码的不同解码方法性能对比

公开数据集中文版详细描述 参考前文&#xff1a;https://editor.csdn.net/md/?not_checkout1&spm1011.2124.3001.6192神经元Spike信号分析 参考前文&#xff1a;https://blog.csdn.net/qq_43811536/article/details/134359566?spm1001.2014.3001.5501神经元运动调制分析 …

心怀祖国放眼世界 爱国人士华国中应邀参加美国旧金山2023(APEC)峰会

据相关媒体美国旧金山报道:2023亚太经合组织&#xff08;APEC&#xff09;领导人非正式会议将于11月15日至17日在美国旧金山召开。11月11日&#xff0c;本年度APEC高级财政官员和部长会晤在旧金山率先启动&#xff0c;APEC CEO峰会将于11月14日至16日开幕。著名爱国人士、亚太一…

HR人才测评,提高招聘效率降低用人风险

随着社会的不断进步&#xff0c;越来越多的企业在人力资源管理中&#xff0c;引入人才测评工具。人才是构成一个企业的基础&#xff0c;是企业不断发展的保障&#xff0c;同时&#xff0c;人才也是一个企业的核心竞争力之一。所以&#xff0c;人才的素质对一个企业至关重要。现…