指针之旅(4)—— 指针与函数:函数指针、转移表、回调函数
目录
1. 函数名的理解
1.1 “函数名”和“&函数名”的含义
1.2 函数(名)的数据类型
2. 函数指针(变量)
2.1 函数指针(变量)的创建格式
2.2 函数指针(变量)的使用格式
2.3 例子 · 判别
3. typedef 关键字
3.1 typedef的作用
3.2 typedef的运作逻辑 和 函数指针类型的重命名
4. 函数指针数组
5. 转移表
5.1 转移表的概念
5.2 转移表 与 加减乘除计算器
6. 回调函数
6.1 回调函数的概念
6.2 回调函数 与 加减乘除计算器
1. 函数名的理解
1.1 “函数名”和“&函数名”的含义
我们在《指针之旅(3)—— 指针与数组》中了解过“数组名”代表数组首元素的地址,“&数组名”代表整个数组的地址,共2种含义。但是在函数这可就不一样了:
“函数名”和“&函数名” 在数值上相同,在含义上也相同,都表示函数的地址。
代码演示:
int add(int a, int b)
{return a+b;
}
int main()
{printf("add = %p\n",add);printf("&add = %p\n", &add);return 0;
}
add与&add数值一样:
它们的含义也相同,且都不能进行+-操作:
1.2 函数(名)的数据类型
函数的数据类型:
- 在函数声明中,去掉函数名就是函数的数据类型了:
- 即“ 返回类型 (参数表) ”
比如:
这里的加法函数add的声明是“int add(int a, int b)”,它的数据类型是“int (int , int)”。(其实在函数声明时,参数表中的变量名可以省略)
2. 函数指针(变量)
2.1 函数指针(变量)的创建格式
我们类比推理一下:
- 字符指针-->是指针变量-->存放的是字符变量的地址
- 整型指针-->是指针变量-->存放的是整型变量的地址
- 函数指针-->是指针变量-->存放的是函数的地址
所以我们知道了函数指针的定义:函数指针(变量)存放的是函数的地址。
函数指针(变量)的2种初始化:
1. 返回类型 函数名 (参数表); //先要有函数
2. ❶同返回类型 (* 函数指针名) (同参数表) = &函数名; //再有函数指针
❷同返回类型 (* 函数指针名) (同参数表) = 函数名;
(两种初始化都可以,因为“函数名”和“&函数名”代表的含义相同)
以加法函数“int add(int a,int b)”举例,函数指针的创建和初始化可以写成:
- int (*pf)( int a, int b) = &add;
- int (*pf)( int , int ) = add;
(参数表中的变量名可写可不写)
为什么创建方式是“ int (*pf)(int, int) = &add ”,而不是“ int(*)(int, int) pf = &add ”?
原因:“(参数表)”中的小括号()是函数调用操作符,函数调用操作符有两种功能,(1)是声明函数(2)是调用函数。无论哪种功能,它的结合规则都是自左向右结合。
补充:函数指针创建时,函数调用操作符此时的作用是(1)声明函数。由于这里声明的对象是指针,所以使得该指针获得了函数的性质。
2.2 函数指针(变量)的使用格式
函数指针(变量)的2种使用格式:
❶ (*函数指针名)(参数1,参数2,…); //可理解为“ *&函数名(参数表)”
❷ 函数指针名 (参数1,参数2,…); //可理解为“ 函数名(参数表)”
这两种方式是一样的。
代码举例:
int add(int a, int b)
{return a + b;
}
int main()
{int (*pf)(int, int) = &add;printf("%d\n", (*pf)(60, 4));printf("%d\n", pf(60, 4));return 0;
}
可以看到,两种使用格式都能算出正确的结果。
2.3 例子 · 判别
例子1:
( *( void (*)( ) ) 0 )( ); //请解读该语句的意思,提示:切入点是viod(*)()
图解:
这句话的意思是:
(1)先将0强制转换成void(*)()型的函数指针。
即:( void (*)() ) 0;
(2)然后去调用0地址处的函数。
即:( *0 )( );
例子2:
void ( * signal(int , void(*)(int) ) )(int); //请解读该语句的意思
提示:
(1)切入点是 signal、void(*)(int)和最后一个(int)。
(2)以 “函数名 (参数表)” 的形式组合。
图解:
写成这样让大家好理解一点:(但在编译器中不能写成这样,是错的)
void (*)(int) signal ( int , void(*)int ) ;
| | |
| | 这是参数表,调用该函数时要输入这两种类型的变量
| signal是函数名
void(*)(int)是函数的返回类型
3. typedef 关键字
3.1 typedef的作用
typedef作用的对象只有数据类型,用来给类型进行重命名(定义新的别名)。可以将复杂的类型,简单化。
最基础的使用方式:
1. typedef 要被重命名的类型 新的别名;
⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
- typedef unsigned int uint;
//虽然unsigned与int直接有空格,但是unsigned int是一种计算机中的内置类型,是合起来看的
可以看到,旧的类型重命名后依然能被使用。
3.2 typedef的运作逻辑 和 函数指针类型的重命名
typedef的运作逻辑是:
在正常创建变量(指针、数组、函数)的格式基础上,原本书写变量名(指针名、数组名、函数名)的地方,typedef把该地方的内容当作是旧类型的别名。
比如,我们要创建一个数组int arr[10],但是要求用typedef的方式创建。
这个arr数组的类型是int [10],我们可以把它重命名为int10。
但正确的重名方式是:typedef int int10[10];
而不是:typedef int [10] int10;
如下:
函数指针类型的重命名规则也是如此:
比如,我们把 指向加法函数的指针类型 重命名成PADD,应该写成:typedef int(*PADD)(int , int);
代码如下:
int add(int a, int b)
{return a + b;
}typedef int (*PADD)(int a, int b); //参数表中的变量名可不写
int main()
{//创建函数指针paddPADD padd = &add;//以函数指针的方式调用函数addint ret = padd(4, 10);printf("4与10之和为%d\n", ret);return 0;
}
结果:
4. 函数指针数组
函数指针数组中的每个元素,它的数据类型都是函数指针类型。
函数指针数组的创建格式:
- 在函数指针名后面加上下标引用操作符[ ],并写上数组元素的个数:
- 即“ 返回类型 ( *函数指针数组名[数组元素个数] )( 参数表 ) ”
补充:谁最先与名字结合,该名称的本质就是什么。[ ]的优先级比*的优先级高,所以下标引用操作符[ ]最先与名字结合,所以函数指针数组的本质是一个数组。
比如我们要创建一个有3个元素的数组parr,每个元素的类型都是int (*)( )。
正确的格式:
- int (*parr[3])( ) = {0};
常见错误格式:
- int *parr[3]( );
- int (*)( ) parr[3];
5. 转移表
5.1 转移表的概念
转移表的本质是一个函数指针数组。传统的条件选择语句(如switch)在处理大量操作时会变得冗长,而转移表通过将操作映射到函数指针数组中,可以显著减少代码的重复性。
5.2 转移表 与 加减乘除计算器
我们来做一个只有加减乘除的整型计算器,一开始我们会这样想:
头文件counter.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
void menu();
函数文件counter.c(包含计算器函数与菜单函数)
#include"counter.h"
int add(int a, int b) //1.加法
{return a + b;
}int sub(int a, int b) //2.减法a-b
{return a - b;
}int mul(int a, int b) //3.乘法
{return a * b;
}int div(int a, int b) //4.除法a/b
{return a / b;
}void menu() //菜单页面
{printf("*************************\n");printf(" 1:加法\t2:减法 \n");printf(" 3:乘法\t4:除法 \n");printf("-> 0:退出 \n");printf("*************************\n");printf("请选择并输入对应的数字:");
}
最后我们写成一个简单的计算器程序: test1.c
版本1——普通版:
#include"counter.h"
int main()
{int input; //input对应菜单的选择int x, y; //x和y分别对应运算的数值a和bint ret; //ret接收计算器运算后结果do{menu();scanf("%d", &input);switch (input){case 1:printf("输入2个操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("计算结果是:%d\n\n", ret);break;case 2:printf("输入2个操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("计算结果是:%d\n\n", ret);break;case 3:printf("输入2个操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("计算结果是:%d\n\n", ret);break;case 4:printf("输入2个操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("计算结果是:%d\n\n", ret);break;case 0:printf("程序已成功退出\a\n");break;default:printf("选择错误,请重新选择:\n");break; //这个break可以不写}} while (input); //input等于0就退出程序return 0;
}
我们可以发现,这个基础的版本有很多重复的部分。比如:
- printf("输入2个操作数:");
- scanf("%d %d", &x, &y);
- ret = add(x, y);
- printf("计算结果是:%d\n\n", ret);
这4句相似语句重复出现了4次
思考一下,能不能让这4句话只出现一次?
- 这4句相似的话唯一不同的地方在于“ret = 函数返回值”。
- 加减乘除这4个函数,他们都是int (int, int)型,我们可以用一个函数指针数组来对这4个函数进行映射。
于是我们有了下面这个版本: test2.c
版本2——转移表法:
int main()
{int input; int x, y; int ret; //创建转移表int (*pfun[5])(int, int) = { 0,add,sub,mul,div }; //pfun[0]=0后,使得函数选择与下标序号一一对应。do{menu();scanf("%d", &input);if (input >= 1 && input <= 4){printf("输入2个操作数:");scanf("%d %d", &x, &y);ret = pfun[input](x, y);//通过下标引用操作符[]和函数调用操作符()来使用对应的函数printf("计算结果是:%d\n", ret);}else if (input == 0){printf("程序已成功退出\a\n");}else{printf("选择错误,请重新选择:\n");}} while (input);return 0;
}
6. 回调函数
6.1 回调函数的概念
回调函数的概念:
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。
回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
举例说明:
假设现在有一个叫blog的函数,它的函数声明是这样的:int blog( int (*pfun)(int , int) , int n);
- 那么pfun所指向的函数就是回调函数,函数指针pfun接收着回调函数的地址。
- blog函数是调用回调函数的函数。(也叫主调函数 或者 回调触发函数)
6.2 回调函数 与 加减乘除计算器
对于版本1的重复的4句话,我们可以通过函数回调的方式使其变成1句话: test3.c
版本3——函数回调版
//调用回调函数的汇合函数
void calculator(int (*pfun)(int, int)) //输入的参数是计算器函数的地址,形参pfun是函数指针
{int x, y;printf("输入2个操作数:");scanf("%d %d", &x, &y);//使用计算器函数int ret = pfun(x, y);printf("计算结果是:%d\n", ret);
}int main()
{int input;do{menu();scanf("%d", &input);switch (input){case 1:calculator(add);//case1的回调函数是addbreak;case 2:calculator(sub);//case2的回调函数是subbreak;case 3:calculator(mul);//case3的回调函数是mulbreak;case 4:calculator(div);//case4的回调函数是divbreak;case 0:printf("程序已成功退出\a\n");break;default:printf("选择错误,请重新选择:\n");}} while (input);return 0;
}
本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ
相关文章:

指针之旅(4)—— 指针与函数:函数指针、转移表、回调函数
目录 1. 函数名的理解 1.1 “函数名”和“&函数名”的含义 1.2 函数(名)的数据类型 2. 函数指针(变量) 2.1 函数指针(变量)的创建格式 2.2 函数指针(变量)的使用格式 2.3 例子 判别 3. typedef 关键字 3.1 typedef的作用 3.2 typedef的运作逻辑 和 函数指针类型…...

打造线上+线下相结合的O2O平台预约上门服务小程序源码系统 带完整的安装代码包以及搭建部署教程
系统概述 本系统采用前后端分离的设计架构,前端以微信小程序为载体,提供直观、易用的用户界面;后端则采用稳定的服务器架构,确保数据处理的高效与安全。系统主要包括用户端、商户端和管理员端三大模块,通过API接口实现…...
python sys模块
在Python中,sys模块提供了访问和使用解释器的许多功能的方法,包括命令行参数、环境变量、路径管理、标准输入输出流等。sys模块是Python的标准库的一部分,不需要额外安装即可使用。 常用的sys模块功能 1. sys.argv sys.argv是一个包含命令…...

【Linux 报错】SSH服务器拒绝了密码。请再试一次。(xshell)
出现该错误 可能的原因: 你写入的登录密码错误了,错误原因有: 1、本来输入就错误了 2、创建用户时,只创建了用户名,但密码没有重新设置 3、多人使用同一台服务器时,该服务器管理员(本体&#x…...

云计算实训43——部署k8s基础环境、配置内核模块、基本组件安装
一、前期系统环境准备 1、关闭防火墙与selinux [rootk8s-master ~]# systemctl stop firewalld[rootk8s-master ~]# systemctl disable firewalldRemoved symlink /etc/systemd/system/multi-user.target.wants/firewalld.service. Removed symlink /etc/systemd/system/dbus…...

TAbleau 可视化 干货分享 | 简单三步助你打造完美仪表板
只需单击几下,你将能轻松创建美观、信息丰富的可视化效果、节省时间并推动业务向前发展! 借助精心设计的仪表板,分析师可以更好地理解复杂数据背后的信息,更有效地向他人分享你的见解,从而做出更明智的决策。 值得思考…...
JVM性能调优之5种垃圾收集器
JDK垃圾收集器 一、Serial GC垃圾收集器Serial GC的工作原理Serial GC的特点Serial GC的配置参数Serial GC的适用场景Serial GC的优缺点优点:缺点: Serial GC的总结 二、Parallel GC垃圾收集器Parallel GC的工作原理Parallel GC的特点Parallel GC的配置参…...

基于单片机的仔猪喂饲系统设计
文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设…...

Helm Deploy Online Rancher v2.9.1
文章目录 准备安装查看下载 准备 $ kubectl get node NAME STATUS ROLES AGE VERSION kube-master01 Ready control-plane 19d v1.29.5 kube-node01 Ready <none> 19d v1.29.5 kube-node02 Ready <none&…...

【办公效率】Axure会议室预订小程序原型图,含PRD需求文档和竞品分析
作品说明 作品页数:共50页 兼容版本:Axure RP 8/9/10 应用领域:中小型企业的会议室在线预订 作品申明:页面内容仅用于功能演示,无实际功能 作品特色 本作品为会议室预订小程序原型图,定位于拥有中大型…...

论文解析一: SuperPoint 一种自监督网络框架,能够同时提取特征点的位置以及描述子
目录 SuperPoint:一种自监督网络框架,能够同时提取特征点的位置以及描述子1.特征点预训练2.自监督标签3.整体网络结构3.1 先对图像进行卷积3.2 特征点提取部分(Interest Point Decoder)3.3 特征描述子提取部分(Descrip…...

【评估指标】Fβ-score
1. Fβ-score 概述 Fβ-score 是一种综合考量精确率(precision)和召回率(recall)的分类评估指标。其公式为: 1.1 Precision(精确率):预测为正类的样本中,实际为正类的比…...

1963Springboot个性化音乐推荐管理系统idea开发mysql数据库web结构java编程计算机网页源码maven项目
博主介绍:专注于Java .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的可以…...
solidity从入门到精通(持续更新)
我一度觉得自己不知何时变成了一个浮躁的人,一度不想受外界干扰的我被干扰了,再无法平静的去看一本书,但我仍旧希望我能够克服这些,压抑着自己直到所有的冲动和奇怪的思想都无法再左右我行动。 自律会让你更加自律,放纵…...
UEFI入门(二):edk2项目编译流程
UEFI入门(二):edk2项目编译流程 一、编译构建流程:1. 安装依赖工具2. 初始化构建环境3. 配置工具链和目标4. 定义平台配置5. 构建并编译 二、uefi-tools编译edk2实践:1. 克隆EDK2 项目2. 构建并编译 参考文章ÿ…...

局域网一套键鼠控制两台电脑(台式机和笔记本)
服务端(有键盘和鼠标的电脑作为服务端) 下载软件 分享文件:BarrierSetup-2.3.3.exe 链接:https://pan.xunlei.com/s/VO66rAZkzxTxVm-0QRCJ33mMA1?pwd4jde# 配置服务端 一, 二, 客户端屏幕名称一定要和…...
最新Nessus2024.9.8版本主机漏洞扫描/探测工具下载Windows版
Nessus号称是世界上最流行的漏洞扫描程序,全世界有超过75000个组织在使用它。该工具提供完整的电脑漏洞扫描服务,并随时更新其漏洞数据库。Nessus不同于传统的漏洞扫描软件,Nessus可同时在本机或远端上遥控,进行系统的漏洞分析扫描…...
关于使用 @iconify/vue2图标库组件的离线使用
Iconify 是最通用的图标框架,将各种图标库的图标集中在这里的一个组件库,例如ant-design,element-ui等 网站地址如下 https://iconify.design/getting-started/ 1.安装依赖 这两个包提供了图标组件和离线图标数据的支持。 npm install iconify/vue2 i…...

pdfmake生成pdf的使用
实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdf…...

PLM系统有哪些品牌推荐?国内不错的PLM厂商有哪些?
在当今快速变化的商业环境中,产品生命周期管理PLM系统已成为企业技术创新和管理创新的重要工具。PLM系统涵盖了产品从概念设计到市场推出、使用维护直至最终报废的整个生命周期,通过整合与产品相关的所有信息,助力企业实现高效、协同的产品开…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...