假设你有一个二维数组:
uint32_t array[][2] = {
{1, 2},
{3, 4},
{5, 6},
{7, 8},
{9, 10},
{0, 0}
};
我们知道这真的不是一个“二维”数组。它实际上是一个数组数组。 array 是一些东西的数组,每个东西是两个uint32_t 的小数组。所以array[0] 是两个uint32_t 的数组,array[1] 是两个uint32_t 的数组,等等。
假设,出于某种原因,您想要操作指向该数组中行的指针。也许您想使用如下代码向下移动数组:
sometype p;
p = array;
while(p is not at the end of array) {
do something with the array pointed to by p;
p++;
}
或者假设您想再次通过指针跟踪数组中的某些行:
sometype row2, row4;
row2 = &array[2];
row4 = &array[4];
我们知道这是有道理的,因为array 是一些东西的数组,所以array[2] 是第二个(实际上是第三个,因为从0 开始)的东西,&array[2] 是指向那个东西的指针。同样,在这种情况下,“某物”是两个 uint32_t 的数组。
但问题是,sometype 是什么?我们希望p、row2 和row4 具有类型“指向两个uint32_t 的数组的指针”。这是一种不寻常的类型,但它确实存在,而且是您一直在询问的类型:
uint32_t (*p)[2];
这表示p 正是一个指向两个uint32_t 的数组的指针。当然,一个大而明显的子问题是:(*p) 部分周围的括号到底是做什么的?答案是没有他们,我们会有
uint32_t *p[2]; /* not what we want */
这会将p 声明为一个由两个指向uint32_t 的指针组成的数组。在 C 中,声明中存在优先关系,就像表达式中存在优先关系一样,[] 的绑定比* 更紧密。所以如果你想说p首先是一个指针(它的指针是任何数组),你需要括号:
uint32_t (*p)[2];
所以现在我们可以重写我之前编写的尝试扫描数组的循环:
uint32_t (*p)[2];
p = array;
while((*p)[0] != 0 || (*p)[1] != 0) {
printf("next row: %d, %d\n", (*p)[0], (*p)[1]);
p++;
}
这是可行的,我鼓励你编译并运行它。还有一些更“有趣”的括号,同样是因为优先级。我们的p 确实是一个指向两个uint32 的小数组的指针。所以(*p) 是 两个uint32 的数组。所以(*p)[0] 是指向数组的第一个元素,(*p)[1] 是第二个元素。但是如果没有括号,我们就会有*p[0] 和*p[1],它们基本上会尝试首先将p 视为一个数组,然后是一个指针,这不是我们想要的。 (实际上,正如@sj95126 和我在 cmets 中讨论的那样,*p[1] 之类的符号 确实 工作 - 起初我认为这是一个错误 - 但它做了一些不同的事情,即访问子数组的[0] 元素过去p,而不是p 的数组的[1] 元素。)
最后,在这一点上观察“复杂”类型(例如指向数组的指针)可能很有用,C 的typedef 机制有时可以使事情变得更清晰。之前我写过,作为伪代码,
sometype p;
因为我们不太确定要使用什么类型。但是,现在我们知道了,如果我们想要更方便地声明变量,例如 p、row2 和 row4,我们可以编写
typedef uint32_2 (*sometype)[2];
这就像我们之前对p 的声明,只是它在前面多了一个关键字typedef。这改变了一些事情:我们没有将“sometype”声明为指向两个uint32_t的数组的指针;相反,我们将“sometype”声明为 别名 或类型的简写,“指向两个 uint32_t 的数组的指针”。然后我们可以字面上说像
sometype p;
或
sometype row2, row4;
typedef 仅在声明 p、row2 和 row4 时有帮助。在我们使用它们的表达式中,我们仍然需要额外的括号。并且包含指针的 typedef 也可能令人困惑(也就是说,比它们更方便更令人困惑),因此许多程序员建议避免使用指针 typedef。 (当然“sometype”在实践中是一个可怕的名字。如果你真的写了这个,你会想要像typedef uint32_2 (*ptrary2)[2]; ptrary2 p;这样的东西。)
有关数组指针的更多讨论,请参阅旧版 C FAQ list 中的 question 6.13。