当使用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])
?
答案 0 :(得分:53)
每当您对复杂的声明有疑问时,您都可以在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
答案 1 :(得分:22)
它声明了一个指向3 int
s。
括号是必要的,因为以下声明了一个指向int
的3个指针的数组:
int* a[3];
使用typedef
时,您的可读性更高:
typedef int threeInts[3];
threeInts* pointerToThreeInts;
答案 2 :(得分:18)
首先,你的意思是“typedef”而非“类型转换”。
在C中,指向类型T
的指针可以指向T
类型的对象:
int *pi;
int i;
pi = &i;
以上内容很容易理解。现在,让我们让它变得更复杂一些。你似乎知道数组和指针之间的区别(也就是说,你知道数组不是指针,它们有时会表现得像)。所以,你应该能够理解:
int a[3];
int *pa = a;
但为了完整起见:在赋值中,名称a
等同于&a[0]
,即指向数组a
的第一个元素的指针。如果您不确定其工作原理和原因,那么有很多答案可以准确地解释数组名称“衰减”到指针的时间以及何时没有:
我确信在SO上还有更多这样的问题和答案,我刚刚提到了一些我在搜索中找到的问题。
回到主题:我们有:
int foo[2][4];
foo
的类型为[2]
的数组[3]
的数组int
。这意味着foo[0]
是3 int
s的数组,foo[1]
是3 int
s的数组。
现在假设我们要声明一个指针,我们想将其分配给foo[0]
。也就是说,我们想做:
/* declare p somehow */
p = foo[0];
上述内容与int *pa = a;
行的形式没有区别,因为a
和foo[0]
的类型相同。因此,我们需要int *p;
作为p
的声明。
现在,要记住关于数组的主要事情是关于数组名称衰减到指向其第一个元素的指针的“规则”仅适用一次。如果你有一个数组的数组,那么在值上下文中,数组的名称将不会衰减为“指向指针”的类型,而是“指向数组的指针”。回到foo
:
/* What should be the type of q? */
q = foo;
上面的名称foo
是指向foo
的第一个元素的指针,即我们可以将上面的内容写成:
q = &foo[0];
foo[0]
的类型是“[3]
”的数组int
。因此,我们需要q
作为指向“[3]
int
数组的指针”:
int (*q)[3];
q
周围的括号是必需的,因为[]
比C中的*
绑定得更紧密,因此int *q[3]
将q
声明为指针数组,并且我们想要一个指向数组的指针。 int *(q[3])
从上面开始,等同于int *q[3]
,即指向int
的3个指针数组。
希望有所帮助。您还应该阅读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的示例。实际上,如果您正在阅读这样一个复杂的声明,那么代码就会出现严重问题!)
答案 3 :(得分:6)
答案 4 :(得分:4)
编译器解释它可能比你更容易解释。语言为构成有效表达式的内容定义了一组规则,编译器使用这些规则来解释这些表达式(可能听起来很复杂,但编译器已经进行了长度研究,并且有许多标准化算法和工具可用,您可能希望阅读parsing)。 cdecl是一个将C表达式翻译为英语的工具。源代码可在网站上找到,以便您查看。 “C ++编程语言”一书包含了编写这样一个程序的示例代码,如果我没有误会的话。
至于自己解释这些表达方式,我在“思考于C ++第1卷”一书中找到了最好的技巧:
要定义一个指向没有参数且没有返回值的函数的指针,你可以说:
void(* funcPtr)();
当你在看一个复杂的时候 像这样的定义,最好的方法 攻击它是从中间开始 并努力工作。 “从入手 中间“意味着从...开始 变量名,即funcPtr。 “努力工作”意味着寻找 在最近的项目的右边 (在这种情况下没有;右边 括号会阻止你),然后 向左看(指示一个指针 由星号),然后期待 对(一个空的参数列表 表示不需要的功能 参数),然后向左看 (void,表示功能 没有回报价值)。这个 右 - 左 - 右动作与 大多数声明。
回顾一下,“从中间开始” (“funcPtr是......”),向右走 (没有任何东西 - 你被停止了 右括号),转到左边 找到'*'(“...指向...的指针”), 向右走,找到空的 参数列表(“... ...的功能 没有参数......“),去吧 离开并找到空(“funcPtr是一个 指向不需要的函数的指针 参数并返回void“)。
您可以在Bruce Eckel's download site免费下载该图书。我们尝试将其应用于int (*a)[3]
:
答案 5 :(得分:2)
为什么 是什么“不是int *(a[3])
”(指你的上一个问题)? int *(a[3])
与普通int *a[3]
相同。大括号是多余的。这是一个指向int
的3个指针的数组,你说你知道它意味着什么。
int (*a)[3]
是指向3 int
数组的指针(即指向int[3]
类型的指针)。在这种情况下,大括号很重要。您自己提供了一个正确的示例,通过中间typedef
进行等效声明。
简单流行的由内而外的C声明阅读规则在这种情况下非常有效。在int *a[3]
声明中,我们必须从a
开始,然后右转,即获取a[3]
,这意味着a
是一个3 的数组(等等)。 ()
中的int (*a)[3]
更改了这一点,因此我们无法正确而必须从*a
开始:a
是指向某事的指针。继续解码声明我们将得出结论某事是3 int
的数组。
在任何情况下,找一个关于阅读C声明的好教程,其中有很多可用于网络。
答案 6 :(得分:2)
您可能会发现本文有用:
如果有人想要为其添加更多资源,我已将此答案作为社区维基(因为它只是文章的链接,而不是答案)。
答案 7 :(得分:2)
在你不知道宣言是什么的情况下,我发现了非常有用的所谓Right-left rule。
顺便说一句。一旦你学会了它,这样的声明就是和平的蛋糕:)