为什么分配表达式的标准C ++语法看起来很奇怪

时间:2013-08-22 13:00:23

标签: c++ grammar

从C ++标准来看,赋值表达式的语法是这样的:

assignment-expression:
  conditional-expression
  logical-or-expression assignment-operator assignment-expression
  throw-expression

assignment-operator: one of
   = *= /= %= += -= >>= <<= &= ^= |=

请注意,“赋值运算符”的左侧是“逻辑或表达式”,即(4 || localvar1)= 5 ;是根据语法的有效赋值表达式。这对我来说没有意义。为什么他们选择“逻辑或表达式”而不是标识符或id_expression?

3 个答案:

答案 0 :(得分:2)

您的特定声明(4 || localvar1) = 5;无效(除非operator||超载),因为您无法指定5到4(4是 r-value )。您必须在左侧有一个 l-value (可以分配给它的东西),例如函数返回的引用。

例如,假设您有一些函数int& get_my_int(),它返回对整数的引用。然后,你可以这样做:

`get_my_int() = 5;`

这会将get_my_int()返回的整数设置为5。 就像在你的第一篇文章中一样,这必须是对整数的引用(而不是值);否则,上述声明将无法编译。

答案 1 :(得分:2)

语法有点复杂,但是如果你继续展开之前的定义,你会发现赋值表达式非常泛型并且几乎可以允许任何东西。虽然您引用的标准片段侧重于 logical-or-expression ,但如果您继续展开其定义,您会发现作业的左侧和右侧都可以几乎任何子表达式(虽然不是字面意思任何)。

之前指出的原因是赋值可以应用于枚举的任何 lvalue 表达式或类类型的基本类型或 rvalue 表达式(其中{{1总是一个成员)。许多表达式,在允许运算符重载的语言中,并且没有定义从运算符返回的类型,可以潜在地满足赋值的需要,并且语法必须允许所有这些用法。

标准中的不同规则稍后将限制可以从语法生成的哪些可能表达式实际上有效。

答案 2 :(得分:1)

关于赋值语句的C ++语法实际上有两个有趣的事情,它们都与有效性无关:

 (4 || localvar1) = 5;

由于括号,该表达式在语法上是有效的(直到类型检查)。 任何带括号的引用类型表达式在赋值运算符的左侧在语法上是正确的。 (并且,正如已经指出的那样,由于运算符重载,几乎任何涉及用户类型或函数的表达式都可以是引用类型。)

语法更有趣的是,它将赋值运算符的左优先级设置为低于几乎所有其他运算符,包括逻辑 - 或者,因此上述表达式在语义上等同于

4 || localvar1 = 5;

尽管许多读者会将上述内容解释为4 || (localvar1 = 5)(假设localvar1属于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)

这里,是一个逗号运算符,它显然需要比围绕它的任何一个分配更紧密地绑定。 (通过使用逗号运算符,C和C ++在此语法中仅是例外;在大多数语言中,,不被视为运算符。)此外,显然不希望将第一个赋值表达式解析为{{ 1}}。

任务操作员与右侧相关联的事实并不显着,但值得注意的是一个历史的好奇心。当Bjarne Stroustrup四处寻找操作员为I / O流重载时,他选择(first = p) = vec.begin()<<,因为虽然赋值运算符可能更自然[1],但赋值绑定到右边,并且流操作符必须绑定到左侧(>>必须是std::cout << a << b。但是,由于(std::cout << a) << b)绑定比赋值更紧密,因此使用流式运算符时会有许多问题(最近抓到我的是移位比按位运算符绑定得更紧。)

[注1]:我没有引用这个,但我记得多年前在 The C ++ Programming Language 中读过它。我记得,关于赋值运算符是自然的没有达成共识,但它似乎比重载移位运算符更自然地与它们的正常语义完全不同。