5.7 Multi-dimensional Arrays
C provides rectangular multi-dimensional arrays, although in practice they are much less used than arrays of pointers. In this section, we will show some of their properties.
C语言提供了类似于矩阵的多维数组,但实际上它们并不像指针数组使用得那样广泛。本节将对多维数组的特性进行介绍。
Consider the problem of date conversion, from day of the month to day of the year and vice versa. For example, March 1 is the 60th day of a non-leap year, and the 61st day of a leap year. Let us define two functions to do the conversions: day_of_year converts the month and day into the day of the year, and month_day converts the day of the year into the month and day. Since this latter function computes two values, the month and day arguments will be pointers:
我们考虑一个日期转换的问题,把某月某日这种日期表示形式转换为某年中第几天的表示形式,反之亦然。例如,3 月1日是非闰年的第60天,是闰年的第61 天。在这里,我们定义下列两个函数以进行日期转换:函数day_of_year将某月某日的日期表示形式转换为某一年中第几天的表示形式,函数month_day则执行相反的转换。因为后一个函数要返回两个值,所以在函数month_day中,月和日这两个参数使用指针的形式。例如,下列语句:
month_day(1988, 60, &m, &d)
sets m to 2 and d to 29 (February 29th).
将把m的值设置为2,把d的值设置为29(2月29日)。
These functions both need the same information, a table of the number of days in each month (``thirty days hath September ...''). Since the number of days per month differs for leap years and non-leap years, it's easier to separate them into two rows of a two-dimensional array than to keep track of what happens to February during computation. The array and the functions for performing the transformations are as follows:
这些函数都要用到一张记录每月天数的表(如“9 月有30 天”等)。对闰年和非闰年来说,每个月的天数不同,所以,将这些天数分别存放在一个二维数组的两行中比在计算过程中判断2 月有多少天更容易。该数组以及执行日期转换的函数如下所示:
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
/* day_of_year: set day of year from month & day */
int day_of_year(int year, int month, int day)//年月日转换为这一年中的第多少天
{
int i, leap;
leap = year%4 == 0 && year%100 != 0 || year%400 == 0;//取值零和一,来判断是闰年还是非闰年
for (i = 1; i < month; i++)
day += daytab[leap][i];//累加计算判断一年中的第多少天
return day;
}
/* month_day: set month, day from day of year */
void month_day(int year, int yearday, int *pmonth, int *pday)//一年中的第多少天转换为年月日
{
int i, leap;
leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
for (i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i];//通过天数的累积减来判断第几个月的第几天
*pmonth = i;
*pday = yearday;
}
Recall that the arithmetic value of a logical expression, such as the one for leap, is either zero (false) or one (true), so it can be used as a subscript of the array daytab.
我们在前面的章节中曾讲过,逻辑表达式的算术运算值只可能是0(为假时)或者1(为真时)。因此,在本例中,可以将逻辑表达式leap用做数组daytab的下标。
The array daytab has to be external to both day_of_year and month_day, so they can both use it. We made it char to illustrate a legitimate use of char for storing small non-character integers.
数组 daytab 必须在函数day_of_year 和month_day 的外部进行声明,这样,这两个函数都可以使用该数组。这里之所以将daytab 的元素声明为char 类型,是为了说明在char类型的变量中存放较小的非字符整数也是合法的。
daytab is the first two-dimensional array we have dealt with. In C, a two-dimensional array is really a one-dimensional array, each of whose elements is an array. Hence subscripts are written as
到目前为止,daytab 是我们遇到的第一个二维数组。在C 语言中,二维数组实际上是一种特殊的一维数组,它的每个元素也是一个一维数组。因此,数组下标应该写成
daytab[i][j] /* [row][col] */
rather than
daytab[i,j] /* WRONG */
Other than this notational distinction, a two-dimensional array can be treated in much the same way as in other languages. Elements are stored by rows, so the rightmost subscript, or column, varies fastest as elements are accessed in storage order.
除了表示方式的区别外,C语言中二维数组的使用方式和其它语言一样。数组元素按行存储,因此,当按存储顺序访问数组时,最右边的数组下标(即列)变化得最快。
An array is initialized by a list of initializers in braces; each row of a two-dimensional array is initialized by a corresponding sub-list. We started the array daytab with a column of zero so that month numbers can run from the natural 1 to 12 instead of 0 to 11. Since space is not at a premium here, this is clearer than adjusting the indices.
数组可以用花括号括起来的初值表进行初始化,二维数组的每一行由相应的子列表进行初始化。在本例中,我们将数组daytab的第一列元素设置为0,这样,月份的值为1~12,而不是0~11。由于在这里存储空间并不是主要问题,所以这种处理方式比在程序中调整数组的下标更加直观。
If a two-dimensional array is to be passed to a function, the parameter declaration in the function must include the number of columns; the number of rows is irrelevant, since what is passed is, as before, a pointer to an array of rows, where each row is an array of 13 ints. In this particular case, it is a pointer to objects that are arrays of 13 ints. Thus if the array daytab is to be passed to a function f, the declaration of f would be:
如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。数组的行数没有太大关系,因为前面已经讲过,函数调用时传递的是一个指针,它指向由行向量构成的一维数组,其中每个行向量是具有13个整型元素的一维数组。在该例子中,传递给函数的是一个指向很多对象的指针,其中每个对象是由13个整型元素构成的一维数组。因此,如果将数组daytab作为参数传递给函数f,那么f的声明应该写成下列形式:
f(int daytab[2][13]) { ... }
It could also be
f(int daytab[][13]) { ... }
since the number of rows is irrelevant, or it could be
f(int (*daytab)[13]) { ... }
which says that the parameter is a pointer to an array of 13 integers. The parentheses are necessary since brackets [] have higher precedence than *. Without parentheses, the declaration
这种声明形式表明参数是一个指针,它指向具有13个整型元素的一维数组。因为方括号[]的优先级高于*的优先级,所以上述声明中必须使用圆括号。如果去掉括号,则声明变成
int *daytab[13]
is an array of 13 pointers to integers. More generally, only the first dimension (subscript) of an array is free; all the others have to be specified.
这相当于声明了一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针。一般来说,除数组的第一维(下标)可以不指定大小外,其余各维都必须明确指定大小。
5.8 Initialization of Pointer Arrays
Consider the problem of writing a function month_name(n), which returns a pointer to a character string containing the name of the n-th month. This is an ideal application for an internal static array. month_name contains a private array of character strings, and returns a pointer to the proper one when called. This section shows how that array of names is initialized.
考虑这样一个问题:编写一个函数month_name(n),它返回一个指向第n 个月名字的字符串的指针。这是内部static 类型数组的一种理想的应用。month_name 函数中包含一个私有的字符串数组,当它被调用时,返回一个指向正确元素的指针。本节将说明如何初始化该名字数组。
The syntax is similar to previous initializations:
/* month_name: return name of n-th month */
char *month_name(int n)
{
static char *name[] = {
"Illegal month",
"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"
};
return (n < 1 || n > 12) ? name[0] : name[n];
}
The declaration of name, which is an array of character pointers, is the same as lineptr in the sorting example. The initializer is a list of character strings; each is assigned to the corresponding position in the array. The characters of the i-th string are placed somewhere, and a pointer to them is stored in name[i]. Since the size of the array name is not specified, the compiler counts the initializers and fills in the correct number.
其中,name 的声明与排序例子中lineptr 的声明相同,是一个一维数组,数组的元素为字符指针。name数组的初始化通过一个字符串列表实现,列表中的每个字符串赋值给数组相应位置的元素。第i 个字符串的所有字符存储在存储器中的某个位置,指向它的指针存储在name[i]中。由于上述声明中没有指明name 的长度,因此,编译器编译时将对初值个数进行统计,并将这一准确数字填入数组的长度。