有两种直观的方式来定义二维数组,可以理解成直接定义和间接定义:
直接定义 :char ch[2][3];
间接定义: typedef char P[3] ; P ch[2];
间接定义首先把char [3]当成一种类型(一维数组),并给这个类型起了个别名P,然后定义了一个P的数组,换言之,间接定义将二维数组理解成了若干个一维数组,通过这两种方式定义的二维数组(GCC 4.8.1)的内存布局是相同的,因此二维数据实际上就是由若干个一维数组组成,下面通过一个简单的程序来观察 ch 的内存布局:
char ch[2][3];
printf("%p\n%p\n%p\n%p\n",&ch[0][0],&ch[0][2],&ch[1][0],&ch[1][2]);
输出结果如下:
addr of ch[0][0] : 0xbf9efb56
addr of ch[0][2] : 0xbf9efb58
addr of ch[1][0] : 0xbf9efb59
addr of ch[1][2] : 0xbf9efb5b
可以看到二维数组的内存是线性布局的,ch的第一个一维数组占用的内存为0xbf9efb56~0xbf9efb58,第二个一维数组占用紧随其后的内存:0xbf9efb59~0xbf9efb5b。所以如果对第一个子数组发生了写越界,越界的部分就会污染第二个子数组里的内容,至于会不会对 ch 以外的内容造成污染,那取决于越界的多少。现在我们来考虑第二个问题:既然P是一个类型(大小为3的一维char数组),那么我们就可以创建一个指向P的指针:P *p; 那么p+1比p大多少呢:答案是:3 = 3*sizeof(char)。在现实生活中我们常常看到另外一种写法:char (*p)[3],这个p其实就是前面提到的p,p指向了第一个一维数组,p+1就指向了第二个一维数组,所以用p[i][j]就可以访问数组的第(i , j)个元素:(p[1] 等价于 p+1),我们可以看一下p和p[0]的类型:
char (*p)[3];
cout<<typeid(p).name()<<endl;
cout<<typeid(p[0]).name()<<endl;
输出结果如下:
Type of p : PA3_c
Type of p[0] : A3_c
所以虽然p是指向一维数组的指针,但是p[0]已经是一个int数组了,所以p[0][j]可以正确访问数组元素(p[0][j] 等价于p[0]+j),此时p[0][1]只比p[0][0]大1。下面我们看一下在传递二维数组给函数时发生了什么:
void fun(char ch[][3]){ cout<<typeid(ch).name()<<endl; }
int main(void)
{
char ch[2][3];
fun(ch);
return 0;
}
我们看一下fun()的输出结果:
Type of ch : PA3_c
是不是似曾相识呢?在fun()里面ch也是指向一维数组的指针,跟前面提到的p一样!这里发生了decay,即将传入的二维数组decay成了一个指向一维数组的指针,既然是个指针,必然要确定其指向的类型,所以ch[ ] [3]的3是必须的,而前面一个 [ ] 内的数字就不是必须的了。基本上就是这些东西了,另外附上一个在实际中遇到的小例子:
附:
假设ch是一个二维数组,我们可以用 ch 存储两个小字符串,比如使用 scanf 从终端读入字符串,并把读入的字符串保存在 ch 中,在读过的代码里遇到过两种写法:
1: scanf("%s", ch[0]);
2: scanf("%s", &ch[0]);
这两种方法的目的都是把一个字符串读入到二维数组的第一个一维子数组中,有的编译器会给第一种方法一个警告,但是两种方法的执行的效果是一样的,最终传递给scanf的都是第一个子数组的首地址,而且如果在程序里用printf 输出 ch[0] 和 &ch[0],也可以发现二者其实是相等的,甚至 &ch 的值也是与之相同的。
现在我们动态地申请一个二维数组,代码如下:
#include <stdio.h>
typedef char p[3];
int main(){
char ch[2][3];
printf("%d\n%d\n%d\n%d\n",&ch,ch[0],&ch[0],&ch[0][0]);
printf("\n");
p *q = new p[2];
printf("%d\n%d\n%d\n%d\n",&q,q[0],&q[0],&q[0][0]);
return 0;
}
输出结果如下:
q 本质上还是个指针,是个变量,它自身位于栈区,但是它的内容是某个二维数组的地址,换言之,它的内容与 ch 等价, 但是 q 不是 ch, 这就是编译器对待指针形式的数组与 直接形式的数组的不同之处, q 作为一个变量,自身也要存在内存的某个位置,而符号“q”就是这个位置的代号,而 ch 直接就是某块内存的首地址的代号。