多维数组那回事儿

本文转自:http://edsionte.com/techblog/archives/2554

前面几篇“那回事儿”的文章更强调一维组和指针之间的关系,本文关注的是多维数组,即“数组的数组”。
多维数组我们可以将多维数组抽象的看作是具有某种类型的一维数组。当“某种类型”为基本的数据类型时,多维数组就退化成普通的一维数组;当“某种类型”仍然为数组时,那么就形成了多维数组。也就是说任何一个多维数组都可以分解成几个一维数组。
下面通过示例程序来深入了解多维数组ma[2][3]的构成。

#include < stdio.h >

int main()
{
int ma[2][3];
int (*r)[2][3];
int (*p)[3];
int *t;

/*代码段1*/
p = ma;
printf("sizeof(ma[0])=%d\n",sizeof(ma[0]));
printf("ma      =%p\tp   =%p\n",ma,p);
printf("p+1 =%p\n",p+1);
/*代码段2*/
r = &ma;
printf("sizeof(ma)=%d\n",sizeof(ma));
printf("&ma     =%p\tr  =%p\n",&ma,r);
printf("&ma+1   =%p\tr+1=%p\n",&ma+1,r+1);
/*代码段3*/
t = ma[0];
printf("sizeof(ma[0][0])=%d\n",sizeof(ma[0][0]));
printf("ma[0]   =%p\tt   =%p\n",ma[0],t);
printf("ma[0]+1 =%p\tt+1 =%p\n",ma[0]+1,t+1);
return 0;
}

由多维数组ma最左维的长度2可知,ma数组包含两个元素ma[0]和ma[1]。数组名ma在表达式中是数组ma首元素的首地址。在代码段1中将ma赋值给数组指针p,则p指向多维数组ma的首元素ma[0],则p+1指向第二个元素ma[1]。其中p是一个数组指针,它指向一个长度为3的数组,则指针p每次移动的偏移量为12。可参考下图:
在代码2中对ma取地址并将其赋值给指针r。r现在指向一个“第一维的大小为2,第二维的大小为3的数组”,则r+1将指向下一个这样的数组(尽管这样的数组并不存在)。由此也可得知r每次的偏移量为24。

ma[0]和ma[1]都是一个长度为3的整型数组,现在以ma[0]为例进行说明。ma[0]中包含三个元素ma[0][0],ma[0][1]和ma[0][2]。在代码段3中将ma[0]赋值给t,则t指向数组ma[0]的第一个元素a[0][0],则t+1和t+2则依次指向第二个元素和第三个元素。

对多维数组ma的结构有了一定了解后,现在再看上述程序的运行结果:

edsionte@edsionte-laptop:~/code/expertC$ gcc array.c -o array
edsionte@edsionte-laptop:~/code/expertC$ ./array
sizeof(ma[0])=12
ma   =0xbfdfaa6c    p=0xbfdfaa6c
p+1  =0xbfdfaa78
sizeof(ma)=24
&ma  =0xbfdfaa6c    r=0xbfdfaa6c
r+1  =0xbfdfaa84
sizeof(ma[0][0])=4
ma[0]=0xbfdfaa6c    t=0xbfdfaa6c
t+1  =0xbfdfaa70

注意在结果中,p,r和t的值均相同,但是所指向的数据却不同。更具体的说,这三个指针每次移动时的偏移量不同。多维数组的初始化数组的初始化只能在对数组进行声明(具体为定义型声明)时进行。一维数组的初始化很简单,只要将所有初始值放在一个大括号中即可。如果声明数组时未指定数组的长度,则编译器会根据初始值的个数来确定数组的长度。

#include < stdio.h >

int main()
{
int m[] = {1,2,3};
int n[] = {1,2,3,};

printf("length(m)=%d\n",sizeof(m)/sizeof(m[0]));
printf("length(n)=%d\n",sizeof(n)/sizeof(n[0]));
return 0;
}

/* 编译并运行 */
edsionte@edsionte-laptop:~/code/expertC$ gcc init_array.c -o init_array
edsionte@edsionte-laptop:~/code/expertC$ ./init_array
length(m)=3
length(n)=3

注意,在最后一个初始值后面可以继续加一个逗号也可以省略,这并不影响数组的长度。对于多维数组而言,通常使用嵌套的大括号进行多维数组的初始化。由于多维的数组其实是有若干个一维数组构成的,则每个大括号都代表一个一维数组。对于多维数组而言只能省略最左边 下标的长度。

#include < stdio.h >

int main()
{
int b[][3] = {1,2,1,1};
int c[][3] = {{1,2,1},{1,2,3},};

printf("length(b)=%d\n",sizeof(b)/sizeof(b[0]));
printf("length(c)=%d\n",sizeof(c)/sizeof(c[0]));
return 0;
}

/* 编译并运行 */
edsionte@edsionte-laptop:~/code/expertC$ gcc init_array.c -o init_array
edsionte@edsionte-laptop:~/code/expertC$ ./init_array
length(b)=2
length(c)=2

可以看到,不使用大括号也可以对多维数组进行初始化,只不过代码可读性较差。它总是迷惑你!一旦涉及到多维数组,总有些让你迷惑的地方。比如:

    char ma[2][3][2]={
{{1,2},{2,3},{3,4}},
{{3,5},{4,5},{3,3}}
};

sizeof(ma[0,1,1])=?

对于上面的代码,我们最后的迷惑点都可能落在ma[0,1,1]上。难道多维数组可以这样使用吗?如果ma[0,1,1]和ma[0][1][1]等价,那么sizeof(ma[0,1,1])的值就是1。很可惜这样的猜测是不正确的,正确答案为6。再比如下面的代码:

        char ma[3][2] = {
(1,2),(3,4),(5,3)
};

ma[0][0]=?

上述代码是为数组ma进行初始化,那么ma[0][0]的值是多少?恐怕很多人都会认为是1。不过正确答案是2。这两个问题都涉及到了逗号表达式。如果你对逗号表达式有基本的了解,那么也就没有上述那种莫名其妙的迷惑了。根据逗号表达式的运算,对于举例1中的ma[0,1,1]实际上等价于ma[1];对于举例2中的初始化其实等价为char ma[3][2] = {2,4,3}。