一个二维数组:
int foo[5][4];
无非就是一个数组的数组:
typedef int row[4]; /* type "row" is an array of 4 ints */
row foo[5]; /* the object "foo" is an array of 5 rows */
这里没有指针对象,无论是显式还是隐式。
数组不是指针。指针也不是数组。
常常会引起困惑的是,在大多数情况下,数组表达式会被隐式转换为指向其第一个元素的指针。(而且另一个规则说,看起来像数组参数声明的东西实际上是一个指针声明,但这不适用于本示例。)数组对象是一个数组对象;声明这样的对象并不会创建任何指针对象。引用数组对象可以创建指针值(数组第一个元素的地址),但内存中没有存储指针对象。
数组对象foo
被存储在内存中作为5个连续的元素,其中每个元素本身都是包含4个连续int
元素的数组;因此,整个数组被存储为20个连续的int
对象。
索引运算符是以指针算术为基础定义的;x[y]
等价于*(x+y)
。通常左操作数要么是指针表达式,要么是数组表达式;如果它是数组表达式,则该数组会被隐式转换为指针。
因此,foo[x][y]
等价于*(foo[x]+y)
,这又等价于*(*(foo+x)+y)
。(请注意,不需要任何强制转换。)幸运的是,你不必以那种方式编写它,foo[x][y]
更容易理解。
请注意,您可以创建一个数据结构,可以使用相同的foo[x][y]
语法访问该结构,但其中foo
确实是指向指向int的指针。(在这种情况下,每个[]
运算符的前缀已经是指针表达式,不需要转换。)但要做到这一点,您必须将foo
声明为指向指向int的指针:
int **foo;
然后分配并初始化所有必要的内存。这比使用
int foo[5][4]
更灵活,因为您可以动态确定行数和每行的大小(甚至是存在与否)。
comp.lang.c FAQ的第6部分很好地解释了这一点。
编辑:
回应Arrakis的评论,重要的是要注意
类型和
表示之间的区别。
例如,这两种类型:
struct pair { int x; int y;};
typedef int arr2[2];
很可能在内存中具有相同的表示方式(两个相邻的int
对象),但是访问元素的语法非常不同。
类似地,类型int[5][4]
和int[20]
具有相同的内存布局(20个连续的int
对象),但是访问元素的语法不同。
您可以将foo[2][2]
作为((int*)foo)[10]
访问(将二维数组视为一维数组)。 有时这样做很有用,但严格来说行为是未定义的。 您可能会成功,因为大多数C实现不进行数组边界检查。 另一方面,优化编译器可以假定您的代码行为是已定义的,并生成任意代码以确保其如此。
*foo == foo[0] == &foo[0][0]
*(foo+1) == foo[1] == &foo[1][0]
(int *)foo + 1 == &foo[0][1]
- Arrakisfoo
不是指针数组;它是一个二维数组。 - Keith Thompson