【问题标题】:Evaluation of OR'd if conditions评估 OR'd if 条件
【发布时间】:2013-12-18 12:21:02
【问题描述】:

我有一个 if 语句,其主体中有一个语句,但条件由多个 OR 表达式组成。

在执行期间,如果 OR'd 条件中的第一个表达式为真,是否仍会计算第二个表达式??

即是使用表达式 OR'd 更好,还是让每个表达式形成 else 的单个条件,其中每个 else 的主体相同?

例如

if ((!x && y && z) || (x && !y && !z) || (x != y) {
    foo();

//vs

if (!x && y && z)
    foo();
else if (x && !y && !z)
    foo();
else if (x != y)
    foo();

【问题讨论】:

    标签: c if-statement conditional-statements evaluation


    【解决方案1】:

    不,如果 OR (AND) 的左侧计算结果为 true (false),则不计算右侧。它被称为短路,是 C 语言中一个众所周知的特性。

    您可以通过以下方式进行检查

    int f () {
        printf("f!\n");
        return 1;
    }
    
    int g () {
        printf("g!\n");
        return 0;
    }
    
    int main () {
        if(f() || g())
          printf("hey\n");
    }
    

    这会打印出来

     f!
     hey
    

    【讨论】:

      【解决方案2】:

      第一个代码“更好”,因为当使用 || 时,返回 true 的第一个评估将结束条件评估。如果您仍希望评估所有条款,请使用单个 |

      【讨论】:

      • 第二个 sn-p 代码不会有什么不同。我假设编译器无论如何都会优化它。顺便说一句:推荐使用按位运算符可能值得解释一下
      【解决方案3】:

      简答:
      C 使用 ||&& 逻辑运算符将所有 if 语句短路。没有办法解决这个问题。事实上:C has no eager operators.

      这背后的原因是,如果您希望在 1、2、3、... 案例中的任何一个案例为真时执行单个操作,那么如果其中一个案例为真,则无需评估所有案例是真实的。换句话说,短路评估更快。
      因此,您的第一个 sn-p 是更好的选择,前提是它不妨碍可读性。

      考虑以下(伪代码):

      if (is_alphabet_letter('a') || is_alphabet_letter('b') || is_alphabet_letter('c'))
          puts("Found letter of the roman alphabet");
      

      这将导致只有 1 次调用 is_alphabet_letter 函数,而不是 3 次。就生成的程序而言,上述 sn-p 的计算结果为:

      if (true /*|| jibberish, don't care*/)
          puts("...");
      

      而这个:

      if (is_alphabet_letter('a'))
         puts("");
      else  if (is_alphabet_letter('b')
         puts("");
      else  if (is_alphabet_letter('c')
         puts("");
      

      只是增加了混乱,并且很可能会被编译器优化为更像第一个语句。
      这同样适用于&&,顺便说一句:

      if (false && true && a_complex_function())
      

      永远不会导致调用a_complex_function,这很简单,因为第一个表达式的计算结果为false,因此检查任何剩余的表达式都是没有意义的。 C 继续前进,要么转到 else 块,要么转到下一条语句。
      这就是为什么在 if 块中选择表达式的顺序有时会产生影响:

      if (a_complex_function() && false)
      

      将始终调用该函数。有时,这可能是您想要的,但有时并非如此,因此考虑将更复杂的表达式移到 if 语句的末尾并没有什么坏处。

      【讨论】:

        【解决方案4】:

        这两种情况很可能会产生完全相同的机器代码,或者非常接近的代码。所以选择哪一个是编码风格的问题。

        我想说,如果操作很复杂,以至于它们不容易放在一行上,那么if-else if 是首选。否则,如果操作有些简单,可能更喜欢|| 版本。虽然这是相当主观的。

        关于这些运算符的内部工作原理的高级讨论如下。如果你对这些东西不是很感兴趣,你可以停止阅读这里!


        然而,有一些细节使这两个版本略有不同。在这两种情况下,每个单独的操作数都使用类型平衡隐式提升。但在 || 的情况下,发生了额外的隐式提升:

        (!x && y && z) 的结果与(x && !y && !z) 的结果相平衡。最后,结果是转换等级最大的操作数类型。

        这有关系吗?我看不出在这种特定情况下会如何。但是假设我们有这样的东西:

        uint8_t  x;
        uint8_t  y;
        uint32_t z;
        

        然后我们有一个表达式:

        if( (x && y) || (x && z) )
        
        // or
        
        if(x && y)
        {}
        else if(x && z)
        {}
        

        假设我们使用的是 8 位或 16 位 CPU,其中int 是 16 位。

        在 if-else 情况下,会发生以下情况:

        • x 和 y 都是从 uint8_t 提升到 int 的整数,因为它们是小整数类型。
        • 表达式(int)x && (int)y 是平衡的。 x && y 是根据 int 类型计算的,结果是 int 类型。
        • 在表达式x && z中,x是上面提升的整数,但不是z,它不是小整数类型,仍然是uint32_t
        • 表达式(int)x && (uint32_t)z 是平衡的,然后在uint32_t 类型上计算。结果是uint32_t 类型。
        • 因此,x && y 始终在 16 位变量上计算,x && z 始终在 32 位变量上计算。

        如果我们查看|| 版本,也会发生同样的事情。但是我们还有另一个额外的平衡:(uint16_t)first_result || (uint32_t)second_result。由于|| 运算符,这种平衡是强制执行的。这意味着如果x==truey==true,那么整个操作的结果将是uint32_t 类型。

        但在 if-else 版本中,x && y 类型始终为 uint16_t|| 版本在 x 类型和 z 类型之间引入了一种模糊的紧密耦合。

        编译器是否能够有效地优化|| 场景,我不知道。我希望它会,但是由于编译器无法对整个表达式的结果做出任何编译时决定,因为它不知道操作数是真还是假,它可能最终只是执行它在 32 位类型上。如果我们有一个 8 位或 16 位 CPU,这将是一个坏消息,这将非常低效地处理 32 位数字。

        【讨论】:

        • +1 提到促销和解释得很好:)
        【解决方案5】:

        不,双逻辑运算符(&& 和 ||)将一个接一个地评估每个参数,直到找到一个令人满意的结果。

        在 && 的情况下,所有的参数都会被计算,直到它找到一个 false,在这种情况下,表达式条件为 false,并且 if 代码将不会被计算。

        在 || 的情况下,将评估参数,直到找到一个 true,在这种情况下,表达式条件为 true,并且将评估 if 代码。

        如果您希望计算表达式的所有元素,请使用 & 和 |运营商。他们将创建一个变量,该变量将被表达式的所有元素进行 AND 或 OR 运算,并且仅在条件中使用变量的最终值。

        结果是一样的,但是使用 & 和 |,你确定你的条件的所有表达式都会被计算。

        【讨论】:

        • 使用& 作为条件是个坏主意。 2 & 1 == 0.
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-27
        • 1970-01-01
        • 2020-06-01
        • 2018-01-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多