关于声明的螺旋规则 – 什么时候出错?
最近我学习了用于反混淆复杂声明的螺旋规则 , 该规则必须是用一系列typedef写的。 但是,下面的评论警告我:
一个经常被引用的简化,只适用于几个简单的情况。
我没有findvoid (*signal(int, void (*fp)(int)))(int);
一个“简单案例”。 顺便说一句,更令人担忧的是
所以,我的问题是, 在哪种情况下我会正确适用这个规则,哪一个会出错呢?
从根本上说,这个规则根本不起作用,或者它是通过重新定义螺旋的含义来进行工作的(在这种情况下,没有任何意义,例如:
int* a[10][15];
螺旋规则会给出一个int指针数组[10]的数组[10],这是错误的。 它是你的网站的情况下,它也不工作; 事实上,在signal
的情况下,甚至不清楚你应该在哪里开始螺旋。
一般来说,find规则失败的例子比find它的例子要容易得多。
我经常试图说parsing一个C ++声明很简单,但没有谁试过复杂的声明的身体会相信我。 另一方面,它并不像有时候那样艰难。 其秘诀在于将声明完全按照您的expression方式来思考,但运算符less得多,而且优先级规则非常简单:右侧的所有运算符优先于左侧的所有运算符。 在没有括号的情况下,这意味着首先处理所有事情,然后处理所有事情,然后按照任何其他expression式处理括号。 实际的困难不是语法本身,而是它的结果是一些非常复杂和违反直觉的声明,特别是涉及函数返回值和指向函数的指针:第一个右边,然后是左边的规则,意味着特定级别的操作符是经常分开,例如:
int (*f( /* lots of parameters */ ))[10];
在这里扩展的最后一个术语是int[10]
,但是在完整的函数说明之后放置[10]
是非常不自然的,我必须每次停下来解决这个问题。 (这可能是逻辑上相邻部分分散的趋势导致了螺旋规则,问题当然是在没有括号的情况下,它们并不总是展开 – 只要你看到[i][j]
,规则是正确的,然后再去正确的,而不是螺旋。)
因为我们现在正在考虑用expression式来expression声明:当expression式变得太复杂时,你会怎么做? 您可以引入中间variables以便于阅读。 在声明的情况下,“中间variables”是typedef
。 特别是,我会争辩说,任何时候返回types的一部分在函数参数(以及许多其他时间)之后结束,您应该使用typedef
来使声明更简单。 (但是,这是一个“按照我所说的做,而不是按我所做”的规则,恐怕我会偶尔使用一些非常复杂的声明。
规则是正确的。 但是,应用时要非常小心。
我build议以更正式的方式应用C99 +声明。
这里最重要的是识别所有声明的下面的recursion结构( 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
/ returning)] [ derived-part2
(pointer to)] base-type
1 。
如果有recursion,你会发现你的object
(如果有的话)在recursion堆栈的底部,它将是最内层的一个,你将得到完整的声明,通过恢复和收集每个派生部分recursion水平。
parsing时,可以将[object]
移动到[derived-part2]
(如果有的话)。 这会给你一个线性的,易于理解的声明(见上面1 )。
因此,在
char* (**(*foo[3][5])(void))[7][9];
你得到:
-
base-type
=char
- 等级1:
derived-part1
=*
,object
=(**(*foo[3][5])(void))
,derived-part2
=[7][9]
- 等级2:
derived-part1
=**
,object
=(*foo[3][5])
,derived-part2
=(void)
- 等级3:
derived-part1
=*
,object
=foo
,derived-part2
=[3][5]
从那里:
- 等级3:
*
[3][5]
foo
- 等级2:
**
(void)
*
[3][5]
foo
- 等级1:
*
[7][9]
**
(void)
*
[3][5]
foo
- 最后,
char
*
[7][9]
**
(void)
*
[3][5]
foo
现在,从右向左阅读:
foo
是一个由3个数组组成的5个指针的数组(不带参数),它返回一个指针,指针指向一个由9个指向char的数组组成的数组。
您也可以在每个derived-part2
中反转数组维度。
这是你的螺旋规则。
很容易看到螺旋。 你从左边潜入更深的嵌套[object]
,然后在右边重新performance,只有在上层有另一对左右等等。
例如:
int * a[][5];
这不是指向int
数组的指针数组。