【C语言】指针深入讲解(下)

目录

  • 前言
  • 回调函数
    • 回调函数的概念
    • 回调函数的使用
  • qsort函数的使用和模拟实现
    • qsort函数的介绍
    • qsort函数的使用
    • qsort函数模拟实现

前言

今天我们来学习指针最后一个知识点回调函数,这个知识点也很重要,希望大家能坚持学习下去。
没学习之前指针知识内容的,可以点击这里进行学习。

回调函数

回调函数的概念

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

回调函数的使用

在指针深入讲解(中)篇中我们写的计算机的实现的代码中。
我们设计了实现功能的函数。

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}

在没学习回调函数之前,主函数中有很多冗余的地方。
下面代码,scanf和printf函数大量重复出现。

int main()
{int x, y;int input = 1;int ret = 0;
do
{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);
switch (input)
{case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;
}
} while (input);
return 0;
}

我们观察功能实现的函数可以发现,函数的结构都是 int 函数名 (int,int) 的形式,这样就可以把函数的地址作为参数传给这样类型的函数指针,指针指向那个函数就调用那个函数,这就是使用的回调函数

用回调函数的形式,简化计算器实现的主函数代码。

void calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main()
{int x, y;int input = 1;int ret = 0;
do
{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");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("退出程序\n");break;default:printf("选择错误\n");break;
}
} while (input);
return 0;
}

这样代码就简化了很多,pf是指向函数的指针,当我们选择哪个功能时,会把实现该功能的函数地址传给pf指针,当条件满足,就会通过pf调用该函数。

qsort函数的使用和模拟实现

qsort函数的介绍

在这里插入图片描述

参数介绍

参数名含义
base指向要排序的数组的第一个对象的指针
num指向的数组中的元素数。是无符号整型,size_t
size数组中每个元素的大小(以字节为单位)。是无符号整型。size_t
compar指向比较两个元素的函数的指针。此函数被重复调用以比较两个元素。

最后一个参数很重要单独说明一下
在这里插入图片描述
compar是指向比较函数的指针,返回值为int型。
返回 <0说明p1指向的元素小于p2指向的元素。
返回 =0说明p1指向的元素等于p2指向的元素。
返回 >0说明p1指向的元素大于p2指向的元素。

qsort函数的使用

使⽤qsort函数排序整型数据

#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;
}

使⽤qsort排序结构数据

struct Stu //学⽣
{char name[20];//名字int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
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 s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{test2();test3();return 0;
}

qsort函数模拟实现

用回调函数,模拟实现qsort函数。
qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,快排会在学习数据结构时讲解。
我们要实现的qsort是可以针对任何数据进行排序,那想一下我们知道用户使用这个函数的时候是拿来排序什么数据吗?显然是不知道的,所以在内部实现时,我们需要更改什么呢?分析如下:

  1. 比较的方法
    由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,是不能进行解引用的,且数据类型是不能传参的,那我们该怎么找到相邻元素比较呢?
    于是我们在参数中添加了数组元素的大小(即宽度,一个元素占几个字节,这是用户可以传参的),这样就能找到相邻元素了
    通过首元素地址加减上一个数组元素占几个字节就可以找到相邻元素。
    公式为:元素地址=首元素地址+数组元素的宽度 * 数组元素下标
    在这里插入图片描述

注意:要把base强制类型转化为char型指针

接下来就是如何比较,由于我们不知道用户排序什么数据,所以没办法实现两个数据的比较,例如整数可以直接使用关系操作符,而字符串需要strcmp函数等等,于是我们把比较两个数据大小的函数交给用户去实现,所以在参数中使用了一个函数指针。

if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)

这里我们默认还是qsort的比较规则,用户实现compare函数时遵守:当第一个元素大于第二个元素时,就返回大于0的数字,此时我们交换,按这个规则排序出来为升序,反之为降序。

2.交换数据的方式
同样的是,我们不知道数据类型,但我们知道数据的大小,所以我们可以一个一个字节的交换

void _swap(void *p1, void * p2, int size)
{int i = 0;for (i = 0; i< size; i++){char tmp = *((char *)p1 + i);*(( char *)p1 + i) = *((char *) p2 + i);*(( char *)p2 + i) = tmp;}
}

这样我们就完成了qsort函数的模拟实现。
源代码如下(排序整数):

#include<stdio.h>
void my_squrt(void* base, size_t num, size_t size, int (*compar)(void* p1, void* p2))
{int i = 0;int j = 1;for (i = 0; i < num - 1; i++){for (j = 0; j < num - i-1; j++){//判断大小if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0){//交换数据for (int k = 0; k < size; k++){char tmp = *((char*)base + j * size + k);*((char*)base + j * size + k) = *((char*)base + (j + 1) * size + k);*((char*)base + (j + 1) * size + k) = tmp;}}}}
}
//整数的比较函数
int int_cmp(void* p1, void* p2)
{return *((int*)p2) - *((int*)p1);
}int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; \int sz = sizeof(arr) / sizeof(arr[0]);my_squrt(arr,sz,sizeof(arr[0]), int_cmp);int i = 0;//打印for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}

总结:
qsout函数是典型的回调函数的例子,排序时我们不知道用户传进来的数据是什么类型,所以使用void *,把比较函数的实现方法交给用户实现,把实现函数通过函数指针传给qsout函数,在qsout函数内部比较时调用该函数。这就是回调函数。


感谢大家的观看, 大家可以在评论区留言,你们的支持就是我最大的动力。

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

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

相关文章

通过FFmpeg和URL查看流的编码格式

FFmpeg下载后会有三个执行文件&#xff0c;跳转到FFmpeg所在文件夹 查看视频流URL地址的编码格式命令&#xff1a; // 在下载ffmpeg的文件夹中执行如下命令&#xff0c;可查看流的编码格式&#xff0c;这里的测试流是H264编码ffprobe http://devimages.apple.com/iphone/sample…

2024数学建模国赛选题建议+团队助攻资料(已更新完毕)

目录 一、题目特点和选题建议 二、模型选择 1、评价模型 2、预测模型 3、分类模型 4、优化模型 5、统计分析模型 三、white学长团队助攻资料 1、助攻代码 2、成品论文PDF版 3、成品论文word版 9月5日晚18&#xff1a;00就要公布题目了&#xff0c;根据历年竞赛题目…

激光二极管知识汇总

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 前言一、什么是激光二极管二、激光二极管的发光原理三、导电特性1、正向特性2、反向特性 四、激光二极管和LED的区别五…

云原生架构概念

云原生架构概念 云原生架构&#xff08;Cloud Native Architechtrue&#xff09;作为一种现代软件开发的革新力量&#xff0c;正在逐渐改变企业构建、部署和管理应用程序的方式。它的核心优势在于支持微服务架构&#xff0c;使得应用程序能够分解为独立、松耦合的服务&#xf…

【C++】windwos下vscode多文件项目创建、编译、运行

目录 &#x1f315;vscode多文件项目创建方法&#x1f319;具体案例⭐命令行创建项目名&#xff0c;并在vscode中打开项目⭐创建include目录和头文件⭐创建src目录和cpp文件⭐根目录下创建main.cpp &#x1f315;运行项目失败&#xff08;找不到include目录下的头文件和src目录…

笔试。牛客.C-消减整数力扣.最长上升子序列(dp)牛客.最长上升子序列(二) (贪心+二分)牛客.爱吃素

目录 牛客.C-消减整数 力扣.最长上升子序列(dp) 牛客.最长上升子序列(二) (贪心二分) 牛客.爱吃素 牛客.C-消减整数 开始的时候我还以为是什么&#xff0c;结果数学才是根本 import java.util.*; public class Main{public static void main(String[]args){Scanner innew Sc…

Linux网络编程2——多进程编程

一.进程 1.概念复习 程序&#xff1a;程序(program)是存放在磁盘文件中的可执行文件 进程&#xff1a;程序的执行实例被称为进程(process)。 进程具有独立的权限与职责。如果系统中某个进程崩溃&#xff0c;它不会影响到其余的进程。每个进程运行在其各自的虚拟地址空间中&a…

Docker打包镜像

Docker打包镜像 前置工作 1.虚拟机中配置好docker环境&#xff0c;并导入nginx&#xff0c;mysql&#xff0c;jdk的镜像 2.下载docker for windows 用idea打包镜像和创建容器需要这个东西支持 下载安装包后执行&#xff0c;无脑回车即可 3.idea中配置docker连接 完成配置后&…

WPS Office for Linux 12 个人版上线deepin 23商店:UI 视觉重构,新增多项 AI 功能

查看原文 全新WPS Office For Linux个人版&#xff08;12.1.0.17881&#xff09;与deepin 23的AI功能深度融合&#xff0c;正式上架在社区商店啦&#xff01; 这是 Linux 版本的一次里程碑式的重大更新。在产品能力层面上&#xff0c;在 deepin 23 上WPS Office 几乎实现了与 …

AMD MI300X vs Nvidia H100/H200:AI计算领域的新一轮较量

随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;计算引擎的性能竞赛也愈演愈烈。近期&#xff0c;MLCommons发布的最新AI训练和推理基准测试结果&#xff0c;他们构建了一套 MLPerf AI 训练和推理基准&#xff0c;将AMD的Instinct“Antares”MI300X GPU与Nv…

软件开发人员从0到1实现物联网项目:项目架构的思考

文章目录 前言单体应用足矣摒弃传统的微信对接后期的维护投入上真正的“云”&#xff1a;云托管0服务器免运维免费的CDN和DDoS防护 技术架构小结 前言 因为种种原因&#xff0c;《软件开发人员从0到1实现物联网项目》这个项目的进度停滞了将近一个月。 鉴于该项目的前期开发和…

【hot100篇-python刷题记录】【跳跃游戏 II】

R7-贪心算法 目录 方法1&#xff1a; 方法2&#xff1a; 很贪心啊&#xff0c;局部最优解就是全局最优解&#xff0c;要求到达nums[n-1]的最小步数&#xff0c;我们每一步都走最远。 方法1&#xff1a; class Solution:def jump(self, nums: List[int]) -> int:nlen(n…

【车载开发系列】ParaSoft入门介绍

【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍 【车载开发系列】ParaSoft入门介绍一. ParaSoft的背景二. 设计理念三. ParaSoft C/CTest简介四. 具备常用功能1&#xff09;静态代码分析2&#xff09;代码覆盖率分析3&#xff09;模糊测试4&#xff09;自…

AI边缘控制器:重塑工业自动化与控制的新篇章

在智能制造和工业4.0的浪潮下&#xff0c;AI边缘控制器作为新一代智能控制设备&#xff0c;正逐步成为推动工业自动化与数字化转型的关键力量。这类基于x86架构的高性能IPC&#xff08;工业个人计算机&#xff09;形态产品&#xff0c;不仅继承了传统PC的可扩展性、开放性和可视…

RPKI应急管控网络拓扑搭建

应急管控网络拓扑搭建 一、网络拓扑图 二、拓扑配置 1.资源库批量导入roas 在rpki.qcl.edu.cn服务器上的/usr/local/rpki/目录下执行脚本 sh roa_get.sh add#!/bin/dash# TODO Aadd Rremove start10000 sum254 run(){for i in seq 1 20dofor j in seq 1 250doas_numberexpr…

深度学习中常见的损失函数

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 深度学习中常见的损失函数 损失函数的作用 损失函数是衡量神经网络输出与真实标签之间差距的指标。在训练过程中&#xff0c;神经网络的目标是最小化损失函数的值。常见的损失函数包括均方误差&#xff08;MS…

弗洛伊德(Floyd)算法(C/C++)

弗洛伊德算法&#xff08;Floyds algorithm&#xff09;&#xff0c;又称为弗洛伊德-沃尔什算法&#xff08;Floyd-Warshall algorithm&#xff09;&#xff0c;是一种用于在加权图中找到所有顶点对之间最短路径的算法。这个算法适用于有向图和无向图&#xff0c;并且可以处理负…

网络层 VIII(网络层设备——路由器)【★★★★★★】

一、冲突域与广播域 这里的“域”表示冲突或广播在其中发生并传播的区域。 1. 冲突域 冲突域是指连接到同一物理介质上的所有结点的集合&#xff0c;这些结点之间存在介质争用的现象&#xff08;能产生冲突的所有设备的集合&#xff09;。也就是说&#xff0c;若这些设备同时发…

「滚雪球学MyBatis」教程导航帖(已完结)

写在前面 我是bug菌&#xff0c;CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家&#xff0c;C站博客之星Top30&#xff0c;华为云2023年度十佳博主&#xff0c;掘金多年度人气作者Top40&#xff0c;掘金等各大社区平台签约作者&#xff0c;51CTO年度博…

酒茶香链接心灵——探寻现代人幸福生活

科技在飞速发展&#xff0c;人类社会以前所未有的速度向前跃进&#xff0c;物质世界的繁荣达到了前所未有的高度。 然而&#xff0c;这光鲜的背后&#xff0c;却无形中拉远了人与人之间的距离&#xff0c;割裂了传统文化的根脉。 传统文化势弱、“快餐文化”层出不穷&#xff0…