10.2 多维数组

例如:气象分析员要分析5年中每月的降水量数据,首先需要解决的问题是如何表示出这些数据。一种方法是用60个变量,每个变量代表一个数据项目(前面曾经提到过这种方法,和前面提到时的情况一样,这不是合适的方法)。使用一个60个元素数组的方法虽然可以采用,但是把各年度的数据单独放置会更好。也可以设置5个数组,每个数组包含12个元素。这是一种比较笨拙的方法,而且如果要处理的数据不是5年,而是50年,这种方法就很不合适。我们需要找到一种更好的方法。

更好的处理方法是使用一个数组的数组,即:主数组包含5个元素,每个元素代表一年。代表一年的元素是包含12个元素的数组。这种数组的数组,我们称之为二维数组。下面是这种数组的声明方法:

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

阅读 ‧ 电子书库

理解这个声明的一种方法是首先查看位于中间的那部分(粗体字部分):

阅读 ‧ 电子书库

这部分说明rain是一个包含5个元素的数组。至于每个元素的情况,需要查看声明的其余部分(粗体字部分):

阅读 ‧ 电子书库

这说明每个元素的类型是float[12];也就是说,rain具有5个元素,并且每个元素都是包含12个float数值的数组。

按此推理,rain的首元素rain[0]是一个包含12个float数值的数组。rain[1],rain[2]等等也是如此。rain[0]是数组,那么它的首元素是rain[0][0],第二个元素是rain[0][1],依此类推其他元素。简单地说,rain是包含5个元素(每个元素又是包含12个float数的数组)的数组,rain[0]是包含12个float数的数组,rain[0][0]是一个float数。如果访问位于2行3列的元素,则用rain[2][3](注意:数组中计数是从0开始的,因此2行实际指的是第3行)。

也可以把rain数组看作是一个二维数组,它包含有5行,每行12列,如图10.1所示。改变第二个下标,可以沿着一行移动,每移动一个单位代表一个月份。改变第一个下标,可以沿着一列垂直移动,每移动一个单位代表一年。

用二维视图表示数组便于我们直观地想象具有两个索引的数组。实际上,数组是顺序存储的,前12个元素之后,跟着就是第二个包含12个元素的数组,依次类推。

我们将在气象分析程序中采用这个二维数组。程序的目标是计算出年降水总量、年降水平均量,以及月降水平均量。要计算年降水总量,需要对某一行的数据求和。要计算某月的降水平均量,需要把对应于这个月份的列的所有数据求和。二维数组使这些计算变得直观有序,实现起来也比较方便。程序清单10.7展示了这个程序。

阅读 ‧ 电子书库

图10.1 二维数组
程序清单10.7 rain.c程序

阅读 ‧ 电子书库

输出如下:

阅读 ‧ 电子书库

研究此程序时,请注意数组的初始化方法和计算方案。而数组初始化是其中较复杂的部分,让我们先来研究相对简单的一部分(计算)。

要计算某年度的降水总量,则保持year为常量,让month遍历整个范围,这正是程序第一部分的内部for循环的作用。程序第一部分的外部循环的目的则是让变量year在值域(5年)内遍历。像这样的嵌套循环结构在处理二维数组时是比较方便的。利用一个循环处理第一个下标,利用另一个循环处理第二个下标。

程序第二部分的结构和第一部分相同,但year被改为内部循环,而month被改为外部循环。注意,外部循环每执行一次,内部循环完整遍历一次。因此,在月份改变之前,年度先遍历。先得到的是5年中一月份的降水平均量,然后依次类推。

10.2.1 初始化二维数组

对二维数组的初始化是建立在对一维数组的初始化之上的。首先,让我们回忆一下对一维数组的初始化方法,如下所示:

阅读 ‧ 电子书库

此处val1、val2等代表同sometype类型相应的数值。例如,如果sometype是int,vall可以是7;如果sometype是double,那么vall可以是11.34。而rain是包含5个元素的数组,每个元素又是包含12个float数的数组。因此,对于rain,vall应该对一维float数组进行初始化。如下所示:

阅读 ‧ 电子书库

也就是说,如果sometype是一个包含12个double数的数组,那么vall就是一个由12个double数构成的数值列表。因此,可以采用以逗号分隔的5个这样的数值列表来初始化像rain这样的二维数组:

阅读 ‧ 电子书库

这个初始化使用了5个数值列表,每个数值列表都用花括号括起来。第一个列表被赋给数组的第一行,第二个列表被赋给数组的第二行,依此进行赋值。前面讨论的数据个数和数组大小的不匹配问题同样适用于此处的每一行。也就是说,如果第一个列表中有10个数值,则第一行只有前10个元素得到赋值,最后2个元素被默认初始化为0。如果列表中的数值多于12个,则报告错误;而且这些数值不会影响到下一行的赋值。

初始化时候也可以省略内部的花括号,只保留最外面的一对花括号。只要保证数值的个数正确,初始化效果就是一样的。如果数值的个数不够,那么在数组初始化时候,按照先后顺序来逐行赋值,因此前面的元素首先得到赋值,直到没有数值为止。后面没有赋值的元素被初始化为0。图10.2示意了这两种初始化方法。

由于数组rain中存放不应该被修改的数据,因此在声明数组时程序使用了const修饰符。

阅读 ‧ 电子书库

图10.2 初始化数组的两种方法
10.2.2 更多维数的数组

前面关于二维数组的讨论对于三维乃至更多维数的数组同样适用。可以用如下方式声明三维数组:

阅读 ‧ 电子书库

可以这样直观地理解:一维数组是排成一行的数据,二维数组是放在一个平面上的数据,三维数组是把平面数据一层一层地垒起来。例如,可以把上面定义的数组box直观想象为数据构成的方块:由10个二维数组(每个二维数组都是20行,30列)堆放起来构成的立方体。

另一种理解box的方法认为它是数组的数组的数组。即:box是包含10个元素的数组,其中每个元素又是包含20个元素的数组,这20个元素中的每一个又是包含30个元素的数组。或者可以简单地按照所需的索引数目去理解数组。

通常处理三维数组时候需要3重嵌套循环,处理四维数组需要4重嵌套循环,对于其他多维数组,依此类推。在后面的章节中,我们只用二维数组来举例。