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

数组和指针的复杂关系

C语言中指针和数组的关系似乎很“纠结”,让人爱恨交织。本文试图帮助读者理清它们之间的复杂关系!

数组名的理解

数组元素在内存中是连续存放的,在C语言中,数组名有特殊的含义,它表示数组首元素的地址。因此,数组元素既可以用下标来访问,也可以用指针来访问

#include<stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };int i = 4;//访问数组下标为4的元素printf("%d\n", arr[i]);printf("%d\n", *(arr + i));return 0;
}

上面代码中arr[i]和*(arr + i)的效果是等价的,都是访问数组下标为i的元素。

arr + i表示arr数组下标为i的元素的地址,而*(arr + i)表示对arr数组下标为i的地址进行解引用操作

但是也有两个例外:

sizeof(数组名):sizeof中单独放数组名,那这个数组名表示的是整个数组,所以计算的是整个数组的大小(单位是字节)

&数组名:这里的数组名也表示的是整个数组,所以&数组名取出的是整个数组的地址。要注意的是:整个数组的地址和数组首元素的地址在数值上是相同的,但它们是有区别的,只是因为它们的起始空间是一样的,而取地址时取出的是空间中地址较小的地址而已!

那整个数组的地址(&数组名)和数组首元素的地址(数组名)到底有什么区别呢?

#include <stdio.h>
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;
}

这段代码的运行结果为

dd4a162fa5ca43eca198f1208cdd4541.png

&arr[0]和arr都是数组首元素(int类型)的地址,所以对它们加1的结果是向后跳过了一个整型即4个字节,但&arr表示的是整个数组,取出的是整个数组的地址,所以对它加1的结果是跳过了整个数组的大小即40个字节

这是因为整个数组的地址和数组首元素的地址的类型是不同的,而指针的类型决定了对指针进行解引用时候的权限

使用指针访问数组

由于在上一篇文章中已经弄清楚了指针的一些基本用法了,这里不在过多阐述,对于使用指针访问数组其实在讲数组名时已经提到了,这里直接上代码

#include<stdio.h>
int main()
{int arr[5] = { 0 };int i = 0;//输入for (i = 0; i < 5; i++){scanf("%d", arr + i);}//输出for (i = 0; i < 5; i++){printf("%d ", *(arr + i));}printf("\n");return 0;
}

运行结果:

e5d5d0d4f30847af80bd2c8ea1d2b5b5.png

数组元素之所以能通过这种方法来引用,是因为数组元素的访问在编译器处理的时候,也是转换成⾸元素的地址+偏移量求出元素的地址,然后解引用来访问的。所以在输入操作时,arr + i等价于&arr[i],表示取数组arr的第i + 1个元素的地址;在输出操作时,*(arr + i)等价于arr[i],表示引用数组首地址所指元素后第i个元素

上面的代码中若把arr赋值给一个整型指针,然后通过这个整型指针来访问数组和直接用数组名访问结果是一样的!

一维数组传参的本质

在没有学指针之前,数组传参传递的是数组名,函数的形参部分也用数组来接收,但是到这里我们已经知道了数组名是数组首元素的地址,那么在数组传参的时候传数组名,其实本质上传递的是数组首元素的地址

其实一维数组做函数形参时,因为它只起到接收数组起始地址的作用,所以会发生数组类型到指针类型的隐式转换,即使将形参声明为一维数组,他也将退化为指针,系统仅仅为其分配指针所占的内存空间,并不为形参数组分配额外的存储空间,而是让形参数组共享实参数组所占的存储空间。

因此用一维数组作函数形参与用指针变量作函数形参本质上是一样的,因为它们接收的都是数组的起始地址,都需按此地址对主调函数中的实参数组元素进行间接寻址,因此在被调函数中既能以下表形式也能以指针形式来访问数组元素

可以看看下面这段代码的运行结果

void test1(int arr[])//参数写成数组形式,本质上还是指针 
{printf("%d\n", sizeof(arr));
}
void test2(int* arr)//参数写成指针形式 
{printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩ 
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };test1(arr);test2(arr);return 0;
}

运行结果:

34a7f0b3669e46bc8f6fa210e9a6cee9.png

因为数组传参的时候传数组名,其实本质上传递的是数组首元素的地址,所以用sizeof计算的就是一个地址变量的大小,在64位机器下就是8个字节

总结:

数组传参的时候传数组名,其实本质上传递的是数组首元素的地址

⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式

写成数组的形式,便于理解

但即使写成数组的形式,本质上还是指针

二级指针

只要是变量,就有地址,指针变量也是变量,也有它对应的地址,那么能不能用指针来存放一个指针变量的地址呢?

答案是可以的,而且存放指针变量的地址用的就是二级指针,其实前面我们所说的存放变量(如整型变量、字符型变量、结构体变量、数组或数组元素)的指针都是一级指针

总结一下就是二级指针是用来存放一级指针变量的地址的,以此内推,存放二级指针

直接上代码

#include<stdio.h>
int main()
{int n = 5;printf("%d\n", n);int* pn = &n;printf("%d\n", *pn);int** ppn = &pn;printf("%d\n", **ppn);return 0;
}

运行结果:

这段代码中,n是整型变量,pn是(一级)指针变量用来存放变量n的地址,类型是int *类型;ppn是二级指针变量用来存放pn的地址,类型是int **类型,比一级指针变量的类型多一个*

其中int* *中的int*表示ppn指向的pn的类型是int*,最后一个*表示ppn是指针变量

n、pn、ppn三者之间的关系:n等价于*pn,也等价于**ppn,pn等价于*ppn,即可以通过*pn或者**ppn来访问变量n,也可以通过*ppn来访问pn变量

要注意的是,二级指针和二维数组之间是没有对应关系的!

指针数组

首先要搞清楚的是,指针数组是数组,是用来存放指针的数组,即数组的每个元素都是指针类型的

例如:char*  arr[5];这句代码中arr数组就是存放字符指针的数组

指针数组模拟二维数组

这里我们用指针数组模拟二维数组的使用来打印二维数组的每个元素

#include<stdio.h>
int main()
{//定义一个3行5列的二维数组int arr[3][5] = { {1,2,3,4,5},{3,4,5,6,7},{5,6,7,8,9} };//打印二维数组中的每个元素printf("打印二维数组中的每个元素\n");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");}//用指针数组模拟二维数组的使用场景int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 3,4,5,6,7 };int arr3[5] = { 5,6,7,8,9 };int* parr[3] = { arr1,arr2,arr3 };printf("用指针数组模拟二维数组的使用场景\n");for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;
}

运行结果:

首先要补充的一点是,二维数组是一维数组的数组,即在二维数组arr中,arr[i]就是arr数组第i行的数组名。

因为parr数组中的元素是整型类型的一维数组的数组名,而数组名相当于数组首元素的地址,即int*类型,所以parr数组就是用来存放int*类型指针的数组,类型也就是int*类型的。

这里用parr打印数组元素和上面二维数组的打印是一模一样的,parr[i]是访问parr数组下标为i的元素,parr[i]找到的数组元素指向了一个整型的一维数组,而parr[i][j]找到的就是整型一维数组中的元素。

上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。

其实用来打印parr数组的代码parr[i][j]也可以写成下面这种形式

*(*(parr + i) + j)

parr[i]相当于*(parr + i),表示对parr数组下标为i的元素解引用操作,相当于找到了parr中数组名所指向的那个一维数组,而parr[i][j]就相当于*(*(parr + i) + j),表示对parr数组第i行第j个元素地址解引用操作,找到的就是parr数组中第i个元素(数组名)所指向的那个一维数组中第j个元素

相关文章:

数组和指针的复杂关系

C语言中指针和数组的关系似乎很“纠结”&#xff0c;让人爱恨交织。本文试图帮助读者理清它们之间的复杂关系&#xff01; 数组名的理解 数组元素在内存中是连续存放的&#xff0c;在C语言中&#xff0c;数组名有特殊的含义&#xff0c;它表示数组首元素的地址。因此&#xf…...

Linux系统I/O调优实例

文章目录 一 、资源限制二、测试硬盘速度&#xff1a; 一 、资源限制 限制用户资源配置文件&#xff1a;/etc/security/limits.conf [rootxuegod63 ~]# vim /etc/security/limits.conf 每行的格式&#xff1a; 用户名/用户组名 类型(软限制/硬限制) 选项 值 通常我们在服务器…...

记录Ubuntu OS的异常

PS: 参加过408改卷的ZJU ghsongzju.edu.cn 开启嘲讽: 你们知道408有多简单吗&#xff0c;操作系统真实水平自己知道就行&#xff5e;&#xff5e; dmesg dmesg 是一个用于显示内核环形缓冲区消息的命令&#xff0c;主要用于查看系统启动时的消息、驱动程序加载信息、硬件错误…...

Vue 3 单元测试与E2E测试

在Vue 3应用的开发过程中&#xff0c;测试是一个至关重要的环节。它不仅能够确保代码的正确性&#xff0c;还能在后续的代码重构和升级过程中提供安全保障。本文将深入探讨Vue 3的单元测试&#xff08;Unit Testing&#xff09;和端到端测试&#xff08;End-to-End Testing, E2…...

猫用空气净化器哪个牌子好?求除毛好、噪音小的宠物空气净化器!

换毛季家里孩子不省心&#xff0c;疯狂掉落的猫毛和空气中乱飞的浮毛可把我折磨死了。每天下班都要抽出时间来清理&#xff0c;不然这个家就不能要了。猫毛靠我自己可以打扫&#xff0c;浮毛还得借助宠物空气净化器这种专业工具。所以我最近着手做功课&#xff0c;打算入手一台…...

第十九课 Vue组件中的方法

Vue组件中的方法 组件中的方法拓展与实例对象中的方法拓展类似 <div id"app"><test></test> </div> <script>Vue.component(test, {template: <input type"button" value"这是个按钮组件" click"fun()…...

【JavaScript】V8,Nodejs 与浏览器

V8 V8 是一个 JavaScript engine&#xff0c;负责编译并执行 JavaScript 源代码&#xff0c;处理对象的内存分配&#xff0c;并对不再需要的对象进行垃圾收集。 V8 包含两个主要组件&#xff1a; Memory Heap&#xff1a;负责存储分配。 Call Stack&#xff1a;代码执行时&am…...

内存马浅析

之前在jianshu上写了很多博客&#xff0c;但是安全相关的最近很多都被锁了。所以准备陆陆续续转到csdn来。内存马前几年一直是个很热门的漏洞攻击手段&#xff0c;因为相对于落地的木马&#xff0c;无文件攻击的内存马隐蔽性、持久性更强&#xff0c;适用的漏洞场景也更多。 J…...

聊一聊Elasticsearch的基本原理与形成机制

1、搜索引擎的基本原理 通常搜索引擎包括&#xff1a;数据采集、文本分析、索引存储、搜索等模块&#xff0c;它们之间的协作流程如下图&#xff1a; 数据采集模块负责采集需要搜索的数据源。 文本分析模块是将结构化数据中的长文本切分成有实际意义的词&#xff0c;这样用户…...

应急救援无人车:用科技守护安全!

一、核心功能 快速进入危险区域&#xff1a; 救援无人车能够迅速进入地震、火灾、洪水等自然灾害或重大事故的现场&#xff0c;这些区域往往对人类救援人员构成极大威胁。 通过自主导航和环境感知技术&#xff0c;无人车能够避开危险区域&#xff0c;确保自身安全的同时&…...

详解Java之Spring MVC篇二

目录 获取Cookie/Session 理解Cookie 理解Session Cookie和Session的区别 获取Cookie 获取Session 获取Header 获取User-Agent 获取Cookie/Session 理解Cookie HTTP协议自身是“无状态”协议&#xff0c;但是在实际开发中&#xff0c;我们很多时候是需要知道请求之间的…...

flutter鸿蒙next 使用 InheritedWidget 实现跨 Widget 传递状态

在 Flutter 中&#xff0c;状态管理是开发过程中一个至关重要的部分。Flutter 提供了多种方式来实现组件间的状态传递&#xff0c;其中一种比较底层的方式是使用 InheritedWidget。虽然 InheritedWidget 主要用于将数据传递给其子树中的小部件&#xff0c;但它也是许多更高级状…...

计算机的错误计算(一百四十六)

摘要 探讨 MATLAB 中正切函数 tan(x)、余切函数 cot(x) 关于 附近数的计算精度问题。 例1. 已知 计算 直接贴图吧&#xff1a; 另外&#xff0c;16位的正确值分别为 -0.7837941516239115e10、-0.1275845192169577e-9、0.4782331334117711e7 与 0.2091030357653982e-…...

国标GB28181视频平台EasyCVR私有化视频平台工地防盗视频监控系统方案

一、方案背景 在当代建筑施工领域&#xff0c;安全监管和防盗监控是保障工程顺利进行和资产安全的关键措施。随着科技进步&#xff0c;传统的监控系统已不足以应对现代工地的安全挑战。因此&#xff0c;基于国标GB28181视频平台EasyCVR的工地防盗视频监控系统应运而生&#xf…...

CUDA系统学习之一软件堆栈架构

一、CPU与GPU体系架构 计算单元分布 CPU: 少量强大的ALU(算术逻辑单元)&#xff0c;通常4-8个核心GPU: 大量小型ALU&#xff0c;成百上千个计算核心特点&#xff1a;GPU更适合并行计算&#xff0c;可以同时处理大量数据控制单元(Control) CPU: 较大的控制单元&#xff0c;复杂的…...

SpringBoot项目中替换指定版本的tomcat

需求&#xff1a;项目使用的SpringBoot框架&#xff0c;因低版本的tomcat的有安全漏洞&#xff0c;根据安全要求需要将项目的tomcat版本升级到9.0.89以上版本。 解决办法&#xff1a; 1、在pom.xml中排除SpringBoot的默认tomcat依赖&#xff1b; <dependency><groupId…...

【5.10】指针算法-快慢指针将有序链表转二叉搜索树

一、题目 给定一个单链表&#xff0c;其中的 元素按升序排序 &#xff0c;将其转换为 高度平衡的二叉搜索树 。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点的左右两个子树的高度差的绝对值不超过 1。 示例: 给定的有序链表&#xff1a; [ -10 , -3 , 0 , …...

机器学习—前向传播的一般实现

可以写一个函数来实现一个密集的层&#xff0c;那是神经网络的单层&#xff0c;所以定义稠密函数&#xff0c;它将上一层的激活作为输入以及给定层神经元的参数w和b。看下边图片所展示的例子&#xff0c;把所有这些权重向量堆叠成一个矩阵&#xff0c;wnp.array([[1,-3,5][2,4,…...

极狐GitLab 签约足下科技,加速国产智驾操作系统的发展与普及

客户背景 足下科技是一家致力于成为智能汽车软件平台、产品与服务领导者的高科技企业&#xff0c;成立于 2022年 3 月&#xff0c;总部位于深圳市。足下科技自主研发的智能驾驶操作系统 Earth 和 Air 工具链&#xff0c;协助OEM和Tier1厂商降低算法和软件开发难度&#xff0c;…...

20241102在荣品PRO-RK3566开发板的预置Android13下适配宸芯的数传模块CX6603N

20241102在荣品PRO-RK3566开发板的预置Android13下适配宸芯的数传模块CX6603N 2024/11/2 18:04 在WIN10使用程序&#xff1a;ViewLink-4.0.7_0708-windows-x64.exe 在荣品PRO-RK3566开发板的预置Android13下使用&#xff1a;ViewLink-2023_12_21-release-0.2.6.apk adb install…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...