"IF"参数的计算顺序是什么?

49
if(a && b)
{
  do something;
}

是否有可能从右到左(b -> a)评估参数?

如果"是",那么会影响评估顺序的因素是什么?

(我正在使用VS2008)


12
if( b && a )会为你完成。 - Prince John Wesley
尝试使用 if(b && a)。请尝试此链接:http://msdn.microsoft.com/en-us/library/2bxt6kc4.aspx - CloudyMarble
6个回答

87

C++中只有几个运算符可以保证评估顺序

  • operator && 先评估左操作数,如果逻辑值为 false 则避免评估右操作数。典型用法是例如 if (x > 0 && k/x < limit) ... 避免除以零的问题。

  • operator || 先评估左操作数,如果逻辑值为 true 则避免评估右操作数。例如 if (overwrite_files || confirm("File existing, overwrite?")) ... 当标志位 overwrite_files 被设置时不会询问确认。

  • operator , 先评估左操作数,然后无论如何都评估右操作数,并返回右操作数的值。这个运算符并不经常使用。请注意,在函数调用的参数之间的逗号不是逗号运算符,评估顺序也不能保证。

  • 三元运算符x?y:z先评估x,然后根据结果的逻辑值仅评估y或仅评估z

对于所有其他运算符,评估顺序未指定。

实际上,情况更糟,因为不仅没有规定顺序,而且表达式本身甚至没有“顺序”,例如在

std::cout << f() << g() << x(k(), h());

可能会按照 h-g-k-x-f 的顺序调用函数(这有点令人不安,因为 << 运算符的心理模型在某种程度上传达了顺序性的概念,但实际上只尊重结果放在流中的顺序而不是计算结果的顺序)。

显然,在表达式中的值依赖关系可能会引入一些顺序保证;例如,在上述表达式中,保证在调用 x(...) 之前将调用 k()h(),因为需要两者的返回值来调用 x(C++ 不是lazy)。

还要注意,对于预定义运算符,&&||, 的保证仅在有效。如果您为自己的类型重载这些运算符,则它们在该情况下将像普通函数调用一样,并且操作数的求值顺序将是未指定的。

C++17 以来的更改

C++17 引入了一些额外的特定保证,关于求值顺序(例如在左移运算符 << 中)。有关所有详细信息,请参见 https://dev59.com/yVkT5IYBdhLWcg3wf_pN#38501596


2
只是一个小细节,但值依赖项也会强制执行顺序。在像(a + b) * c这样的表达式中,必须在执行加法之前计算ab,并且必须在乘法之前执行加法。通常,这种影响是不可观察的,但如果运算符被重载,它可能是可见的。 - James Kanze
@JamesKanze:好的。我认为对于像C++这样的语言来说,这是显而易见的,但我还是加了一条注释。 - 6502

61
评估顺序由标准指定,并且是从左到右。最左边的表达式将始终首先使用 && 子句进行评估。
如果你想首先评估 b:
if(b && a)
{
  //do something
}

如果两个参数都是方法,并且您希望无论它们的结果如何都对它们进行评估:

bool rb = b();
bool ra = a();

if ( ra && rb )
{
  //do something
}

12
太令人宽慰了……这一次 C++ 的默认行为是正确的,而不是一个让人惊讶且晦涩难懂的行为。 - Aviad Rozenhek
3
@AviadRozenhek: 你的担心是正确的,因为确实有可能在求值表达式a之前先求值表达式b。然而,要发生这种情况,运算符必须不是预定义的&&逻辑与运算符,而是用户定义类型的重载运算符。在这种情况下,参数的求值顺序是未指定的(更糟糕的是,你甚至不能期望有实际的顺序,因为求值甚至可以被“交错”:一点点a,然后一些b,然后更多的a,然后再一些b的剩余部分,最后才调用自定义运算符)。 - 6502
如果使用 ||,情况会怎样?除非编译器进行了一些优化使其混乱,否则它应该保持不变,对吧 @6502? - mdw7326
2
@mdw7326 || 也是短路运算符 - 如果第一个条件为真,则不会评估第二个条件。这不是编译器优化,而是标准要求。 - Luchian Grigore
@LuchianGrigore 我理解了,我想我没有仔细措辞我的问题。使用 || 运算符时,表达式按顺序(从左到右)进行评估,直到所有表达式都被评估为真或找到一个为真的表达式,对吗? - mdw7326
如果您查看我的答案,您将看到C++中保证评估顺序的唯一运算符的详细描述,并且还包括&&||短路常见用法的示例。 - 6502

8
在这种情况下,由于你使用了&&a将始终首先被评估,因为结果用于确定是否短路表达式。
如果a返回false,则不允许评估b

1
除非重载operator&&,否则不可能实现。虽然这是可能的,但被认为是不良设计。 - dalle
1
我非常确定以下情况是正确的,但像这样做是否安全呢? Obj * obj; if(obj && obj->foo()) {...} 我认为它检查obj是否不为NULL,并且仅在obj不为NULL时才会检查obj->foo()的结果,对吗? - ruhig brauner

4
内置逻辑与运算符&&和内置逻辑或运算符||的第一个(左)参数的每个值计算和副作用都在第二个(右)参数的每个值计算和副作用之前被排序执行。
更详细的规则说明请参阅: 求值顺序

2
内置的&&运算符总是先评估其左操作数。例如:
if (a && b)
{
   //block of code
}

如果afalse,那么b将不会被评估。
如果你想要先评估b,只有在b为真时才评估a,那么只需要反过来写表达式即可:
if (b && a)
{
   //block of code
}

2

它将从左到右进行评估,并在可能的情况下短路评估(例如,如果a评估为false,则不会评估b)。

如果您关心它们被评估的顺序,您只需要在if语句中按所需的评估顺序指定它们即可。


这个问题是关于C++,而不是C#(尽管您可能正确,两者都受到C的启发,做了相同的事情)。 - Jonathan Leffler
哎呀!我想它们应该是一样的,就像你所说的。 - Dylan Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接