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

我现在正在学习 C + + ,这是一本关于 C + + 入门的书,书中的练习之一是:

解释下列表达式的作用: someValue ? ++x, ++y : --x, --y

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

For the ternary operator I would do:

(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 + + 编译器生成的代码对于三元运算符的 true 分支只递增 x,而对于三元运算符的 false 分支则同时递减 xy

我甚至在真分支前面加上括号,就像这样:

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

但它仍然导致 11 10

9205 次浏览

哇哦,真狡猾。

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

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

三元操作符需要一个 :,它不能在这个上下文中独立存在,但是在它之后,没有理由逗号应该属于假大小写。

现在可能更能解释为什么要得到这个输出。如果 someValue为 true,则执行 ++x++y--y,这不会有效地改变 y,而是将其添加到 x

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

为什么 C + + 编译器生成的代码对于三元运算符的真分支只增加 x

You misinterpreted what has happened. The true-branch increments both x and y. However, y is decremented immediately after that, unconditionally.

这是如何发生的: 自 在 C + + 中,条件运算符的优先级比逗号运算符高以来,编译器解析表达式如下:

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

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

我甚至在真分支前面加上括号,就像这样:

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

你在正确的路径上,但是你用圆括号括起了一个错误的分支: 你可以通过用圆括号括起 else 分支来解决这个问题,像这样:

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

Demo (prints 11 11)

正如 @ Rakete在他们出色的回答中所说,这是一个棘手的问题。

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

logical-or-expression ? 表情 : assignment-expression

我们有以下映射:

  • someValue: logical-or-expression
  • ++x, ++y: 表情
  • 赋值表达式赋值表达式 --x, --y还是只有 --x

事实上,它只是 --x,因为 赋值表达式赋值表达式不能被解析为两个表达式,用逗号分隔(根据 C + + 的语法规则) ,所以 --x, --y不能被视为 赋值表达式赋值表达式

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

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

考虑 ++x,++y被计算为 好像括号中的 (++x,++y),这可能有助于提高可读性; ?:之间的任何内容都将被排序为 after的条件。(我会在文章的其余部分用括号括起来)。

并按照以下顺序进行评估:

  1. someValue?
  2. (++x,++y) or --x (depending on boolresult of 1.)

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

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

这意味着左边是 丢弃值表达式,这意味着它肯定被计算了,但是我们计算了右边并返回它。

那么当 someValuetrue时会发生什么呢?

  1. (someValue?(++x,++y):--x)执行并递增 xy1111
  2. 丢弃左表达式(尽管增量的副作用仍然存在)
  3. 我们计算逗号运算符的右边: --y,然后将 y递减回 10

为了“修复”这种行为,您可以用括号将 --x, --y分组,将其转换为 主要表达式,而 assignment-expression * 的有效条目:

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

* 这是一个相当有趣的长链,连接 赋值表达式赋值表达式回到一个主要的表达式:

赋值表达 ——-(可由)—— > 条件表达式—— > 逻辑或表达式—— > 逻辑和表达—— > 包容性或表达性—— > 排他性或表达式—— > 和-表达—— > 平等-表达—— > 关系表达式—— > shift-expression—— > 加性表达式—— > 逻辑或表达式0—— > 逻辑或表达式1—— > 逻辑或表达式2—— > 逻辑或表达式3—— > 逻辑或表达式4—— > < em > 初级表达

在回答中有一点被忽略了(虽然涉及到评论) ,那就是条件运算符总是被使用(是设计出来的?)在实际代码中作为将两个值中的一个赋给变量的快捷方式。

因此,更大的背景是:

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

从表面上看是荒谬的,所以罪行是多方面的:

  • 这种语言允许在作业中产生荒谬的副作用。
  • 编译器没有警告你,你正在做奇怪的事情。
  • 这本书似乎专注于“陷阱”问题。我们只能希望后面的答案是: “这个表达式所做的是依赖于一个人为设计的例子中的奇怪边缘情况,以产生没有人预料到的副作用。永远不要这么做”

您的问题是,三元表达式的优先级并不比逗号高。事实上,C + + 不能简单地用优先级来准确地描述,而恰恰是三元运算符和逗号之间的交互作用导致了 C + + 的崩溃。

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

视为:

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

(逗号的优先级似乎更高)。另一方面,

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

视为:

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

三元运算符优先级较高。