多维顺序存储数组之地址求解问题

来源:互联网 发布:黄石淘宝运营招聘 编辑:程序博客网 时间:2024/05/18 02:36

近期数据结构课讲到多维数组及矩阵,说到数据存储在计算机中实际存储顺序的时候就晕了,概念自己倒是知道,但是自己算的时候就迷迷糊糊。身边也有同学和我抱怨说老师上课进度太快了,认为虽然c里面提过但上课还是需要再解释。想了想就写了这篇文章,也算是帮同学也帮自己大概理一下思路。


本文将就多维数组的概念、C语言中多维数组的定义方法及内存分析等点进行解析。

在讲多维数组前,我会先说最基本的一维数组,然后再讲二维数组,再由二维数组引申至多维数组。所以建议已经了解数组的同学可以直接看二维数组,已经熟悉数组想搞清多维数组元素地址求解的同学直接往下拉到最后一部分。

一维数组

数组是含有多个具有相同数据类型的数据的有序(此处的“有序”指所有元素依次存放在内存中相邻的一组线性空间内,除了第一个和最后一个元素外,每个元素有且只有一个前驱和后继)集合,数组中的数据称为数组元素。这些数据在内存中占用连续的存储空间。数组一旦定义,在程序执行期间其位置和大小不能再发生变化。

这里涉及到的知识点有关计算机内存的存储结构了,接触过汇编或者是组成原理的同学肯定懂,不懂的同学建议去了解一下。简单形象地理解记忆,如图,我们的数据存储在相应存储器的存储单元中,计算机中存储单元都是按顺序排号的,每个存储单元有一个地址,通过对这个地址的查找你可以访问到对应的存储单元。
数据存储示意图
假设我们定义一个一维数组

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

则它在内存中相应的存储形式如图。
一维数组存储示意图

一维数组元素地址求解

数组寻址可由首地址+偏移量得到。也就是说,要想知道当前元素地址,则必须知道它所在数组的首址以及相对数组第一个元素的偏移量,而偏移量可以由 计算得到,所以也就是说,除了数组首址,我们还必须知道在这个元素前还有多少个元素*, 即 首地址+前面元素个数×单个元素所占空间 。譬如上面数组a[2](即值为3的元素)的地址,a即为该数组的首地址,这个元素前面还有两个元素,所以它的地址为 a+2×sizeof(int)

二维数组

二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。二维数组又称矩阵。 ——摘自《百度百科》

如果拿我们最熟悉的坐标来类比的话,刚才的一维数组相当于是数轴上点的结合,一个下标就相当与其坐标,可以对应一个元素;而二维数组就是平面直角坐标系中的点的集合,两个下标课已对应一个元素。

但是,计算机是线性的存储结构,是一条带子一样顺下来的,所以这种二维在计算机中也要按某种规则转化成一维

《百度百科》中有一句话很关键:以数组作为数组元素。这句话从字面也不难理解,也就是我们定义了一个数组,这个数组的元素类型是数组类型,而这个为数组类型的元素其内部元素则为其他类型。

举个栗子:一个2*3的矩阵A:

A=[142536]

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

上面我定义了一个二维数组,那么实际上它是由两个数组组成,一个是{1, 2, 3},一个是{4, 5, 6},而这两个数组又作为元素被存储在另一个更大的数组a[2][3]中。
按行优先存储的话,它在内存中的存储形式如图:
二维行存储
相比较来说,我个人更喜欢把它立体化成一个平面,照着定义来话,只不过需注意的是两组数组间的地址连续
二维行存储1
默认对二维数组定义的话就是按行优先,但如果按列优先存储的话,它在内存中的存储形式如图:
二维列存储
同样的,立体化成平面:
二维列存储1

按行优先存储和按列优先存储本质上没有什么区别,在代码上,也只是a[i][j]顺序调换一下即可。

\\按行优先int i, j; for(i=0; i<2; i++)    for(j=0; j<3; j++) {        \\按行操作    }\\按列优先int i, j;for(j=0; j<3; j++)    for(i=0; i<2; i++)        \\按列操作 
二维数组元素地址求解

和一维地址一样,我们需要知道的就是数组首址以及所求元素前面的元素个数。数组首址不需要我们计算,我们需要计算的就是元素个数,这个时候大家可以看立体化成面的图,我们可以看当前元素前面又多少行或列,因为这些行或者列都是完整的,所以我们只需用 行数(或列数)×每行(或每列)的元素个数×单个元素的所占内存大小,最后再加上该元素相对其所在行首址的偏移量即可。以上面的二维数组为例,求 a[1][2] 的地址,若按行优先存储,那么该元素前面有一行元素,每行有3个元素,第二行排在该元素前面有2个元素,那么这个元素的相对于首址的地址就是,a+1×3×sizeof(int)+2×sizeof(int);而按列优先存储的话,则为 a+2×2×sizeof(int)+1×sizeof(int)。

划(考试)重点:可以看到所求哪个元素,其数组下标所对应的就代表了该元素前面有多少行(或列)该行(或列)有多少元素(仅针对于数组下标从0开始的语言)。按行优先是从右往左维数增加,按列优先是从左往右维数增加。这一点从代码能很清楚的看到,按行优先的时候,内层循环是数组下标靠右的,这个循环事先完成形成第一维,然后一维再不断地堆叠成二维。

多维数组

其实二维数组写完我觉得已经很清楚了,只要能理解二维数组,那么多维数组也是一样的道理,而上面的(考试)重点也是对多维数组也成立的。

这里我将不再进行过多的文字说明,举一个三维数组的栗子,画一下图,大家根据图再进行理解。

定义一个三维数组:

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

相对应的存储示意图如下:
三维数组的行存储
三维数组的列存储

大家可以自行假设要求某个元素的地址,进行计算。

一维、二维、三维数组我们可以与数轴、平面直角坐标系、空间直角坐标系分别关联记忆,但是维数再往上加就找不到类似的了。但是,方法还是一样的,大家可以理解为,每增加一个维度,就相当于将先前的等空间地复制一份进行存储

四维及更多维数组的存储形式:
多维数组

总结

划重点: 多维数组a[isize][jsize][ksize][…][lsize]某元素地址的求解公式:按行优先存储:

&a[i][j][k][...][l] = i×jsize×ksize×...×lsize + j×ksize×..×lsize + ksize×...×lsize + ... + l

按列优先存储:

&a[i][j][k][...][l] = l×...×ksize×jsize×isize + ... + k×jsize×isize + j×isize + i

求解公式其实就是这么简单,但是重要的是在于理解。理解的关键点就在于对维数的分解。如果还有不理解的同学,建议结合空间想象和从代码的循环实现角度去想:n重循环实现n维数组的存储,最内层循环是第一维,每往外出一层循环相应的维数就加一,计算地址的时候则是由外至内拆解。


本文为本人原创,如有错误欢迎指正~

原创粉丝点击