C语言 是否强制使用短路逻辑运算符?评估顺序呢?

xam8gpfp  于 2023-10-16  发布在  其他
关注(0)|答案(7)|浏览(120)

ANSI标准是否 * 强制 * 在C或C中的逻辑运算符被短路?
我很困惑,因为我记得K&R的书说你的代码不应该依赖于这些被短路的操作,因为它们可能不会。有人能指出标准中哪里说逻辑运算总是短路的吗?我最感兴趣的是C
,一个C的答案也会很棒。
我还记得阅读过(不记得在哪里读过)求值顺序没有严格定义,所以你的代码不应该依赖或假设表达式中的函数将以特定的顺序执行:在语句结束时,所有引用的函数都将被调用,但编译器可以自由选择最有效的顺序。
标准是否指示此表达式的求值顺序?

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

lc8prwob1#

是的,在C和C标准中,运算符||&&都需要短路和求值顺序。
C
标准说(C标准中应该有一个等效的子句):

1.9.18

  • 在下列表达式的求值中 *
a && b
a || b
a ? b : c
a , b
  • 使用这些表达式中运算符的内置含义,在第一个表达式的求值之后有一个序列点(12)。*

在C++中有一个额外的陷阱:短路不适用于重载运算符||&&的类型。

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

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

vaj7vani

vaj7vani2#

短路求值和求值顺序是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节逻辑与运算符指出,
(4).与按位二进制&运算符不同,&&运算符保证从左到右求值;在对第一操作数求值之后存在序列点。如果第一个操作数比较等于0,则不计算第二个操作数。
类似地,第6.5.14节逻辑或运算符
(4)与按位|运营商||操作符保证从左到右求值;在对第一操作数求值之后存在序列点。如果第一个操作数比较不等于0,则不计算第二个操作数。
类似的措辞可以在C++标准check section 5.14 in this draft copy中找到。正如checkers在另一个答案中指出的那样,如果你重写&&或||,则当它成为常规函数调用时,必须对两个操作数进行求值。

y53ybaqx

y53ybaqx3#

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

C++标准在5.14/1中说:
&&运算符从左到右分组。操作数都隐式转换为bool类型(子句4)。如果两个操作数都为true,则结果为true,否则为false。与&,&&不同,保证从左到右求值:如果第一操作数为假,则不评估第二操作数。
5.15/1中:
的||操作员组从左到右。操作数都隐式转换为bool(子句4)。如果其中一个操作数为true,则返回true,否则返回false。不像|、||保证从左到右的求值;此外,如果第一操作数求值为真,则不对第二操作数求值。
旁边写着:
其结果是一个bool。第一个表达式的所有副作用,除了临时变量的破坏(12.2),都发生在第二个表达式被求值之前。
此外,1.9/18表示,
在每个表达式的求值中

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

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

gab6jxml

gab6jxml4#

来自K&R的经典歌曲:
C保证&&||是从左到右计算的-我们很快就会看到这种情况。

yacmzcpb

yacmzcpb5#

一定要非常非常小心。
对于基本类型,这些是快捷操作符。
但如果为自己的类或枚举类型定义这些运算符,它们就不是快捷方式。由于在这些不同的情况下它们的用法存在语义差异,因此建议您不要定义这些运算符。
对于基本类型的operator &&operator ||,求值顺序是从左到右(否则很难捷径:-)但是对于你定义的重载运算符,这些基本上是定义方法的语法糖,因此参数的求值顺序是未定义的。

uxh89sit

uxh89sit6#

你的问题归结为C++ operator precedence和结合性。基本上,在有多个运算符且没有括号的表达式中,编译器按照以下规则构造表达式树。
对于优先级,当你有像A op1 B op2 C这样的东西时,你可以把东西分组为(A op1 B) op2 CA op1 (B op2 C)。如果op1的优先级高于op2,则会得到第一个表达式。否则,你会得到第二个。
对于关联性,当你有像A op B op C这样的东西时,你可以再次将thin分组为(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,其中所有运算符都具有相同的优先级,我们可以形成的二元表达式树的数量由所谓的Catalan numbers给出。对于大的n,这些增长非常快。D

vom3gejh

vom3gejh7#

如果你信任维基百科:
[ &&|| ]在语义上不同于按位运算符&和|因为如果结果可以单独从左操作数确定,则它们永远不会计算右操作数
C(编程语言)

相关问题