C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)

目录

1.回调函数

1.1引入

1.2回调函数的使用,加减乘除计算器改造

2.回调函数的应用-qsort函数

实例1:整型数组排序

实例2:结构体排序 

①按照整型数据来排序,这里是按照年龄来排序

②按照名字来排序,也就是比较的是字符串

③strcmp函数的补充

3.qsort库函数的模仿实现

3.1实现问题分析

3.2元素比较分析及实现

3.3排序分析 

3.4实现及源代码

3.4.1实现1整型排序 

3.4.2.1结构体排序实现1-按照整型数据排序

3.4.2.2结构体排序实现1-按照字符串数据排序

4.结语


 

1.回调函数

1.1引入

通过前几天的分享,大家应该对函数指针等有了一定的认识,今天我们要分享的知识就是函数指针的一个经典应用——回调函数。

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

回调函数依赖于函数指针。 

1.2回调函数的使用,加减乘除计算器改造

 在上期的文章中,我们说计算器不用函数指针来实现的时候,代码冗余,除了红色部分,绿色部分的内容几乎是一样的。上期我们使用了回调函数的方法来GANKd掉了Switch语句,现在介绍了回调函数的概念,我们就想能不能这样,我们直接写一个调用函数,将加减乘除函数的地址传给调用函数,冗余的部分就只用在调用函数里写一次就可以了,我们来看一下:

我们的switch部分就可以这样:

 

就少了很多的代码冗余。

2.回调函数的应用-qsort函数

用于排序

回顾一下冒泡排序:C语言详解冒泡排序

我们写的这个冒泡排序只适合于我们的整型数据,如果我要排序结构体类型或者其他类型就做不到,所以这种排序是存在缺陷的。 

接下来我们来看一下库函数qsort

qsort函数的特点:

1.采用的快速排序的方法

2.适合于任意类型的数据的排序

 让我们具体来学习一下这个函数:

 Sort elements of array:排序数组中的元素

翻译:排序由base指向的num个元素,每个元素是size个字节长度,使用comper指针指向的这个函数去比较

 

函数的头文件为: 

#include <stdlib.h>   

函数的参数为:(size_t为无符号整型的意思)

void qsort (
void* base, 指向要被排序这个数组的第一个元素的地址。
size_t num, 要被排序的元素的数目
size_t size, 每个元素的大小
int (*compar)(const void*,const void*):函数指针类型,一个指针指向的比较函数,能够比较base指向数组中的两个元素两个指针参数,p1指向的元素的值小于P2指向的元素的值返回一个小于0的数字,p1指向的元素的值等于P2指向的元素的值返回一个0,p1指向的元素的值大于P2指向的元素的值返回一个大于0的数字
(这个比较函数要自己写)
);

接下来我们就来使用一下这个排序函数:

实例1:整型数组排序

我们对第一个参数进行解引用,我们想的是直接通过解引用来操作比较,但是却报错了,这是为什么呢?

补充:
1.void * 类型的指针是不能直接解引用操作的

2.也不能直接进行指针运算

3.void* 就是一种通用类型的指针,无具体类型的指针

这怎么理解呢,像我们平时使用这种

int  a = 10;

int *pa = &a;

这种就是平时的使用

但是如果我这样写:

char * pc = &a;这样写是可以但是会有woring,类型不兼容

但是如果写出

void *  pe = &a就没有问题,

所以void* 类型的指针可以接受任意类型的地址

在Qsort函数中,编者也不知道未来会传什么样的地址进来,或者数组或者结构体,所以就写成了void*

4.需要用的时候要强制类型转换

解决了void * 的问题过后,我们现在来看一下实例1的实现

#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);}
void print(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
void test1()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);print(arr, sz);
}
int main()
{test1();return 0;
}

关于排序结果是升序还是降序,我们自己通过这个函数11能够操作的部分只有这里

因为内部是如何实现的我们不知道。

实例2:结构体排序 

①按照整型数据来排序,这里是按照年龄来排序
struct Stu
{char name[20];int age;
};
void print2(struct Stu* p1, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%s,%d \n ", (p1+i)->name, (p1+i)->age);}
}
int  cmp_byage(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{struct Stu arr[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_byage);print2(arr, sz);}int main()
{//test1();test2();return 0;
}

 

这是升序比较结果。 

注意一点:当我们使用年龄比较的时候我们就可以使用算术运算来作为比较返回值,但是如果我们使用字符串来比较,比如使用结构体当中的名字作为比较标准,在比较函数中就需要实用strcmp来比较两个字符串。 

②按照名字来排序,也就是比较的是字符串
struct Stu
{char name[20];int age;
};
void print2(struct Stu* p1, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%s,%d \n ", (p1+i)->name, (p1+i)->age);}
}
int  cmp_byage(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int  cmp_byname(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name ,((struct Stu*)p2)->name);}
void test2()
{struct Stu arr[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };int sz = sizeof(arr) / sizeof(arr[0]);//qsort(arr, sz, sizeof(arr[0]), cmp_byage);qsort(arr, sz, sizeof(arr[0]), cmp_byname);print2(arr, sz);}int main()
{//test1();test2();return 0;
}

排序结果为:

③strcmp函数的补充

这里的字符串使用strcmp函数来比较,因为这个函数返回值和qsort函数要我们自己实现的比较函数的返回值期望是一模一样的。其引用头文件为:string.h

3.qsort库函数的模仿实现

3.1实现问题分析

因为目前还没有接触到快速排序的算法,所以我们这里使用冒泡排序的思想,实现一个功能类似Qsort的函数。

函数功能分析:

1.使用冒泡排序思想

2.适用于任意类型数据的排序。

3.要仿照库函数改造冒泡排序的参数:使用void*base来知道要排序数据的起始位置,num来告诉元素个数,size来知道一个元素有多大。这是参数部分

4.进入实现部分我们想一下,冒泡排序对于不同的数据类型,比如结构体类型或者整型,我们排序的次数和数据之间比较的次数在元素个数相同的情况下是不变的,唯一不同的就是比较方式:整型使用大于、小于、等于比较,但是两个结构体比较的话有可能比较整型又可以比较浮点型,就不能简单用大于小于来比较。那这个比较实现,我们的解决办法是:

比较的方式方法,由外部传入,在我们的函数里面定义一个函数指针来接收一个比较函数的地址,如何比较两个数据的方式方法由用户传入,用户需要怎么比较我们就调用这个比较例子方式来用。 由于我们也不知道要比较的数据是什么类型所以就使用void *类型作为参数,然后两个指针指向的参数只用于比较,不需要修改所以可以用const来修饰

   有了这些分析,我们来实现一下看,图中遇到问题我们一起分析:

 

3.2元素比较分析及实现

按照上述的分析,我们来到了比较元素的这一步,我们来想一下是如何进行比较的。

在对数组元素进行比较的时候,我们知道两个元素两两相比较,是不是比较的是两个地址指向的内容,那么我们这里并不是数组,我们如何根据已知条件比较第j个元素和第J+1个元素呢? 

 

那么第j个元素和第j+1个元素的地址一定通过这个base来获得,可以使用base+吗?,显然不行,因为base的类型是void*,不支持算数运算,那只有强制类型转换,转换为那个类型呢,结构体指针还是整型指针?我们发现将它转换为字符类型指针是最为方便的,算数运算可以访问一个自己的元素,当我们要访问第j个元素的时候就可以这样来写:

(char*)base+j*size 

访问第j+1个元素可以这样实现

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

比较方式由用户传入就可以了。

3.3排序分析 

假设要升序排序,cmp返回大于0,前面大于后面就需要交换
                //如何实现交换呢?
  并不能直接交换因为未来可能传入的数据类型不一样,这里没有办法定义中间的交换变量的类型,那么要交换的数据的地址和数据大小都知道,交换下标为j和j+1的元素,size是以字节为单位,我们可以以字节为单位来交换地址中的内容
                //因为指针变量被强制类型转换了,也是char,解引用访问是1个字节。 我们可以定义char 类型的中间变量来实现交换每个字节的内容就好了,实现如下:

void Swap(char* buf1, char* buf2, int size)
{int i = 0;char temp = 0;for (i = 0; i < size; i++){temp = *buf1;*buf1 = *buf2;*buf2 = temp;buf1++;buf2++;}
}

3.4实现及源代码

3.4.1实现1整型排序 

#include<stdio.h>int  cmp_int(const void* p1, const void* p2)
{return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1, char* buf2, int size)
{int i = 0;char temp = 0;for (i = 0; i < size; i++){temp = *buf1;*buf1 = *buf2;*buf2 = temp;buf1++;buf2++;}
}void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{int i = 0;//趟数for (i = 0; i < num-1; i++){int j = 0;for (j = 0; j < num-1-i; j++){if (cmp((char*)base + j*size,(char*)base + (j + 1) * size)>0){//假设要升序排序,cmp返回大于0,前面大于后面就需要交换//如何实现交换呢?//地址和数据大小都需要知道,交换下标为j和j+1的元素,size是以字节为单位,我们以字节为单位来交换地址中的内容//因为指针变量也是char,解引用访问是1个字节。Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}}//测试bubble_sort 排序整型数据
void test1()
{int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
int main()
{test1();return 0;
}

 

3.4.2.1结构体排序实现1-按照整型数据排序

 

void Swap(char* buf1, char* buf2, int size)
{int i = 0;char temp = 0;for (i = 0; i < size; i++){temp = *buf1;*buf1 = *buf2;*buf2 = temp;buf1++;buf2++;}
}void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{int i = 0;//趟数for (i = 0; i < num-1; i++){int j = 0;for (j = 0; j < num-1-i; j++){if (cmp((char*)base + j*size,(char*)base + (j + 1) * size)>0){//假设要升序排序,cmp返回大于0,前面大于后面就需要交换//如何实现交换呢?//地址和数据大小都需要知道,交换下标为j和j+1的元素,size是以字节为单位,我们以字节为单位来交换地址中的内容//因为指针变量也是char,解引用访问是1个字节。Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}}//测试bubble_sort 排序整型数据
//void test1()
//{
//	int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//}
//排序结构体数据
struct Stu
{char name[20];int age;
};
int cmp_byage(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;}
void test2()
{struct Stu arr1[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };int sz = sizeof(arr1) / sizeof(arr1[0]);bubble_sort(arr1,sz,sizeof(arr1[0]),cmp_byage);int i = 0;for (i = 0; i < sz; i++){printf("%s,%d \n ",arr1[i].name,arr1[i].age);}
}int main()
{//test1();test2();return 0;
}

排序结果为:

 

3.4.2.2结构体排序实现1-按照字符串数据排序

void Swap(char* buf1, char* buf2, int size)
{int i = 0;char temp = 0;for (i = 0; i < size; i++){temp = *buf1;*buf1 = *buf2;*buf2 = temp;buf1++;buf2++;}
}void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{int i = 0;//趟数for (i = 0; i < num-1; i++){int j = 0;for (j = 0; j < num-1-i; j++){if (cmp((char*)base + j*size,(char*)base + (j + 1) * size)>0){//假设要升序排序,cmp返回大于0,前面大于后面就需要交换//如何实现交换呢?//地址和数据大小都需要知道,交换下标为j和j+1的元素,size是以字节为单位,我们以字节为单位来交换地址中的内容//因为指针变量也是char,解引用访问是1个字节。Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}}//测试bubble_sort 排序整型数据
//void test1()
//{
//	int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//}
//排序结构体数据
struct Stu
{char name[20];int age;
};
int cmp_byage(const void* p1, const void* p2)
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;}
int  cmp_byname(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{struct Stu arr1[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };int sz = sizeof(arr1) / sizeof(arr1[0]);//bubble_sort(arr1,sz,sizeof(arr1[0]),cmp_byage);qsort(arr1, sz, sizeof(arr1[0]), cmp_byname);int i = 0;for (i = 0; i < sz; i++){printf("%s,%d \n ",arr1[i].name,arr1[i].age);}
}int main()
{//test1();test2();return 0;
}

 排序结果为

4.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是nicn,正在c++方向前行的新手,感谢大家的关注与喜欢。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 面试题之 IO(一)

字节流 文章目录 字节流InputStream&#xff08;字节输入流&#xff09;OutputStream&#xff08;字节输出流&#xff09; 文章来自Java Guide 用于学习如有侵权&#xff0c;立即删除 InputStream&#xff08;字节输入流&#xff09; InputStream用于从源头&#xff08;通常是…

【Mac系统PyCharm连接至Linux服务器,实现本地与服务器的实时交互】

Mac系统PyCharm连接至Linux服务器&#xff0c;实现本地与服务器的实时交互 0、目录 1、动机2、说明3、PyCharm 连接Linux服务器操作过程 3.1 连接服务器3.2 验证连接服务器是否成功3.3 查看Linux服务器文件构成3.4 本地与Linux服务器上传内容3.5 从PyCharm的terminal进入到刚…

华为OD-华为机试精讲500篇系列文章目录介绍(持续补充ing)

目录 背景介绍 什么是华为OD&#xff1f; OD现状 OD趋势 华为OD机考刷题攻略 1、刷题资料&#xff1a;投递岗位通过筛选后提供 2、注意事项&#xff1a; 真题代码目录 背景介绍 经济下行的这几年&#xff0c;每个人都感同身受&#xff0c;如何让自己在芸芸众生中脱颖而…

无心剑中译莎士比亚《可否将你比夏天》

莎士比亚十四行诗第18首 Sonnet 18 可否将你比夏天 Shall I compare thee to a summer’s day? Thou art more lovely and more temperate: Rough winds do shake the darling buds of May, And summer’s lease hath all too short a date: Sometime too hot the eye of he…

2024美赛算法预测-1一个强大算法模型,随机森林!!

大家好&#xff0c;今儿咱们来说说关于随机森林的一些核心点~ 首先&#xff0c;随机森林是一种集成学习方法&#xff0c;通过组合多个决策树来进行预测。每个决策树都是在不同的数据子集上训练的&#xff0c;同时引入了随机性&#xff0c;使得每棵树都有差异。 最终的预测结果…

transformer架构的理解

一、transformer 架构 如上图所示&#xff0c;transformer&#xff08;形状像变压器&#xff1f;或者翻译成变形金刚&#xff0c;由不同模块拼装而成&#xff09;的架构左边是n个结构体相同的编码器&#xff08;例如&#xff0c;原论文是6个编码器的串联&#xff09;&#xff0…

动网格-网格重构之铺层(三)

铺层 本文章详细介绍FLUENT动态网格体网格再生方法铺层法。 铺层基本特点&#xff1a; (1)铺层过程中包含了网格的生成和销毁。当区域扩大时&#xff0c;生成网格;缩小时&#xff0c;销毁网格。 (2)适用网格种类:四边形、六面体、三棱柱(网格的分布要服从一定的规则)。 (3)铺…

使用cmake配置opencv c++项目

1、cmake简介 cmake是什么 CMake是一个开源、跨平台的编译&#xff08;Build&#xff09;工具&#xff0c;是用来构建、测试和打包软件的。它能够用简单的语句来描述所有平台的编译过程。它能够输出各种各样的makefile或者project文件&#xff0c;能测试编译器所支持的C特性,类…

Day02-课后练习2-参考答案(数据类型和运算符)

文章目录 巩固题1、案例&#xff1a;今天是周2&#xff0c;100天以后是周几&#xff1f;2、案例&#xff1a;求三个整数x,y,z中的最大值3、案例&#xff1a;判断今年是否是闰年4、分析如下代码的计算结果5、分析如下代码的计算结果6、分析如下代码的计算结果7、分析如下代码的计…

最新即时通讯社交APP源码 支持H5群聊、红包转账和朋友圈

(购买本专栏可免费下载栏目内所有资源不受限制,持续发布中,需要注意的是,本专栏为批量下载专用,并无法保证某款源码或者插件绝对可用,介意不要购买) 功能简介 我们的即时通讯应用提供了完备的IM功能,支持文字、表情、图片、语音等多种聊天方式,包括单聊、群聊、已读…

不是能画原型就叫会“快速原型”的,好吗?

原型设计作为当前软件、互联网行业最有效的信息传达方式之一&#xff0c;是所有产品经理、交互设计师、UX设计师必须掌握的技能。掌握原型设计同样也是UI设计师、开发工程师甚至运营、市场人员提高自身竞争力的加分项。并且&#xff0c;学习原型设计的难度低&#xff0c;上手快…

【算法专题】贪心算法

贪心算法 贪心算法介绍1. 柠檬水找零2. 将数组和减半的最少操作次数3. 最大数4. 摆动序列(贪心思路)5. 最长递增子序列(贪心算法)6. 递增的三元子序列7. 最长连续递增序列8. 买卖股票的最佳时机9. 买卖股票的最佳时机Ⅱ(贪心算法)10. K 次取反后最大化的数组和11. 按身高排序12…