我最近学到了用螺旋法则来分解复杂的声明,这些声明本应该使用一系列typedefs来编写。然而,下面的评论让我感到惊讶:
我并不认为 void (*signal(int, void (*fp)(int)))(int);
是一个“简单的情况”。这让我更加担忧。
因此,我的问题是:在哪些情况下使用这个规则是正确的,哪些情况下使用它会出错?
我最近学到了用螺旋法则来分解复杂的声明,这些声明本应该使用一系列typedefs来编写。然而,下面的评论让我感到惊讶:
我并不认为 void (*signal(int, void (*fp)(int)))(int);
是一个“简单的情况”。这让我更加担忧。
因此,我的问题是:在哪些情况下使用这个规则是正确的,哪些情况下使用它会出错?
基本上说,这个规则根本不起作用,要不就是通过重新定义螺旋的含义来实现(如果是这样的话,那就没有意义了)。例如:
int* a[10][15];
螺旋法给出的是一个指向大小为15的int数组的指针数组[10],这是错误的。在你提到的情况下,它也不适用;事实上,在signal
的情况下,甚至不清楚应从哪里开始螺旋。
一般来说,更容易找到规则失败的例子,而不是它起作用的例子。
我经常想说解析C++声明很简单,但尝试过复杂声明的人不会相信我。另一方面,它并不像有时所说的那样难。秘诀是将声明视为表达式,但运算符较少,并且有一个非常简单的优先级规则:右侧所有运算符的优先级高于左侧所有运算符。在没有括号的情况下,这意味着首先处理右侧的所有内容,然后处理左侧的所有内容,并且像在任何其他表达式中一样处理括号。实际的困难不在于语法本身,而在于它导致了一些非常复杂和反直觉的声明,特别是涉及函数返回值和指向函数的指针的情况:第一个右侧,然后左侧的规则意味着特定级别的运算符通常相距很远,例如:
int (*f( /* lots of parameters */ ))[10];
这里展开式的最后一个术语是int[10]
, 但将[10]
放在完整函数说明之后(至少对我来说)非常不自然,每次都需要停下来仔细思考。 (可能是这种逻辑上相邻的部分扩散的趋势导致了螺旋规则。问题在于,在没有括号的情况下,它们并不总是扩散-任何时候看到[i][j]
,规则是向右移动,然后再向右移动,而不是螺旋。)typedef
。 特别是,我认为任何时候返回类型的一部分出现在函数参数之后(以及许多其他时间),您都应该使用typedef
使声明更简单。(但是这是一个“说话容易,做起来难”的规则。恐怕我偶尔会使用一些非常复杂的声明。)postfix is higher precedence than prefix.
就是这样。这是你需要记住的所有内容。当你有括号来覆盖后缀优先于前缀时,会出现“复杂”情况,但你只需要找到匹配的括号,然后首先查看括号内的内容,如果不完整,则拉入下一个级别的内容,先处理后缀。
所以看看你的复杂例子:
void (*signal(int, void (*fp)(int)))(int);
(*fp)
在处理后缀之前,没有后缀。然后,前缀*
表示指针。指向什么?还不完整,因此要查看另一个级别。
void (*fp)(int)
fn
是“指向带有int参数且返回void的函数的指针”。signal
,第一级有一个后缀(函数)和一个前缀(返回指针)。需要看出下一级指向的是什么(返回void的函数)。因此,我们最终得到“带两个参数(int和指向函数的指针)且返回一个带有一个参数(int)且返回void的函数指针的函数”。const
、volatile
、static
、extern
、inline
、struct
、union
、typedef
已从图中删除,但可以轻松添加回来):base-type [derived-part1: *'s] [object] [derived-part2: []'s or ()]
where
base-type is one of the following (I'm using a bit compressed notation):
void
[signed/unsigned] char
[signed/unsigned] short [int]
signed/unsigned [int]
[signed/unsigned] long [long] [int]
float
[long] double
etc
object is
an identifier
OR
([derived-part1: *'s] [object] [derived-part2: []'s or ()])
* is *, denotes a reference/pointer and can be repeated
[] in derived-part2 denotes bracketed array dimensions and can be repeated
() in derived-part2 denotes parenthesized function parameters delimited with ,'s
[] elsewhere denotes an optional part
() elsewhere denotes parentheses
一旦你解析了所有的4个部分,
[object
]就是[derived-part2
(包含/返回)][derived-part2
(指向)]base-type
1。
如果有递归,你可以在递归栈的底部找到你的object
(如果有的话),它将是最内层的一个,并且你需要回溯并收集和组合每个递归级别的派生部分,以获得完整的声明。
在解析过程中,你可能会将[object]
移动到[derived-part2]
之后(如果有的话)。这将给你一个线性化的、易于理解的声明(见上文1)。
因此,在
char* (**(*foo[3][5])(void))[7][9];
base-type
= char
derived-part1
= *
,object
= (**(*foo[3][5])(void))
,derived-part2
= [7][9]
derived-part1
= **
,object
= (*foo[3][5])
,derived-part2
= (void)
derived-part1
= *
,object
= foo
,derived-part2
= [3][5]
从这里开始:
*
[3][5]
foo
**
(void)
*
[3][5]
foo
*
[7][9]
**
(void)
*
[3][5]
foo
char
*
[7][9]
**
(void)
*
[3][5]
foo
现在,从右往左阅读:
foo
是一个由 3 个数组组成的数组,每个数组中有 5 个指向函数(不带参数)返回一个指向包含了 7 个数组的数组,每个数组中有 9 个指向字符的指针。
你也可以在过程中反转每个 derived-part2
中的数组维度。
这就是螺旋规则。
很容易看到螺旋形状。你从左边深入嵌套的 [object]
,然后从右边浮出来,只注意到上一层还有另一对左右,以此类推。
where
部分有什么不清楚的吗?还是你忽略了它? - Alexey FrunzeE.g.:
int * a[][5];
int
数组的指针数组。
Vorac
寻求帮助;我不知道它会在什么情况下出错。 - Jonathan Lefflerint* a[][10];
,也会出现错误。否则,您重新定义“螺旋”,最终得到的结果甚至比实际定义更糟糕。 - James Kanzeint* a[][10]
这个例子中,右左法则 是适用的。你知道还有哪些情况下这个规则是无效的,例如螺旋规则吗?基本上我正在试图找到一条支配所有规则的规则。 - legends2k