为什么当i为值为1的int时,"if (i++ && (i == 1))"会返回false?

45
{
    int i = 1;
    if (i++ && (i == 1))
        printf("Yes\n");
    else
        printf("No\n");
}
依据我的理解,在if条件语句中,首先会对表达式(i==1)进行求值,其应该返回1,然后将其与i的值即1逻辑上做与运算,所以表达式应该返回1 && 1 == 1,但实际上执行了else部分。请问有人能解释一下为什么会执行else部分吗?

2
不是问题,但是你不需要在 i == 1 周围加上括号。 - Pete Becker
26
看起来你可能期望 i == 1 先被评估,因为你有一些期望括号中的事情先发生。但实际上不是这样的;括号与评估顺序无关,它们只控制参数分组。 - user2357112
9
@Pete Becker:是的,你需要这样做,这样才能让读者清楚地理解你的意图,并确保编译器执行你真正想要的操作。虽然在这种情况下这并不真正适用:如果这段片段实际上是生产代码,那么作者就不应该再工作了。 - jamesqf
有点相关:您还可以使用单个和符号(&)。它将检查第一个参数,如果它为 false(或 _false-ly_),它将跳过检查下一个参数,因为整个参数已经是 false - Gideon
2
@Gideon 使用 & 而不是 && 不会强制操作数的左侧先进行求值:在两个操作数之间,使用 & 时不存在序列点。使用 & 时,右侧可能会先进行求值。OP 在询问 && 是正确的。使用 & 会导致未定义行为。请参见答案 - chux - Reinstate Monica
显示剩余3条评论
5个回答

92

在C语言中,&&运算符的左侧和右侧有一个序列点,而且必须在右侧被评估之前执行并完成增量。因此,i++(相当于i++ != 0)被执行并完成增量(表达式评估为true),所以在右侧被评估时,i == 2,因此整个表达式为false,你会看到打印出'No'。如果&&运算符的左侧评估为false(0),则由于&&运算符的“短路”属性,右侧不会被评估。

只有少数运算符具有LHS和RHS之间具有序列点的属性:&&||,(作为运算符,而不是在参数列表中作为分隔符)- 还有? :,它不是二元运算符,但在条件评估后和?后面的表达式或:后面的表达式之前有一个序列点被评估(其中一个被评估,但不是两者都被评估)。 &&||运算符是唯一具有“短路”属性的运算符。当LHS评估为真时,仅评估&&的RHS;当LHS评估为假时,仅评估||的RHS。

序列点澄清

我不会存在 我不存在正确地断言

C11标准并没有取消序列点,只有C++11标准这样做了。

C++11(ISO/IEC 14882:2011)说:

1.9 程序执行

¶13 Sequenced before 是单个线程(1.10)执行的评估之间的一种非对称、传递性、成对关系,它在这些评估之间引入了一个偏序关系。给定任意两个评估 AB,如果 AB 之前被排序,则 A 的执行应该在 B 的执行之前。如果 A 不在 B 之前排序,且 B 不在 A 之前排序,则 ABunsequenced。(注意:未排序评估的执行可能重叠。—注释结束)当 AB 之前排序或者 BA 之前排序时,评估 ABindeterminately sequenced,但是不确定哪个先执行。(注意:不确定顺序的评估不能重叠,但是任何一个都可以先执行。—注释结束

术语“sequence point”在C++11中根本没有出现(唯一接近的匹配项是“sequence pointer”)。

C11(ISO/IEC 9899:2011)如下所述:

5.1.2.3 程序执行
第3段 "Sequenced before" 是单个线程执行的评估之间的一种非对称、传递、成对关系,它在这些评估之间引入了一个偏序关系。对于任何两个评估 A 和 B,如果 A 在 B 之前排序,则 A 的执行应在 B 的执行之前进行。 (反之,如果 A 在 B 之前排序,则 B 在 A 之后排序)。如果 A 既不在 B 之前也不在之后排序,则 A 和 B 是未排序的。当 A 在 B 之前或之后排序时,评估 A 和 B 是不确定排序的,但是未指定其顺序。13) 在表达式 A 和 B 的评估之间存在“序列点”意味着与 A 相关的每个值计算和副作用都在与 B 相关的每个值计算和副作用之前排序。(有关序列点的摘要请参见附录 C。) 13) 未排序评估的执行可以交错进行。不确定排序的评估不能交错,但可以按任何顺序执行。

因此,C11保留了序列点,但使用与C++11基本相同的术语添加了“顺序前”和相关术语。


9
完美。如果 ((i == 1) && i++),将会打印出 Yes - Bhargav Rao
1
@JonathanLeffler:感谢您用曾经被称为序列点的术语进行限定。我没有意识到这个术语在C++11中已经被改变和扩展了(参见Wikipedia)。 - Rusty Shackleford
3
@Equality7-2521:在C11中也适用于“先序列化”、“后序列化”、“不确定顺序序列化”和“未序列化”的术语。我相信,引入新术语部分是因为两个标准都增加了线程支持。 - Jonathan Leffler
@JonathanLeffler:(旁注)如果我没记错,?:的序列点仅在其第一个操作数(即条件)评估后立即保证一次,没有其他情况。 - Grzegorz Szpetkowski
1
@JonathanLeffler C11标准并没有取消序列点,只有C++11标准这样做了(出于某种原因)。§6.5.13p4:与按位二进制&运算符不同,&&运算符保证从左到右进行评估;如果评估第二个操作数,则在评估第一个和第二个操作数之间存在序列点。如果第一个操作数等于0,则不评估第二个操作数。(强调添加) - Iwillnotexist Idonotexist
显示剩余2条评论

41

这里是一个简单的解释

输入图片描述

这就是为什么这个条件变成了“false”


没有新增除了已有的答案外的内容,因此给予了一个负评! - Am_I_Helpful
17
不,我本人认为这个答案最清晰明了,除非你想以正式的方式理解这个概念。 - user2953119
9
简洁明了,但它没有解释为什么括号中的表达式没有被首先执行。 - Derek 朕會功夫
5
这句话表述了发生了什么,但没有解释原因。例如,代码 if (i++ & (i == 1)) [...] (注意单个和号)会产生不同的结果,但是你的回答没有提到任何概念,帮助某人弄清楚为什么会这样。 - David Richerby
3
其实我认为很简单。它通过图解释了行为,对我来说很好理解。 - Zizouz212
显示剩余2条评论

29
当在表达式中使用&&时,其参数保证从左到右进行评估。因此,当评估(i==1)时,i将具有值2。因此,该表达式为假,将执行else部分。
然而,请注意,如果左侧参数评估为false或0,则根本不会评估右侧参数。

1
这个表述不是很清晰。如果第二个参数甚至不能保证被评估,那么“从左到右保证被评估”是什么意思?(这意味着如果第二个参数被评估了,那么这是在完成第一个参数的评估(包括其任何副作用)之后进行的;所以你应该这样说。) - Marc van Leeuwen
在这个上下文中,保证意味着括号不会首先被评估,对我来说非常清楚。+1 给 @MichaelL 的简单解释,尽管序列点的讨论是理解的核心。 - strattonn

1
我想你已经清楚了1&&1=1和1&&0=0。 迈克尔L的回答对我来说很好。但是我还是会尝试详细解释一下。 这里有一个链接,提供运算符优先级列表:

http://www.difranco.net/compsci/C_Operator_Precedence_Table.htm

如果您访问此链接并参考该表,则会知道&&具有从左到右的结合性。因此,在左部分(Sruit试图使用图表来展示)之后,首先我将变为2;然后对于右侧部分,将进行i==1检查。我们可以通过编写以下类似的代码来检查:
这段代码解释了当执行流到达i==1部分时,i=2。
#include <stdio.h>
int main() {
    
    int i = 1;
    if (i++ && (printf("%d\n",i)))
        printf("Yes\n");
    else
        printf("No\n");
    return 0;
}

因此,输出结果为:

2

是的

所以2==1最终得出的结果是false,这让人感到惊讶! 右侧括号给出0,左侧括号给出1,所以1&&0=0。 我认为这已经足够理解了。

尽管我觉得你在最后一个代码示例中打印_i_的值的想法有些有趣,但是在没有注释的情况下,该代码不能作为解释使用。而且,我也没有找到_1&2 gives o;_示例的任何用途,因为它与问题的情况不太相似。此外,当你参考某些来源时,请在你的帖子中包括其必要部分。这样做是为了让你的答案即使链接失效也能保持其价值。 - Roope Hakulinen

0
你感到困惑是因为在循环语句中使用了后增量 i++。 例如:
for (i=0;i<1;i++)

在上面的循环程序中,i 首先获取初始值并检查条件。由于 if 条件为真,因此它会将 i 递增,并将 i 评估为循环体的旧值,但在循环体外部,它具有新值 1
在您的问题中,您正在使用 if,而 i 的旧值作用域将在遇到逻辑运算符时结束,它会自动获取新值并递增为 2,因此条件返回 false。

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