指针后的方括号 declaration/reference

Square brackets after pointer declaration/reference

我正在将代码从 C 移植到 Go,并且在 C 中遇到了一些我以前从未见过的东西。我喜欢认为自己在搜索和学习这样的新概念时非常有能力和自给自足,但是在这种情况下我绝对做不到。

我正在移植的代码声明和引用(我认为)是指针,但它们被括号括起来,然后用方括号索引。一些例子:

结构定义

typedef struct {
    uint32_t *S;
    uint32_t (*S0)[2], (*S1)[2], (*S2)[2];
} example;

正在赋值:

ex.S0 = (uint32_t (*)[2])ex.S;
ex.S1 = ex.S0 + (1 << someInt1) * someInt2;
ex.S2 = ex.S1 + (1 << someInt1) * someInt2;

事实上,命名、声明和使用不足以让我制定一个能告诉我更多信息的搜索。那么,对于 S0、S1 和 S2,它们是什么,我在哪里可以了解有关声明和使用它们的更多信息?

这个

uint32_t (*S0)[2], (*S1)[2], (*S2)[2];

是指向数组类型uint32_t[2]的指针的声明。

例如,如果你有一个像这样的二维数组

uint32_t a[N][2];

其中 N 是一些(在这种情况下不重要)值然后你可以声明一个指向数组第一个元素的指针,如

uint32_t ( *p )[2] = a;

用作初始值设定项的数组指示符 a 被隐式转换为指向其第一个元素的指针。数组的元素是 uint32_t[2].

类型的一维数组

或者如果你有一个像

这样的一维数组
uint32_t a[2];

然后声明一个指向数组的指针可以像

uint32_t ( *p )[2] = &a;

假设你有一个二维数组:

uint32_t array[][2] = {
    {1, 2},
    {3, 4},
    {5, 6},
    {7, 8},
    {9, 10},
    {0, 0}
};

我们知道这不是真正的“二维”数组。它实际上是一个数组的数组。 array 是 somethings 的数组,每个 something 是两个 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基)的东西,ao&array[2] 是指向那个东西的指针。而且,在这种情况下,“某物”是两个 uint32_t 的数组。

但问题是,sometype是什么?我们希望 prow2row4 具有类型“指向两个 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 和我在评论中讨论的那样,像 *p[1] 这样的符号确实 起作用——起初我认为这是一个错误——但它做了一些事情不同,即访问 p 之后的子数组的 [0] 元素,而不是 p 处数组的 [1] 元素。)

最后,在这一点上观察对于诸如数组指针之类的“复杂”类型,C 的 typedef 机制有时可以使事情变得更清晰。早些时候我写过,作为伪代码,

sometype p;

因为我们不太确定要使用哪种类型。但是,现在我们知道了,如果我们想要更方便地声明像 prow2row4 这样的变量,我们可以写

typedef uint32_2 (*sometype)[2];

这就像我们之前声明的 p 一样,只是它在前面多了一个关键字 typedef。这改变了事情:我们没有将“sometype”声明为指向两个 uint32_t 的数组的指针;相反,我们将“sometype”声明为类型的 别名 或 shorthand,“指向两个 uint32_t 的数组的指针”。然后我们可以从字面上说

sometype p;

sometype row2, row4;

typedef 仅在声明 prow2row4 时有用。在我们使用它们的表达式中,我们仍然需要额外的括号。包含指针的 typedef 也可能令人困惑(也就是说,比它们方便更令人困惑),因此许多程序员建议避免使用指针 typedef。 (当然,在实践中,“sometype”是一个可怕的名字。如果你真的写了这个,你会想要像 typedef uint32_2 (*ptrary2)[2]; ptrary2 p; 这样的名字。)

有关数组指针的更多讨论,请参阅 question 6.13 in the old C FAQ list

括号是 声明符 语法的一部分。它们表示数组。

在 C 语言中,声明 声明说明符 的列表,后跟 声明符 带有可选的初始化。

声明说明符 指定类型,例如 intdoublelong longstruct foo 或 typedef姓名。 (它们还包括其他类型的说明符,如 externinline,这些说明符在此答案中无关紧要。)无论类型如何 <i>T</i> 这是,声明符然后指定类型为 <i>T</i>.

的事物

声明符有几种形式:

  • 一个普通的名字,<i>D</i>,表示<i>D</i> 的类型为 <i>T</i>.
  • 括号中的声明符,(<i>D</i>),表示表达式(<i>D</i>) 的类型为 <i>T</i>,表示 <i>D</i> 也有类型 <i>T</i>,因此这与声明符中的普通名称具有相同的含义。但是,它将声明符分组以进行进一步解析。
  • 带括号的声明符,<i>D</i>[<em>expression</em>],表示表达式<i>D</i>[i] 将具有类型 <i>T</i>,这意味着<i>D</i> 必须是类型数组。
  • 带有星号的声明符,*<i>D</i>,表示表达式 *<i>D</i> 将具有类型 <i>T</i>,因此 <i>D</i> 必须是指向 <i>T</i>.
  • 的指针
  • 带括号的声明符,<i>D</i>(),表示表达式<i>D</i>() 将具有类型 <i>T</i>,这意味着 <i>D</i> 必须是 returns <i>T</i>.
  • 的函数

这些表格可以合并。声明符 (*<i>D</i>)[] 表示 (*<i>D</i>)[] 具有类型 <i>T</i>,因此 (*<i>D</i>)<i>T</i>的数组,所以*<i>D</i>也是<i>T</i>的数组,所以<i>D</i> 是指向 <i>T</i>.

数组的指针

从本质上讲,声明符是一个名称将如何在表达式中使用的图片,名称的实际类型是通过从该表达式的类型向后计算得出的。

(上面没有讨论声明符的其他部分,例如函数的参数列表和声明数组时括号内容的一些选项。)