资讯专栏INFORMATION COLUMN

C语言 指针+二维数组详解 (应付期末、考研的最强笔记,建议收藏)

FrozenMap / 3737人阅读

摘要:需要注意的是用矩阵形式如行列表示二维数组,是逻辑上的概念,能形象地表示出行列关系。再次强调二维数组名如是指向行的。一维数组名如是指向列元素的。

哈喽!这里是一只派大鑫,不是派大星。本着基础不牢,地动山摇的学习态度,从基础的C语言语法讲到算法再到更高级的语法及框架的学习。更好地让同样热爱编程(或是应付期末考试 狗头.jpg)的大家能够在学习阶段找到好的方法、路线,让天下没有难学的程序(只有秃头的程序员 2333),学会程序和算法,走遍天下都不怕!

目录

 ​前言​

一、​二维数组的介绍​

1.1 怎样定义二维数组

1.2 二维数组初步理解

1.3 怎样引用二维数组的元素

1.4 二维数组的初始化

 二、​二维数组元素的地址​

2.1 回顾一维数组引用

2.2 二维数组元素地址的表示

2.3 二维数组元素地址小结

 三、​通过指向数组元素的指针变量来引用多维数组​

3.1 指向数组元素的指针变量

四、​通过数组指针引用二维数组​

 4.1 指向 由m个元素组成的一维数组 的指针变量

五、​通过指针数组引用二维数组​

5.1 什么是指针数组

5.2 指针数组怎么引用二维数组

六、多种方式引用二维数组元素的比较


 前言

本文通过从二维数组的介绍开始,讲解什么是二维数组——>怎么定义和使用二维数组——>怎么引用二维数组的元素——>怎么通过指针来引用二维数组(具体包括多种分式)来逐步说明指针+二维数组的配合使用,相信学完本文内容,读者对于指针和二维数组会有更加深刻的印象和理解。通过学校期末考试和考研C语言内容完全足够!!! 


一、二维数组的介绍

1.1 怎样定义二维数组

怎么定义二维数组呢?其基本概念与方法和一维数组相似。

如: float pay[3][6];

这样就是定义了一个二维数组,其第一维有3个元素,第二维有6个元素。

每一维的长度分别用一队方括号括起来。

由此引出 二维数组定义的一般形式为:

类型说明符 数组名[常量表达式][常量表达式]

例如:

float a[3][4],b[5][10];

定义a为3×4(3行4列)的数组,b为5×10(5行10列的数组)。

注意,不能写成   float a[3,4],b[5,10];  //在一对方括号内写两个下标,错误

1.2 二维数组初步理解

C语言中,对于二维数组采用以上的定义方式,使得二维数组可被看做一种特殊的一维数组: 它的元素又是一个一维数组。 例如,可以把a看做一个一维数组,它有三个元素:

如图:     

 并且它的每一个元素又是包含了4个元素的一维数组:

 因此可以把a[0],a[1],a[2]看做3个一维数组的名字,且是3个行元素,即a[0],a[1],a[2]是一维数组名,这一点对于后面二维数组元素的引用很重要!

 C语言中,二维数组中的元素排列的顺序是按行存放的,即在内存中先顺序存放第0行的元素,接着再存放第1行的元素。

 假设数组a存放在从2000字节开始的一段内存单元中,一个元素占4个字节,前16个字节(2000~2015)存放序号为0的行中4个元素,接着的16个字节(2016~2031)存放序号为1的行中4个元素,以此类推。

需要注意的是: 用矩阵形式(如3行4列)表示二维数组,是逻辑上的概念,能形象地表示出行列关系。而在内存中,各元素是连续存放的,不是二维的,是线性的。

1.3 怎样引用二维数组的元素

最通常的方法:

数组名[下标][下标]

例如 a[1][2];

需要注意引用时不能超过下标范围

1.4 二维数组的初始化

方法①    分行给二维数组赋初值。 如:

int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

方法②    写在一个花括号内,按排列顺序赋值。 如:

int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

方法③    可以对部分元素赋初值。 如:

1) int a[3][4] = {{1},{5},{9}};

赋值的结果为:

 2)int a[3][4] = {{1},{0,6},{0,0,11}};

初始化后的数组元素如下:

 3)int a[3][4] = {{1},{5,6}};

数组元素为:

 4)int a[3][4] = {{1},{ },{9}};

注:在定义并赋值时,可以省略第一维的大小(实际上不管几维数组都只能省略第一维大小),但第二维的大小不能省。


 二、二维数组元素的地址

2.1 回顾一维数组引用

 为了让读者更加清楚多维数组的地址问题,我们先回顾一下一维数组的地址问题。

在一维数组中,数组名代表的是数组首元素的地址

 例如: int a[4] = {1,2,3,4};

a的值就是第一个元素即a[0]的地址,a+1,往右移一个位置,所以是a[1]的地址,例如如下程序:

#includeint main(){	int a[4] = {1,2,3,4};	printf("%d/n",a);	printf("%d/n",a+1);	return 0;}

 运行结果为:

 可以看出输出的是地址,且地址是连续存放的。

那么一维数组中的元素值呢?

我们所熟悉且最常见的就是用[ ]表示法来获取数组元素值,

所以 a[i] 就是 第 i 个元素的值,既然 a+i 是第 i 个元素的地址,那么我们知道地址引用其值的方法就是在前面加一个“ * ”符号, 所以*(a+i) = a[i] = 第 i 个元素的值

需要明确, a[i] 和 *(a+i) 是无条件等价的(非常重要)!!!

好了,这里我们主要为了明确:一维数组名 代表的是 一维数组首元素的地址。

解决了这个问题,我们就可以开始思考二维数组的地址问题了。

2.2 二维数组元素地址的表示

  首先 我们定义一个二维数组为: int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};

a 是二维数组名。

a 数组包含3行,即3个元素:a[0]、a[1]、a[2]。

而每一个行元素又是一个一维数组,它包含4个元素(即4个元素)。

(这里有点疑惑的读者,可以上翻二维数组的初步理解再次细看一下~~~)

一定要非常敏感 行 和 列 的区分。

例如:a[0]所代表的一维数组又包含4个元素:a[0][0],a[0][1],a[0][2],a[0][3],见下图

 可以认为: 二维数组a是由3个一维数组所组成的,或者看做3个行组成。

从二维数组的角度来看,a代表二维数组首元素的地址,现在的首元素不是一个简单的整型元素哦,而是由4个整型元素所组成的一维数组。

因此,a代表的是首行(即序号为0的行)的起始地址。

a+1则是序号为1的的起始地址。 (a是指向一行的,a+1当然在行方向移动~~~)

如果二维数组的首行起始地址为2000的话,按照一个int类型数据占4个字节来算,则a+1的值应该是2000+4×4 = 2016 (因为一行有4个数组嘛~~~)

又因为a+1指向a[1],也能说a+1的值是a[1](代表序号为1的行)的起始地址

既然a[0],a[1],a[2]是一维数组名,从复习一维数组部分可以知道,代表的是数组首元素的地址,

因此,a[0]代表一维数组a[0]中第0列的地址,即&a[0][0]。 (这里一定要反复琢磨是指向列的)

 同理,a[1]的值是第1列的地址,即&a[1][0]。

请思考,a数组0行1列元素的地址怎么表述(除了&a[0][1],这个太简单了)?

 a[0]是一维数组名,也是第0行的首元素(或是第0列)的地址,所以同一维数组一样,

第1列就是第0列+1得到,所以a[0]+1就是a[0][1]的地址

 此时“a[0]+1”中的1,代表的是1个列元素的字节数,即4个字节,a[0]的值是2000,a[0]+1的值是2004而不是2016。

同理,既然a[0]是a[0][0]的地址,a[1]是a[1][0]的地址,并且a[0]+1是a[0][1]的地址,

所以,a[i]+j 就是 a[i][j]的地址。 注意:是地址,而不是元素的值

看图理解一下:

                   

 前面一维复习部分已经讲述了,*(a+i)和a[i]是等价的,

因此,a[0]+1 和 *(a+0)+1 和 *(a)+1 都是 &a[0][1]

所以 a[i]+j 和 *(a+i)+j 都是 &a[i][j],是一个地址而不是值。

注意不要写成了 *(a+1+2),这样就是 *(a+3) 即 a[3] 了。

 进一步分析,若想得到某行某列元素的值呢?

 如果把地址弄明白,那么这就变得简单了。

& 取地址运算符, * 指针运算符(或称“间接访问运算符”),如 *p代表p指向的对象。

得到地址取值的方法就是加一个“ * ”。

因此, *(a[i]+j) 或者 *(*(a+i)+j)是a[i][j]的值,或说 *(a[i]+j) =  *(*(a+i)+j) = a[i][j] 。

2.3 二维数组元素地址小结

总结一下:二维数组 a 的有关指针 

 举个栗子

在军训中,一个排分3个班,每个班站成一行,3个班为3行,相当于一个二维数组。

为方便比较,班和战士的序号也从0开始。

请思考:班长点名和排长点名的方法有什么不同。

班长从第0个战士开始逐个检查本班战士是否在队列中,班长每移动一步,走过一个战士。

而排长点名则是以班为单位,排长先站在第0班的起始位置,检查该班是否到齐,然后走到第1班的起始位置,检查该班是否到齐。

班长移动的方向是横向的,而排长移动的方向是纵向的。排长看起来只走了一步,但实际上他跳过了一个班的10个战士。这相当于从a移到a+1(见下图)

班长“指向”的是战士,排长“指向”的是班,班长相当于列指针,排长相当于行指针。

一定要好好理解,并能分清楚 指向行 和 指向列!!!

 

 为了找到某一班内某一个战士,必须给两个参数,即第i班第j个战士,先找到第i班,然后由该班班长在本班范围内找第j个战士。这个战士的位置就是a[i]+j(这是一个地址)。

开始时班长面对第0个战士。注意,排长和班长的初始位置是相同的(如图的a和a[0]都是2000),但他们面对的对象是不同的,班长面向的对象是战士,排长面向的对象是班。排长“指向”班,在图上是“纵向管理”,他纵向走一步就跳过一个班,而班长“指向”战士,在图上是“横向管理”,横向走一步只是指向下一个战士。

所以 a+1 是在行方向上移动,a[i]+1(或者 *(a+i)+1)是在列的方向上移动!!!


二维数组a相当于排长,而每一行(即一维数组a[o],a[1],a[2])相当于班长,每一行中的元素(如 a[1][2])相当于战士。

再次强调二维数组名( 如a )是指向行的。因此a + 1中的 “1’’ 代表一行中全部元素所占的字节数 。 一维数组名 (如a[0],a[1])是指向列元素的。a[0] + 1 中的1代表一个 a 元素所占的字节数。

在指向行的指针前面加一个 *,就转换为指向列的指针。 例如,a 和 a+1 是指向行的指针,在它们前面加一个 * 就是 *a 和 *(a+1),它们就成为指向列的指针,分别指向a数组0行0列的元素和1行0列的元素。

反之,在指向列的指针前面加&,就成为指向行的指针。例如 a[0] 是指向0行0列元素的指针,在它前面加一个&,得&a[0],由于a[0] 与 *(a+0)等价,因此 &a[0]与&*a等价,也就是与a等价,它指向二维数组的 0 行。

 不要把&a[i]简单的理解为a[i]元素的存储单元的地址,因为二维中并不存在a[i]这样一个实际的数据存储单元。 它只是一种地址的计算方法,能得到第 i 行的起始地址。

到这里为止,我们通过二维数组名的不同方式来获取元素地址、值的内容就这些了,请读者要反复思考,加深理解。(ps 比较期末、考研的C语言的试题,这可是重点+难点)

 

 啊这,你以为已经结束了吗? Impossible!!! 还没有和指针更深入的结合呢~~~


 三、通过指向数组元素的指针变量来引用多维数组

 一看标题那么长有木有,但实际就是普通的指针!

3.1 指向数组元素的指针变量

 指向数组元素的指针变量,不就是指针变量嘛

例如: 定义一个 int a = 10;      int *p = &a;

所以 p=&a, *p = a = 10;

此时用来指向数组的 具体元素 也是一样的,不管是一维还是二维。

例如这样一个简单的程序:

#includeint main(){	int a[4] = {1,2,3,4};	int *p = a;	for(int i=0;i<4;i++)		printf("%d ",*(p+i));	return 0;}

结果为:

用在二维中效果也是一样的:

#includeint main(){	int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};	int *p = a[0];	for(int i=0;i<4;i++)		printf("%d ",*(p+i));	return 0;}

 结果为:

 但要注意,p是列指针,a在二维代表行,所以需要把p赋值为a[i]才是指向列的!

 同时,因为在内存中地址是连续的!所以也可以输出全部的元素,而不只是一行

for(int i=0;i<12;i++)		printf("%d ",*(p+i));

 


四、通过数组指针引用二维数组

 4.1 指向 由m个元素组成的一维数组 的指针变量

 指向数组的指针变量,就是 数组指针

定义方式例如:    int (*p)[4];

解释:①别忘了 (),因为[ ]的优先级比 * 高。

           ②4代表的 指向的数组 有4个元素,如果是二维数组,那么就是第二维(即列)的个数。

 第三个标题说的方法是指向具体元素(列)的,我们也可以改用另一种方法,使p不是指向整型变量,而是指向一个包含m个元素的一维数组。

这是,如果p指向a[0](即 p = &a[0] 当然也就是 p = a),则p+1 不是指向a[0][1]了,就是指向a[1],

p的增量是以一行字节为单位的。 这里其实也相当于 第二部分说的 用数组名来引用二维数组一样,毕竟 p = a 嘛。

看一个简单的引用例子:

#includeint main(){	int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};	int (*p)[4] = a;	printf("数组指针引用方式1:%d/n",p[1][1]);	printf("数组指针引用方式2:%d/n",*(*(p+1)+1));	return 0;}

结果为:

由此看出这样的确和用 a 来引用方式是一样的。


五、通过指针数组引用二维数组

 不仅数组指针可以引用二维数组,指针数组也可以哦

5.1 什么是指针数组

 一个数组,若其元素均为指针类型数据,则成为 指针数组

也就是说,指针数组中的每一个元素都是存放一个地址。

定义样例:  int *p[3];      //这里的3 我的理解是 如果指向二维数组,则3是代表行数。

注意:这里就不用()了,[ ]优先级比 * 高。因此p先与[3]结合为p[3] ,表示是一个数组。

5.2 指针数组怎么引用二维数组

 既然指针数组是一个数组,所以肯定得用p[0] =    p[1]=   p[2]=    这样的形式来赋值~~~

而又因为,数组里面应该放的是一行的地址起始,而且在二维中,a[0],a[1],a[2]不就是行地址嘛

所以就有如下例子: 

#includeint main(){	int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};	int *p[3];	p[0] = a[0], p[1] = a[1], p[2] = a[2];	return 0;}

那么元素的值呢? 很简单,我们拿到了地址,所以加一个“ * ”即可得到

printf("%d/n",p[0]); //错误写法printf("%d/n",*(p[0]));  //正确写法

结果为: 

 注意正确写法,因为我们的p[0]才是地址,不要以为p[0] = *(p+0) 就是值了。

六、多种方式引用二维数组元素的比较

总结一下前面的方法,比较一下对于各种定义,应该怎么赋值以及引用。

#include#includeusing namespace std;int main(){	int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};	int (*p)[4];	p = a;	int *p2[3];	p2[0] = a[0], p2[1] = a[1], p2[2] = a[2];	int *p3 = a[1];	printf("1.数组名本身的引用:%d/n",a[1][1]);	printf("2.数组指针引用方式1:%d/n",p[1][1]);	printf("3.数组指针引用方式2:%d/n",*(*(p+1)+1));	printf("4.指针数组引用:%d/n",*(p2[1]+1));	printf("5.指针引用1:%d/n",*p3);	printf("6.指针引用2:%d/n",*(p3+1));	return 0;	}

 


自此,关于指针+二维数组的 地址和值 的引用讲解就结束了,指针本就是难点,再加上二维、地址就更懵了,希望读者能够好好理解文章的内容。 如果在阅读时,发现有什么没有提及或是有错误的地方,欢迎广大读者留言反映。

最后,写文不易,如有转载 请标明出处!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119106.html

相关文章

  • 思维导图整理大厂面试高频数组24: 合并两个有序数组两种双指针思想, 力扣88

    摘要:此专栏文章是对力扣上算法题目各种方法的总结和归纳整理出最重要的思路和知识重点并以思维导图形式呈现当然也会加上我对导图的详解目的是为了更方便快捷的记忆和回忆算法重点不用每次都重复看题解毕竟算法不是做了一遍就能完全记住的所 ...

    darkerXi 评论0 收藏0
  • ❤️导图整理大厂面试高频数组8: 移除元素指针优化, 力扣27❤️

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), 毕竟算法不是做了一遍就能完全记住的. 所以本文适合已经知道解题思路和方法, 想进一步加强理解和记忆的朋友, 并不适合第一次接触此题的朋友(可以根据题号先去力扣看看官方题解, 然后再看本文内容). 关...

    zhangyucha0 评论0 收藏0
  • Java学习路线总结,搬砖工逆袭Java架构师(全网最强

    摘要:哪吒社区技能树打卡打卡贴函数式接口简介领域优质创作者哪吒公众号作者架构师奋斗者扫描主页左侧二维码,加入群聊,一起学习一起进步欢迎点赞收藏留言前情提要无意间听到领导们的谈话,现在公司的现状是码农太多,但能独立带队的人太少,简而言之,不缺干 ? 哪吒社区Java技能树打卡 【打卡贴 day2...

    Scorpion 评论0 收藏0
  • 深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)

    摘要:所以是数组指针,而是指针数组。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。 ...

    miracledan 评论0 收藏0
  • ❤️思维导图整理大厂面试高频数组10: 3种方法彻底解决中位数问题, 力扣4❤️

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), 毕竟算法不是做了一遍就能完全记住的. 所以本文适合已经知道解题思路和方法, 想进一步加强理解和记忆的朋友, 并不适合第一次接触此题的朋友(可以根据题号先去力扣看看官方题解, 然后再看本文内容). 关...

    XanaHopper 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<