首页 文章

是否要求短路逻辑运营商?和评估顺序?

提问于
浏览
128

ANSI标准是否要求逻辑运算符在C或C中短路?

我很困惑,因为我记得K&R的书说你的代码不应该依赖于这些操作被短路,因为它们可能没有 . 有人可以指出标准中的哪个位置逻辑操作始终是短路的吗?我最感兴趣的是C,C的答案也很棒 .

我还记得读(不记得在哪里)评估顺序没有严格定义,所以你的代码不应该依赖或假设表达式中的函数将按特定的顺序执行:在语句的末尾所有引用的函数将被调用,但编译器可以自由选择最有效的顺序 .

标准是否表明该表达式的评估顺序?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";

7 回答

  • 0

    如果您信任维基百科:

    [&&和||]在语义上与逐位运算符&和|不同因为如果只能从左边确定结果,他们永远不会评估正确的操作数

    http://en.wikipedia.org/wiki/C_(programming_language)#Characteristics

  • 4

    是的,C和C标准中的操作员 ||&& 都需要短路和评估顺序 .

    C标准说(C标准中应该有一个等价的条款):

    1.9.18在评估以下表达式a && b
    a || b
    一个 ? b:c
    a,b
    使用这些表达式中运算符的内置含义,在评估第一个表达式(12)之后有一个序列点 .

    在C中有一个额外的陷阱:短路确实 NOT 适用于超载运算符 ||&& 的类型 .

    脚注12:本段中指示的运算符是内置运算符,如第5节所述 . 当其中一个运算符在有效上下文中重载(第13节),从而指定用户定义的运算符函数时,表达式指定一个函数调用,操作数形成一个参数列表,它们之间没有隐含的序列点 .

    除非您有非常具体的要求,否则通常不建议在C中重载这些运算符 . 您可以这样做,但它可能会破坏其他人的代码中的预期行为,特别是如果通过实例化类型重载这些运算符的模板间接使用这些运算符 .

  • 11

    短路评估和评估顺序是C和C中的强制语义标准 .

    如果不是这样,那么这样的代码就不是常见的习语

    char* pChar = 0;
       // some actions which may or may not set pChar to something
       if ((pChar != 0) && (*pChar != '\0')) {
          // do something useful
    
       }
    

    C99规范(PDF link)的第 6.5.13 Logical AND operator 节说

    (4) . 与按位二进制和运算符不同,&&运算符保证从左到右的评估;在评估第一个操作数后有一个序列点 . 如果第一个操作数比较等于0,则不计算第二个操作数 .

    同样,第 6.5.14 Logical OR operator 节说

    (4)与按位|不同运算符,||运营商保证从左到右的评估;在评估第一个操作数后有一个序列点 . 如果第一个操作数比较不等于0,则不计算第二个操作数 .

    类似的措辞可以在C标准中找到,check section 5.14 in this draft copy . 正如跳棋在另一个答案中指出的那样,如果你重写&&或||,那么必须对两个操作数进行评估,因为它变成了常规函数调用 .

  • 136

    是的,它要求(评估顺序和短路) . 在您的示例中,如果所有函数都返回true,则调用的顺序严格来自functionA,然后是functionB,然后是functionC . 用于此类似

    if(ptr && ptr->value) { 
        ...
    }
    

    逗号运算符相同:

    // calls a, then b and evaluates to the value returned by b
    // which is used to initialize c
    int c = (a(), b());
    

    &&||, 的左右操作数之间以及 ?: (条件运算符)的第一个和第二个/第三个操作数之间的一个是"sequence point" . 在此之前完全评估任何副作用 . 所以,这是安全的:

    int a = 0;
    int b = (a++, a); // b initialized with 1, and a is 1
    

    请注意,不要将逗号运算符与用于分隔事物的语法逗号混淆:

    // order of calls to a and b is unspecified!
    function(a(), b());
    

    C标准在_1125996中说:

    &&运算符组从左到右 . 操作数都隐式转换为bool类型(第4节) . 如果两个操作数都为真,则结果为true,否则为false . 与&不同,&&保证从左到右的评估:如果第一个操作数为假,则不评估第二个操作数 .

    并在 5.15/1

    ||操作员组从左到右 . 操作数都隐式转换为bool(第4节) . 如果其任一操作数为true,则返回true,否则返回false . 与|,||不同保证从左到右的评估;此外,如果第一个操作数的计算结果为true,则不计算第二个操作数 .

    它说两者都有旁边那些:

    结果是一个布尔 . 除了临时破坏之外,第一个表达式的所有副作用(12.2)发生在评估第二个表达式之前 .

    除此之外, 1.9/18

    在评估每个表达式a && b a ||时b? b:C a,b使用这些表达式中的运算符的内置含义(5.14,5.15,5.16,5.18),在评估第一个表达式后有一个序列点 .

  • 16

    直接来自古老的K&R:

    C保证&&和||从左到右进行评估 - 我们很快就会看到重要的案例 .

  • 0

    要非常小心 .

    对于POD类型,这些是快捷运算符 .

    但是如果你为自己的类定义这些运算符,它们就不是捷径 . 由于在这些不同情况下它们的使用存在语义差异,因此建议您不要定义这些运算符 .

    对于运算符&&和运算符||对于POD类型,评估顺序是从左到右(否则短切会很难:-)但是对于您定义的重载运算符,这些基本上是定义方法的语法糖,因此参数的评估顺序是不确定的 .

  • 68

    你的问题归结为C++ operator precedence和关联性 . 基本上,在具有多个运算符且没有括号的表达式中,编译器通过遵循这些规则来构造表达式树 .

    对于优先级,当您有 A op1 B op2 C 之类的内容时,可以将事物分组为 (A op1 B) op2 CA op1 (B op2 C) . 如果 op1 的优先级高于 op2 ,那么'll get the first expression. Otherwise, you'将获得第二个优先级 .

    对于关联性,当你有 A op B op C 之类的东西时,你可以再次将这些组分为 (A op B) op CA op (B op C) . 如果 op 已离开关联,我们最终会得到第一个表达式 . 如果它具有正确的关联性,我们最终得到第二个 . 这也适用于具有相同优先级的操作员 .

    在这种特殊情况下, && 的优先级高于 || ,因此表达式将被计算为 (a != "" && it == seqMap.end()) || isEven .

    订单本身在表达式树形式上是"left-to-right" . 所以我们首先评估 a != "" && it == seqMap.end() . 如果确实整个表达式都是真的,否则我们转到 isEven . 当然,该过程在左子表达式内递归地重复 .


    有趣的花絮,但优先的概念源于数学符号 . 同样的事情发生在 a*b + c 中,其中 * 的优先级高于 + .

    更有趣/模糊,对于不明显强调的表达式 A1 op1 A2 op2 ... opn-1 An ,其中所有运算符具有相同的优先级,我们可以形成的二进制表达式树的数量由所谓的Catalan numbers给出 . 对于大型 n ,这些增长速度非常快 . d

相关问题