【问题标题】:Interpretation of int (*a)[3]int(*a)的解释[3]
【发布时间】:2011-01-16 01:30:33
【问题描述】:

在 C 语言中使用数组和指针时,人们很快就会发现它们绝不是等价的,尽管乍一看似乎是等价的。我知道 L 值和 R 值的差异。不过,最近我试图找出可以与二维数组结合使用的指针类型,即

int foo[2][3];
int (*a)[3] = foo;

但是,尽管*[] 有常规的运算符优先级规则,但我就是不知道编译器如何“理解”a 的类型定义。相反,如果我使用 typedef,问题就会变得简单得多:

int foo[2][3];
typedef int my_t[3];
my_t *a = foo;

最后,有人可以回答我有关编译器如何读取术语int (*a)[3] 的问题吗? int a[3] 很简单,int *a[3] 也很简单。但是,为什么不是int *(a[3])

编辑:当然,我指的是“typedef”而不是“typecast”(这只是一个错字)。

【问题讨论】:

标签: c++ c arrays pointers types


【解决方案1】:

为什么 what 完全是“不是int *(a[3])”(参考您的最后一个问题)? int *(a[3]) 与普通的 int *a[3] 相同。大括号是多余的。它是一个由 3 个指向 int 的指针组成的数组,您说您知道它的含义。

int (*a)[3] 是一个指向 3 个 int 数组的指针(即指向 int[3] 类型的指针)。在这种情况下,大括号很重要。您自己提供了一个正确的示例,通过中间 typedef 进行等效声明。

简单流行的由内而外的 C 声明阅读规则在这种情况下非常有效。在int *a[3] 声明中,我们必须从a 开始并首先向右,即得到a[3],这意味着a 是一个由3 个something 组成的数组(依此类推)。 int (*a)[3] 中的 () 改变了这一点,所以我们不能正确,必须从 *a 开始:a 是一个指向 something 的指针。继续解码声明,我们将得出结论,something 是一个由 3 个 int 组成的数组。

无论如何,找一个阅读 C 声明的好教程,网上有不少。

【讨论】:

    【解决方案2】:

    编译器可能比你更容易解释。一种语言为构成有效表达式的内容定义了一组规则,编译器使用这些规则来解释这些表达式(可能听起来很复杂,但是已经对编译器进行了深入研究,并且有很多标准化的算法和工具可用,您可能想要阅读parsing)。 cdecl 是将 C 表达式翻译成英文的工具。源代码在网站上提供,您可以查看它。如果我没记错的话,《The C++ Programming Language》一书包含了编写此类程序的示例代码。

    至于自己解释这些表达式,我在 Thinking in C++ volume 1 一书中找到了最好的技术:

    要定义一个指向没有参数也没有返回值的函数的指针,你说:

    void (*funcPtr)();

    当你在看一个复杂的 像这样的定义,最好的方法 攻击是从中间开始 并努力解决问题。 “开始于 中”的意思是从 变量名,即 funcPtr。 “走出去”意味着寻找 最近项目的右侧 (在这种情况下没有;权利 括号阻止你短),然后 向左看(一个指针表示 由星号),然后寻找 对(一个空的参数列表 表示一个不接受的函数 论点),然后向左看 (void,表示函数 没有返回值)。这 左右运动适用于 大多数声明。

    要复习,“从中间开始” (“funcPtr is a ...”),向右 (那里什么都没有——你被 右括号),转到左边和 找到'*'(“...指向...的指针”), 向右走,找到空的 参数列表(“...函数 不带任何参数...”),转到 离开并找到 void (“funcPtr is a 指向不接受的函数的指针 参数并返回 void”)。

    您可以在Bruce Eckel's download site 上免费下载这本书。让我们尝试将其应用于int (*a)[3]

    1. 从中间的名称开始,在本例中为 a。到目前为止,它的内容是:'a is a...'
    2. 向右走,遇到一个右括号阻止你,所以你向左走,找到*。现在你读到了:'a 是指向...的指针'
    3. 向右走,您会找到 [3],即包含 3 个元素的数组。表达式变为:'a is a pointer to an array of 3...'
    4. 最后你走到左边找到 int,所以最后你得到“a 是指向 3 个 int 数组的指针”。

    【讨论】:

    • 谢谢,这也缩小了我的意思。
    【解决方案3】:

    您可能会发现这篇文章很有帮助:

    如果有人想向其中添加更多资源,我已将此答案设为社区 wiki(因为它只是文章的链接,而不是答案)。

    【讨论】:

      【解决方案4】:

      首先,您的问题是指“typedef”而不是“typecast”。

      在 C 中,指向 T 类型的指针可以指向 T 类型的对象:

      int *pi;
      int i;
      pi = &i;
      

      以上内容很容易理解。现在,让我们让它更复杂一些。您似乎知道数组和指针之间的区别(即,您知道数组不是指针,但有时它们的行为类似于它们)。所以,你应该能够理解:

      int a[3];
      int *pa = a;
      

      但为了完整起见:在赋值中,名称a 等价于&a[0],即指向数组a 的第一个元素的指针。如果您不确定它的工作原理和原因,有很多答案可以准确解释数组的名称何时“衰减”为指针,何时没有:

      我相信在 SO 上还有更多这样的问题和答案,我只是提到了一些我从搜索中找到的。

      回到主题:当我们有:

      int foo[2][3];
      

      foo 的类型为“数组[2] 的数组[3]int”。这意味着foo[0] 是一个由 3 个ints 组成的数组,foo[1] 是一个由 3 个ints 组成的数组。

      现在假设我们要声明一个指针,并将其分配给foo[0]。也就是说,我们想做:

      /* declare p somehow */
      p = foo[0];
      

      上面的形式与int *pa = a; 行没有什么不同,因为afoo[0] 的类型是相同的。所以,我们需要int *p; 作为p 的声明。

      现在,关于数组要记住的主要一点是,关于数组名称衰减为指向其第一个元素的指针的“规则”仅适用一次。如果您有一个数组的数组,那么在值上下文中,数组的名称不会衰减为“指向指针的指针”类型,而是“指向数组的指针”。回foo

      /* What should be the type of q? */
      q = foo;
      

      上面的名字foo是指向foo的第一个元素的指针,也就是说,我们可以把上面写成:

      q = &foo[0];
      

      foo[0] 的类型是“数组[3] of int”。所以我们需要q 是一个指向“数组[3] of int”的指针:

      int (*q)[3];
      

      q 周围的括号是必需的,因为 [] 在 C 中的绑定比 * 更紧密,所以 int *q[3]q 声明为一个指针数组,我们想要一个指向数组的指针。从上面看,int *(q[3]) 等价于 int *q[3],即一个由 3 个指向 int 的指针组成的数组。

      希望对您有所帮助。您还应该阅读 C for smarties: arrays and pointers 以获得关于此主题的非常好的教程。

      关于一般的阅读声明:从“变量”的名称(如果有的话)开始“由内而外”地阅读它们。除非有一个 [] 在紧邻的右边,否则你尽可能向左走,并且你总是尊重括号。 cdecl应该可以帮到你:

      $ cdecl
      cdecl> declare p as  pointer to array 3 of int
      int (*p)[3]
      cdecl> explain int (*p)[3]
      declare p as pointer to array 3 of int
      

      阅读

      int (*a)[3];
      
            a            # "a is"
          (* )           # parentheses, so precedence changes.
                         # "a pointer to"
              [3]        # "an array [3] of"
      int        ;       # "int".
      

      对于

      int *a[3];
      
           a             # "a is"
            [3]          # "an array [3] of"
          *              # can't go right, so go left.
                         # "pointer to"
      int      ;         # "int".
      

      对于

      char *(*(*a[])())()
      
                a         # "a is"
                 []       # "an array of"
               *          # "pointer to"
              (    )()    # "function taking unspecified number of parameters"
            (*        )   # "and returning a pointer to"
                       () # "function"
      char *              # "returning pointer to char"
      

      (来自c-faq question 1.21的示例。实际上,如果您正在阅读如此复杂的声明,则代码存在严重错误!)

      【讨论】:

      • +1 用于解释数组名称衰减到指向第一个元素的指针仅适用一次。
      • 非常感谢您的详细解答。事实上,我已经知道数组和指针的东西,但我不知道如何读取类型声明规则。很好的例子!
      【解决方案5】:

      它声明了一个指向 3 个ints 数组的指针。

      括号是必要的,因为下面声明了一个由 3 个指向 int 的指针组成的数组:

      int* a[3];
      

      使用typedef 时,您会获得更好的可读性:

      typedef int threeInts[3];
      threeInts* pointerToThreeInts;
      

      【讨论】:

      • 对不起,我不明白你的意思。这在OP中已经说过了吗?
      • @kapuzineralex: 4 月 15 日的所有答案都被另一个问题的模组合并到这里,我猜这里不太适合:/
      【解决方案6】:

      每当您对复杂的声明有疑问时,您可以在类 Unix 系统中使用cdecl 工具:

      [/tmp]$ cdecl
      Type `help' or `?' for help
      cdecl> explain int (*a)[10];
      declare a as pointer to array 10 of int
      

      编辑:

      此工具还有一个在线版本here

      感谢 Tiberiu Ana 和 gf

      【讨论】:

      • 很好的建议,尽管据我所知cdecl 是一个 linux 主义。它绝对不在 unix 或 posix 规范中。
      • 我很高兴意外得到了这个答案。我刚刚学到了非常宝贵的东西!
      【解决方案7】:

      意思是

      将 a 声明为指向数组 3 的指针 诠释

      请参阅cdecl 以供将来参考。

      【讨论】:

      • 谢谢,但我知道这意味着什么。我的问题针对的是编译器,而不是表达式的最终含义。
      【解决方案8】:

      在您不知道什么是声明的情况下,我发现所谓的Right-left rule 非常有用。

      顺便说一句。一旦你学会了,这样的声明就很容易了:)

      【讨论】:

        猜你喜欢
        • 2018-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 2012-11-23
        • 2021-12-21
        • 1970-01-01
        • 2011-09-17
        相关资源
        最近更新 更多