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

Joe*_*eda 137 c c++ operator-precedence short-circuiting logical-operators

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

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

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

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

if( functionA() && functionB() && functionC() ) cout<<"Hello world";
Run Code Online (Sandbox Code Playgroud)

Ale*_*x B 147

是的,运营商||以及&&C和C++标准都需要短路和评估顺序.

C++标准说(C标准中应该有一个等价的子句):

1.9.18

在评估以下表达式时

a && b
a || b
a ? b : c
a , b
Run Code Online (Sandbox Code Playgroud)

使用这些表达式中运算符的内置含义,在评估第一个表达式(12)之后有一个序列点.

在C++中有一个多余的陷阱:短路确实适用于重载操作类型||&&.

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

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

  • litb:如果不对它进行评估,就不可能将b传递给operator &&(a,b).并且没有办法撤消评估b,因为编译器不能保证没有副作用. (10认同)
  • 是的,这是合乎逻辑的.它作为运算符&&(a,b)的参数.这是它的实现,说明会发生什么. (4认同)
  • 不知道短路是否适用于过载的逻辑操作,这是正确的.您能否添加对标准或来源的引用?我不是不信任你,只是想了解更多. (3认同)
  • 我觉得这很伤心.我会想,如果我重新定义运算符&&和||*并且它们仍然是完全确定的*,编译器会检测到并保持评估的短路:毕竟,顺序是无关紧要的,它们保证没有副作用! (2认同)
  • @Joe:但是运算符的返回值和参数可能会从布尔值变为其他值.我曾经用三个值("真","假"和"未知")实现"特殊"逻辑.返回值是确定性的,但短路行为是不合适的. (2认同)
  • 标准的引用部分保证了从左到右的评估,但不是短路.该保证可在相关运营商的规范中找到. (2认同)

Pau*_*xon 70

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

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

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

   }
Run Code Online (Sandbox Code Playgroud)

6.5.13节C99规范的逻辑AND运算符(PDF链接)

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

同样,6.5.14Logical OR运算符

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

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


Joh*_*itb 18

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

if(ptr && ptr->value) { 
    ...
}
Run Code Online (Sandbox Code Playgroud)

逗号运算符相同:

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

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

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1
Run Code Online (Sandbox Code Playgroud)

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

// order of calls to a and b is unspecified!
function(a(), b());
Run Code Online (Sandbox Code Playgroud)

C++标准中说5.14/1:

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

并在5.15/1:

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

它说旁边的那些:

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

除此之外,1.9/18

在评估每个表达式时

  • a && b
  • a || b
  • a ? b : C
  • a , b

使用这些表达式(5.14,5.15,5.16,5.18)中运算符的内置含义,在评估第一个表达式后有一个序列点.


Joh*_*n T 10

直接来自古老的K&R:

Ç保证&&||评估左至右-我们将要看到的情况下,此事项.

  • K&R第2版p40."通过&&或||连接的表达式从左到右进行评估,一旦知道结果的真实性或错误性,评估就会停止.大多数C程序都依赖于这些属性." 我在书中的任何地方都找不到你引用的文字.这是非常过时的第1版吗?请澄清您在哪里找到此文本. (3认同)
  • 好吧,原来你在引用 [这个古老的教程](https://www.lysator.liu.se/c/bwk-tutor.html)。它是从 1974 年开始的,非常无关紧要。 (2认同)

Mar*_*ork 5

要非常非常小心。

对于基本类型,这些是快捷方式运算符。

但是,如果您为自己的类或枚举类型定义这些运算符,则它们不是捷径。由于在不同情况下它们的用法在语义上存在差异,因此建议您不要定义这些运算符。

对于operator &&operator ||类型,评估顺序从左到右(否则,捷径将很难:-),但是对于您定义的重载运算符,它们基本上是定义方法的语法糖,因此参数的评估顺序为未定义。

  • 您错误地使用了术语“ POD类型”。您可以为任何结构,类,联合或枚举(无论是否是POD)重载&amp;&amp;。如果双方都是数字类型或指针,则不能重载&amp;&amp;。 (3认同)
  • 运算符重载与类型是否为 POD 无关。要定义运算符函数,至少一个参数需要是类(或结构或联合)或枚举,或对其中之一的引用。成为 POD 意味着您可以在其上使用 memcpy。 (2认同)
  • 因此,您的意思是“基本类型”,但写了“ POD类型”吗? (2认同)