实际上,关于赋值语句的 C++ 语法有两件有趣的事情,它们都与以下内容的有效性无关:
(4 || localvar1) = 5;
由于括号,该表达式在语法上是有效的(直到类型检查)。引用类型的任何带括号的表达式在赋值运算符的左侧在语法上都是正确的。(而且,正如已经指出的那样,几乎任何涉及用户类型或函数的表达式都可以是引用类型,这是运算符重载的结果。)
语法更有趣的是它建立了赋值运算符的左优先级低于几乎所有其他运算符,包括逻辑或,因此上述表达式在语义上等价于
4 || localvar1 = 5;
即使许多读者将上述解释为4 || (localvar1 = 5)
(假设它localvar1
是可以由 an 分配的类型,这将是完全正确的int
,即使所述分配永远不会发生——除非,当然,||
在这种情况下被重载) .
那么赋值运算符左侧的优先级较低的是什么?正如我所说,很少,但一个重要的例外是?:
:
// Replace the larger of a and b with c
a > b ? a = c : b = c;
是有效且方便的无括号。(许多风格指南在这里坚持使用多余的括号,但我个人更喜欢不带括号的版本。)这与右手优先不同,因此以下也可以在没有括号的情况下使用:
// Replace c with the larger of a and b
c = a > b ? a : b;
与赋值运算符相比,在赋值运算符左侧绑定得更紧密的唯一其他运算符是,
运算符和另一个赋值运算符。(换句话说,与几乎所有其他二元运算符不同,赋值是右结合的。)这些都不足为奇——事实上,它们是如此必要,以至于很容易忽略以这种方式设计语法的重要性。考虑以下不起眼的for
条款:
for (first = p = vec.begin(), last = vec.end(); p < last; ++p)
这里 the,
是一个逗号操作符,它显然需要比围绕它的任何一个赋值更紧密地绑定。(C 和 C++ 仅在此语法中例外,因为它具有逗号运算符;在大多数语言中,,
不被视为运算符。)此外,显然不希望将第一个赋值表达式解析为(first = p) = vec.begin()
.
赋值运算符与右侧相关联这一事实并不引人注目,但出于对历史的好奇,值得注意的是。当 Bjarne Stroustrup 正在四处寻找运算符以重载 I/O 流时,他选择了<<
,>>
因为尽管赋值运算符可能更自然 [1],但赋值绑定到右侧,而流式运算符必须绑定到左侧(std::cout << a << b
必须是(std::cout << a) << b)
。但是,由于<<
绑定比赋值更紧密,因此在使用流式运算符时存在许多问题。(最近让我感到困惑的是,移位比按位运算符绑定得更紧密。)
[注 1]:我没有对此的引用,但我记得很多年前在The C++ Programming Language中读过它。我记得,关于赋值运算符是否自然并没有达成共识,但似乎比重载移位运算符更自然,使其与正常语义完全不同。