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

C语言指针进阶之三——函数指针、函数指针数组、指向函数指针数组的指针

目录

1.指针知识回顾 

2.数组知识回顾 

3.函数指针

3.1函数指针引入

3.2函数指针的使用

3.3阅读两段有趣的代码

3.4类型重定义,typedef补充

4.函数指针数组

4.1函数指针数组引入

4.2函数指针数组的使用(转移表)

 5.指向函数指针数组的指针


 

1.指针知识回顾 


①.指针就是个变量,用来存放地址,地址唯一标识了一片空间。

  内存会划分成一个个的内存单元,每个内存单元都有一个独立的编号,编号也称为地址,而C语言也把地址叫做指针。地址(指针)需要存储在变量中,这个变量就被称为指针变量。

例如:

int  a = 10;

int * p = &a;

这里的p就是一个指针变量

     

②指针的大小是固定的4/8字节(32位平台、64位平台)

    地址是由物理的电线产生的(高低频的电信号),32位机器对应的就是32根地址线,产生32个0/1序列,32个0/1组成的二进制序列,把这个二进制序列就作为地址,32个bit位才能存储这个地址也就是需要8个字节才能存储,所以在32位机器下,地址的大小就是4个字节。

而对于64位机器来说, 对应的就是32根地址线,产生64个0/1序列,64个0/1组成的二进制序列,把这个二进制序列就作为地址,64个bit位才能存储这个地址也就是需要8个字节才能存储,所以在64位机器下,地址的大小就是8个字节。

③指针是有类型的,指针的类型决定了指针的+-整数的步长,指正解引用操作时候的权限。

列如一个字符类型的数据解引用只可以访问一个字节的内存空间 

2.数组知识回顾 

C语言基础 -数组

3.函数指针

3.1函数指针引入

指向函数的指针

整型指针-----指向整型数据的指针

字符指针----指向字符的指针

那么函数指针就是指向函数的指针,函数指针变量里面保存了函数的地址。

那就是说函数是不是应该有自己的地址,我们知道取地址数组名就得到函数的地址,那么对于函数来说,是不是也是取地址函数名就可以拿到函数的地址呢,我们来看一下。,见如下代码

 

 说明:&函数名和函数名都是函数的地址

那么现在如果我要把函数的地址保存到一个变量里边去,那么就应该是一个指针变量,我们思考一下那么这个指针变量的类型应该是什么样的呢?

 首先应该是一个指针

(*p)

接着这个指针的类型应该是函数指针类型,就应该是函数类型,函数类型应该有参数和返回值呀,所以:上述代码中的函数指针就可以写为:

int (*p)(int ,int) = &add;

P先和*结合,说明其为一个指针,指向一个函数,返回类型是int ,有两个参数

再来看一个例子:

 

 这个函数的指针该如何写呢?

我们应该关注两个点:函数的返回值,函数的参数类型

所以这个函数的函数指针就可以写为:

①void (*p)(char *,int [10]) = &test;//10也可以省略

注意第二个参数为数组类型,当然也可以写为:void (*p)(char *,int *) = &test;因为数组作为形参的本质还是一个指针。

②如果去掉括号可以吗?

形如void * p(char *,int [10]) = &test;

这样写就导致P先和()结合成为一个函数,void 和 * 结合变成一个返回值类型

3.2函数指针的使用

这是从指针角度解引用然后进行传参使用。

然后我们对比一下下面这个写法,看看有什么结论

 

一样是可行的说明,这里的*号可有可无

3.3阅读两段有趣的代码

① (*(void(*)() )  0) ()

然后末尾一个括号就是正常的函数传参

所以这段代码要表示的含义就是: 

调用0地址处的函数

第一步:把0强制类型转换为void(*)()类型的函数指针

第二步:解引用调用0地址处的函数

②void (*signal(int ,(void(*)(int)))(int)

 

那么这段代码就这样理解

1.signal是一个函数声明

2.signal 函数有两个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型

3.该函数指针指向的函数有一个int类型的参数,返回类型是void

4.signal函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void

那么对代码②进行简化:

3.4类型重定义,typedef补充

平常我们说这样一个类型比如:unsigned  int这个类型,我们觉得每次书写都太长了很麻烦,那我们就可以进行类型重定义:

typeddef unsigned int  uint;

那么下次我们书写的时候就可以使用uint来代替unsigned int,使得类型简化。

同样的,对于指针类型也可以重定义,比如将int *重定义

typedef int* ptr

那么原来的:int * p1

就可以改写为: ptr p1

同理,也可以对我们的数组指针或者函数指针类型进行重定义,但是稍微不同的是,重定义的类型名要和*在一起。

比如:这是一个数组指针类型:int (*)[10];如何进行类型重定义?

①typedef  int (*)[10]  parr_t  这样定义是错误的

正确的定义方法为:

typedef  int (*parr_t )[10],重定义的类型名放在1括号里才行,对于函数指针来说也是一样的。

那么上面的代码就可以1这样来做:

typedef  void(*ptr_t)(int)

那么:void (*signal(int ,(void(*)(int)))(int)

就可以简化为: ptr_t  signal(int ,ptr_t);

4.函数指针数组

4.1函数指针数组引入

引言:我们前面知道可以把一个整型类型的指针放入一个数组,比如:int * arr[10],可以把一个字符类型的指针放入一个数组,比如:char *arr[5],那么可不可以把统一类型的函数指针也放入一个数组里面呢。

我们来看一下函数指针数组的形式:

指针:int *p

函数指针:int (*p)(int ,int)

指针数组:int  *parr[10]

那么函数指针数组首先肯定是数组,数组的每一个元素应该是函数类型的指针

那么函数指针数组的形式: int (*parr[5])(int,int)

 例子引入:计算器实现加减乘除(只针对整数的运算),如果没有引入这个函数指针数组我们会怎么写呢

我们发现四个函数,除了执行的运算不一样,函数的返回类型和参数个人、类型都是一样的,那么如果我们要把函数的地址保存在一个指针变量里面,这个指针变量的类型是不是应该都是一样的,都是 int (*)(int,int)类型,那么我们是不是就可以把这四个指针放到一个数组里面呢(数组的每一个元素的类型是一样的) 

 如果没有引用到函数指针数组我们来看一下这个计算器的实现过程:

#include<stdio.h>
//定义执行加减乘除的四个运算
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 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;defult:printf("选择错误重新选择\n");break;}} while (input);return 0;
}

但是,这个代码虽然实现了功能,很多代码确实重复了,代码冗余,,第二个这个函数只能实现四个功能,但是像按位与等运算类型本质也是一样的,如果这样写case语句就会越来越长。所以我们需要更好更简洁的代码书写方式:

4.2函数指针数组的使用(转移表)

  看代码:

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 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) = { NULL,Add,Sub,Mul,Div };//                            0    1   2   3   4do{menu();printf("请选择:\n");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入两个操作数:\n");scanf("%d %d", &x, &y);ret = pfarr[input](x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("选择错误,重新选择\n");}

使用了函数指针,就直接省略了很长的Switch并且冗余的代码,并且后续添加的时候只用修改很小一部分内容。

我们看一下实现效果:

 5.指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。

形式:

int (*pf)(int ,int)//这是函数指针

int(*pfarr[4])(int ,int) //这是函数指针数组

数组可以取地址:&pfarr 这是函数指针数组的指针。

在函数指针数组的基础上得到指向函数指针数组的指针

int (*(*p)[4])(int ,int) = &pfarr

 数组和指针我个人决定就有点像面多加水,水多加面的相互状态,大家可以多多理解使用。这是本期的所有内容。欢迎大家指正与讨论,创作不易,希望可以收获到大家的三连。

相关文章:

  • kali系统入侵电脑windows(win11系统)渗透测试,骇入电脑教学
  • diff命令详解
  • PalWorld/幻兽帕鲁Ubuntu 22.04 LTS 一键部署脚本
  • React中使用LazyBuilder实现页面懒加载方法一
  • 头歌C语言结构体
  • 【驱动】TI AM437x(内核调试-07):devmem2直接读写内存、寄存器,devkmem读取内核变量
  • Android 系统启动流程
  • 运维SRE-01 目录结构体系、find
  • Codeforces Round 921 (Div. 2)补题
  • ambari hdp 企业级安装实战
  • C语言之猜凶手
  • 04 Redis之命令(Hash型Value命令+List型Value命令+Set型Value命令+有序集合ZSET型Value命令)
  • MySQL必看表设计经验汇总-上(精华版)
  • Linux之常见的管理命令
  • java日志框架总结(三 、Log4j日志框架)
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • CAP 一致性协议及应用解析
  •  D - 粉碎叛乱F - 其他起义
  • Elasticsearch 参考指南(升级前重新索引)
  • extract-text-webpack-plugin用法
  • gitlab-ci配置详解(一)
  • JS笔记四:作用域、变量(函数)提升
  • Linux Process Manage
  • maven工程打包jar以及java jar命令的classpath使用
  • Octave 入门
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 猴子数据域名防封接口降低小说被封的风险
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 力扣(LeetCode)357
  • 浅谈Golang中select的用法
  • 区块链将重新定义世界
  • 使用 QuickBI 搭建酷炫可视化分析
  • 一文看透浏览器架构
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • # Apache SeaTunnel 究竟是什么?
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (Python) SOAP Web Service (HTTP POST)
  • (二)linux使用docker容器运行mysql
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (转)http协议
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • .NET Core WebAPI中封装Swagger配置
  • .net framework 4.0中如何 输出 form 的name属性。