C语言之指针进阶篇(3)

目录

思维导图

回调函数

案例1—计算器

案例2—qsort函数

关于qsort函数  

演示qsort函数的使用

案例3—冒泡排序 

整型数据冒泡排序

回调函数搞定各类型冒泡排序

cmp_int比较大小

 cmp传参数

NO1.

NO2.

解决方案

交换swap

总代码


今天我们学习指针难点之回调函数🆗🆗🆗。

首先我们用思维导图回顾一下前面的内容。

思维导图

回调函数

回调函数就是一个通过函数指针调用的函数。

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

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

案例1—计算器

就前文我们学习的计算器,我们再用回调函数来解决一下!🆗🆗🆗

#define _CRT_SECURE_NO_WARNINGS 1
//计算器
#include<stdio.h>
void meau()
{printf("**************************\n");printf("** 1.add   2.sub      ****\n");printf("** 3.mul   4.div      ****\n");printf("** 0.exit            *****\n");printf("**************************\n");
}
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{meau();printf("请选择>");scanf("%d", &input);switch (input){case 1:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret=%d\n", ret);break;case 2:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret=%d\n", ret);break;case 3:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret=%d\n", ret);break;case 4:printf("请输入2个操作数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret=%d\n", ret);break;case 0:printf("退出游戏");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}

回调函数 

#define _CRT_SECURE_NO_WARNINGS 1
//计算器
#include<stdio.h>
void meau()
{printf("**************************\n");printf("** 1.add   2.sub      ****\n");printf("** 3.mul   4.div      ****\n");printf("** 0.exit            *****\n");printf("**************************\n");
}
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
void calc(int(*p)(int, int))//函数指针传参
{int x = 0;int y = 0;printf("请输入两个操作数\n");scanf("%d %d", &x, &y);int ret = p(x, y);//函数调用printf("ret=%d\n", ret);
}
int main()
{int input = 0;do{meau();printf("请选择>\n");scanf("%d", &input);switch (input){case 1:calc(&Add);break;case 2:calc(&Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出游戏");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}

解释如下: 

在main函数中,没有直接去调用函数。而是把函数指针传参给另外的一个函数calc,在calc内部使用函数指针调用,通过函数指针就可以找到指针指向的函数,此刻被指向的函数就是回调函数。

像上图所示,通过calc函数调用Add函数指针,找到Add函数,就把Add函数称为回调函数

calc称为回调函数的机制

老板>>组长>>员工

案例2—qsort函数

那出了上面回调函数的案例,还有一个经典回调函数的案例:qsort

 qsort是一个库函数,底层使用的是快速排序的方式,对不同数据进行排序的。

这个函数可以直接使用。

这个函数可以用来排序任意类型的数据。

对数据进行排序方法很多:

冒泡排,序选择排序,插入排序,快速排序等等。 

关于qsort函数  

关于qsort函数的点--->qsort - C++ Reference (cplusplus.com)

需要包含头文件#include<stdlib.h>

  •  排序整型数组,两个整型可以直接使用>比较
  • 排序结构体数组,两个结构体的数据可能不能直接使用>比较

也就是不同类型的数据,比较大小的方法是有差异的

最后一个参数,排序不同数据的重要点,需要封装不同的函数去比较不同的数据的大小

void qsort(void* base, //指向了待排序数组第一个元素的首地址size_t num, //待排序数组的元素个数size_t size,//每个待排序数组元素的大小int (*compar)(const void* e1, const void* e2));
//函数指针,compar指向了一个函数,这个函数是用来比较两个元素的大小,
//e1和e2存放的是两个元素的地址
//在qsort内部调用这个函数,指向这个函数,这个函数就被称为回调函数
// 
//qsort内部怎么排序我们不需要过多去探讨
//const也暂不做讲解//因为不知道要比较的元素类型,所以我们使用void*指针的类型,来统一存放各种类型的指针

那怎样通过元素地址,去比较两个元素数据的大小呢? 

以int的数据为例:将void*类型的数据强制转化成(int*),再作差

当e1>e2,函数返回>0的值;

当e1<e2,函数返回<0的值;

当e1=e2,函数返回=0

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

那有人询问为什么不直接对元素地址const void* e1和 const void* e2解引用?

作为void*指针不能直接解引用。

void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作
void* 类型的指针是用来存放任意类型数据的地址
void* 无具体类型的指针
void*和int*和char*一样都是指针类型

#include<stdio.h>
int main()
{char a = 'x';char* pa = &a;int b = 1;void* p = &b;//存放int*p = &a;//存放char*return 0;
}

演示qsort函数的使用

#include<stdio.h>
#include<stdlib.h>
void print(int arr[], int sz)
{int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}
}void qsort(void* base, size_t num,size_t size,int (*compar)(const void*, const void*));void compar_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
void test1()
{int arr[] = { 10,9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);print(arr, sz);printf("\n");qsort(arr, sz, sizeof(arr[0]), compar_int);print(arr, sz);
}int main()
{test1();test2();return 0;
}

以上我只是以整型为例,结构体数据数组也是一样的逻辑,大家可以自行分析。

下面结构体:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>//?void qsort(void* base,size_t num,size_t size,int (*compar)(const void*, const void*));struct Stu
{char name[20];int age;
};
//结构体数据怎么比较呢?
//按照年龄比较
//按照名字比较//按照年龄
void compar_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//return (*(struct Stu*)e1).age - (*(struct Stu*)e2).age;
}
void test2()
{struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), compar_stu_by_age);}
//按照名字
void compar_stu_by_name(const void* e1, const void* e2)
{return ((struct Stu*)e1)->name - ((struct Stu*)e2)->name;//return (*(struct Stu*)e1).name - (*(struct Stu*)e2).name;
}
void test2()
{struct Stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",12} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), compar_stu_by_name);}int main()
{test2();return 0;
}

只要qsort函数使用得当,可以对任何数据进行排序!🆗🆗 

案例3—冒泡排序 

(使用回调函数,模拟实现qsort(采用冒泡的方式)

整型数据冒泡排序

(这种方式只能排列整数,存在局限性)

//冒泡排序
#include<stdio.h>
void bubble_sort(int arr[], int sz)
{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]){int tmp = 0;tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}
void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
int main()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);print_arr(arr, sz);
}

回调函数搞定各类型冒泡排序

 经过分析冒泡排序,我们得到

void bubble_sort(void* base, size_t num, size_t size,

                            int (*cmp)(const void* e1, const void* e2))

cmp_int比较大小

以整型为例 

int (*cmp)(const void* e1, const void* e2)

e1是一个指针,存放了一个要比较的元素的地址。

e2是一个指针,存放了一个要比较的元素的地址。

e1指向的元素>e2指向的元素,返回>0的数字。

e1指向的元素<e2指向的元素,返回>0的数字。

e1指向的元素==e2指向的元素,返回>0的数字。

cmp是函数指针指向一个我们程序想要待排序的数组。

将比较函数cmp_int的地址传给cmp即可。

//比较大小
void cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
//这里就是将cmp_int的地址在调用函数bubble_sort时将其传过去即可。
 cmp传参数
NO1.

有同学提出直接对待排序的数组首元素地址解引用找到e1的地址,然后通过一个元素的大小或者+1可以找到e2的地址,可以吗?当然不可以

  • 作为void*指针不能直接解引用。

    void* 类型的指针—不能进行解引用操作符,也不能进行+-整数的操作
    void* 类型的指针是用来存放任意类型数据的地址
    void* 无具体类型的指针
    void*和int*和char*一样都是指针类型

NO2.

有同学又提出那将void*的指针强制转换成我们想要的int*或double*等,再+1可以吗?           不可以,理由就是,强制转换存在在于我们公共的bubble_sort排序函数中时不能随着待排序的数组数据类型不同而改变,我们只能改变不同数据类型的不同比较方法。

解决方案

 

			//if(arr[j]>arr[j+1])if (cmp( (char*)base+j*size,(char*)base+(j+1)*size )>0){int tmp = 0;tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}
交换swap

当我们只知道元素的起始地址,并不知道元素的类型所以我们并没有合适的中间值类型tmp创建。所以我们换一种方法。

我们已知元素e1和e2的起始地址每个元素的大小

那我们可以用一个一个char类型的数据交换用for循环

直到每个元素的大小size结束,也就是元素交换完成。

 

//交换数据
void change(char* buf1, char* buf2,size_t size)
{char i = 0;for (i = 0; i < size; i++){char tmp = 0;tmp = *buf1;*buf1=*buf2;*buf2 = tmp;buf1++;//*buf1++buf2++;//*buf2++}
}
总代码
//冒泡排序
#include<stdio.h>
void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}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){change((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
//交换数据
void change(char* buf1, char* buf2,size_t size)
{char i = 0;for (i = 0; i < size; i++){char tmp = 0;tmp = *buf1;*buf1=*buf2;*buf2 = tmp;buf1++;//*e1++buf2++;//*e2++}
}
//比较大小
void cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;//>0
}void test1()
{int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);print_arr(arr, sz);printf("\n");bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);
}int main()
{test1();
}

当然我们也可以用结构体类型去测试一下! 🆗🆗试试

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!旗鼓相当

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com】

联系------→【邮箱:2784139418@qq.com】

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

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

相关文章

支付宝小程序排名优化,一个小白的成长手记

那是一个风和日丽的周末早上,阳光透过窗帘洒进屋内,温暖了我的双脚。这是我加入新公司的第一个周末,我坐在桌前,满怀激情地准备开发我的第一个支付宝小程序。【名即薇】 经过两天两夜的奋战,我终于完成了一个初版的支付宝小程序。是一个集美食资讯、餐厅点评、外卖订餐于一体的…

连nil切片和空切片一不一样都不清楚?那BAT面试官只好让你回去等通知了。

连nil切片和空切片一不一样都不清楚&#xff1f;那BAT面试官只好让你回去等通知了。 问题 package mainimport ("fmt""reflect""unsafe" )func main() {var s1 []ints2 : make([]int,0)s4 : make([]int,0)fmt.Printf("s1 pointer:%v, s2 p…

两种方法教你在postman设置请求里带动态token

问题描述 在使用postman调试接口时&#xff0c;遇到一些需要在请求里加上token的接口&#xff0c;若token出现变化&#xff0c;需要手动修改接口的token值&#xff0c;带来重复的工作量&#xff0c;翻看postman使用手册后&#xff0c;我发现了两种方法可以解决这个问题。 01 …

MySQL之数据类型

目录 一、MySQL数据类型分类 二、数值类型 1、整数类型 2、bit类型 3、小数类型 三、字符串类型 1、char 2、varchar 3、char和varchar比较 四、日期和时间类型 五、enum和set 一、MySQL数据类型分类 MySQL 数据类型可以大致分为以下三类&#xff1a; 数值类型&#xff1a;用于…

git快速查看某个文件修改的所有commit

1. git blame file git blame 可以显示历史修改的每一行记录,有时候我们只想了解某个文件一共提交几次commit,只显示commit列表,这种方式显然不满足要求。 2.git log常规使用 (1)显示整个project的所有commit (2)显示某个文件的所有commit 这是git log不添加参数的常规…

.Net MVC 使用Areas后存在相同Controller时报错的解决办法; 从上下文获取请求的Area名及Controller名

先来说个额外的问题&#xff1a;如何在请求上下文&#xff08;比如过滤器的中&#xff09;获取请求对应的Area和Controller 名字&#xff1f;&#xff08;假设请求上下文对象为 filterContext &#xff09;&#xff1a; 1. 获取Area名: (string)filterContext.RouteData.DataTo…

Windows下防火墙端口配置

在电脑或者服务器上部署某个应用后&#xff0c;如果需要对外提供服务可能就需要在主机防火墙上设置开启需要的端口&#xff0c;那么具体怎样操作呢 1.打开windows防火墙 2.设置防火墙入站规则 如下图“高级安全Windows Defender 防火墙”页面&#xff0c;点击左侧“入站规则”…

并联电容器交流耐压试验方法

对被试并联电容器两极进行充分放电。 检查电容器外观、 污秽等情况, 判断电容器是否满足试验要求状态。 用端接线将并联电容器两极短接连接湖北众拓高试工频耐压装置高压端, 外壳接地。 接线完成后经检查确认无误, 人员退出试验范围。 接入符合测试设备的工作电源&#xff0c;…

[Linux]进程间通信--管道

[Linux]进程间通信–管道 文章目录 [Linux]进程间通信--管道进程间通信的目的实现进程间通信的原理匿名管道匿名管道的通信原理系统接口管道特性管道的协同场景管道的大小 命名管道使用指令创建命名管道使用系统调用创建命名管道 进程间通信的目的 数据传输&#xff1a;一个进…

【多线程】常见的锁策略

常见的锁策略 1. 乐观锁 vs 悲观锁2. 读写锁 vs 普通互斥锁3. 重量级锁 vs 轻量级锁4. 自旋锁&#xff08;Spin Lock&#xff09;vs 挂起等待锁5. 公平锁 vs 非公平锁6. 可重入锁 vs 不可重入锁7. Synchronized8. 相关面试题 1. 乐观锁 vs 悲观锁 悲观锁&#xff1a; 总是假设…

GStreamer MIME类型

MIME type的全称是 Multipurpose Internet Mail Extensions (MIME) &#xff0c;可以标志一个文件的类型。 Table of Audio Types Media Type Description All audio types. audio/* All audio types channels integer channel-mask bitmask format string layou…

NLP机器翻译全景:从基本原理到技术实战全解析

目录 一、机器翻译简介1. 什么是机器翻译 (MT)?2. 源语言和目标语言3. 翻译模型4. 上下文的重要性 二、基于规则的机器翻译 (RBMT)1. 规则的制定2. 词典和词汇选择3. 限制与挑战4. PyTorch实现 三、基于统计的机器翻译 (SMT)1. 数据驱动2. 短语对齐3. 评分和选择4. PyTorch实现…

MybatisPlus分页插件使用

一. 效果展示 二. 代码编写 2.1 pom <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version> </dependency>2.2 添加配置类 Configuration MapperScan(…

软考知识汇总--结构化开发方法

文章目录 1 结构化开发2 耦合3 内聚4 设计原则5 系统文档6 数据流图6.1 数据流图的基本图形元素 7 数据字典 1 结构化开发 结构化方法总的指导思想是自顶向下、逐层分解&#xff0c;它的基本原则是功能的分解与抽象。它是软件工程中最早出现的开发方法&#xff0c;特别适合于数…

U3D外包开发框架及特点

U3D&#xff08;Unity3D&#xff09;是一款流行的跨平台游戏开发引擎&#xff0c;用于创建2D和3D游戏以及交互性应用程序。U3D有许多常用的开发框架和库&#xff0c;这些框架和库可以扩展其功能&#xff0c;使开发人员更轻松地构建游戏和应用程序。以下是一些常用的U3D开发框架…

Si3262 一款低功耗刷卡+触摸+mcu 三合一SOC芯片

Si3262是-款高度集成的低功耗soC芯片&#xff0c;其集成了基于RISC-V 核的低功耗MCU和工作在13.56MHz的非接触式读写器模块。 该芯片ACD模式下刷卡距离可达4-5cm&#xff08;天线决定&#xff09;&#xff0c;适用于智能门锁&#xff0c;电子锁&#xff0c;柜锁&#xff0c;桑拿…

Android相机调用-CameraX【外接摄像头】【USB摄像头】

Android相机调用有原生的Camera和Camera2&#xff0c;我觉得调用代码都太复杂了&#xff0c;CameraX调用代码简洁很多。 说明文档&#xff1a;https://developer.android.com/jetpack/androidx/releases/camera?hlzh-cn 现有查到的调用资料都不够新&#xff0c;对于外接摄像…

【C++】仿函数和priority_queue(优先级队列)

目录 一、仿函数 二、priority_queue(优先级队列) 1、概念&#xff1a; 2、使用&#xff1a; 3、数组中第K个最大元素 4、priority_queue的模拟实现 一、仿函数 ①、概念&#xff1a; 仿函数&#xff0c;即函数对象。一种行为类似函数的对象&#xff0c;调用者可以像函…

Java+Tif图片转Jpg

Tif转Jpg使用心得&#xff1a; 如果tif图片需要压缩&#xff0c;或者需要做转换&#xff0c;常用方法&#xff1a; File file1 new File("E:\\www\\ffw\\images\\73.jpg");byte[] bigContent Files.readAllBytes(file1.toPath());ByteArrayInputStream byteArrayIn…

解决Maven依赖下载问题:从阿里云公共仓库入手

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…