程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<6>

在这里插入图片描述

大家好啊,我是小象٩(๑òωó๑)۶
我的博客:Xiao Xiangζั͡ޓއއ

很高兴见到大家,希望能够和大家一起交流学习,共同进步
在这里插入图片描述
今天我们继续来学习数组指针变量,二维数组传参的本质,函数指针变量,typedef关键字,函数指针数组,转移表…

目录

  • 数组指针变量
    • 1.1 数组指针变量是什么?
    • 1.2 数组指针变量怎么初始化
  • 二、二维数组传参的本质
  • 三、 函数指针变量
    • 3.1 函数指针变量的创建
    • 3.2 函数指针变量的使用
    • 3.3.1 typedef 关键字
  • 四、函数指针数组
  • 五、转移表
  • 六、结尾

数组指针变量

1.1 数组指针变量是什么?

在C语言中,数组指针变量(也叫指向数组的指针)是一种特殊的指针变量,它指向的是一个数组。
之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。
数组指针变量是指针变量?还是数组?
答案是:指针变量。
我们已经熟悉:
• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
形式:

int (*p)[5];

这里定义了一个名为p的指针变量,它指向一个包含5个int类型元素的数组。注意,(p)两边的括号不能省略,因为[]的优先级高于,如果没有括号int *p[5]; 这定义的是一个指针数组(一个包含5个int指针的数组),而不是指向数组的指针。

解释:p先和结合,说明p是⼀个指针变量,然后指针指向的是⼀个大小为5个整型的数组。所以p是
⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[]的优先级要⾼于
号的,所以必须加上()来保证p先和*结合。

1.2 数组指针变量怎么初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名 。

int arr[10] = {0};
&arr;//得到的就是数组的地址

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

int(*p)[10] = &arr;

我们调试也能看到 &arr 和 p 的类型是完全⼀致的。
数组指针类型解析:

int (*p) [10] = &arr;| | || | || | p指向数组的元素个数| p是数组指针变量名p指向的数组的元素类型

二、二维数组传参的本质

二维数组传参的本质是传递指向一维数组的指针,函数通过这个指针和已知的列数信息,按照连续内存存储的方式来访问和处理二维数组中的元素

有了数组指针的理解,我们就能够讲⼀下二维数组传参的本质了。
过去我们有⼀个二维数组的需要传参给⼀个函数的时候,我们是这样写的:

#include <stdio.h>
void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++)
{for (j = 0; j < c; j++){printf("%d ", a[i][j]);}printf("\n");
}
}
int main()
{int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

#include <stdio.h>
void test(int (*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", *(*(p + i) + j));}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}

总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

三、 函数指针变量

3.1 函数指针变量的创建

什么是函数指针变量呢?
根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
那么函数是否有地址呢?
我们做个测试:

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

结果:

test: 005913CA
&test: 005913CA

确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:

void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

函数指针类型解析:

int (*pf3) (int x, int y)| | ------------ | | || | pf3指向函数的参数类型和个数的交代| 函数指针变量名pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型

3.2 函数指针变量的使用

通过函数指针调⽤指针指向的函数。

include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));printf("%d\n", pf3(3, 5));return 0;
}

3.3.1 typedef 关键字

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:

typedef unsigned int uint;
//将unsigned int 重命名为uint

如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t;

但是对于数组指针和函数指针稍微有点区别:
⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边

函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void(*pfun_t)(int);//新的类型名必须在*的右边

那么要简化代码2,可以这样写:

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

四、函数指针数组

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

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

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

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

五、转移表

函数指针数组的⽤途:转移表
举例:计算器的⼀般实现:

#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 x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);switch (input){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;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;
}
int main()
{int x, y;int input = 1;int ret = 0;int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d", &input);if ((input <= 4 && input >= 1)){printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = (*p[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输⼊有误\n");}} while (input);return 0;
}

六、结尾

这一课的内容就到这里了,下节课继续学习指针的其他一些知识
如果内容有什么问题的话欢迎指正,有什么问题也可以问我!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/68111.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL时间类型相关总结(DATETIME, TIMESTAMP, DATE, TIME, YEAR)

MySQL时间类型相关总结(DATETIME, TIMESTAMP, DATE, TIME, YEAR) MySQL官方文档&#xff1a; https://dev.mysql.com/doc/refman/8.0/en/date-and-time-types.html 一. 对比&#xff1a; 在 MySQL 中&#xff0c;处理时间相关的数据类型主要有以下几种&#xff1a;DATE、TIME、…

前缀和练习——洛谷P8218:求区间和

题目: 这道题很简单&#xff0c;直接根据题目无脑套公式 代码&#xff1a; #include<bits/stdc.h> using namespace std; const int N 1e5 9; using ll long long; ll a[N], perfix[N]; int main() {ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);//取消同步输…

【STM32】蓝牙模块数据包解析

使用到的蓝牙模块为DX-BT24&#xff0c;他可以将串口转蓝牙&#xff0c;实现与手机蓝牙的通信&#xff0c;本次实现使用手机蓝牙发送数据包来控制单片机LED的亮灭&#xff0c;规则如下&#xff1a; AA 05 01 FF AF 该数据包表示包头为AA&#xff0c;05表示该数据包的大小&#…

NSS-DAY2

Crypto [HNCTF 2022 Week1]A dictator 题目&#xff1a; from random import randint from secret import flagoffset randint(1,100) % 26 # print(offset)assert flag.startswith(NSSCTF{) assert all([ord(c) not in range(ord(A),ord(Z)) for c in flag[7:-1]])for cha…

【vue3 入门到实战】7. 标签中的 ref

目录 1. ref 的作用 2. 如何使用 1. ref 的作用 用于注册模板引用 用在普通DOM标签上&#xff0c;获取的是DOM节点。 用在组件标签上&#xff0c;获取的是组件的实例对象。 2. 如何使用 代码如下 <template><div class"app"><h2 ref"titl…

手写MVVM框架-实现简单的数据代理

MVVM框架最显著的特点就是虚拟dom和响应式的数据、我们以Vue为例&#xff0c;分别实现data、computed、created、methods以及虚拟dom。 这一章我们先实现简单的响应式&#xff0c;修改数据之后在控制台打印。 我们将该框架命名为MiniVue。 首先我们需要创建MiniVue的类(src/co…

Redis命令:列表模糊删除详解

前言 在Redis中&#xff0c;列表&#xff08;List&#xff09;是一种非常常用的数据结构&#xff0c;允许存储多个有序的元素。然而&#xff0c;在实际应用中&#xff0c;可能会遇到需要删除列表中符合某种模式的元素的需求。本文将详细介绍如何在Redis中实现列表的模糊删除。…

spy-debugger + Charles 调试移动端/内嵌小程序H5

简介说明&#xff1a; PC端可以用F12进行console等进行调试&#xff0c;但移动端App中使用webview就无法进行实时调试&#xff0c;针对这种情况 1. 安装 全局安装 spy-debugger sudo npm install spy-debugger -g // window不用加sudo2. spy-debugger 证书 其实spy-debugg…

【目标检测】模型验证:K-Fold 交叉验证

K-Fold 交叉验证 1、引言1.1 K 折交叉验证概述 2、配置2.1 数据集2.2 安装包 3、 实战3.1 生成物体检测数据集的特征向量3.2 K 折数据集拆分3.3 保存记录3.4 使用 K 折数据分割训练YOLO 4、总结 1、引言 我们将利用YOLO 检测格式和关键的Python 库&#xff08;如 sklearn、pan…

Android studio ternimal 中gradle 指令失效(gradle环境变量未配置)

默认gradle路径&#xff1a;C:\Users\ylwj.gradle\wrapper\dists\gradle-8.10.2-bin\a04bxjujx95o3nb99gddekhwo\gradle-8.10.2\bin 环境变量-系统环境变量-双击path-配置上即可-注意重启studio才会生效

Axure大屏可视化动态交互设计:解锁数据魅力,引领决策新风尚

可视化组件/模板预览&#xff1a;https://8dge09.axshare.com 一、大屏可视化技术概览 在数据驱动决策的时代&#xff0c;大屏可视化技术凭借直观、动态的展示方式&#xff0c;已成为众多行业提升管理效率和优化决策过程的关键工具。它能够将复杂的数据转化为易于理解的图形和…

Resnet 改进:尝试在不同位置加入Transform模块

目录 1. TransformerBlock 2. resnet 3. 替换部分卷积层 4. 在特定位置插入Transformer模块 5. 使用Transformer全局特征提取器 6. 其他 Tips:融入模块后的网络经过测试,可以直接使用,设置好输入和输出的图片维度即可 1. TransformerBlock TransformerBlock是Transfo…

PromptSource和LangChain哪个更好

目录 1. 设计目标与定位 PromptSource LangChain 2. 功能对比 3. 优缺点分析 PromptSource LangChain 4. 如何选择&#xff1f; 5. 总结 PromptSource 和 LangChain 是两个在自然语言处理&#xff08;NLP&#xff09;领域非常有用的工具&#xff0c;但它们的设计目标和…

MySQL调优02 - SQL语句的优化

SQL语句的优化 文章目录 SQL语句的优化一&#xff1a;SQL优化的小技巧1&#xff1a;编写SQL时的注意点1.1&#xff1a;查询时尽量不要使用*1.2&#xff1a;连表查询时尽量不要关联太多表1.3&#xff1a;多表查询时一定要以小驱大1.4&#xff1a;like不要使用左模糊或者全模糊1.…

langchain教程-12.Agent/工具定义/Agent调用工具/Agentic RAG

前言 该系列教程的代码: https://github.com/shar-pen/Langchain-MiniTutorial 我主要参考 langchain 官方教程, 有选择性的记录了一下学习内容 这是教程清单 1.初试langchain2.prompt3.OutputParser/输出解析4.model/vllm模型部署和langchain调用5.DocumentLoader/多种文档…

如何实现网页不用刷新也能更新

要实现用户在网页上不用刷新也能到下一题&#xff0c;可以使用 前端和后端交互的技术&#xff0c;比如 AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;、Fetch API 或 WebSocket 来实现局部页面更新。以下是一个实现思路&#xff1a; 1. 使用前端 AJAX 或 Fetch…

在ubuntu22.04上先部署docker,再编译安装kamailio,附详细操作流程及docker和makailio的版本号

以下是在Ubuntu 22.04上部署Docker并编译安装Kamailio的详细操作流程&#xff0c;包含版本号信息&#xff1a; 一、部署Docker&#xff08;版本&#xff1a;24.0.7&#xff09; 更新系统包 sudo apt update && sudo apt upgrade -y安装依赖工具 sudo apt install -y ap…

大模型中提到的超参数是什么

在大模型中提到的超参数是指在模型训练之前需要手动设置的参数&#xff0c;这些参数决定了模型的训练过程和最终性能。超参数与模型内部通过训练获得的参数&#xff08;如权重和偏置&#xff09;不同&#xff0c;它们通常不会通过训练自动学习&#xff0c;而是需要开发者根据任…

位运算及常用技巧

涉及位运算的运算符如下表所示&#xff1a; 位运算的运算律&#xff1a; 负数的位运算 首先&#xff0c;我们要知道&#xff0c;在计算机中&#xff0c;运算是使用的二进制补码&#xff0c;而正数的补码是它本身&#xff0c;负数的补码则是符号位不变&#xff0c;其余按位取反…

组合总和III(力扣216)

这道题在回溯的基础上加入了剪枝操作。回溯方面我就不过多赘述&#xff0c;与组合(力扣77)-CSDN博客 大差不差&#xff0c;主要讲解一下剪枝(下面的代码也有回溯操作的详细注释)。我们可以发现&#xff0c;如果我们递归到后面&#xff0c;可能集合过小&#xff0c;无法满足题目…