哈喽!这里是一只派大鑫,不是派大星。本着基础不牢,地动山摇的学习态度,从基础的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]的地址,例如如下程序:

#include<stdio.h>
int 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]的值是(第一行)第0列的地址,即&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;

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

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

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

结果为:

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

#include<stdio.h>
int 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 嘛。

看一个简单的引用例子:

#include<stdio.h>
int 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]不就是行地址嘛

所以就有如下例子: 

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

 


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

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

Logo

汇聚原天河团队并行计算工程师、中科院计算所专家以及头部AI名企HPC专家,助力解决“卡脖子”问题

更多推荐