1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > C语言——指针篇(四)多维数组和多维指针(内含数组指针和指针数组笔记)

C语言——指针篇(四)多维数组和多维指针(内含数组指针和指针数组笔记)

时间:2020-07-30 03:55:50

相关推荐

C语言——指针篇(四)多维数组和多维指针(内含数组指针和指针数组笔记)

前言:笔者本来是打算在上一篇文章中,把多维数组和多维指针与数组的访问方式结合起来一起写掉。但是在写作过程中,发现创作篇幅明显过长,不利于日后按图索骥,所以又重开了一篇,说说自己的感悟,仅供参考

多维数组和多维指针

1.一维数组和多维数组的存储区别

假设我们现在有如下代码分别声明了一个一维数组和一个二维数组:

int array1[3];int array2[3][3];

一维数组的存储

我们假设此代码编译运行在32位机上,一个int类型数据占4个字节,array1数组从地址0x5000_0000处开始分配空间,则可得array1数组的存储框图如下:

在上图中使用一个 ?? 符号代表一个未知具体数值的字节数据,array1中的3个int类型的数据其实实际所占字节数为12。作为数组元素连续存储,因此从数组首地址0x5000_0000向后地址递增存放数据,其实际所占地址为0x5000_0000至0x5000_000B。

多维数组的存储

多维数组中,以二维数组为代表重点阐述,其余三维四维乃至N维可以以此类推。在C语言中,多维数组的存储是以行序为主,下面的阐述也遵循这个规律。

我们假设array2数组的首地址是0x6000_000C,其后按地址连续存储,则有如下存储框图:

从上图可以看出,该数组含有9个int类型元素,共占36个字节,从首地址开始连续存储分配。

总结

1)任何数组(不论是一维数组还是多维数组),其数组首地址都是一个和数组名直接关联的地址常量。

即,如上述array1和array2等数组名直接和数组首地址关联,它们可以出现在赋值符号=的右边,作为一个地址量赋给指针,但是不能出现在=的左边,对其重新进行赋值。其首地址的常量值,在代码编译链接时期,由编译器和链接器确定,整个程序运行期间不能被更改。

2)数组元素是以顺序方式连续存储的,这意味着,我们可以通过一个首地址和一个偏移量定位到数组中的每个元素。

每次操作数组中的元素时,不管是通过下标引用还是指针访问的方式,都是先从数组的首地址开始,加减一个偏移量,找到该元素的地址,再对该元素进行操作。

2.一维指针和多维指针

一维指针和多维指针没有特别需要阐述的地方,它们之间的区别主要是:一维指针仅仅进行了单次地址转换即可以取出数据操作;而多维指针则根据声明的维数需要进行多次地址转换才能够取到目标数据。但是,指针作为一个数据变量,可以多次赋值,使得其成为对数组操作访问的一大利器,所以指针和数组的结合才是重中之重。

3.数组指针和指针数组

3.1 数组指针

数组指针,顾名思义就是一个指向数组的指针。一维指针可以指向一个同类型的一维数组,但多维指针不一定可以直接指向一个多维数组。有如下声明:

int vector[10],*vp = vector;int matrix[3][10],*mp = matrix;

在上述声明中,vector是一个含有10个元素的数组,每个元素都是int类型,vector作为数组首地址,其类型是一个int的地址,因此可以赋值给int *类型的变量vp

而在二维数组的声明中,结合行序优先的规律看,其实是先声明了一个数组matrix[3],含有三个元素,每个元素是int [10]类型,matrix作为数组首地址,存储的matrix[0]元素的类型也是int [10]类型,与被声明为int *类型的mp类型不符合,不能被赋值。

若想要声明一个指向matrix的指针,则应该如下声明:

int (*p)[10];

之前说到,下标引用[ ]的优先级大于指针引用*,但若出现()则,其优先级最高。在上述声明中,先看( * p)声明了一个指针,余下的int [10],则是其指向的数据类型。因此,matrix可以赋给p,它们的数据类型相同。而matrix[0]则可以赋给mp,它们的数据类型都是int *

3.2 指针数组

指针数组,其本质是一个数组,只不过其元素类型都是指针类型。如下所示:

int *a[10];

下标引用的优先级高于间接访问,所以在这个表达式中首先执行下标引用,可以得出a是一个含10个元素的数组,其元素类型为int *。其数据类型与上述vector相同,因此a[0] = vector成立。

3.数组指针的数组和多维数组的关联

让我们回到一开始的多维数组,在脑海中重新组织我们对数组的理解。对于int array2 [3][3],我们一般对于其最常见的理解是array2是一个数组的首地址,它含有3个元素,每个元素又都是另外一个数组的首地址,如下图所示(灵魂手稿突现):

4.1数组指针数组

当我们采用数组指针数组的方式存储这些数据时,其操作方式类似于多级索引。我们不仅要花空间存储原来的数据,还要花多余的空间存储索引项,即这里的数组指针项。如下图:

并且,由于数组指针已经存储了实际数据数组的索引,所以实际的数组可以采用数组间离散存储的方式,其每个数组的大小也可以不相等

4.2多维数组存储

采用多维数组存储数据时,由于其子数组大小相等,都采用顺序存储方式存储数据,每个子数组的首地址可以通过二维数组的首地址+偏移量计算所得,所以不需要采用额外的空间存储子数组首地址的索引。如下图所示:

总结:

我们可以将多维数组的第一维看作是一个数组指针,其存放的指针地址指向其随后的每一维子数组,但实际存储上,二者有很大差别:

1)多维数组必须顺序存储,大小固定,所以其子数组的首地址是常量,可通过二维数组首地址+偏移量计算所得,不需要占用额外的存储空间。

2)数组指针数组需要占用额外的存储空间记录子数组的首地址,因此子数组大小可变,也可以采用离散存储方式,更加灵活。

3)关于二种方式的应用场景:

如果存储的数据大小比较接近紧凑,建议声明为多维数组会更加节省存储空间,操作方便;若数据集合中,数据多为长短不一且最长数据和最短数据相差较大,例如:字符串存储,建议声明为数组指针数组会更加节省空间。

5.作为函数参数的数组和指针

函数调用传参的过程都是都先将**实际参数复制给形式参数。**作为数组传参是将数组的首地址传给形式参数,作为指针是将指针变量的内容传入。

5.1一维数组和一维指针的函数原型

void fun (int * v); //一维指针

void fun (int v [ ]); //一维数组

上面两个函数的声明功能相等,其传入形参的类型都是int类型的地址。对于数组当形参的声明来说,编译器并不关心数组有多少个元素(即数组下标越界是代码编写者需要关注的事),编译器只关心数组的首地址。因此上述形参中数组下标[ ]中的数值缺省。5.2多维数组和多维指针的函数原型

void fun (int( * v)[10]); //数组指针

void fun (int v [ ][10]); //二维数组

上述两个函数声明的功能相等,其都传入了一个首地址,且该首地址都指向一个int [10]数据类型。值得注意的是,数组作形参时,其第一维长度必须省略,其后所有维度的长度必须列出Q:为什么数组除第一维长度外的所有长度都不能省略?A:当数组作为形参时,我们需要传入其数组首地址,然后对首地址进行加减运算以得到下一个数据地址,在第一个例子中,int类型占4个字节,所以每次对地址进行加减时,都是以4为步进单位。第二个例子中,int [10]类型占40个字节,所以对传入地址的加减步进值为40.除第一维外的所有维数的长度是为了确定该数组首地址的加减步进值,因此不能省略

 而有时可以省略第一维长度的场合,比如:

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

 编译器会根据初始化内容自动判定数组的第一维长度为2.因此,在某些情况下,数组的第一维长度可省略

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。