数组和指针的复杂关系

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个元素

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

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

相关文章

前后端分离,Jackson,Long精度丢失

案例:后端接口放回一个Long数据 GetMapping("/testForLong")public Map<String, Object> testForLong() {Map<String, Object> map new HashMap<>();map.put("aaa", 1234567890123456789L);return map;}实际前端接收的数据 前后端数据…

1.3 自然语言处理的应用

自然语言处理&#xff08;NLP&#xff09;在多个领域有广泛应用&#xff0c;如自动文摘、机器翻译、情感分析等。本实战将通过NLTK库&#xff0c;演示文本预处理的关键技术&#xff0c;包括小写转换、去噪、文本规范化、词干提取、词形还原、标记化以及删除停止词。这些技术为构…

深度学习经典模型之LeNet-5

1 LeNet-5 1.1 模型介绍 ​ LeNet-5是由 L e C u n LeCun LeCun 提出的一种用于识别手写数字和机器印刷字符的卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09; [ 1 ] ^{[1]} [1]&#xff0c;其命名来源于作者 L e C u n LeCun LeCun的名字…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01目录1. A Perspective for Adapting Generalist AI to Specialized Medical AI Applications and Their Challenges2. Synergi…

D60【python 接口自动化学习】- python基础之数据库

day60 数据库定义 学习日期&#xff1a;20241106 学习目标&#xff1a;MySQL数据库-- 128&#xff1a;数据库定义 学习笔记&#xff1a; 无处不在的数据库 数据库如何存储数据 数据库管理系统&#xff08;数据库软件&#xff09; 数据库和SQL的关系 总结 数据库就是指数据…

mysql error:1449权限问题 及 用户授权

一、权限问题 Got error: 1449: The user specified as a definer (skip-grants userskip-grants host) does not exist when using LOCK TABLES 在迁移数据库时&#xff0c;定义的definer&#xff0c;在两个数据库之间不同步时&#xff0c;要将不存在的definer改成数据库中已…

HTB:Grandpa[WriteUP]

目录 连接至HTB服务器并启动靶机 1.Which version of Microsoft IIS is running on TCP port 80? 2.Which 2017 CVE abuses a Buffer overflow in the ScStoragePathFromUrl function in that specific IIS version, allowing remote attackers to execute arbitrary code?…

AI笔筒操作说明及应用场景

AI笔筒由来&#xff1a; 在快节奏的现代办公环境中&#xff0c;我们一直在寻找既能提升效率、增添便利&#xff0c;又能融入企业文化、展现个人品味的桌面伙伴。为此&#xff0c;我们特推出专为追求卓越、注重细节的您设计的AI笔筒礼品版&#xff0c;它集高科技与实用性于一身…

ssm+vue684基于WEB技术的在线商品交易平台的设计

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…

【青牛科技】GC8549替代LV8549/ONSEMI在摇头机、舞台灯、打印机和白色家电等产品上的应用分析

引言 在现代电子产品中&#xff0c;控制芯片的性能直接影响到设备的功能和用户体验。摇头机、舞台灯、打印机和白色家电等领域对控制精度、功耗和成本等方面的要求日益提高。LV8549/ONSEMI等国际品牌的芯片曾是这些产品的主要选择&#xff0c;但随着国内半导体技术的进步&…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-15

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

[SAP ABAP] 面向对象程序设计-类和对象

面向对象开发的特点&#xff1a;封装、继承和多态 什么是类和对象&#xff1f; 类(CLASS)是创建对象的模板&#xff0c;对象(OBJECT)是类的实例 一个类可以创建多个对象 类 > 类型 对象 > 个体 在ABAP语言中&#xff0c;定义一个类&#xff0c;需要包含定义(defin…

模型 泰斯勒定律(复杂性守恒定律)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。复杂性守恒&#xff0c;转移而非消除。 1 泰斯勒定律的应用 1.1 电视遥控器的复杂性转移 在过去&#xff0c;电视遥控器设计得非常复杂&#xff0c;拥有许多按钮和功能&#xff0c;这使得用户在使用…

KubeSphere v4 扩展组件使用指南

KubeSphere v4 扩展组件使用指南 日前&#xff0c;KubeSphere v4 发布&#xff0c;相较于之前的版本&#xff0c;新版本在架构上有了较大的变化。其中&#xff0c;有一个新的概念——扩展组件。 本文我们将针对扩展组件做一个详细的说明&#xff0c;让大家对扩展组件能够了解…

【Orange Pi 设备】window11主机下使用VNC可视化控制RK3566

【Orange Pi 设备】window11主机下使用VNC可视化控制RK3566 前言VNC连接搭建(WiFi模式)Orange Pi 3B操作本地主机操作 总结 XFCE桌面VNC连接后灰屏问题&#xff0c;可以优先尝试本文的方式 前言 Orange Pi 3B 是一款基于瑞芯微 RK3566 处理器的单板计算机&#xff0c;旨在为开…

背靠背MOS管-锂电池充放电控制详解

目录&#xff1a; 1、概述 2、外接适配器 3、使用锂电池 4、电池检测回路 1、概述 本锂电池充放电控制电路采用 TP4055 作为电池 BAT 的充电控制&#xff0c;如下图1.1绿色框所示。 TP4055 引脚功能描述&#xff1a; 1CHRG开漏输出的充电状态指示引脚&#xff0c;需要上…

嵌入式软件八股文

1.指针的大小是固定的&#xff0c;和指针的类型没有关系 只与编译器有关&#xff0c;32位系统指针大小为8个字节&#xff0c;x64一般为64位系统&#xff0c;指针大小一般为4个字节。 2.sizeof()和strlen() sizeof()计算所占内存的大小,可以计算int float大小 strlen()计算的…

美畅物联丨物联网通信新纪元:Cat.1与5G RedCap的差异化应用

​ 在物联网&#xff08;IoT&#xff09;迅猛发展的时代&#xff0c;通信标准对物联网设备的连接性、性能和适用性有着极为关键的作用。小编在《美畅物联丨Cat.1与NB-IoT&#xff1a;物联网设备的通信标准对比》中提到Cat.1与NB-IoT的对比区别&#xff0c;后来就有小伙伴问&…

vue用jenkins 打包项目项目关闭eslint检查

问题描述&#xff1a;创建vue脚手架项目后&#xff0c;使用jenkins 打包项目&#xff0c;出现如下图所示错误&#xff0c;显示错误来源于eslint检测。 解决方法&#xff1a;在根目录下找到vue.config.js文件&#xff0c;添加lintOnSave: false以关闭eslint检测&#xff0c;项目…

序列中删除指定数字【四种解法】

文章目录 解法1&#xff1a;另辟空间法解法2&#xff1a;覆盖法解法3&#xff1a;覆盖法&#xff08;进阶版&#xff09;解法4&#xff1a;异或取巧法 题目&#xff1a;有一个整数序列&#xff08;可能存在重复的整数&#xff09;&#xff0c;编写程序删除序列中指定的某一个整…