当前位置: 首页 > news >正文

深度剖析指针(中)——“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 )();

  1.  将0强制类型转化为void (*)()类型的函数指针
  2.  这就意味着0地址处放着一个函数,函数没参数,返回类型是void
  3.  调用0地址处的这个函数

其实这句代码的意思就是一次函数调用

void (   *signal (  int , void(*)(int) )   )(int);

 signal括号里面只有类型,没有变量名,说明这是一个函数声明

void (*)(int) signal(int, void(*)(int));——可以这样理解

  1. 这句代码是一次函数声明
  2. 函数的名字是signal
  3. signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
  4. 该函数指针指向的函数参数是int,返回类型是void
  5. signal函数的返回类型也是一个函数指针
  6. 该函数指针指向的函数参数是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们你们好呀&#xff0c;今天小雅兰的内容仍旧是深度剖析指针噢&#xff0c;在上一篇博客中&#xff0c;我已经写过了字符指针、数组指针、指针数组、数组传参和指针传参的知识点&#xff0c;那么这篇博客小雅兰会讲解一下函数指针、函数指针数组 、指向函数指针数组…...

论文阅读 | Video Frame Synthesis using Deep Voxel Flow

前言&#xff1a; 视频帧生成方法&#xff08;视频插帧/视频预测&#xff09;ICCV2017 oral Video Frame Synthesis using Deep Voxel Flow 引言 当下进行视频帧合成的方法分为两种&#xff0c;第一种是光流法&#xff0c;光流准确的话效果好&#xff0c;光流不准确的话则生…...

我所理解的生活

诞生 人真正意义上的诞生应该是社会学意义上的&#xff0c;是一种意识到自我、自我与社会关系的存在&#xff0c;只有这种诞生&#xff0c;才是完整人生的基点&#xff0c;大千世界中&#xff0c;唯有人类以生活作为自己的存在方式&#xff0c;除人类以外&#xff0c;从无机界…...

debian 部署nginx https

我是flask 处理请求单进程&#xff0c; 差点意思 &#xff0c; 考虑先flask 在往下走 一&#xff1a;安装nginx 因为我是debian 系统&#xff0c;所以我的建议是直接 sudo apt-get install nginx 你也可以选择在官网下载&#xff0c; 但是我搭建ssl 的时候安装openssl非常的麻…...

SQL 层功能改进 - lookupJoin 的优化

一、传统 join 算法lookupJoin 是 join 查询的一种&#xff0c;传统 join 算法为&#xff1a;1. 遍历 A 表&#xff0c;读取一条数据 r2. 遍历 B 表&#xff0c;对于每条数据&#xff0c;与 r 进行 join 操作3. 重复 1、2 操作&#xff0c;直到 A 表遍历完所有数据二、lookupJo…...

动态规划:鸣人的影分身

在火影忍者的世界里&#xff0c;令敌人捉摸不透是非常关键的。我们的主角漩涡鸣人所拥有的一个招数——多重影分身之术——就是一个很好的例子。影分身是由鸣人身体的查克拉能量制造的&#xff0c;使用的查克拉越多&#xff0c;制造出的影分身越强。针对不同的作战情况&#xf…...

如何为三星active2手表安装自己DIY的表盘

一、步骤介绍 Step 1. 下载Galaxy watch studio&#xff1b; Step 2. 按照up主“隔壁张师傅2022”的文章进行安装。 二、安装流程简单说明&#xff1a; ① 电脑端官网下载并安装Galaxy Watch Designer或者Galaxy Watch Studio程序。 ② 关闭手表蓝牙连接&#xff0c;并打开调…...

Android 项目必备(四十二)-->Android 多窗口模式

简介 自由窗口模式: 该模式类似于常见的桌面操作系统&#xff0c; 应用界面的窗口可以自由的拖动和修改大小。 分屏模式 该模式可以在手机上使用&#xff0c; 该模式将屏幕一分为二&#xff0c; 同时显示两个应用界面。 画中画模式: 该模式主要用于TV&#xff0c; 在该模式下…...

OpenHarmony的未来和如何做好一个开源社区

今天要分享的文章&#xff0c;可能更多只是作为一种观点。主要包括2个内容。OpenHarmony的未来和如何做好一个开源社区&#xff0c;好的&#xff0c;接下来开始今天的内容。 你对OpenHarmony的未来如何看待&#xff1f; OpenHarmony的未来看起来非常光明&#xff0c;因为它具…...

二叉搜索树实现

树的导览 树由节点&#xff08;nodes&#xff09;和边&#xff08;edges&#xff09;构成&#xff0c;如下图所示。整棵树有一个最上端节点&#xff0c;称为根节点&#xff08;root&#xff09;。每个节点可以拥有具有方向的边&#xff08;directed edges&#xff09;&#xf…...

解决Spring Data Jpa 实体类自动创建数据库表失败问题

先说一下我遇到的这个问题&#xff0c;首先我是通过maven创建了一个spring boot的工程&#xff0c;引入了Spring data jpa&#xff0c;结果实体类创建好之后&#xff0c;运行工程却没有在数据库中自动创建数据表。 找了半天发现是一个配置的问题! hibernate.ddl-auto节点的配…...

Elasticsearch:创建一个简单的 “你的意思是?” 推荐搜索

“你的意思是” 是搜索引擎中一个非常重要的功能&#xff0c;因为它们通过显示建议的术语来帮助用户&#xff0c;以便他可以进行更准确的搜索。比如&#xff0c;在百度中&#xff0c;我们进行搜索时&#xff0c;它通常会显示一些更为常用推荐的搜索选项来供我们选择&#xff1a…...

urllib之ProxyHandler代理以及CookieJar的cookie内存传递和本地保存与读取的使用详解

处理更高级操作时(Cookies处理&#xff0c;代理设置)&#xff0c;需要一个强大的工具Handler&#xff0c;可以理解成各种处理器&#xff0c;有处理登录认证的、有处理Cookies的、有处理代理设置的。利用这些几乎可以做到HTTP请求中所有事情。当中urllib.request模块里的 BaseHa…...

华为造车锚定智选模式, 起点赢家赛力斯驶入新能源主航道

文|螳螂观察 作者| 易不二 近日&#xff0c;赛力斯与华为的一纸联合业务深化合作协议&#xff0c;给了频频猜测赛力斯与华为之间关系的舆论一个明确的定调&#xff1a;智选模式已成为华为与赛力斯共同推动中国新能源汽车产业高质量发展的坚定选择。 自华为智能汽车业务开启零…...

[oeasy]python0096_游戏娱乐行业_雅达利_米洛华_四人赛马_影视结合游戏

游戏娱乐行业 回忆上次内容 游戏机行业从无到有 雅达利 公司 一枝独秀并且带领 行业 发展起来 雅达利公司 优秀员工 乔布斯 在 朋友 帮助下完成了《pong》 Jobs 黑了 Woz 一部分收入 然后拿着钱 去印度禅修了 游戏行业 会如何继续 呢&#xff1f;?&#x1f914; 灵修 乔布…...

使用python测试框架完成自动化测试并生成报告-实例练习

练习一: 使用unittest 完成自动化测试并使用HttpTestRunner生成报告 1、写个简单的计算器功能&#xff0c;大小写转换功能&#xff0c;随机生成字符串功能 2、编写测试用例&#xff0c;不同的数据&#xff08;你能想到的所有测试用例&#xff09;&#xff0c;并进行断言。除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 该文章本人认为十分有用&#xff0c;便自己敲一遍笔记加固印象原文链接 原文这个笔记感觉比我老师讲的更加透彻&#xff0c;清晰。很好的展示了线性代数的原…...

人工智能写的十段代码,九个通过测试了

“抢走你工作的不会是 AI &#xff0c;而是先掌握 AI 能力的人” 编程测试 1. 我想用golang实现二叉树前序&#xff0c;请你帮我写一下代码。 // 定义二叉树节点 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }// 前序遍历 func PreOrderTraversal(root *Tre…...

巴塞尔问题数值逼近方法

巴塞尔问题&#xff1a;计算所有平方数的导数和 ∑n1∞1n2lim⁡n→∞(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∑∞​n21​n→∞lim​(121​221​⋯n21​) 其理论解为…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

SpringTask-03.入门案例

一.入门案例 启动类&#xff1a; package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...