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

掌握指针进阶:一篇带你玩转函数指针、函数指针数组及指向函数指针数组的指针!!

🍁博客主页:江池俊的博客

💫收录专栏:C语言进阶之路

💡代码仓库:江池俊的代码仓库

🎪我的社区:GeekHub

🎉欢迎大家点赞👍评论📝收藏⭐

在这里插入图片描述

文章目录

  • 一、函数指针
      • 代码1:
      • 代码2:
  • 二、函数指针数组
    • 什么是函数指针数组?
    • 为什么使用函数指针数组?
    • 函数指针数组的基本用法
  • 三、 指向函数指针数组的指针
    • 指向函数指针数组的指针是什么?
    • 为什么使用指向函数指针数组的指针?
  • 总结


一、函数指针

在C语言中,函数是一等公民,可以像其他变量一样被传递和使用。而函数指针就是指向函数的指针变量,可以用来调用函数。本文将介绍函数指针的定义、使用方法以及注意事项。

函数指针的定义格式为:

返回值类型 (*指针变量名)(参数列表);

其中,返回值类型表示函数的返回值类型,指针变量名是指向函数的指针变量的名称,参数列表表示函数的参数类型和数量。

例如,定义一个指向返回值为int、参数为两个int类型的函数的指针:

int (*pAdd)(int, int);

这个指针可以指向任何返回值为int、参数为两个int类型的函数。

先看一段代码:

#include <stdio.h>
void test()
{printf("hehe\n");
}int main()
{printf("%p\n", test);printf("%p\n", &test);return 0;
}

输出结果:
在这里插入图片描述
输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:

void test()
{printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

阅读两段有趣的代码:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

这两段代码都涉及了函数指针的用法,让我们逐一来解释它们:

代码1:

(*(void (*)())0)();

这段代码是一个函数指针调用的例子。让我们逐步分解它:

  • void (*)() 表示一个函数指针类型,它指向一个不接受任何参数(void),并且返回类型为 void 的函数。

  • (void (*)())0这里的(void (*)())实际上是一个强制转化的操作,它是将0强制转化为函数指针类型,即是将函数指针初始化为一个地址为 0 的空指针,也就是一个无效的函数指针。

  • (*(void (*)())0)(); 则是将这个无效的函数指针进行了间接调用,实际上是试图调用地址为 0 的函数,这通常会导致程序崩溃(因为操作系统不允许在地址 0 处执行代码,会触发段错误)。


这段代码在 C 语言中属于未定义行为,不应该在实际代码中使用,因为它可能导致程序的崩溃或其他不可预测的行为。

代码2:

void (*signal(int, void(*)(int)))(int);
//我们也可以将它简化为以下这种形式:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

这段代码涉及的是 C 语言中的信号处理函数 signal 的声明。让我们逐步解释它:

  • void(*)(int) 表示一个函数指针类型,它指向一个接受一个 int 参数并返回 void 的函数。

  • signal 是一个函数,它接受两个参数:一个 int 参数和一个函数指针参数,然后返回一个与上述函数指针类型匹配的函数指针。


所以整个代码声明的含义是:signal 是一个函数,它接受一个 int参数和一个函数指针参数,返回一个函数指针,该函数指针指向一个接受一个 int 参数并返回 void 的函数,这个函数通常用于处理信号。


这段代码通常用于在 C 语言中设置信号处理函数,以便在程序接收到特定信号时执行特定的操作。


请注意,这里只是声明了 signal 函数的原型,实际使用时需要根据具体情况编写函数体。

:推荐《C陷阱和缺陷》

这本书中提及这两个代码。


二、函数指针数组

函数指针数组是 C 语言中一个强大且常用的工具,用于存储指向不同函数的指针,允许根据需要调用特定的函数。在本文中,我们将深入介绍函数指针数组的概念、用途和实例,帮助你理解并充分利用这一重要的 C 语言特性。

什么是函数指针数组?

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
int (*)() 类型的函数指针。
函数指针数组的用途:转移表

总之,函数指针数组实际上是一个数组,其元素都是指向函数的指针。这使得我们可以将不同的函数存储在数组中,并通过索引来调用特定的函数。这种灵活性使得函数指针数组在编写菜单驱动程序、状态机、回调机制等方面非常有用。以下是一个简单的示例:

#include <stdio.h>void func1() 
{printf("调用 func1函数\n");
}void func2() 
{printf("调用 func2函数\n");
}int main() 
{void (*funcPtrArray[2])() = {func1, func2};//函数指针数组funcPtrArrayfuncPtrArray[0](); //通过函数指针数组调用 func1funcPtrArray[1](); //通过函数指针数组调用 func2return 0;
}

为什么使用函数指针数组?

函数指针数组在以下情况下非常有用:

  1. 菜单驱动程序: 当需要实现一个用户界面,允许用户从菜单中选择不同的操作时,函数指针数组可以用来存储每个操作的处理函数。

  2. 状态机: 在状态机的实现中,可以使用函数指针数组来存储每个状态的处理函数,从而实现状态转换时的操作。

  3. 回调机制: 当你需要在某个事件发生时调用不同的函数,比如事件处理、信号处理等,函数指针数组提供了一种简洁的方式。

  4. 动态选择算法: 如果你有多个算法实现,但在运行时决定使用哪一个算法,函数指针数组可以帮助你实现动态选择算法。

函数指针数组的基本用法

让我们通过一个简单的例子来演示函数指针数组的基本用法:实现一个简单的计算器,允许用户选择不同的操作。
例子:(计算机)

#include <stdio.h>int add(int a, int b)
{return a + b;
}int subtract(int a, int b)
{return a - b;
}int multiply(int a, int b)
{return a * b;
}int division(int a, int b)
{return a / b;
}int main()
{int x, y;int input = 1;int restult = 0;// 定义函数指针数组,存储不同的操作函数//转移表int(*operation[])(int x, int y) = { 0,add,subtract,multiply,division };while (input){printf("\n*************************\n");printf(" 1:add           2:subtract \n");printf(" 3:multiply      4:division \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("输入操作数:");scanf("%d %d", &x, &y);restult = (*operation[input])(x, y);// 调用选定的函数printf("restult = %d\n", restult);}elseprintf("输入有误,请重新输入\n");}return 0;
}

在上述示例中,我们定义了一个函数指针数组 operation,其中的元素分别指向 addsubtractmultiplydivision 函数。用户可以根据选择来执行不同的操作。


三、 指向函数指针数组的指针

指向函数指针数组的指针是什么?

指向函数指针数组的指针是一个指针,指针指向一个的数组,数组元素都是函数指针。这种指针提供了对函数指针数组的更高级别的访问方式,使我们能够更灵活地处理函数指针数组。以下是一个示例:

#include <stdio.h>void func1() 
{printf("调用 func1函数\n");
}void func2() 
{printf("调用 func2函数\n");
}int main() 
{void (*funcPtrArray[2])() = {func1, func2};//函数指针的数组funcPtrArrayvoid (*(*ptrToFuncPtrArray))() = funcPtrArray;//指向函数指针数组funcPtrArray的指针ptrToFuncPtrArrayptrToFuncPtrArray[0](); // 通过指向函数指针数组的指针调用 func1ptrToFuncPtrArray[1](); // 通过指向函数指针数组的指针调用 func2return 0;
}

为什么使用指向函数指针数组的指针?

指向函数指针数组的指针可能在日常编程中不常见,但在某些情况下非常有用。以下是一些使用情况:

  1. 函数指针数组的参数传递: 通过传递指向函数指针数组的指针作为参数,可以避免复制整个数组,从而提高效率。

  2. 动态函数调用: 使用指向函数指针数组的指针,可以在运行时根据条件选择不同的函数进行调用。

  3. 代码模块化: 当函数指针数组较大或需要在多个函数之间共享时,使用指向函数指针数组的指针可以提高代码的模块化性。

  4. 函数指针数组的排序: 可以使用指向函数指针数组的指针来执行对函数指针数组的排序操作,以实现按照某种规则调用函数。

总结

  • 在本篇博客中,我们深入探讨了 C语言中的三个重要概念:函数指针、函数指针数组和指向函数指针数组的指针。这些概念虽然可能听起来有些复杂,但它们为我们在C编程中提供了更大的灵活性和功能。
  • 函数指针允许我们将函数作为数据,传递给其他函数或存储在数据结构中。通过使用函数指针,我们可以实现更动态和可配置的程序设计,同时避免代码的重复。
  • 函数指针数组进一步扩展了这种灵活性,允许我们将多个函数指针组织在一个数组中,以便在运行时根据需要选择和调用不同的函数。这在构建可插拔的模块和实现动态行为时特别有用。
  • 最后,我们介绍了指向函数指针数组的指针,这为我们提供了一种更高级的访问方式,使得处理函数指针数组变得更加优雅。它可以应用于参数传递、动态函数调用、代码模块化以及对函数指针数组的排序等各种场景,从而增强了程序的模块性、可维护性和性能。

🔥今天的分享就到这里, 如果觉得博主的文章还不错的话, 请👍三连支持一下博主哦🤞

在这里插入图片描述

相关文章:

掌握指针进阶:一篇带你玩转函数指针、函数指针数组及指向函数指针数组的指针!!

&#x1f341;博客主页&#xff1a;江池俊的博客 &#x1f4ab;收录专栏&#xff1a;C语言进阶之路 &#x1f4a1;代码仓库&#xff1a;江池俊的代码仓库 &#x1f3aa;我的社区&#xff1a;GeekHub &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文章目录 一…...

在Docker上部署2台节点,利用Keeplived实现双节点VIP 高可用,不需要关闭Keeplived,实现vip来回切换。

前言: keeplived的做高可用网上有很多例子,但是都存在这样那样的问题,比如: 1.使用的是默认抢占式,这样在主节点恢复后,又会将VIP 漂移回到主节点上,因此需要使用非抢占式模式,故障恢复时,可避免 VIP 切换造成的服务延迟。 2.使用的是默认组播,信息都会向默认的224.0.…...

leetcode 279. 完全平方数

2023.8.18 与零钱兑换相似&#xff0c;本题属于完全背包问题&#xff1a;完全平方数为物品&#xff0c;整数n为背包。 直接上代码&#xff1a; class Solution { public:int numSquares(int n) {vector<int> dp(n1 , INT_MAX);dp[0] 0;for(int i1; i*i<n; i){for(in…...

【从零学习python 】48.Python中的继承与多继承详解

文章目录 在Python中&#xff0c;继承可以分为单继承、多继承和多层继承。单继承 继承语法多继承 语法格式使用多继承时需要注意以下事项Python中的MRO新式类和旧式&#xff08;经典&#xff09;类 进阶案例 在Python中&#xff0c;继承可以分为单继承、多继承和多层继承。 单…...

二、编写第一个 Spring MVC 程序(总结项目报 404 问题以及 Spring MVC 的执行流程)

文章目录 一、编写第一个 Spring MVC 程序二、项目运行时报 404错误原因总结三、Spring MVC 的执行流程 一、编写第一个 Spring MVC 程序 创建 maven 项目&#xff0c;以此项目为父项目&#xff0c;在父项目的 pom.xml 中导入相关依赖 <dependencies><dependency…...

okhttp源码简单流程分析

拦截器详细解析可以看大佬简书 "https://www.jianshu.com/p/6fac73f7570f"和 “https://www.jianshu.com/p/3c740829475c” okhttp请求流程 1&#xff1a;OkHttpClient okHttpClient new OkHttpClient.Builder() 构建一个okhttpClient对象&#xff0c;传入你想传入的…...

SpringBoot整合Shiro实现登录认证,鉴权授权

文章目录 前言一、shiro简介二、环境搭建2.1.数据库2.1.1user用户表2.1.2user_role用户角色关系表2.1.3role角色表2.1.4role_permission角色权限关系表2.1.5permission权限表 2.2导坐标2.3实体类2.3.1User2.3.2Role2.3.3Permission 2.4MVC三层2.4.1User2.4.1.1mapper层2.4.1.2s…...

Airbnb开源数据可视化工具Visx

一、什么是visx visx 是用于 React 的富有表现力的底层可视化组件集合,结合了 d3 的强大功能来生成可视化,以及 React 更新 DOM 的诸多优势。 在 Airbnb 内部,visx 的目标是统一整个公司的可视化堆栈,在此过程中,创建了 visx 项目,从而有效的将 D3 的强大功能与 React …...

VR仿真实训系统编辑平台赋予老师更多自由和灵活性

为了降低院校教师在VR虚拟现实方面应用的门槛&#xff0c;VR公司深圳华锐视点融合多年的VR虚拟仿真实训系统制作经验&#xff0c;制作了VR动物课件编辑器&#xff0c;正在逐渐受到师生们的关注和应用。 简单来说&#xff0c;VR畜牧专业课件编辑器是一种可以制作虚拟现实动物教学…...

父类对象转成子类对象

import org.springframework.beans.BeanUtils; import java.util.ArrayList; import java.util.List; public class Test {public static void main(String[] args) {List<B> bList new ArrayList<>();B b new B("a","这是a","b",…...

Spring Boot中如何使用Flyway进行数据库迁移

在本文中&#xff0c;我们将了解如何使用 Flyway 来管理 Spring Boot 应用程序中的 SQL 数据库架构。 在本文中&#xff0c;我们将了解如何使用 Flyway 来管理Spring Boot应用程序中的SQL 数据库架构。 Flyway是一个数据库迁移工具&#xff0c;它提供迁移历史和回滚的功能&…...

web在线编辑器(vue版)

目录 前言一、monaco-editor1、源码2、体积优化 二、ace-editor&#xff1f;1、源码2、体积优化 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多…...

【论文阅读】 Model Sparsity Can Simplify Machine Unlearning

Model Sparsity Can Simplify Machine Unlearning 背景主要内容Contribution Ⅰ&#xff1a;对Machine Unlearning的一个全面的理解Contribution Ⅱ&#xff1a;说明model sparsity对Machine Unlearning的好处Pruning方法的选择sparse-aware的unlearning framework Experiments…...

Spring Clould 部署 - Docker

视频地址&#xff1a;微服务&#xff08;SpringCloudRabbitMQDockerRedis搜索分布式&#xff09; 初识Docker-什么是Docker&#xff08;P42&#xff0c;P43&#xff09; 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&…...

linux--链表动态创建

头插法&#xff1a; 核心代码&#xff1a; s->next head->next; head->next s; 尾插法 核心代码&#xff1a; tail head; s->next NULL; tail->next s; tail s; 当用头插法依次插入值分别为1,2,3,4,5的结点后&#xff0c; 单链表顺序为&#xff1a; he…...

iBooker 布客技术评论 20230818

一、程序员自检手册 为了避免焦虑&#xff0c;你首先需要做的就是梳理你的业务&#xff1a; &#xff08;1&#xff09;你所在的行业是轻资产还是重资产&#xff1f; 重资产就是人绕着机器转&#xff0c;创业需要买一大堆设备。如果是重资产&#xff0c;赶紧换一个。 &…...

CK-A60180、CK-B1542、CK-L3095单向离合器

CK-A1542、CK-A1747、CK-A2052、CK-A2652、CK-A3072、CK-A3580、CK-A4090、CK-A45100、CK-A450110、CK-A60130、CK-A65140、CK-A70150、CK-A75160、CK-A80170、CK-A1250、CK-A1855、CK-A2060、CK-A2563、CK-A2563T、CK-A2870、CK-A3080T、CK-A3585、CK-A35100、CK-A35140、CK-A…...

单因素多变量方差分析

多变量方差分析&#xff1a;是对多个独立变量是否受单个或多个因素影响而进行的方差分析。它不仅能够分析多个因素对观测变量的独立影响&#xff0c;更能够分析多个因素的交互作用能否对观测变量产生影响。本章以单因素多变量分析为例&#xff0c;即一个分组变量和多个欲分析的…...

Python Web:Django、Flask和FastAPI框架对比

原文&#xff1a;百度安全验证 Django、Flask和FastAPI是Python Web框架中的三个主要代表。这些框架都有着各自的优点和缺点&#xff0c;适合不同类型和规模的应用程序。 1. Django&#xff1a; Django是一个全功能的Web框架&#xff0c;它提供了很多内置的应用程序和工具&am…...

【CI/CD】Rancher K8s

Rancher & K8s Rancher 和 K8s 的关系是什么&#xff1f;K8s 全称为 Kubernetes&#xff0c;它是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用。而 Rancher 是一个完全开源的企业级多集群 Kubernetes 管理平台&#xff0c;实现了 Kubernetes 集群在混合…...

nodejs 之 express 实现下载网络图片并上传到七牛云对象存储oss空间

为方便阅读&#xff0c;本文将所有逻辑放在一个函数里&#xff0c;可根据自己的情况拆分。 安装依赖 在项目根目录下运行以下命令安装依赖 npm install express qiniu axios业务逻辑 在项目根目录下创建一个名为 app.js 的文件&#xff0c;并添加以下内容 const express re…...

综合能源系统(7)——综合能源综合评估技术

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 综合能源系统是多种能源系统非线性耦合的、多时间与空间尺度耦合的“源-网-荷一储”一体化系统&#xff0c;通过能源耦合、多能互补&#xff0c;能够实现能源的高效利用&#xff0c;并提高新能源的利用水平。对…...

【JS 线性代数算法之向量与矩阵】

线性代数算法 一、向量的加减乘除1. 向量加法2. 向量减法3. 向量数乘4. 向量点积5. 向量叉积 二、矩阵的加减乘除1. 矩阵加法2. 矩阵减法3. 矩阵数乘4. 矩阵乘法 常用数学库 线性代数是数学的一个分支&#xff0c;用于研究线性方程组及其解的性质、向量空间及其变换的性质等。在…...

配置 yum/dnf 置您的系统以使用默认存储库

题目 给系统配置默认存储库&#xff0c;要求如下&#xff1a; YUM 的 两 个 存 储 库 的 地 址 分 别 是 &#xff1a; ftp://host.domain8.rhce.cc/dvd/BaseOS ftp://host.domain8.rhce.cc/dvd/AppStream vim /etc/yum.repos.d/redhat.repo [base] namebase baseurlftp:/…...

Docker容器与虚拟化技术:Docker资源控制、数据管理

目录 一、理论 1.资源控制 2.Docker数据管理 二、实验 1.Docker资源控制 2.Docker数据管理 三、问题 1.docker容器故障导致大量日志集满&#xff0c;造成磁盘空间满 2、当日志占满之后如何处理 四、总结 一、理论 1.资源控制 (1) CPU 资源控制 cgroups&#xff0…...

python生成器有几种写法,python生成器函数例子

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python生成器有几种写法&#xff0c;python生成器函数例子&#xff0c;今天让我们一起来看看吧&#xff01; 本文部分参考&#xff1a;Python迭代器&#xff0c;生成器–精华中的精华 https://www.cnblogs.com/deeper/p…...

动态动画弹窗样式css

点击下载图片素材 html <div class"popWin"> </div> <div class"popPic"><div class"popWinBtn01">查看证书</div><div class"wintips01">恭喜您已完成训练营学习任务&#xff0c;荣誉证书已发放…...

数据生成 | MATLAB实现WGAN生成对抗网络数据生成

数据生成 | MATLAB实现WGAN生成对抗网络数据生成 目录 数据生成 | MATLAB实现WGAN生成对抗网络数据生成生成效果基本描述程序设计参考资料 生成效果 基本描述 1.WGAN生成对抗网络&#xff0c;数据生成&#xff0c;样本生成程序&#xff0c;MATLAB程序&#xff1b; 2.适用于MATL…...

PHP实现每日蛋白质摄入量计算器

1.laravel 路由 //每日蛋白质摄入计算器Route::get(api/protein/intake, FormulaControllerproteinIntakeCal); 2.代码 /*** 每日蛋白质摄入计算器*/public function proteinIntakeCal(){$number intval($this->request(number));$goalFactor array(0.8, 1.16, 0.8, 1.16,…...

vue elment 表格内表单校验代码

<p v-if"scope.row.id">{{ scope.row.bidderCode }}</p><el-form-itemclass"formitem"v-else:prop"bidderCode scope.row.id":rules"getValidationRules(投标人/供应商代码, scope.row.id)"><el-input v-model&…...