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

C语言——指针进阶(2)

在这里插入图片描述
继续上次的指针,想起来还有指针的内容还没有更新完,今天来补上之前的内容,上次我们讲了函数指针,并且使用它来实现一些功能,今天我们就讲一讲函数指针数组等内容,废话不多说,我们开始今天的学习吧。

函数指针数组
在引出函数指针的时候,我们会先引出字符指针还有整型指针,那同样的道理,函数指针数组是什么,我们可能还是比较陌生,但是如果是字符指针数组,还有整型指针数组我们可能听起来不是特别陌生。
那再讲之前,我给大家先讲一下函数指针,函数指针是啥呢,函数指针竟然是指针,那就是来存放函数的地址的,那我们先写一个简单的函数,然后来存放它的地址。

int Add(int x, int y)
{return x + y;
}
int main()
{printf("%p\n", &Add);printf("%p\n", Add);return 0;
}

我们可以发现打印出来的两个地址都是同一个地址,所以表示两个方式都可以取出函数的地址

在这里插入图片描述
在这里我们可以看到我们的地址是一样的,那我们要存放他们的地址改怎么写呢

我们来看看

#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{printf("%p\n", &Add);printf("%p\n", Add);int (*pf)(int, int) = &Add;return 0;
}

这样我们就可以把它函数的地址写出来,这和那个数组指针也特别相似,取出的数组地址也是这样的。
因为时间太长,我帮大家在回忆一下我们的数组指针,比如我们现在要取出的是数组的地址,我们改怎么表示,让我们来看一看吧。

int main()
{int arr[] = { 1,2,3,4,5,6 };int(*pa)[6] = &arr;return 0;
}

在这里我们可以看到的是数组指针,是指向数组地址的指针,可以看到它的格式其实和函数指针的差不多。
那我们的函数指针有什么用呢,答案是可以调用这个函数,比如就我们上面的Add函数,我们来看代码。

int Add(int x, int y)
{return x + y;
}
int main()
{printf("%p\n", &Add);printf("%p\n", Add);int (*pf)(int, int) = &Add;return 0;
}

我们可以这样写

int Add(int x, int y)
{return x + y;
}
int main()
{printf("%p\n", &Add);printf("%p\n", Add);int (*pf)(int, int) = &Add;int ret = (*pf)(3, 5);printf("%d\n", ret);return 0;
}

在这里插入图片描述
在这里其实可以不写,意思就是int ret = (*pf)(3, 5);可以写成int ret = pf(3, 5);,其实这两个的结果是一样的,或者说就算我们加很多也不影响我们的结果,来看看吧

int Add(int x, int y)
{return x + y;
}
int main()
{printf("%p\n", &Add);printf("%p\n", Add);int (*pf)(int, int) = &Add;int ret = (*pf)(3, 5);printf("%d\n", ret);ret = printf("%d\n", (*********pf)(4, 6));return 0;
}

写了这么多主要是为了后面做铺垫,那我们现在才来讲我们的函数指针数组
数组是什么,我们之前见过字符指针数组,他是来存放字符指针,我们也讲过整型指针数组,它是来存放整型指针的数组,那同样函数指针数组就是来存放函数指针的,我们来举个列子


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;
}int main()
{int (*pf1)(int, int) = &Add;int (*pf2)(int, int) = &Sub;int (*pf3)(int, int) = &Mul;int (*pf4)(int, int) = &Div;return 0;
}

我们首先看到这么多的函数,他们的功能类似我们的计算器,这里就只写了加减乘除的功能,通过我们的代码我们可以看到的是四个一样的函数指针,那我们是不能就可以用数组的形似来存放它们。因为数组是不是能存放多个类型相同的元素。
那我们就可以来写一写看

int main()
{int (*pf1)(int, int) = &Add;int (*pf2)(int, int) = &Sub;int (*pf3)(int, int) = &Mul;int (*pf4)(int, int) = &Div;int(*pfarr[4])(int, int) = { &Add,&Sub,&Mul,&Div };//pfarr就是存放函数指针的数组return 0;
}

那函数指针数组难道就是一个摆设吗,不!!!它还是有一点作用的,那它的作用是什么呢,让我们来一起看看吧。
我们是不是就可以用它来实现一个计算器,我们来设计一个吧

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("***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("请选择>");scanf("%d" ,&input);switch (input){case 0:printf("退出计算器\n");break;case 1:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret = %d\n", ret);break;case 2:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret = %d\n", ret);break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;}

虽然我们实现一个简单的计算器,但是其实我们的代码看起来并不是特别好看,反而有点冗余,重复的内容有点多,而且如果我们加一些其他的运算的时候,函数会变得越来越长,那我们该怎么改改让我们的代码看起来又好看又实用呢,这个时候我们就可以用函数指针数组进行改正,现在我们来对它进行整改。

我们用这个的时候之前一定要注意的问题是计算器这个函数的类型是一样的,否则其他的话就不行了。

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("***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("请选择>");scanf("%d", &input);int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };if (input == 0){printf("退出计算器\n");}else if (input >= 1 && input <= 4){printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = pf[input](x, y);printf("%d\n", ret);}} while (input);return 0;}

这样也能实现我们计算器的效果,而且好处也是特别大,首先解决了我们的代码冗余的现象,其次是我们如果想加入其他的运算,也可以直接加入,只要改一下数组的大小,并把函数的地址放到里面就可以解决问题,其实很简单。我们也将这个东西叫做转移表,这就是我们函数数组指针应用,但是这只是一个例子,其他还有很多的作用,这里就不扩展了。

指向函数指针数组的指针

一看这个标题不知道大家头是不是大了,反正小编一开始看这个有点云里雾里的感觉,但是经过一段时间的学习,发现它也就这啊,那现在我们开始举列子。

int a = 10;
int b = 20;
int c = 30;
int* arr[] = { &a,&b,&c };//整型指针数组

我们先来看简单的,然后慢慢的引入,先是整型指针数组,那如果是这样的一个问题,要指向整型指针数组的指针,我们该怎么写

int* (*parr)[3] = &arr;

这就是指向整型指针数组的指针,同样的道理我们来看指向函数指针数组的指针。

int (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div };int(*(*ppf)[5])(int, int) = &pf;

我们就拿计算器的来写,就是这个样子的。
但是这个其实不用理解太深,稍微了解一下就知道了,不是特别重要。

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。

什么意思呢,就是假设我们有两个函数一个函数叫A,一个函数叫B,我们B函数的参数是A函数的地址,我们在B函数里使用这个函数A。
今天就讲一个例子,还是我们的计算器,下次给大家讲一个qsort,这也叫快速排序,让大家更好的理解我们的内容。

先把我们的计算器在拿出来给大家看一下

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("***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("请选择>");scanf("%d" ,&input);switch (input){case 0:printf("退出计算器\n");break;case 1:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret = %d\n", ret);break;case 2:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("请输入两个操作数 :");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret = %d\n", ret);break;default:break;}} while (input);return 0;}

我们可以把相同的部分也就是case语句分装成一个函数的思路去实现

我们可以看到我们的代码哪里是不太一样的地方,其实就是我们调用函数的地方,如果我们能写一个函数calc,来放这些函数的地址,问题是不是解决了,其实就是写成这个样子。

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("***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("请选择>");scanf("%d" ,&input);switch (input){case 0:printf("退出计算器\n");break;case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;default:break;}} while (input);return 0;}

那现在要做的是什么,就是完成我们的calc函数这样就可以实现计算器的功能

calc我们的返回类型就是void 函数他们的地址,那我们是不是可以写成int (*pf)(int,int)


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("***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("ret = %d\n", ret);
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择>");scanf("%d", &input);switch (input){case 0:printf("退出计算器\n");break;case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;default:break;}} while (input);return 0;}

这样就把之前的功能也实现了。我们后面再讲一个qsort,今天懒得讲了,摆烂ing

我们下次再见,拜拜

相关文章:

C语言——指针进阶(2)

继续上次的指针&#xff0c;想起来还有指针的内容还没有更新完&#xff0c;今天来补上之前的内容&#xff0c;上次我们讲了函数指针&#xff0c;并且使用它来实现一些功能&#xff0c;今天我们就讲一讲函数指针数组等内容&#xff0c;废话不多说&#xff0c;我们开始今天的学习…...

【汇编中的寄存器分类与不同寄存器的用途】

汇编中的寄存器分类与不同寄存器的用途 寄存器分类 在计算机体系结构中&#xff0c;8086CPU&#xff0c;寄存器可以分为以下几类&#xff1a; 1. 通用寄存器&#xff1a; 通用寄存器是用于存储数据和执行算术运算的寄存器。在 x86 架构中&#xff0c;这些通用寄存器通常包括…...

基于文本提示的图像目标检测与分割实践

近年来&#xff0c;计算机视觉取得了显着的进步&#xff0c;特别是在图像分割和目标检测任务方面。 最近值得注意的突破之一是分段任意模型&#xff08;SAM&#xff09;&#xff0c;这是一种多功能深度学习模型&#xff0c;旨在有效地从图像和输入提示中预测对象掩模。 通过利用…...

【4-5章】Spark编程基础(Python版)

课程资源&#xff1a;&#xff08;林子雨&#xff09;Spark编程基础(Python版)_哔哩哔哩_bilibili 第4章 RDD编程&#xff08;21节&#xff09; Spark生态系统&#xff1a; Spark Core&#xff1a;底层核心&#xff08;RDD编程是针对这个&#xff09;Spark SQL&#xff1a;…...

04 卷积神经网络搭建

一、数据集 MNIST数据集是从NIST的两个手写数字数据集&#xff1a;Special Database 3 和Special Database 1中分别取出部分图像&#xff0c;并经过一些图像处理后得到的[参考]。 MNIST数据集共有70000张图像&#xff0c;其中训练集60000张&#xff0c;测试集10000张。所有图…...

【hadoop运维】running beyond physical memory limits:正确配置yarn中的mapreduce内存

文章目录 一. 问题描述二. 问题分析与解决1. container内存监控1.1. 虚拟内存判断1.2. 物理内存判断 2. 正确配置mapReduce内存2.1. 配置map和reduce进程的物理内存&#xff1a;2.2. Map 和Reduce 进程的JVM 堆大小 3. 小结 一. 问题描述 在hadoop3.0.3集群上执行hive3.1.2的任…...

数据结构--6.5二叉排序树(插入,查找和删除)

目录 一、创建 二、插入 三、删除 二叉排序树&#xff08;Binary Sort Tree&#xff09;又称为二叉查找树&#xff0c;它或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a; ——若它的左子树不为空&#xff0c;则左子树上所有结点的值均小于它的根结构的值…...

无需公网IP,在家SSH远程连接公司内网服务器「cpolar内网穿透」

文章目录 1. Linux CentOS安装cpolar2. 创建TCP隧道3. 随机地址公网远程连接4. 固定TCP地址5. 使用固定公网TCP地址SSH远程 本次教程我们来实现如何在外公网环境下&#xff0c;SSH远程连接家里/公司的Linux CentOS服务器&#xff0c;无需公网IP&#xff0c;也不需要设置路由器。…...

Java工具类

一、org.apache.commons.io.IOUtils closeQuietly() toString() copy() toByteArray() write() toInputStream() readLines() copyLarge() lineIterator() readFully() 二、org.apache.commons.io.FileUtils deleteDirectory() readFileToString() de…...

makefile之使用函数wildcard和patsubst

Makefile之调用函数 调用makefile机制实现的一些函数 $(function arguments) : function是函数名,arguments是该函数的参数 参数和函数名用空格或Tab分隔,如果有多个参数,之间用逗号隔开. wildcard函数:让通配符在makefile文件中使用有效果 $(wildcard pattern) 输入只有一个参…...

算法通关村第十八关——排列问题

LeetCode46.给定一个没有重复数字的序列&#xff0c;返回其所有可能的全排列。例如&#xff1a; 输入&#xff1a;[1,2,3] 输出&#xff1a;[[1,2,3]&#xff0c;[1,3,2]&#xff0c;[2,1,3]&#xff0c;[2,3,1]&#xff0c;[3,1,2]&#xff0c;[3,2,1]] 元素1在[1,2]中已经使…...

基于STM32设计的生理监测装置

一、项目功能要求 设计并制作一个生理监测装置&#xff0c;能够实时监测人体的心电图、呼吸和温度&#xff0c;并在LCD液晶显示屏上显示相关数据。 随着现代生活节奏的加快和环境的变化&#xff0c;人们对身体健康的关注程度越来越高。为了及时掌握自身的生理状况&#xff0c…...

Go-Python-Java-C-LeetCode高分解法-第五周合集

前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接&#xff1a;LeetCode-Go-Python-Java-C Go-Python-Java-C-LeetCode高分解法-第一周合集 Go-Python-Java-C-LeetCode高分解法-第二周合集 Go-Python-Java-C-LeetCode高分解法-第三周合集 G…...

【前端知识】前端加密算法(base64、md5、sha1、escape/unescape、AES/DES)

前端加密算法 一、base64加解密算法 简介&#xff1a;Base64算法使用64个字符&#xff08;A-Z、a-z、0-9、、/&#xff09;来表示二进制数据的64种可能性&#xff0c;将每3个字节的数据编码为4个可打印字符。如果字节数不是3的倍数&#xff0c;将会进行填充。 优点&#xff1…...

leetcode 925. 长按键入

2023.9.7 我的基本思路是两数组字符逐一对比&#xff0c;遇到不同的字符&#xff0c;判断一下typed与上一字符是否相同&#xff0c;不相同返回false&#xff0c;相同则继续对比。 最后要分别判断name和typed分别先遍历完时的情况。直接看代码&#xff1a; class Solution { p…...

[CMake教程] 循环

目录 一、foreach()二、while()三、break() 与 continue() 作为一个编程语言&#xff0c;CMake也少不了循环流程控制&#xff0c;他提供两种循环foreach() 和 while()。 一、foreach() 基本语法&#xff1a; foreach(<loop_var> <items>)<commands> endfo…...

Mojo安装使用初体验

一个声称比python块68000倍的语言 蹭个热度&#xff0c;安装试试 系统配置要求&#xff1a; 不支持Windows系统 配置要求: 系统&#xff1a;Ubuntu 20.04/22.04 LTSCPU&#xff1a;x86-64 CPU (with SSE4.2 or newer)内存&#xff1a;8 GiB memoryPython 3.8 - 3.10g or cla…...

艺术与AI:科技与艺术的完美融合

文章目录 艺术创作的新工具生成艺术艺术与数据 AI与互动艺术虚拟现实&#xff08;VR&#xff09;与增强现实&#xff08;AR&#xff09;机器学习与互动性 艺术与AI的伦理问题结语 &#x1f389;欢迎来到AIGC人工智能专栏~艺术与AI&#xff1a;科技与艺术的完美融合 ☆* o(≧▽≦…...

Android常用的工具“小插件”——Widget机制

Widget俗称“小插件”&#xff0c;是Android系统中一个很常用的工具。比如我们可以在Launcher中添加一个音乐播放器的Widget。 在Launcher上可以添加插件&#xff0c;那么是不是说只有Launcher才具备这个功能呢&#xff1f; Android系统并没有具体规定谁才能充当“Widget容器…...

探索在云原生环境中构建的大数据驱动的智能应用程序的成功案例,并分析它们的关键要素。

文章目录 1. Netflix - 个性化推荐引擎2. Uber - 实时数据分析和决策支持3. Airbnb - 价格预测和优化5. Google - 自然语言处理和搜索优化 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...