数组和指针的复杂关系

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,一经查实,立即删除!

相关文章

pandas和numpy介绍

pandas和numpy都是Python中用于数据处理和分析的重要库&#xff0c;它们有以下特点和区别&#xff1a; numpy&#xff08;Numerical Python&#xff09; 基本功能&#xff1a; numpy是一个功能强大的Python库&#xff0c;主要用于对多维数组执行计算。它提供了一个高性能的多…

GitHub每日最火火火项目(11.6)

项目名称&#xff1a;zmh-program/chatnio 项目介绍&#xff1a;“zmh-program/chatnio”是下一代 AI 一站式国际化解方案&#xff0c;无论是 B 端还是 C 端用户都能从中受益。它支持众多知名的 AI 模型&#xff0c;如 OpenAI、Midjourney、Claude、讯飞星火、Stable Diffusion…

前后端分离,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的关系 总结 数据库就是指数据…

windows C#-弃元(上)

弃元是一种在应用程序代码中人为取消使用的占位符变量。 弃元相当于未赋值的变量&#xff1b;它们没有值。 弃元将意图传达给编译器和其他读取代码的文件&#xff1a;你打算忽略表达式的结果。 你可能需要忽略表达式的结果、元组表达式的一个或多个成员、方法的 out 参数或模式…

【图论】——理论基础总结

图论这一章尤其需要图例进行说明&#xff0c;方便理解&#xff0c;对于作者来说很费时间&#xff0c;本文主要为自己复习方便&#xff0c;所以并不会写的非常详细&#xff0c;见谅。 图论 图的基本概念 基本要素&#xff1a; 边节点 两点连成线&#xff0c;多个点连成的线…

printf影响单片机中断速度

printf是我们常用的调试程序的手段&#xff0c;在第一版程序中&#xff0c;经常会使用printf来验证程序是否工作正确。这样的调试手段应该在正式版的程序发布前注释掉或者删除。而且不当地使用printf也会带来某些功能性问题&#xff0c;例如&#xff0c;在某项目中&#xff0c;…

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…

C语言---程序设计基础练习题目3

对之前的练习和文章进行回顾&#xff0c;并在此文章中解决新问题 如在题目中有看不明白的地方 可跳转我的主页去查看 Wanyu的主页 以下题目是留在文章中的练习&#xff1a;&#xff08;点击即可查看&#xff09; Wanyu C语言—程序设计练习题目及学习方法2 习题1&#xff1a;…

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

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

实验05多重循环---7-04 小于m的最大的10个素数

给定一个整数m&#xff08;50<m<20000&#xff09;&#xff0c;找出小于m的最大的10个素数。 输入格式: 输入在一行中给出一个正整数m&#xff08;50<m<20000&#xff09;。 输出格式: 在一行中按递减顺序输出10个满足条件的素数&#xff0c;每个素数输出占6列。…

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

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