【问题标题】:Why does the ternary operator with commas evaluate only one expression in the true case?为什么带逗号的三元运算符在真实情况下只计算一个表达式?
【发布时间】:2018-05-12 08:13:48
【问题描述】:

我目前正在通过《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。

【问题讨论】:

  • “优先级”只是 C++ 中的一种新兴现象。直接看语言语法,看看表达式是如何工作的,可能会更简单。
  • 我们不太关心这些原则。 :-) 你必须在这里问这个事实表明代码永远不会通过你的程序员同事的代码审查。这使得关于这个实际上如何工作的知识变得不那么有用了。当然,除非你想参加Obfuscated C Contest。
  • @BoPersson 没有这样的例子可以学习,未来的审稿人永远不会知道为什么他们应该从生产中拒绝这个。
  • @Leushenko - 警钟无论如何都应该响起。同一语句中的多个增量和减量(叮、叮、叮!)。可以使用 if-else 的三元运算符(叮、叮、叮!)。等等,那些逗号是可怕的逗号运算符吗? (叮,叮,叮!)所有这些运算符,可以有一些优先权吗? (叮,叮,叮!)所以我们永远无法使用它。那为什么还要浪费时间弄清楚它的作用呢?
  • 次要问题:? 的名称是 条件运算符。术语三元运算符仅表示具有三个操作数的运算符。条件运算符是三元运算符的一个示例,但一种语言(理论上)可以有多个三元运算符。

标签: c++ language-lawyer operator-precedence conditional-operator


【解决方案1】:

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

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

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

所以我们有以下映射:

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

其实只有--x,因为一个赋值表达式不能被解析为两个用逗号隔开的表达式(根据C++的语法规则),所以--x, --y不能被当作赋值表达式。

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

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

为了便于阅读,考虑将++x,++y 计算为as-if 括号中的(++x,++y); ? 和 : 之间包含的任何内容都将在条件之后进行排序。 (我将在帖子的其余部分用括号括起来)。

并按此顺序评估:

  1. someValue?
  2. (++x,++y) 或 --x(取决于 boolresult of 1。)

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

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

这意味着左侧是一个丢弃值表达式,意味着它肯定被评估,但是我们评估右侧并返回它。

那么当someValue 是true 时会发生什么?

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

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

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

*这是一个相当有趣的长链,将 assignment-expression 连接回主表达式:

赋值表达式 ---(可以包括)--> 条件表达式 --> 逻辑或表达式 - -> 逻辑和表达式 --> 包含或表达式 --> 排除或表达式 --> 和表达式 --> 等式表达式 --> 关系表达式 --> 移位表达式 --> additive-expression --> multiplicative-expression --> pm-expression --> cast-expression -- > 一元表达式 --> 后缀表达式 --> 主表达式

【讨论】:

  • 感谢您不厌其烦地解开语法规则;这样做表明 C++ 的语法比您在大多数教科书中找到的要多。
  • @sdenham:当人们问为什么“面向表达式的语言”很好(即{ ... } 可以被视为表达式时),我现在有一个答案 => 这是为了避免引入逗号运算符的行为方式如此棘手。
  • 你能给我一个链接来阅读assignment-expression链吗?
  • @MiP:我从标准本身中提取出来的,你可以在gram.expr下找到它
【解决方案2】:

哇,这很棘手。

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

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

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

现在,为什么要获得该输出可能更有意义。如果someValue 为真,则++x、++y 和--y 会被执行,这不会有效地改变y,而是在x 上加一。

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

【讨论】:

    【解决方案3】:

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

    你误解了发生的事情。真正的分支增加x 和y。但是,y 之后会立即无条件地递减。

    这是如何发生的:因为the conditional operator has higher precedence than comma operator in C++,编译器解析表达式如下:

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

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

    我什至像这样在真分支周围加上括号:

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

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

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

    Demo (prints 11 11)

    【讨论】:

      【解决方案4】:

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

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

      被视为:

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

      (逗号表现得好像它具有更高的优先级)。另一方面,

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

      被视为:

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

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

      【讨论】:

      • 我认为这仍然在优先级范围内,因为中间行只有一个有效的解析,对吧?虽然仍然是一个有用的例子
      【解决方案5】:

      答案中被忽略的一点(尽管涉及 cmets)是条件运算符总是在实际代码中使用(设计意图?)作为将两个值之一分配给变量的快捷方式。

      所以,更大的上下文是:

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

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

      • 该语言允许在作业中产生荒谬的副作用。
      • 编译器没有警告您您正在做奇怪的事情。
      • 这本书似乎专注于“技巧”问题。只能希望后面的答案是“这个表达式的作用是依赖于人为示例中的奇怪边缘情况,从而产生没有人预料到的副作用。永远不要这样做。”

      【讨论】:

      • 分配两个变量之一是三元运算符的常见情况,但有 种情况下使用 if 的表达式形式很有用(例如, for 循环中的增量表达式)。更大的上下文很可能是for (x = 0, y=0; x+y &lt; 100; someValue?(++x, ++y) :( --x, --y)),它带有一个可以独立修改x 和y 的循环。
      • @MartinBonner 我不相信,这个例子似乎很好地说明了 bo-perrson 的观点,因为他引用了 Tony Hoare。
      猜你喜欢
      • 1970-01-01
      • 2013-11-22
      • 2014-08-03
      • 2021-10-17
      • 2011-07-21
      • 1970-01-01
      • 1970-01-01
      • 2010-12-21
      • 2020-10-16
      相关资源
      最近更新 更多