深度剖析指针(中)——“C”
各位CSDN的uu们你们好呀,今天小雅兰的内容仍旧是深度剖析指针噢,在上一篇博客中,我已经写过了字符指针、数组指针、指针数组、数组传参和指针传参的知识点,那么这篇博客小雅兰会讲解一下函数指针、函数指针数组 、指向函数指针数组的指针的知识点,现在,就让我们进入指针的世界吧
函数指针
函数指针数组
指向函数指针的数组
回调函数
函数指针
仍然是采用我们的类比法!!!
整型指针——指向整型的指针 int *
字符指针——指向字符的指针 char *
数组指针——指向数组的指针 int arr[10]; int (*p)[10]=&arr;
函数指针——指向函数的指针
数组指针中存放的是数组的地址
函数指针中存放的应该是函数的地址
那么,函数有地址吗?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
//&Add和Add就是一样的,没有区别
int main()
{printf("%p\n", Add);printf("%p\n", &Add);return 0;
}
输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
//&Add和Add就是一样的,没有区别
int main()
{printf("%p\n", Add);printf("%p\n", &Add);//函数的地址要存起来,就得放在函数指针变量中//pf就是函数指针int (*pf)(int, int) = Add;int ret = (*pf)(3, 5);int ret = Add(3, 5);int ret = pf(3, 5);//这三种写法都是可以的//pf前面的这颗*就是一个摆设return 0;
}
下面,再来看看:
void test()
{printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
阅读两段有趣的代码:
源于《C陷阱和缺陷》
(* ( void (*)() ) 0 )();
- 将0强制类型转化为void (*)()类型的函数指针
- 这就意味着0地址处放着一个函数,函数没参数,返回类型是void
- 调用0地址处的这个函数
其实这句代码的意思就是一次函数调用
void ( *signal ( int , void(*)(int) ) )(int);
signal括号里面只有类型,没有变量名,说明这是一个函数声明
void (*)(int) signal(int, void(*)(int));——可以这样理解
- 这句代码是一次函数声明
- 函数的名字是signal
- signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
- 该函数指针指向的函数参数是int,返回类型是void
- signal函数的返回类型也是一个函数指针
- 该函数指针指向的函数参数是int,返回类型是void
可以把这句代码简化一下:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);//将void(*)(int)重新起个别名叫pfun_t
注意:
typedef void (*pf_t2)(int);
//pf_t2是类型名
void (*pf)(int);
//pf是函数指针变量的名字
函数指针数组
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:
int *arr[10];//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();int *parr2[10]();int (*)() parr3[10];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的使用:
#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;
}
int main()
{//存放函数指针的数组——函数指针数组int (*pf[4])(int, int) = { Add,Sub,Mul,Div };//0 1 2 3int i = 0;for (i = 0; i < 4; i++){int ret = pf[i](8, 4);printf("%d\n", ret);}return 0;
}
下面,我们来写一个计算器,来完成整数的+ - * /
#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;
}
void menu()
{printf("#######################################\n");printf("#######1.Add 2.Sub################\n");printf("#######3.Mul 4.Div################\n");printf("#######0.exit ################\n");printf("#######################################\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);switch (input){case 1:ret=Add(x, y);break;case 2:ret=Sub(x, y);break;case 3:ret=Mul(x, y);break;case 4:ret=Div(x, y);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}printf("%d\n", ret);} while (input);return 0;
}
很轻松的,一个简易计算器的功能就实现了,我们来运行一下这个程序
但是这有问题啊!!!我选择了一个8,理应打印选择错误,而不应该打印请输入两个操作数呀!!!也不应该再打印这个ret!!!所以,我们要把程序修改一下!!!
#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;
}
void menu()
{printf("#######################################\n");printf("#######1.Add 2.Sub################\n");printf("#######3.Mul 4.Div################\n");printf("#######0.exit ################\n");printf("#######################################\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}
这样一修改,功能实现才完全正确,但是,还是有问题,这个代码代码冗余的问题非常严重!!!如果未来要增加一些其他的功能,例如:<< >> & | && || 那么我们的代码会越写越长,case会·越写越多,这样显然是不太好的!!!
使用函数指针数组的实现:
#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;
}
void menu()
{printf("#######################################\n");printf("#######1.Add 2.Sub################\n");printf("#######3.Mul 4.Div################\n");printf("#######0.exit ################\n");printf("#######################################\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };// 0 1 2 3 4do{menu();printf("请选择:>\n");scanf("%d", &input);if (input == 0){printf("退出计算器\n");break;}else if (input >= 1 && input <= 4){printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("%d\n", ret);}else{printf("选择错误\n");}} while (input);return 0;
}
函数指针数组的用途:转移表
指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组 ,数组的元素都是函数指针
#include <stdio.h>
int Add(int a, int b)
{return a + b;
}
int Sub(int a, int b)
{return a - b;
}
int main()
{int (*pf)(int, int) = Add;//函数指针数组int (*pfArr[4])(int, int) = { Add,Sub };//ppfArr是一个指向函数指针数组的指针变量int (*(*ppfArr)[4])(int, int) = &pfArr;return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
之前我们实现了一个简易版计算器功能,但是那个代码的冗余问题非常严重,后面我们用函数指针数组的方法改造了一下这段代码,那现在,我们换一种新的方式来改造,就是我们的回调函数啦!!!
改造前:
#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;
}
void menu()
{printf("#######################################\n");printf("#######1.Add 2.Sub################\n");printf("#######3.Mul 4.Div################\n");printf("#######0.exit ################\n");printf("#######################################\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>\n");scanf("%d", &input);switch (input){case 1:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个操作数:>\n");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}
改造后:
#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;
}
void menu()
{printf("#######################################\n");printf("#######1.Add 2.Sub################\n");printf("#######3.Mul 4.Div################\n");printf("#######0.exit ################\n");printf("#######################################\n");
}
void Calc(int (*pf)(int,int))//函数指针
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = pf(x, y);printf("%d\n", ret);
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();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("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}
也就是说:上述代码中的Add、Sub、Mul、Div都是回调函数!!!
之前用函数指针数组改造的那个代码实质上解决的是case语句过多的问题,而用回调函数改造的此代码实质上解决的是代码冗余的问题。
改造的两份代码解决的是完全不一样的问题,所以谈不上哪个方法更好!!!
其实回调函数还有更多内容,下一篇博客小雅兰带你玩转qsort,今天的内容就先到这里啦
好啦,告辞!!!
相关文章:
深度剖析指针(中)——“C”
各位CSDN的uu们你们好呀,今天小雅兰的内容仍旧是深度剖析指针噢,在上一篇博客中,我已经写过了字符指针、数组指针、指针数组、数组传参和指针传参的知识点,那么这篇博客小雅兰会讲解一下函数指针、函数指针数组 、指向函数指针数组…...
论文阅读 | Video Frame Synthesis using Deep Voxel Flow
前言: 视频帧生成方法(视频插帧/视频预测)ICCV2017 oral Video Frame Synthesis using Deep Voxel Flow 引言 当下进行视频帧合成的方法分为两种,第一种是光流法,光流准确的话效果好,光流不准确的话则生…...
我所理解的生活
诞生 人真正意义上的诞生应该是社会学意义上的,是一种意识到自我、自我与社会关系的存在,只有这种诞生,才是完整人生的基点,大千世界中,唯有人类以生活作为自己的存在方式,除人类以外,从无机界…...
debian 部署nginx https
我是flask 处理请求单进程, 差点意思 , 考虑先flask 在往下走 一:安装nginx 因为我是debian 系统,所以我的建议是直接 sudo apt-get install nginx 你也可以选择在官网下载, 但是我搭建ssl 的时候安装openssl非常的麻…...
SQL 层功能改进 - lookupJoin 的优化
一、传统 join 算法lookupJoin 是 join 查询的一种,传统 join 算法为:1. 遍历 A 表,读取一条数据 r2. 遍历 B 表,对于每条数据,与 r 进行 join 操作3. 重复 1、2 操作,直到 A 表遍历完所有数据二、lookupJo…...
动态规划:鸣人的影分身
在火影忍者的世界里,令敌人捉摸不透是非常关键的。我们的主角漩涡鸣人所拥有的一个招数——多重影分身之术——就是一个很好的例子。影分身是由鸣人身体的查克拉能量制造的,使用的查克拉越多,制造出的影分身越强。针对不同的作战情况…...
如何为三星active2手表安装自己DIY的表盘
一、步骤介绍 Step 1. 下载Galaxy watch studio; Step 2. 按照up主“隔壁张师傅2022”的文章进行安装。 二、安装流程简单说明: ① 电脑端官网下载并安装Galaxy Watch Designer或者Galaxy Watch Studio程序。 ② 关闭手表蓝牙连接,并打开调…...
Android 项目必备(四十二)-->Android 多窗口模式
简介 自由窗口模式: 该模式类似于常见的桌面操作系统, 应用界面的窗口可以自由的拖动和修改大小。 分屏模式 该模式可以在手机上使用, 该模式将屏幕一分为二, 同时显示两个应用界面。 画中画模式: 该模式主要用于TV, 在该模式下…...
OpenHarmony的未来和如何做好一个开源社区
今天要分享的文章,可能更多只是作为一种观点。主要包括2个内容。OpenHarmony的未来和如何做好一个开源社区,好的,接下来开始今天的内容。 你对OpenHarmony的未来如何看待? OpenHarmony的未来看起来非常光明,因为它具…...
二叉搜索树实现
树的导览 树由节点(nodes)和边(edges)构成,如下图所示。整棵树有一个最上端节点,称为根节点(root)。每个节点可以拥有具有方向的边(directed edges)…...
解决Spring Data Jpa 实体类自动创建数据库表失败问题
先说一下我遇到的这个问题,首先我是通过maven创建了一个spring boot的工程,引入了Spring data jpa,结果实体类创建好之后,运行工程却没有在数据库中自动创建数据表。 找了半天发现是一个配置的问题! hibernate.ddl-auto节点的配…...
Elasticsearch:创建一个简单的 “你的意思是?” 推荐搜索
“你的意思是” 是搜索引擎中一个非常重要的功能,因为它们通过显示建议的术语来帮助用户,以便他可以进行更准确的搜索。比如,在百度中,我们进行搜索时,它通常会显示一些更为常用推荐的搜索选项来供我们选择:…...
urllib之ProxyHandler代理以及CookieJar的cookie内存传递和本地保存与读取的使用详解
处理更高级操作时(Cookies处理,代理设置),需要一个强大的工具Handler,可以理解成各种处理器,有处理登录认证的、有处理Cookies的、有处理代理设置的。利用这些几乎可以做到HTTP请求中所有事情。当中urllib.request模块里的 BaseHa…...
华为造车锚定智选模式, 起点赢家赛力斯驶入新能源主航道
文|螳螂观察 作者| 易不二 近日,赛力斯与华为的一纸联合业务深化合作协议,给了频频猜测赛力斯与华为之间关系的舆论一个明确的定调:智选模式已成为华为与赛力斯共同推动中国新能源汽车产业高质量发展的坚定选择。 自华为智能汽车业务开启零…...
[oeasy]python0096_游戏娱乐行业_雅达利_米洛华_四人赛马_影视结合游戏
游戏娱乐行业 回忆上次内容 游戏机行业从无到有 雅达利 公司 一枝独秀并且带领 行业 发展起来 雅达利公司 优秀员工 乔布斯 在 朋友 帮助下完成了《pong》 Jobs 黑了 Woz 一部分收入 然后拿着钱 去印度禅修了 游戏行业 会如何继续 呢??🤔 灵修 乔布…...
使用python测试框架完成自动化测试并生成报告-实例练习
练习一: 使用unittest 完成自动化测试并使用HttpTestRunner生成报告 1、写个简单的计算器功能,大小写转换功能,随机生成字符串功能 2、编写测试用例,不同的数据(你能想到的所有测试用例),并进行断言。除0的…...
JavaWeb 实战 01 - 计算机是如何工作的
计算机是如何工作的1. 计算机发展史2. 计算机的基本组成2.1 冯诺依曼体系结构2.2 CPU的内部结构2.3 指令2.3.1 指令表2.3.1.1 寄存器2.3.2 CPU的工作流程2.4 小结3. 操作系统3.1 核心功能3.2 操作系统的软硬件结构3.3 什么是进程 / 任务3.4 进程管理3.4.1 管理3.4.2 PCB : 进程…...
线性代数学习-1
线性代数学习-1行图像和列图像行图像列图像总结本文转载于https://herosunly.blog.csdn.net/article/details/88698381 该文章本人认为十分有用,便自己敲一遍笔记加固印象原文链接 原文这个笔记感觉比我老师讲的更加透彻,清晰。很好的展示了线性代数的原…...
人工智能写的十段代码,九个通过测试了
“抢走你工作的不会是 AI ,而是先掌握 AI 能力的人” 编程测试 1. 我想用golang实现二叉树前序,请你帮我写一下代码。 // 定义二叉树节点 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }// 前序遍历 func PreOrderTraversal(root *Tre…...
巴塞尔问题数值逼近方法
巴塞尔问题:计算所有平方数的导数和 ∑n1∞1n2limn→∞(112122⋯1n2)\sum_{n1}^{\infty} \frac{1}{n^{2}}\lim _{n \rightarrow\infty}\left(\frac{1}{1^{2}}\frac{1}{2^{2}}\cdots\frac{1}{n^{2}}\right)n1∑∞n21n→∞lim(121221⋯n21) 其理论解为…...
【深度学习环境】Docker
1. Docker 相关安装配置 1.1 docker 安装 参考:https://www.runoob.com/docker/ubuntu-docker-install.html 1.2 nvidia-docker 安装 参考:https://zhuanlan.zhihu.com/p/37519492 1.3 代理加速 参考:https://yeasy.gitbook.io/docker_…...
基于vscode开发vue项目的详细步骤教程 2 第三方图标库FontAwesome
1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 3、基于vscode开发vue项目的详细步骤教程_水w的博客-CSDN博客 目录 六、第三方图标库FontAwesome 1 安装FontAwesome 解决报…...
今天面了个腾讯拿25K出来的软件测试工程师,让我见识到了真正的天花板...
今天上班开早会就是新人见面仪式,听说来了个很厉害的大佬,年纪还不大,是上家公司离职过来的,薪资已经达到中高等水平,很多人都好奇不已,能拿到这个薪资应该人不简单,果然,自我介绍的…...
OSG三维渲染引擎编程学习之六十九:“第六章:OSG场景工作机制” 之 “6.9 OSG数据变量”
目录 第六章 OSG场景工作机制 6.9 OSG数据变量 第六章 OSG场景工作机制 作为一个成熟的三维渲染引擎,需要提供快速获取场景数据、节点等信息,具备自定义数据或动画更新接口,能接收应用程序或窗口等各类消息。OSG三维渲染引擎能较好地完成上述工作,OSG是采用什么方式或工作…...
Tektronix泰克TDP3500差分探头3.5GHz
附加功能: 带宽:3.5 GHz 差分输入电容:≤0.3 pF 差分输入电阻:100 kΩ DC pk 交流输入电压:15 V >60 dB 在 1 MHz 和 >25 dB 在 1 GHz CMRR 出色的共模抑制——减少较高共模环境中的测量误差 低电容和电阻负载…...
轻松实现内网穿透:实现远程访问你的私人网络
导语:内网穿透是什么?为什么我们需要它?今天我们将介绍这个令人惊叹的技术,让你实现远程访问你的私人网络。 使用内网穿透,轻松实现外网访问本地部署的网站 第一部分:什么是内网穿透? 通俗解释…...
MySQL长字符截断
MySQL超长字符截断又名"SQL-Column-Truncation",是安全研究者Stefan Esser在2008 年8月提出的。 在MySQL中的一个设置里有一个sql_mode选项,当sql_mode设置为default时,即没有开启STRICT_ALL_TABLES选项时(MySQLsql_mo…...
python计算量比指标
百度百科是这么写的:量比定义:股市开市后平均每分钟的成交量与过去5个交易日平均每分钟成交量之比。计算公式:量比(现成交总手数 / 现累计开市时间(分) )/ 过去5日平均每分钟成交量。这里公式没有问题,但是…...
下拉框推荐-Suggest-SUG
什么是下拉框推荐 在我们使用各种app(飞猪)想要搜索我们想要的东西,假设我想要上海迪士尼的门票,那么精确的query是“上海迪士尼门票”,要打7个字,如果在你输入“上海”的时候app就推荐了query“上海迪士尼…...
Nmap的几种扫描方式以及相应的命令
Nmap是一款常用的网络扫描工具,它可以扫描目标网络上的主机和服务,帮助安全研究员了解目标网络的拓扑结构和安全情况。以下是Nmap的几种扫描方式以及相应的命令: 1.Ping扫描 Ping扫描可以用来探测网络上响应的主机,可以使用“-sn…...
从凡客诚品的案例分析b2c电子商务网站该怎样建设网络品牌/如何建网站要什么条件
📖目录 摘要文章汇总 HTTP和HTTPSAjax请求 技术栈作者致谢 🌂摘要 本文章意在汇总HTTP的历史文章方便查阅! 🎈文章汇总 🐱👤干货 HTTP:SSL证书简介!HTTP:HTTP和HTTPS…...
wordpress 做的网站/百度收录官网
【编者按】本文作者为 Hugo Giraudel,主要从各个角度论证了代码审查的重要性以及实现方法。文章系国内 ITOM 管理平台 OneAPM 编译呈现。以下为正文。 最近,笔者在Twitter上看到这样一句话: 可悲的是,对于很多学生、自由职业者以及…...
温州专业网站建设/推广产品
原标题:Python的新手如何配置适合自己的开发环境? Python语言是一种强大而简洁的编程语言。据IEEE Spectrum消息,Python在2020年继续蝉联最受欢迎的编程语言第一名。对于刚接触Python的新手来说,配置一个容易上手又适合自己的开发…...
p2p网贷网站建设方案/seo的基础优化
一、 先安装 orcale10.1客户端 setup右键属性,按下图设置 net manager 设置,不设置 pl/sql developer没办法连接 二、再安装 pl/sql developer 有了 net manager 设置 下面才自动出现,选择连接...
做网站需要什么人员/电商运营怎么自学
题意描述: 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 注意,返回任意的子集,所以与之前的题目在终止条…...
未央免费做网站/企业网站有哪些功能
点击上方“蓝色字”可关注我们!暴走时评:在加密货币领域,人们越来越怀疑官方监管是否能够跟上不断发展的市场趋势。因此,日本采取了大胆的新举措:日本金融服务厅(FSA)正式授予日本虚拟货币交易协…...