【问题标题】:Why does "++x || ++y && ++z" calculate "++x" first, even though operator "&&" has higher precedence than "||"为什么“++x || ++y && ++z”首先计算“++x”,即使运算符“&&”的优先级高于“||”
【发布时间】:2010-09-13 12:28:44
【问题描述】:

为什么++x || ++y && ++z 会先计算++x,即使运算符&& 的优先级高于||

【问题讨论】:

  • 我总是惊讶于有多少问题与此相似。人们是否渴望编写这样的代码?
  • 呃!你为什么在乎?重做您的代码,以免混淆。
  • @gnibbler:你错了。 ||&& 引入了序列点,因此顺序是明确定义的。甚至对所有三个增量都使用相同的变量也是有效的。
  • ...这就是为什么您应该始终使用括号,即使运算符按您希望它们的顺序绑定 - 使您的意图明确并避免只是完全错误。 ;-)
  • 我永远不会明白为什么愚蠢的问题会得到这么多赞的答案......

标签: c operator-precedence


【解决方案1】:

嗯?

如果您说&& 的绑定比|| (which is true) 更紧密,则表达式相当于

++x || (++y && ++z)

由于||short-circuits,需要先计算左侧。

如果你的意思是它应该等价于

(++x || ++y) && ++z

同样如此,因为&& 也会短路,这意味着需要首先评估||,这反过来又使得首先评估++x

【讨论】:

    【解决方案2】:

    由于&&的优先级高于||,这意味着你的表达式相当于:

    ++x || (++y && ++z)
    

    因此,在程序甚至开始计算 && 的结果之前,它必须计算 ++x 以确定是否应该计算 || 的右手操作数(逻辑二元运算符是“短路",这意味着如果左侧足以确定结果,则它们不会评估右侧)。这里没有任何可疑或特定于编译器的内容。此表达式的行为完全由 C 标准指定,并且在任何编译器上都是相同的。如果 xyz 都是同一个变量,它甚至会起作用,因为 ||&& 引入了序列点。

    【讨论】:

    • 谢谢jk。实际上我只是改变了我的整个答案,因为我的优先级错误,但希望新版本至少一样详细和准确。
    • R..,我很惊讶你一开始就弄错了(我希望你做得更好!),但新的答案就在 =)
    【解决方案3】:

    UnwindR 和其他人已经解释了真正发生的事情。所以让我补充一下:

    你问题的前提是错误的。 && 具有较高优先级这一事实并不意味着围绕它的操作数必须在表达式中具有较低优先级的任何操作数之前计算。即使||&& 的特殊情况短路也不一定如此。

    例如,考虑a=b+c+d*e* 的优先级高于 +,但这并不意味着必须在 b+c 之前评估 d*e。它只是意味着在我们将乘积作为一个整体添加到表达式之前必须对其进行评估。编译器可以将此表达式评估为temp1=d*etemp2=b+ca=temp1+temp2,或者它可以评估temp1=b+ctemp2=d*ea=temp1+temp2。两者都同样有效。

    由于||&& 的短路行为,对评估顺序施加了一些额外的限制。


    附带说明:通常我会避免编写这样的代码。我可以很容易地看到另一个试图阅读这段代码的程序员对增量何时会发生以及何时不会发生感到困惑。好吧,也许如果你使用真正的变量名,它看起来就不会那么奇怪了。

    我偶尔会依靠短路来防止副作用。喜欢

    if (!eof() && readNextInt()>0) 
    

    如果我们已经在文件末尾,我依靠短路来防止读取,或者

    if (confirmDelete==YES && deleteEntry()!=-1) 
    

    我依靠第一个测试来短路 false,所以我不应该在不应该的时候进行删除。但是这些例子对我来说似乎很简单,我希望任何有能力的程序员都会看到我在做什么。但是当这些例子变得神秘时,我认为它需要被打破。考虑

    if (customerType==RETAIL || lastDepositAmount()>100.00)
    

    如果lastDepositAmount() 有副作用,那么如果customerType 是零售的,则这种副作用永远不会发生。我认为这对读者来说不一定是显而易见的。 (部分是因为函数名称暗示它正在检索数据而不执行任何更新,部分是因为客户类型和存款金额之间没有明显的关系——这听起来像是两个独立的东西。)不可否认,这是主观。但是,如果有疑问,请选择简单明了而不是微不足道的性能改进。始终选择简单和清晰,而不是 “嘿,这是使用晦涩的功能的一种很酷的方式,任何阅读本文的人都会对我必须多么聪明才能理解该语言才能做到这一点印象深刻”

    【讨论】:

      【解决方案4】:

      这里有可怕的错误答案。

      此表达式的计算顺序与实现无关。定义明确!

      由于&& 的绑定比|| 更高,所以这个表达式等价于++x || (++y && ++z)。此外,&&||短路,因此如果表达式的左侧足以确定值,则右侧是 never 已评估。

      什么时候会这样?好吧,我们知道False && a 总是解析为False,无论a 的值如何,特别是即使a 不是单个值而是一个复杂的表达式。因此我们根本不评估a。同样,True || a 始终解析为 True

      这导致在此示例中的评估顺序非常明确:first ++xthen(如果有的话)++ythen(再次:如果有的话)++z.

      【讨论】:

      • 只是对您回答的风格说明:起初我对您在“何时是这种情况?”开头的段落中使用“x”感到困惑。因为“x”也是示例的一部分,所以我以为您指的是同一个“x”。不得不读两遍。
      【解决方案5】:

      运算符使用波兰树数据结构进行评估,如图所示,这是一种深度优先评估算法。 让我们为这些操作命名: A = ++x B = ++y C = ++z D = B && C E = C || D 评估顺序:A、B、C、D、E。 当然,C有一个优化,如果A为真,则E为真,不评价B C和D。同理,如果B为假,则不评价C,优化评价速度。

      【讨论】:

      • 谢谢,我也喜欢你的回答。理解波兰树和优化是理解问题答案的关键。这就是我制作图表的原因。请注意,它并不完美,因为 ++ 是树的一个节点,而 x、y 或 z 是给定操作的子节点,但我牺牲了 100% 的正确性以确保我们谈论的是 ++x 而不是 x++。
      • 首先,说一般的算子是根据某棵树来求值的,这是不正确的。实际上,运算符评估中几乎没有顺序,除非它是 special 运算符。 &&||, 这样的运算符实际上是特殊的。其次,评估短路的事实不是优化(即它不是编译器优化),它是运算符 &&|| 的原生属性。
      • 首先,运算符之间存在优先级关系,例如 X + Y * Z 就是一个很好的例子。 Y * Z 将始终在 + 之前计算,因为 * 的优先级高于 +。所以,你错了,算子评估是有顺序的。
      • 其次,您始终可以使用波兰树查看这些订单。我不确定这是否在编译器中实现,但实际上我可以证明波兰树在组织运算符方面是准确的。如果您有兴趣,您可以提出有关此问题的问题,如果您需要证明,请在此处的评论中添加指向您问题的链接。
      • 第三,&&运算符的第二个操作数如果第一个为false则不考虑,||的第二个操作数当第一个为真时,将不考虑运算符。这是一种有用的优化方法,因为当操作数是一个复杂度很高的函数时,通常最好将其设为第二个操作数,此外,最好将操作数从不可能到可能的 && 排序,并将操作符从可能排序对 || 来说是不可能的。正如你所看到的,有一些优化选项,你也错了。
      【解决方案6】:

      ++ 具有最高优先级,因为它的 pre 递增。这意味着此操作的结果值将传递给二元运算符。然而有趣的是 ++x or'ed 与其余部分的短路。如果 ++x 为真,那么其余的可能根本不做。

      【讨论】:

        【解决方案7】:

        优先级和评估顺序不是一回事,尤其是在这种情况下。所有优先级都告诉你表达式应该被解析为

        ++x || (++y && ++z)
        

        IOW,这意味着被 OR'd 在一起的表达式是 ++x(++y && ++z)。它确实意味着(++y && ++z)将在++x之前评估

        对于逻辑运算符 ||&&,求值总是从左到右(Online C Standard,第 6.5.13 和 6.5.14 节)。对于任何表达式

        a || b
        

        a 将被完全评估;除非a 的结果为0,否则b 不会被评估(想想a 代表++xb 代表++y && ++z)。同样,对于任何表达式

        a && b 
        

        a 将被完全评估;除非a 的结果不为0,否则不会评估b

        因此,对于您的情况,首先评估表达式 ++x。如果结果为 0,只有才会评估 ++y && ++z

        ||&& 的特殊之处在于保证评估顺序是从左到右。对于按位或算术运算符,情况并非如此。例如,在表达式中

        ++x + ++y * ++z
        

        表达式++x++y++z 可以以任何顺序进行计算;所有优先级都告诉您(y+1) * (z+1) 的结果将被添加到x+1 的结果中。

        【讨论】:

          【解决方案8】:

          前两个答案很好地解释了它...我要指出 ||是“否则”。

          如果左侧为真,则右侧不会被触及。如果右侧为假,则 && (AndAlso) 的左侧不会被触及。

          如果您希望双方都增加,请使用 |和&。

          http://msdn.microsoft.com/en-us/library/2h9cz2eb(v=VS.80).aspx

          一个逻辑操作被称为 如果编译的代码短路 可以绕过一个评价 表达式取决于结果 另一种表达方式。如果结果 评估的第一个表达式 决定最终结果 操作,无需 评估第二个表达式, 因为它不能改变决赛 结果。短路可以改善 绕过表达式时的性能 是复杂的,或者如果它涉及 过程调用。

          MSDN 有表格显示部件如何“(未评估)”。

          如果第 1 部分预先确定了结果,为什么还要涉及第 2 部分?

          【讨论】:

            【解决方案9】:

            自从我使用 C 以来已经有很长时间了,但如果我没记错的话,++ 是一个一元运算符,它总是先于二元运算符。仔细想想就明白了。

            【讨论】:

              【解决方案10】:

              如果您的操作对顺序敏感,则不应依赖感知的运算符优先级,因为不同的编译器(以及同一编译器的不同版本)可能会以不同的方式实现优先级。

              无论哪种方式 b 定义 ++x 意味着 x 必须在使用之前递增,因此您会期望它在执行中被赋予高优先级。

              【讨论】:

              • 无关;此示例中没有任何实现定义。
              • @R:既然你对每个人关于这件事的帖子发表了评论并投了反对票,你也许可以写下你自己的答案并解释......
              • @Cedric:除了这里有很多错误的答案,其中一些已经更正,R.的答案是一个好答案。
              • @David:我认为我的评论是在 R. 的回答之前(或者至少当时我没有看到)。
              【解决方案11】:

              不不不

              if (++x || ++y && ++z) { /* ... */ }
              

              是的

              ++x;
              ++y;
              ++z;
              if (x || (y && z)) { /* ... */ }
              

              编辑在同行投诉后:)

              是的!

              if (is_data_valid(x, y, z)) process_data(&x, &y, &z);
              

              【讨论】:

              • 那些代码不做同样的事情!它们等效。
              • 您的回答仍然具有误导性。虽然原始代码肯定是不行的,但类似的问题也会出现在代码中,而这些代码又是完全合理的。
              • 是的,原始代码很糟糕,但在原始情况下,在增量 ++y 和 ++zz 后 x 的任何非 0 值都不会被执行。即使代码写得不好,也并不意味着您可以用做其他事情的代码替换它。
              猜你喜欢
              • 2015-09-06
              • 2016-12-28
              • 2016-12-27
              • 1970-01-01
              • 2016-04-04
              • 1970-01-01
              • 2011-08-30
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多