一元运算符是否具有结合性是有意义的吗?
一元运算符是否具有结合性是有意义的吗?
C++运算符优先级表来自于http://en.cppreference.com/w/cpp/language/operator_precedence(我知道这不是规范,但标准并未讨论优先级或结合性),将一元运算符标记为右/左结合。从另一个的问题中,我有些疑问。一元运算符是否有结合性是有意义的吗?
一元运算符的结合性问题和解决方法
考虑以下代码片段:
int *p; *p++;
表达式`*p++`可以解释为`(*p)++`(对p指向的对象进行递增)或者`*(p++)`(指向p指向的下一个对象)。
因为一元运算符是右结合的,所以表达式`*p++`将被解释为`*(p++)`。(我在阅读《Kernighan和Ritchie》时想到的。)
看起来优先级和结合性已经改变了,后缀`++`比解引用`*`运算符的优先级高。
根据C11标准,上述表达式将被解释为`*(p++)`。
希望这样更清楚一元运算符为什么有结合性了。
感谢Lucian Grigore指出这一点。
后缀运算符是从左到右结合的-en.cppreference.com/w/cpp/language/operator_precedence,所以这个论点是不成立的。此外,在这种情况下,评估顺序由优先级决定,因为后缀递增比解引用有更高的优先级。
感谢您的见解。
我认为一元运算符根本没有结合性,因为不允许链接相同的运算符,所以应该使用优先级来解决,而不是结合性-正如您所说,他们现在明白了。
在讨论一元运算符的关联性时,我们需要考虑其出现在操作数的哪一侧,因为它们是不同的运算符。一元运算符可能会出现两次,一次是作为前缀,一次是作为后缀。因此,它们是关联的,只是与其他运算符的关联性不同。一元运算符不会像二元运算符那样引起二义性,但当一元运算符位于两个可能的操作数之间时,我们需要确定它应用于哪个操作数。举个例子,当我们有一个表达式"a - b - c"时,它可以解释为"(a - b) - c"或"a - (b - c)",这两种解释会得到不同的结果。结合律规则指定了"-"是左结合的,因此正确的解释是"(a - b) - c"。
对于"a + b + c"和"a = b = c"这两个表达式,它们的分组方式是不同的,因为"+"是左结合的,而"="是右结合的。然而,对于"+ + i"这个表达式,只有一种解析方式。
一元运算符不存在二义性。如果一元运算符位于两个操作数之间,必定存在另一个二元运算符,一元运算符会与其相邻的操作数结合在一起。
实际上,运算符的关联性决定了运算符的具体含义。例如,编译器可以根据关联性判断"a++"是后增操作,而不是前增操作,因为前增操作是右结合的,而"a++"右边没有其他操作数可以与之结合。
另一种解释是,当递归下降解析器遇到{++
, a
}这样的标记序列时,首先尝试将其解析为后缀表达式,失败后再尝试解析为一元表达式,并成功解析。相比之下,操作符优先级解析器(例如shunting yard算法)会显式地使用优先级和关联性标记运算符。对于可能引起二义性的标记(如++
),在操作符优先级解析器中可能需要像Seth所说的那样进行处理。我对解析器不太擅长。
一种方式是通过查看一元运算符左边或右边是否有其他内容来确定运算符的具体含义。
在编程语言中,运算符的结合性是由文法推导出来的。加法是左结合的原因是,其中一个加法表达式的产生式是 additive-expression + multiplicative-expression,加法表达式在左边。因此,当我们看到 a + b + c 时,这必须等价于 (a + b) + c,因为唯一匹配这个产生式的方式是将 a + b 作为 additive-expression,将 c 作为 multiplicative-expression。a 自己是一个 additive-expression,但是 b + c 不是一个 multiplicative-expression,所以如果我们将 a 作为 additive-expression,a + b + c 就无法匹配产生式。
如果你以前没有这样做过,我建议你阅读一下“表达式”章节,只关注文法产生式而不涉及语义。然后你就会看到,优先级和结合性是如何由文法定义的。其中一个关键是,每个“高优先级”的表达式都是“低优先级”的表达式。因此,每个 multiplicative-expression 都是 additive-expression,但反之则不成立,这就使得乘法比加法“绑定得更紧”。
前缀一元运算符在文法中的定义如下:unary-expression: ++ cast-expression 等等,前缀运算符在左边,后缀运算符在右边。换句话说,我们在后缀运算符的左边“插入括号”,在前缀运算符的右边“插入括号”。也就是说,后缀运算符的分组是从左到右的,前缀运算符的分组是从右到左的。事实上,C++ 标准也确实是这么说的(在 C++03 中的 5.2/1 和 5.3/1)。把这种一元运算符的分组称为“结合性”可能是滥用术语,或者至少是一个新的词汇,但很明显可以理解其意思。
这里唯一的区别是,二元运算符如果以相反的顺序分组仍然是有意义的,所以 a - b - c 的意思是 a - (b - c)。这可能会让人感到意外,但并不会影响语言的其他方面。然而,对于一元运算符来说,如果将 !!a 分组为 (!!)(a),不仅令人意外,而且语言还必须为子表达式 !! 提供一个含义,而当前它没有意义。函数式语言可以给它一个含义:!! 可能表示由 ! 和 ! 组成的函数,即与 static_cast
所以是的,可以说前缀运算符以从右到左的方式分组,后缀运算符以从左到右的方式分组。但是这也是“显而易见”的,因为我们对 C++ 语言的其他方面有了更多的了解。
顺便说一下,在 C++ 中,严格来说,后缀 ++ 不是一元运算符。它是一个后缀运算符。但除了标准中的术语外,这并不重要,因为很明显它是一个运算符,它有一个操作数,因此在英语中是“一元”的。
参见我在这里的回答(stackoverflow.com/a/14084830/1740808),其中提供了一个假设的语言,其中前缀运算符是左结合的。