(复杂)声明如何解释在优先级和关联性? [英] How are (complex) declarations parsed in terms of precedence and associativity?

查看:149
本文介绍了(复杂)声明如何解释在优先级和关联性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

符号,如& * em>和声明,这是两个鲜明的概念。



表达式中,符号是运算符,我们有一个明确的优先级和关联性。当表达式很复杂时,我们可以使用此表格对其进行分解和分析。
例如

 (a + b)* ++ c 

问题
声明中,这些符号不是运算符无法应用运算符的优先级和关联性表在声明中有符号的优先级和关联性表吗?
或者换句话说,当一个声明变得复杂时(尝试这个 int *&(*& f(int *))系统的方法来分解和分析它?



一个紧密相关的后续问题:
一些书(底漆)教我们如何用一个例子 typedef

  typedef int(* tp_alias)[10] //将tp_alias定义为指向数组10的数组int 

本书教授的方法:使用别名作为读取的起始点 tp_alias 是新的类型名称。向左看,它有一个 * ,所以它是一个指针。然后看外面的括号:在右边, [10] 意味着它是一个大小为10的数组;到左边, int 表示数组的元素是int。

后续问题:
我们如何读取其他类型别名声明,例如使用?因为别名不再在位置?例如使用tp_alias = int(*)[10]

也许从(),但是如果有多个() s(我没有看到一个,但它是一种可能性。)



操作符的优先级是从语法推断。例如,考虑 logical-and-expression logical-or-expression 的定义:

 逻辑和表达式:
包含或表达
逻辑和表达式&& inclusive-or-expression

逻辑或表达式:
逻辑和表达式
逻辑或表达式||逻辑和表达式

现在考虑表达式 a || b&&& c 。它必须解析为一个 logical-or-expression (其中左边是 logical-or-expression a ,右边是 logical-and-expression b&& c )。它不能被解析为一个 logical-and-expression ,因为如果是,那么左边, a || b 也必须是一个逻辑和表达式



另一方面,在(a || b)&& c ,因此您不能将其解析为 logical-or-expression ,因为那样您将有(a 作为左侧和 b)&& c 作为右侧,并且都不是有效的表达式。您可以将其解析为逻辑和表达式,因为(a || b) $ c> a || b 是一个有效的逻辑和表达式



编译器解析代码,树。如果根节点是 logical-or-expression ,则逻辑或操作最后完成。如果根节点是 logical-and-expression ,则逻辑AND操作最后完成。等等。



好的,这是坏消息。现在的好消息。



即使表达式中没有运算符的优先级表,大多数时候你只能假装有(当然,可以通过括号覆盖);这就是你如何解析表达式。所以,事实证明,声明器也是如此。没有优先级表,但你可以精神分析他们,就像有。



事实上,C的设计师聪明地使声明使用相同的优先级规则作为表达式。这是声明遵循用法规则。例如,在表达式

 (* a)[10] 
pre>

我们有一个包含间接表达式的数组索引表达式,而不是反之亦然。以同样的方式,在

  int(* a)[10] 

我们有一个包含指针声明符的数组声明符, em>。因此,如果你记住从 [] (数组索引)和()(函数调用)的优先级高于 * (间接),你可以应用同样的顺序来理解声明。 (引用是一种特殊情况;很容易记住引用在指针的声明中具有相同的优先级。)


Symbols, like &, *, etc., are used in both expressions and declarations, which are two distinctive concepts.

In expressions, the symbols are operators, for which we have a well-defined table of precedence and associativity. When an expression is complex, we can decompose and analyze it using this table. e.g.

    (a + b) * ++c

Question: In declarations, these symbols are not operator and hence we cannot apply the table of precedence and associativity for operators. Is there a table of precedence and associativity for symbols in declarations? Or in other words, when an declaration gets complicated (try this one int*& (*&f(int*))), is there a systematic way to decompose and analyze it?

A closely related follow-up question: Some book (primer) taught us how to read complex declaration with an example of typedef:

typedef int (*tp_alias)[10]; //defines tp_alias as an pointer to an array of 10 int

Method taught by the book: use the the alias name as the starting point of reading, tp_alias is the new type name. Looking to the left, it has a *, so it is a pointer. Then look outside the parenthesis: to the right, [10] means it is an array of size 10; to the left, int means the element of the array is int.
Follow-up Question: How do we read other type aliasing declaration, such as using? Since the alias name is no longer in position? e.g. using tp_alias = int (*)[10]?
Maybe to read from within the (), but whatif there is more than one ()s?(I have not seen one but it is a possibility.)

解决方案

There is no precedence table for expressions!

The precedence of operators is inferred from the grammar. For example, consider the definitions of logical-and-expression and logical-or-expression:

logical-and-expression:
    inclusive-or-expression
    logical-and-expression && inclusive-or-expression

logical-or-expression:
    logical-and-expression
    logical-or-expression || logical-and-expression

Now consider the expression a || b && c. It must be parsed as a logical-or-expression (where the left side is the logical-or-expression a and the right side is the logical-and-expression b && c). It can't be parsed as a logical-and-expression, because if it were, then the left side, a || b, would have to be a logical-and-expression too, and it isn't.

On the other hand, in (a || b) && c, you can't parse it as a logical-or-expression because then you'd have (a as the left side and b) && c as the right side, and neither is a valid expression. You can parse it as a logical-and-expression because (a || b), unlike a || b, is a valid logical-and-expression.

The compiler parses the code, and builds a syntax tree. If the root node is a logical-or-expression, then the logical OR operation is done last. If the root node is a logical-and-expression, then the logical AND operation is done last. And so on.

OK, so that was the bad news. Now the good news.

Even though there's no precedence table for operators in expressions, most of the time you can just pretend there is (and of course it can be overridden by parentheses); that's how you parse expressions mentally. So, it turns out that the same is true for declarators. There is no precedence table, but you can mentally parse them as though there is.

In fact, the designers of C were wise enough to make declarations use the same "precedence rules" as expressions. This is the "declaration follows usage" rule. For example, in the expression

(*a)[10]

we have an array indexing expression containing an indirection expression, and not vice versa. In the same way, in

int (*a)[10];

what we have is an array declarator containing a pointer declarator, and not vice versa. (Therefore, the result is a pointer to array.) So if you remember from expressions that [] (array indexing) and () (function call) have higher precedence than * (indirection), you can apply that same ordering to understanding declarations too. (References are a special case; it's easy to remember that references have the same "precedence" in a declaration as pointers.)

这篇关于(复杂)声明如何解释在优先级和关联性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆