为什么在真实情况下带逗号的三元运算符只求一个表达式?


119

我目前正在使用C ++ Primer一书学习C ++,这本书中的练习之一是:

说明以下表达式的作用: someValue ? ++x, ++y : --x, --y

我们知道什么?我们知道,三元运算符的优先级高于逗号运算符。对于二元运算符,这很容易理解,但是对于三元运算符,我有点挣扎。对于二进制运算符,“具有更高的优先级”意味着我们可以在具有更高优先级的表达式周围使用括号,并且不会更改执行。

对于三元运算符,我将执行以下操作:

(someValue ? ++x, ++y : --x, --y)

有效地产生相同的代码,这无助于我理解编译器如何对代码进行分组。

但是,通过使用C ++编译器进行测试,我知道表达式可以编译,而且我不知道:运算符本身可以代表什么。因此,编译器似乎正确地解释了三元运算符。

然后,我通过两种方式执行该程序:

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

结果是:

11 10

另一方面,someValue = false它会打印:

9 9

为什么C ++编译器生成的代码只对三元运算符的真分支递增x,而对于三元运算符的假分支却使x和都递减y

我什至在这样的真实分支上加上括号:

someValue ? (++x, ++y) : --x, --y;

但它仍然导致11 10


5
“优先级”只是C ++中的一种新兴现象。直接查看语言语法并查看表达式的工作方式可能会更简单。
Kerrek SB

26
我们不太在乎这些原则。:-)您在这里必须提出的事实表明,该代码永远不会通过您的其他程序员的代码审查。这使得有关如何实际工作原理毫无用处。当然,除非您想参加混淆C竞赛
Bo Persson

5
@BoPersson没有像这样的例子可以学习,将来的审阅者将永远不会知道为什么他们应该从生产中拒绝这个例子。
Leushenko '17

8
@Leushenko-无论如何,警钟应该响起。多次递增同一条语句中的减量(ding,ding,ding!)。当您可以使用if-else(ding,ding,ding!)时的三元运算符。等等,那些逗号是可怕的逗号运算符吗?(丁,丁,丁!)与所有这些运算符一样,可以有优先权吗?(叮,叮,叮!)所以我们永远无法使用它。那么,为什么要浪费时间弄清楚它的作用(如果有的话)呢?
Bo Persson

4
次要:的名称?条件运算符。术语三元运算符仅表示具有三个操作数的运算符。条件运算符是三元运算符的一个示例,但是一种语言可以(理论上)具有多个三元运算符。
bta

Answers:


122

正如@Rakete在其出色的回答中所说,这很棘手。我想补充一点。

三元运算符必须具有以下形式:

逻辑或 ? 表达式 : 赋值表达式

因此,我们有以下映射:

  • someValue逻辑或表达式
  • ++x, ++y表达
  • ??? 是赋值表达式 --x, --y还是仅--x

实际上,这仅仅--x是因为赋值表达式不能解析为两个用逗号分隔的表达式(根据C ++的语法规则),因此--x, --y不能将其视为赋值表达式

这将导致三元(条件)表达式部分如下所示:

someValue?++x,++y:--x

它可以帮助提高可读性的缘故考虑++x,++y到计算的,如果括号(++x,++y); ?和之间包含的任何内容:将测序的条件。(我将在后面的文章中用括号括起来)。

并按以下顺序评估:

  1. someValue?
  2. (++x,++y)--x(取决于bool 1的结果)。

然后将此表达式视为逗号运算符的左子表达式,右子表达式为--y,如下所示:

(someValue?(++x,++y):--x), --y;

这意味着左侧是一个 舍弃值表达式,这意味着它肯定是被求值的,但随后我们对右侧求值并返回它。

当会发生什么someValuetrue

  1. (someValue?(++x,++y):--x)执行并递增xy成为1111
  2. 左边的表达式被丢弃(尽管增加的副作用仍然存在)
  3. 我们评估逗号运算符:的右侧--y,然后递减y10

要“修复”该行为,可以--x, --y用括号分组以将其转换为一个主表达式,该主表达式赋值表达式 * 的有效条目:

someValue?++x,++y:(--x, --y);

*这是一条很有趣的长链,连接了作业表达与主表达式:

赋值表达式 ---(可以包含)-> 条件表达式 -> 逻辑或表达式 -> 逻辑与表达式 -> 包含或表达式 -> 异或表达式 - -> 和表达式 -> 相等表达式 -> 关系表达式 -> 移位表达式 -> 加法表达式 -> 乘法表达式 -> pm表达式 ->转换表达式 -> 一元表达式 -> 后缀表达式 -> 主表达式


10
感谢您为阐明语法规则而付出的麻烦;这样做表明C ++的语法比大多数教科书都多。
sdenham

4
@sdenham:当人们问“面向表达式的语言”为什么很好(即何时{ ... }可以被视为表达式)时,我现在有一个答案=>避免引入必须以这种棘手的方式表现的逗号运算符。
Matthieu M.

您能给我一个链接来阅读有关assignment-expression链条的信息吗?
MiP

@MiP:我从标准本身中删除了它,可以在gram.expr
AndyG

88

哇,这很棘手。

编译器将您的表达式视为:

(someValue ? (++x, ++y) : --x), --y;

三元运算符需要 :,它在那种情况下不能独立存在,但是在此之后,没有理由为什么逗号应该属于错误的情况。

现在可能更有意义了,为什么要获得该输出。如果someValue是真的,那么++x++y--y得到执行,这不有效改变y,但增加了一个x

如果someValue为false,则将执行--x--y,并将它们减一。


42

为什么C ++编译器生成的代码只对三进制运算符的真实分支递增 x

您误解了发生了什么。真实分支将x和同时递增y。但是,y此后立即无条件递减。

这是怎么发生的:由于在C ++中条件运算符的优先级比逗号运算符的优先级高,因此编译器按以下方式解析表达式:

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

注意--y逗号后的“孤立” 。这就是导致y最初递增的递减的原因。

我什至在这样的真实分支上加上括号:

someValue ? (++x, ++y) : --x, --y;

您走在正确的道路上,但用括号括了一个错误的分支:您可以通过用括号括起来else分支来解决此问题,如下所示:

someValue ? ++x, ++y : (--x, --y);

演示(打印11 11)


5

您的问题是三元表达式的优先级确实没有比逗号高。实际上,不能仅仅通过优先级来准确地描述C ++,而恰恰是它破坏了三元运算符和逗号之间的相互作用。

a ? b++, c++ : d++

被视为:

a ? (b++, c++) : d++

(逗号的行为好像优先级更高)。另一方面,

a ? b++ : c++, d++

被视为:

(a ? b++ : c++), d++

三元运算符的优先级更高。


我认为这仍然在优先领域之内,因为中间行只有一个有效的解析,对吗?仍然是一个有用的示例
sudo rm -rf斜杠

2

答案(尽管有一些评论)被忽略了一点,就是在实际代码中始终使用条件运算符(设计意图?)作为将两个值之一分配给变量的快捷方式。

因此,更大的上下文是:

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

从表面上看这是荒谬的,所以犯罪是多种多样的:

  • 该语言允许作业中出现荒谬的副作用。
  • 编译器没有警告您您在做奇怪的事情。
  • 这本书似乎集中在“技巧”问题上。只能希望后面的答案是“此表达式的作用取决于一个奇怪的例子中的奇怪边缘情况,产生的副作用是没人期望的。永远不要这样做。”

1
分配两个变量之一是三元运算符的常见情况,但是在某些情况下,使用表达式形式if(例如,for循环中的增量表达式)很有用。在更大的范围内很可能是for (x = 0, y=0; x+y < 100; someValue?(++x, ++y) :( --x, --y))一个循环,可以修改xy独立。
马丁·邦纳

@MartinBonner我不敢相信,这个例子似乎很好地说明了bo-perrson的观点,正如他引用Tony Hoare的话。
Taryn
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.