关于声明的螺旋规则 - 何时出错?

Vor*_*rac 14 c declaration

我最近学习用于去复制复杂声明的螺旋规则,它必须用一系列typedef编写.但是,以下评论警告我:

经常引用的简化,仅适用于少数简单案例.

我找不到void (*signal(int, void (*fp)(int)))(int);"简单的案例".顺便说一句,哪个更令人担忧.

所以,我的问题是,在哪种情况下,我应用规则是正确的,哪些是错误的?

Jam*_*nze 20

基本上说,规则根本不起作用,否则它通过重新定义螺旋的含义来起作用(在这种情况下,它没有任何意义.例如:考虑:

int* a[10][15];
Run Code Online (Sandbox Code Playgroud)

螺旋规则会给出一个指向int的数组[15]的指针的数组[10],这是错误的.你的网站是这样的,它也不起作用; 事实上,在这种情况下signal,你甚至不清楚你应该在哪里开始螺旋.

通常,查找规则失败的示例比它工作的示例更容易.

我经常试图说解析C++声明很简单,但是没有尝试过复杂声明的人会相信我.另一方面,它并不像有时那样难以实现.秘诀在于完全按照表达式来考虑声明,但运算符少得多,并且有一个非常简单的优先级规则:右边的所有运算符都优先于左边的所有运算符.在没有括号的情况下,这意味着首先处理所有内容,然后处理左边的所有内容,并完全像处理任何其他表达式一样处理括号.实际的困难不是语法本身,而是它产生的是一些非常复杂和违反直觉的声明,特别是涉及函数返回值和指向函数的指针时:第一个权利,然后是左边的规则意味着特定级别的运算符是经常分开,例如:

int (*f( /* lots of parameters */ ))[10];
Run Code Online (Sandbox Code Playgroud)

这里扩展的最后一个术语是int[10],但是[10]在完成功能规范之后(至少对我而言)非常不自然,我必须每次都停下来解决它.(这可能是逻辑相邻部分分散的趋势,导致螺旋规则.问题当然是,在没有括号的情况下,它们并不总是分散 - 任何时候你看[i][j],规则是正确的,然后再向右走,而不是螺旋式.)

因为我们现在正在考虑表达式的声明:当表达式变得太复杂而无法阅读时,你会怎么做?您引入了中间变量,以便于阅读.在声明的情况下,"中间变量"是typedef.特别是,我认为任何时候返回类型的一部分最终都会在函数参数之后(以及很多其他时间),你应该使用a typedef来使声明更简单.(然而,这是"像我说的那样,而不是像我一样"的规则.我担心我偶尔会使用一些非常复杂的声明.)


Chr*_*odd 9

螺旋法则实际上是一种过于复杂的看待它的方式。实际的规则要简单得多:

postfix is higher precedence than prefix.
Run Code Online (Sandbox Code Playgroud)

就是这样。这就是您需要记住的全部内容。“复杂”的情况是当你用括号来覆盖后缀高于前缀的优先级时,但你实际上只需要找到匹配的括号,然后首先查看括号内的内容,如果不完整,在括号外拉入下一级,后缀在前。

所以看看你的复杂例子

void (*signal(int, void (*fp)(int)))(int);
Run Code Online (Sandbox Code Playgroud)

我们可以从任何名字开始并找出那个名字是什么。如果你从 开始int,你就完成了 --int是一种类型,你可以自己理解它。

如果你从 开始fp, fp 不是一种类型,它是一个被声明为某种东西的名称。所以看看第一组括号:

                        (*fp)
Run Code Online (Sandbox Code Playgroud)

没有后缀(先处理后缀),那么前缀*就是指针。指针指向什么?尚未完成,所以请留意另一个级别

                   void (*fp)(int)
Run Code Online (Sandbox Code Playgroud)

后缀首先是“带有 int 参数的函数”,然后前缀是“返回 void”。所以我们有fn“指向函数的指针,采用 int 参数,返回 void”

如果我们开始 a signal,第一层有一个后缀(函数)和一个前缀(返回指针)。需要下一个级别来查看它指向什么(函数返回 void)。所以我们最终得到“具有两个参数(int 和函数指针)的函数,返回具有一个(int)参数的函数指针,返回 void”


Ale*_*nze 5

规则是正确的。但是,在应用它时应该非常小心。

我建议以更正式的方式将其应用于 C99+ 声明。

这里最重要的是认识到所有声明的下面的递归结构(constvolatilestaticexterninlinestructuniontypedef从图片的简单移除,但你回来很容易地添加):

base-type [derived-part1: *'s] [object] [derived-part2: []'s or ()]
Run Code Online (Sandbox Code Playgroud)

是的,就是这样,四个部分。

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
Run Code Online (Sandbox Code Playgroud)

一旦你解析了所有 4 个部分,

  [ object] 是 [ derived-part2(包含/返回)] [ derived-part2(指向)] base-type 1

如果有递归,您会object在递归堆栈的底部找到您的(如果有的话),它将是最里面的一个,您将通过返回并收集和组合每个级别的派生部分来获得完整声明的递归。

在解析时,您可以移动[object]到之后[derived-part2](如果有)。这将为您提供一个线性化的、易于理解的声明(参见上面的1)。

因此,在

char* (**(*foo[3][5])(void))[7][9];
Run Code Online (Sandbox Code Playgroud)

你得到:

  1. base-type = char
  2. 级别 1: derived-part1= *, object= (**(*foo[3][5])(void)), derived-part2=[7][9]
  3. 级别 2: derived-part1= **, object= (*foo[3][5]), derived-part2=(void)
  4. 级别 3: derived-part1= *, object= foo, derived-part2=[3][5]

从那里:

  1. 第 3 级: * [3][5] foo
  2. 第 2 级: ** (void) * [3][5] foo
  3. 1级: * [7][9] ** (void) * [3][5] foo
  4. 最后, char * [7][9] ** (void) * [3][5] foo

现在,从右到左阅读:

foo 是一个包含 5 个指向函数的指针的 3 个数组的数组(不带参数),返回一个指向一个指针的指针,该指针指向一个包含 7 个指向字符的 9 个指针的数组的数组。

您也可以在每个derived-part2过程中反转数组维度。

这就是你的螺旋法则。

而且很容易看到螺旋。你[object]从左边深入到越来越深的嵌套,然后在右边重新浮出水面,只是注意到在上层还有另一对左右等等。


CB *_*ley 1

例如:

int * a[][5];
Run Code Online (Sandbox Code Playgroud)

这不是指向 数组的指针数组int

  • 那么这是什么? (5认同)