深入理解指针2

各位小伙伴们,我们继续来学习指针,指针和结构体以及动态内存管理对后面的数据结构学习有非常大的帮助,所有我们一定要把这些知识点学会。OK,正式进入学习之旅吧

1.数组名的理解

在上⼀个章节我们在使⽤指针访问数组的内容时,有这样的代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
这⾥我们使⽤ &arr[0] 的⽅式拿到了数组第⼀个元素的地址,但是其实数组名本来就是地址,而且
是数组⾸元素的地址,我们可以来做个测试。
int main()
{int arr[10] = { 0 };printf("%p\n", &arr[0]);printf("%p\n", arr);return 0;
}

我们发现数组名和数组⾸元素的地址打印出的结果⼀模⼀样,数组名就是数组⾸元素(第⼀个元素)的地址。
但是如果数组名是首元素的地址的话,那么我们可以看一看下面这个代码输出结果是什么?
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));return 0;
}
输出的结果是:40,如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对。其实数组名是首元素的地址这句话是对的,但是有2个例外:

1.sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.&数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)。​​​​​​​

除此之外,任何地方使用数组名,数组名都表示首元素的地址。
我们继续试一试下面这个代码
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);printf("arr = %p\n", arr);printf("&arr = %p\n", &arr);return 0;
}

奇怪的是三个代码的结果一模一样,那么arr和&arr区别在哪?

我们可以看一下这段代码,就知道数组首元素的地址和整个数组的地址到底是怎么回事了

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0]   = %p\n", &arr[0]);printf("&arr[0]+1 = %p\n", &arr[0]+1);printf("arr       = %p\n", arr);printf("arr+1     = %p\n", arr+1);printf("&arr      = %p\n", &arr);printf("&arr+1    = %p\n", &arr+1);return 0;
}

这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是⾸元素的地址,+1就是跳过⼀个元素。
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
到这⾥大家应该搞清楚数组名的意义了吧。

2.使用指针访问数组

有了前⾯知识的支持,再结合数组的特点,我们就可以很方便的使⽤指针访问数组了。
int main()
{int arr[10] = { 0 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;int i = 0;for (int i = 0; i < sz; i++){scanf("%d", p + i);//scanf("%d", arr+i);//也可以这样写}for (i = 0; i < sz; i++){printf("%d ", *(p + i));}return 0;
}

这个代码搞明白后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?答案是可以的。 我们只需要将打印的代码*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i)。
同理arr[i] 应该等价于 *(arr+i),数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移量求出元素的地址,然后解引⽤来访问的。
因为加法支持交换律,所以还能写成这样”i[arr]“,*(i+arr)等价于i[arr]。不过这种写法不常用,小伙伴们还是用自己习惯的方式来写。

3.一维数组传参的本质

数组我们学过了,之前也讲了,数组是可以传递给函数的,这个小节我们讨论⼀下数组传参的本质。⾸先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给⼀个函数后,函数内部求数组的元素个数吗?我们可以先想一个下面这段代码的结果是什么。
void test(int arr[])
{int sz = sizeof(arr) / sizeof(arr[0]);printf("%d", sz);
}int main()
{int arr[10] = { 0 };test(arr);return 0;
}

结果是1,这是因为数组传参的时候,传递的并非是数组,而是是首元素的地址,传地址要用指针来接收。在x86环境下,int类型的指针变量大小是4个字节,一个元素的大小是4,所以结果是1。

也就是说本质上数组传参本质上传递的是数组首元素的地址。
函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)⽽不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
总之一句话:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

 4.二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?所以接下来我们要学习二级指针。

 

对于⼆级指针的运算有:
*pp 通过对pp中的地址进⾏解引⽤,这样找到的是 p *pp  其实访问的就是 p。
**pp 先通过 *pp  找到 ,然后对 进⾏解引⽤操作: *p  ,那找到的是 a。

5.指针数组

指针数组是指针还是数组?
我们类比⼀下,整型数组:是存放整型的数组;字符数组:是存放字符的数组。
那指针数组呢?是存放指针的数组。这样写是不是比较好理解了。如图所示
那么指针数组到底有什么用呢?接下来我们通过一段代码来看一下有什么用

6.指针数组模拟二维数组

值得注意的是实际上并非完全是⼆维数组,因为每⼀行并非是连续的。

int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* arr[3] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", arr[i][j]);}printf("\n");}return 0;
}

arr[i]是访问arr数组的元素,arr[i]找到的数组元素指向了整型⼀维数组,arr[i][j]就是整型⼀维数组中的元素。
OK,今天指针内容的学习就到这里了,还有一部分指针的内容我们以后在学,小伙伴们一定要把今天的知识点吃透,我们再接再励。

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

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

相关文章

【大数据】Flink SQL 语法篇(四):Group 聚合

Flink SQL 语法篇&#xff08;四&#xff09;&#xff1a;Group 聚合 1.基础概念2.窗口聚合和 Group 聚合3.SQL 语义4.Group 聚合支持 Grouping sets、Rollup、Cube 1.基础概念 Group 聚合定义&#xff08;支持 Batch / Streaming 任务&#xff09;&#xff1a;Flink 也支持 G…

SpringMVC 学习(四)之获取请求参数

目录 1 通过 HttpServletRequest 获取请求参数 2 通过控制器方法的形参获取请求参数 3 通过 POJO 获取请求参数&#xff08;重点&#xff09; 1 通过 HttpServletRequest 获取请求参数 public String handler1(HttpServletRequest request) <form action"${pageCont…

7.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-通过逆向分析确定游戏明文接收数据过程

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;通过逆向分析确定游戏明文发送数据过程 上一个内容中得出它是使用的send函数发送的数据包&#xff0c;所以接收数据它指定用的是recv函数接收的数据 然后在跳转recv函数分析时发现跳转到了wsock32.d…

【嵌入式移植】7、U-Boot源码分析4—链接脚本分析

U-Boot源码分析4—链接脚本分析 1 u-boot-spl.lds1.1 链接脚本的生成1.2 u-boot-spl.lds内容分析1.3 text - 程序代码段1.4 sram其它段定义1.4.1 .rodata只读数据段1.4.2 .data数据段1.4.3 .u_boot_list段 1.5 BSS段1.6 /DISCARD/ 从上一篇文章【嵌入式移植】6、U-Boot源码分析…

Linux字符设备驱动中同类型多设备节点的创建---一个驱动程序支持多个同类型设备

文章目录 前言1 代码解析1.1 驱动层1.2 应用层 2 运行结果总结 前言 本期分享的内容相对比较简单&#xff0c;那就是同时注册多个同类型的字符设备驱动&#xff0c;那么这样我们就可以同时支持多个同类型的设备了&#xff01;下面来带大家看一下&#xff1a; 1 代码解析 1.1 …

【Flink精讲】Flink性能调优:CPU核数与并行度

常见问题 举个例子 提交任务命令&#xff1a; bin/flink run \ -t yarn-per-job \ -d \ -p 5 \ 指定并行度 -Dyarn.application.queuetest \ 指定 yarn 队列 -Djobmanager.memory.process.size2048mb \ JM2~4G 足够 -Dtaskmanager.memory.process.size4096mb \ 单个 TM2~8G 足…

MySQL知识点总结(五)——锁

MySQL知识点总结&#xff08;五&#xff09;——锁 锁分类表锁 & 行锁如何添加表锁&#xff1f;如何添加行锁&#xff1f; 读锁 & 写锁行锁 & 间隙锁&#xff08;gap lock&#xff09;& 临键锁&#xff08;next-key lock&#xff09; 加锁机制分析可重复读隔离…

OpenAI视频生成Sora技术简析

基本介绍 Sora是春节期间OpenAI发布的产品&#xff0c;主要是通过文字描述生成视频&#xff0c;通过大规模视频数据训练而成的生成模型&#xff0c;当前还没开放试用。官方发布的技术报告&#xff1a;https://openai.com/research/video-generation-models-as-world-simulators…

趣学贝叶斯定理:贝叶斯定理的先验概率、似然和后验概率(1)

在第7章中&#xff0c;我们讨论了如何利用空间推理去推导贝叶斯定理。现在研究如何将贝叶斯定理当作一种概率工具&#xff0c;对不确定性进行逻辑推理。本章将利用贝叶斯定理来计算和量化在给定数据的情况下&#xff0c;信念有多大的可能性为真。为此&#xff0c;需要使用该定理…

019 Spring Boot+Vue 电影院会员管理系统(源代码+数据库+文档)

部分代码地址&#xff1a; https://github.com/XinChennn/xc019-cinema 一、系统介绍 cinema项目是一套电影院会员管理系统&#xff0c;使用前后端分离架构开发包含管理员、会员管理、会员卡管理、电影票、消费记录、数据统计等模块 二、所用技术 后端技术栈&#xff1a; …

【泰山派RK3566】智能语音助手(一)移植Kaldi语音转文字

文章目录 移植过程硬件资源下载测试 移植过程 参考我的这篇博客 【RV1126】移植kaldi实时语音识别 硬件 资源下载 链接&#xff1a;https://pan.baidu.com/s/1x1udT5eNzzQHoPOTCQ182A?pwdlief 提取码&#xff1a;lief –来自百度网盘超级会员V6的分享 下载的文件里面有一个…

全志H713/H618方案:调焦电机(相励磁法步进电机)的驱动原理、适配方法

一、篇头 全志H713平台&#xff0c;作为FHD投影的低成本入门方案&#xff0c;其公板上也配齐了许多投影使用的模组&#xff0c;本文即介绍投影仪调焦所用的步进电机&#xff0c;此模组的驱动原理、配制方法、调试方法。因为条件限制&#xff0c;本文采用的是H618香橙派Z3平台&…