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

我很困惑,因为我记得K&R书中说您的代码不应该依赖于这些运算符是否简短电路,因为他们可能没有。有人可以指出逻辑运算符在标准中总是短路的地方吗?我对C ++最为感兴趣,对C的回答也很好。

我还记得阅读(不记得在哪里)评估顺序没有严格定义,因此您的代码不应依赖或假定表达式中的函数将按特定顺序执行:在语句结束时,将调用所有引用的函数,但是编译器可以自由选择最有效的顺序。

该标准是否表示​​该表达式的求值顺序?

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


评论

细心:POD类型确实如此。但是,如果您重载了运算符&&或运算符||,对于特定的课程,这些不是我重复的NOT快捷方式。这就是为什么建议您不要为自己的类定义这些运算符的原因。
不久前,当我创建一个将执行一些基本布尔代数运算的类时,我重新定义了这些运算符。可能应该贴警告警告“这会破坏短路和左右评估!”万一我忘记了这一点。还重载* / +并使它们成为同义词:-)

在if块中进行函数调用不是一种好的编程习惯。始终声明一个变量,该变量保存方法的返回值,并在if块中使用它。

@SRChaitanya这是不正确的。您随意描述为不良实践的情况一直在发生,尤其是使用返回布尔值的函数,如此处所示。

#1 楼

是的,在C和C ++标准中,运算符||&&都需要短路和评估顺序。

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


1.9.18

在对以下表达式的求值中

a && b
a || b
a ? b : c
a , b


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


在C ++中有一个额外的陷阱:短路不适用于类型脚注12:如第5节所述,本段中所示的运算符是内置运算符。当这些运算符之一被过载时(子句) 13)在有效上下文中,从而指定一个用户定义的运算符函数,该表达式指定一个函数调用,并且操作数形成一个参数列表,而没有隐含的序列点在它们之间。


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

评论


不知道短路是否不适用于过载的逻辑操作,这就是证明。您能否添加对标准的引用或来源?我并不是不信任您,只是想了解更多有关此的信息。

–乔·皮内达(Joe Pineda)
09年10月10日在0:40

是的,这是合乎逻辑的。它充当operator &&(a,b)的参数。它的实现说明发生了什么。

– Johannes Schaub-小人
09年10月10日在0:43

litb:如果不进行评估,就不可能将b传递给运算符&&(a,b)。而且,无法撤消对b的求值,因为编译器无法保证没有副作用。

– jmucchiello
09年10月10日在0:51

我感到很难过。我本以为应该重新定义运算符&&和||。而且它们仍然是完全确定性的,编译器将检测到该问题并将其评估短路:毕竟,顺序是无关紧要的,并且它们保证没有副作用!

–乔·皮内达(Joe Pineda)
09年10月10日在4:02

@Joe:但是运算符的返回值和参数可能从布尔值更改为其他值。我曾经用三个值(“ true”,“ false”和“ unknown”)实现“特殊”逻辑。返回值是确定性的,但短路行为不合适。

– Alex B
09年10月10日在7:41

#2 楼

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

如果不是这样,则这样的代码将不是常见的习惯用法

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

   }


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


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



同样,第6.5.14节逻辑OR运算符表示


(4)与按位|运算符,||
运算符保证从左到右
求值;在第一个
操作数的求值之后有一个序列点
。如果第一个操作数比较
不等于0,则第二个操作数不求值。


类似的措辞可以在C ++标准中找到,请参阅第5.14节。该草稿副本。正如检查人员在另一个答案中指出的那样,如果您覆盖&&或||,则必须对两个操作数求值,因为它会成为常规函数调用。

评论


啊,我在找什么!好的,因此评估顺序和短路均符合ANSI-C 99!我非常希望看到ANSI-C ++的等效参考,尽管我几乎99%都必须相同。

–乔·皮内达(Joe Pineda)
09年10月10日在0:38

很难找到一个很好的C ++标准免费链接,并且已经链接到我通过谷歌搜索找到的草稿副本。

– Paul Dixon
09年10月10日在0:44

对于POD类型为True。但是,如果您重载了运算符&&或运算符||,这些不是捷径。

–马丁·约克
09年10月10日在1:58

是的,有趣的是,对于布尔来说,您始终可以保证评估顺序和短路行为。因为您不能使两种内置类型的operator &&重载。您需要至少一个用户定义类型的操作数来使其表现不同。

– Johannes Schaub-小人
09年10月10日在2:33

我希望我可以接受Checkers和这个答案。由于我对C ++最为感兴趣,因此我接受另一种,尽管必须承认这也是极好的!非常感谢你!

–乔·皮内达(Joe Pineda)
09年10月10日在4:06

#3 楼

是的,它要求这样做(评估顺序和短路)。在您的示例中,如果所有函数都返回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()); 


在左边和左边之间说一个&&||,的右操作数以及?:的第一和第二/第三操作数之间(条件运算符)是一个“序列点”。在此之前,将完全评估任何副作用。因此,这是安全的:

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());


C43标准在5.14/1中说:


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


并且在5.15/1中:


||操作员组从左到右。操作数都隐式转换为布尔值(第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)中运算符的内在含义,在第一个表达式求值后会有一个序列点。


#4 楼

直接从良好的旧K&R:


C保证&&||从左到右进行求值-我们很快就会发现这很重要的情况。


评论


K&R第二版p40。 “由&&或||连接的表达式从左到右求值,并且一旦知道结果的真假就立即停止求值。大多数C程序都依赖于这些属性。”我在书的任何地方都找不到您引用的文字。这是从过时的第一版中摘录的吗?请说明您在何处找到此文字。

–伦丁
17-4-27在6:55



好吧,事实证明您在引用这个古老的教程。它是从1974年开始的,与当时无关。

–伦丁
17年4月27日在6:59

#5 楼

请非常小心。

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

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

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

评论


操作符重载与POD类型无关。要定义运算符功能,至少一个参数需要是一个类(或struct或union)或一个枚举,或对其中之一的引用。成为POD意味着您可以在其上使用memcpy。

–德里克·勒德贝特(Derek Ledbetter)
09年3月12日在21:45

这就是我的意思。如果您为类重载&&,则实际上只是一个方法调用。因此,您不能依赖参数评估的顺序。显然,您不能为POD类型重载&&。

–马丁·约克
09年3月12日在22:09

您错误地使用了术语“ POD类型”。您可以为任何结构,类,联合或枚举(无论是否是POD)重载&&。如果双方都是数字类型或指针,则不能重载&&。

–德里克·勒德贝特(Derek Ledbetter)
09年3月12日在23:02

我将POD用作(char / int / float等)而不是集成的POD(这就是您在说的),通常被单独地或更明确地引用,因为它不是内置类型。

–马丁·约克
09年3月13日在0:12

所以您的意思是“基本类型”,但写了“ POD类型”吗?

–Öö Tiib
15年2月18日在6:43

#6 楼

您的问题归结为C ++运算符的优先级和关联性。基本上,在具有多个运算符且没有括号的表达式中,编译器将遵循以下规则来构造表达式树。

对于优先级,当您使用A op1 B op2 C之类的东西时,可以将其分组为(A op1 B) op2 CA op1 (B op2 C)。如果op1的优先级高于op2,则将获得第一个表达式。否则,您将获得第二个。

对于关联性,当您使用类似A op B op C的东西时,可以再次将薄层分组为(A op B) op CA op (B op C)。如果op离开了关联性,我们将得出第一个表达式。如果它具有正确的关联性,我们将得出第二个结论。这同样适用于相同优先级的运算符。在这种情况下,&&的优先级高于||,因此表达式将被评估为(a != "" && it == seqMap.end()) || isEven

顺序本身在表达式树形式上是“从左到右”的。因此,我们首先评估a != "" && it == seqMap.end()。如果为真,则整个表达式为真,否则转到isEven。该过程在课程的左子表达式内递归地重复。


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

对于一个无括号的表达式A1 op1 A2 op2 ... opn-1 An,所有运算符具有相同的优先级,二进制表达式树的数目可能的形式由所谓的加泰罗尼亚语数字给出。对于较大的n,这些增长非常快。

评论


所有这些都是正确的,但它与运算符的优先级和关联性有关,而与评估顺序和简短性无关。那是不同的东西。

–托马斯·帕德隆·麦卡锡
19年11月17日在5:44

#7 楼

如果您信任维基百科:


[&&||]在语义上不同于按位运算符&和| |。因为如果只能从左边确定结果,它们将永远不会评估右边的操作数



评论


当我们有了标准时,为什么还要信任维基!

–马丁·约克
2009年3月10日,3:46

如果您信任维基百科,则“维基百科不是可靠的资源”。

–user207421
17年6月14日在22:18

就目前而言,这是正确的,但不完整,因为C ++中的重载运算符不会短路。

–托马斯·帕德隆·麦卡锡
19年11月17日在5:35