22-函数和多维数组
10.7.3 函数和多维数组
如果要编写处理二维数组的函数,首先要能正确地理解指针才能写出声明函数的形参。在函数体中,通常使用数组表示法进行相关操作。
下面,我们编写一个处理二维数组的函数。一种方法是,利用 for 循环把处理一维数组的函数应用到二维数组的每一行。如下所示:
int junk[3][4] = { {2,4,5,8}, {3,5,6,9}, {12,10,8,6} };
int i, j;
int total = 0;
for (i = 0; i < 3 ; i++)
total += sum(junk[i], 4); // junk[i]是一维数组
记住,如果 junk 是二维数组, junk[i] 就是一维数组,可将其视为二维数组的一行。这里, sum() 函数计算二维数组的每行的总和,然后 for 循环再把每行的总和加起来。
然而,这种方法无法记录行和列的信息。用这种方法计算总和,行和列的信息并不重要。但如果每行代表一年,每列代表一个月,就还需要一个函数计算某列的总和。该函数要知道行和列的信息,可以通过声明正确类型的形参变量来完成,以便函数能正确地传递数组。在这种情况下,数组 junk 是一个内含3个数组元素的数组,每个元素是内含4个 int 类型值的数组(即 junk 是一个3行4列的二维数组)。通过前面的讨论可知,这表明 junk 是一个指向数组(内含 4 个 int 类型值)的指针。可以这样声明函数的形参:
void somefunction( int (* pt)[4] );
另外,如果当且仅当 pt 是一个函数的形式参数时,可以这样声明:
void somefunction( int pt[][4] );
注意,第1个方括号是空的。空的方括号表明 pt 是一个指针。这样的变量稍后能以同样的方式用作 junk 。下面的程序示例中就是这样做的,如程序清单10.17所示。注意该程序清单演示了3种等价的原型语法。
程序清单10.17 array2d.c 程序
// array2d.c -- 处理二维数组的函数
#include <stdio.h>
#define ROWS 3
#define COLS 4
void sum_rows(int ar[][COLS], int rows);
void sum_cols(int [][COLS], int); // 省略形参名,没问题
int sum2d(int(*ar)[COLS], int rows); // 另一种语法
int main(void)
{
int junk[ROWS][COLS] = {
{ 2, 4, 6, 8 },
{ 3, 5, 7, 9 },
{ 12, 10, 8, 6 }
};
sum_rows(junk, ROWS);
sum_cols(junk, ROWS);
printf("Sum of all elements = %d\n", sum2d(junk, ROWS));
return 0;
}
void sum_rows(int ar[][COLS], int rows)
{
int r;
int c;
int tot;
for (r = 0; r < rows; r++)
{
tot = 0;
for (c = 0; c < COLS; c++)
tot += ar[r][c];
printf("row %d: sum = %d\n", r, tot);
}
}
void sum_cols(int ar[][COLS], int rows)
{
int r;
int c;
int tot;
for (c = 0; c < COLS; c++)
{
tot = 0;
for (r = 0; r < rows; r++)
tot += ar[r][c];
printf("col %d: sum = %d\n", c, tot);
}
}
int sum2d(int ar[][COLS], int rows)
{
int r;
int c;
int tot = 0;
for (r = 0; r < rows; r++)
for (c = 0; c < COLS; c++)
tot += ar[r][c];
return tot;
}
该程序的输出如下:
row 0: sum = 20
row 1: sum = 24
row 2: sum = 36
col 0: sum = 17
col 1: sum = 19
col 2: sum = 21
col 3: sum = 23
Sum of all elements = 80
程序清单10.17中的程序把数组名 junk (即,指向数组首元素的指针,首元素是子数组)和符号常量 ROWS (代表行数 3 )作为参数传递给函数。每个函数都把 ar 视为内含数组元素(每个元素是内含 4 个 int 类型值的数组)的数组。列数内置在函数体中,但是行数靠函数传递得到。如果传入函数的行数是 12 ,那么函数要处理的是 12 × 4 的数组。因为 rows 是元素的个数,然而,因为每个元素都是数组,或者视为一行, rows 也可以看成是行数。
注意, ar 和 main() 中的 junk 都使用数组表示法。因为 ar 和 junk 的类型相同,它们都是指向内含 4 个 int 类型值的数组的指针。
注意,下面的声明不正确:
int sum2(int ar[][], int rows); // 错误的声明
前面介绍过,编译器会把数组表示法转换成指针表示法。例如,编译器会把 ar[1] 转换成 ar+1 。编译器对 ar+1 求值,要知道 ar 所指向的对象大小。下面的声明:
int sum2(int ar[][4], int rows); // 有效声明
表示 ar 指向一个内含4个 int 类型值的数组(在我们的系统中, ar 指向的对象占16字节),所以 ar+1 的意思是“该地址加上16字节”。如果第2对方括号是空的,编译器就不知道该怎样处理。
也可以在第1对方括号中写上大小,如下所示,但是编译器会忽略该值:
int sum2(int ar[3][4], int rows); // 有效声明,但是3将被忽略
与使用 typedef (第5章和第14章中讨论)相比,这种形式方便得多:
typedef int arr4[4]; // arr4是一个内含 4 个int的数组
typedef arr4 arr3x4[3]; // arr3x4 是一个内含3个 arr4的数组
int sum2(arr3x4 ar, int rows); // 与下面的声明相同
int sum2(int ar[3][4], int rows); // 与下面的声明相同
int sum2(int ar[][4], int rows); // 标准形式
一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
int sum4d(int ar[][12][20][30], int rows);
因为第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。下面的声明与该声明等价:
int sum4d(int (*ar)[12][20][30], int rows); // ar是一个指针
这里, ar 指向一个 12 × 20 × 30 的 int 数组。