前面几篇“那回事儿”的文章更强调一维组和指针之间的关系,本文关注的是多维数组,即“数组的数组”。
多维数组
我们可以将多维数组抽象的看作是具有某种类型的一维数组。当“某种类型”为基本的数据类型时,多维数组就退化成普通的一维数组;当“某种类型”仍然为数组时,那么就形成了多维数组。也就是说任何一个多维数组都可以分解成几个一维数组。
下面通过示例程序来深入了解多维数组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}。
参考:
《C专家编程》 人民邮电出版社;(美)林登(LinDen.P.V.D) 著,徐波 译;