C编程 - while循环中的逗号运算符

11

编程 1:

#include<stdio.h>
 int main()
 {
     int i=0;
     while(i<=8,i++);
     printf("%d",i);
     return 0;
  }

程序设计2:

#include<stdio.h>
 int main()
{
  int i=0;
  while(i++,i<=8);
  printf("%d",i);
  return 0;
}

程序1的输出为1,程序2的输出为9。

有人能解释一下这是怎么回事吗?这两个代码有什么不同吗?


2
我认为这个问题并不是重复的。当然,如果你对while循环语法和逗号运算符语法及其功能有正确的理解,你就能在没有外部帮助的情况下理解这个问题。但我认为假设一个问题不恰当只因为知识可以防止这个问题是不正确的。由于许多人可能会以完全相同的方式感到困惑,所以从混淆中产生的问题也可能是有益的,从而受益于两个概念细节及其关系的定制解释。 - szpanczyk
3个回答

12
逗号操作符依次评估其两个参数,丢弃结果,除了最后一个。最后一个被评估的表达式确定整个表达式的结果。 i<=8,i++ - 这里表达式的值是i++的值,即在增加之前的i 的值。这是0,所以循环立即终止。 i ++,i<=8 - 这里表达式的值是 i<=8的值,只有当i增加到9时才为0。
个人意见:我认为第二种形式虽然有点类似于for循环,但对代码读者来说比实际的for循环不够清晰。

2
1 while ( condition )
2    statement;
3 more_code();

在上述代码片段中,只要conditiontrue,就可以重复执行statement。在while循环的每次迭代中,condition都会被评估为truefalse。如果是false,则while循环结束,并在其范围之外继续执行(在本例中,是第4行的more_code())。
通常,我们习惯用花括号{}将我们想要在循环中执行的代码部分括起来,但这不是强制性的。如果我们不这样做,循环代码将由单个语句组成,即紧随while部分后面的语句。
实际上,我们经常结合while和花括号括起来的代码块,可以解释为提供此代码块替代单个语句,括号提供信息,指示编译器(通过分析它与前面和后面的代码的关系)应该将该块视为单个语句一样
然而,由于提供单个语句而不是常见的代码块是完全有效的,因此值得了解有一个有效的空语句。我们通过在没有前导代码引起任何事情的情况下键入分号来获得空语句。因此,以下是完全有效的:
1 code;
2 ; // empty statement
3 ; // another empty statement

或者实际上是这样的:
1 code;; // a "code" statement followed by empty statement in the same line
while( condition ) 部分没有以分号结尾,因此如果它应该控制一些实际代码(除了 condition),那么它后面就不应该跟着分号。如果它紧接着被一个分号跟着,那么这个分号将构成(并且被编译器解释为)一个空语句,所以循环代码将是空的。如果这不是我们想要的,那么我们想要循环的代码,无论是代码块还是语句,都不会被循环,而是在循环结束后(如果循环结束了)执行一次。
1 int a = 0;
2 while ( a < 3 ) ; // Next line is not part of loop - only the empty statement this semicolon creates is. This loop is infinite, or in other words it will never end.
3    a++; // This won't be executed even once.
4 printf("This never happens."); 

值得注意的是,在C语言中,行号只对我们人类重要。如果代码行数和缩进不符合程序员的意图,那么它们可能会误导我们,因为程序员未能编写按照他想要的方式运行的代码。
因此,在问题中的这两个片段中,我们会不断地评估 condition 直到其返回 false。为了理解正在发生的事情,我们需要检查逗号操作符的工作方式。
(请注意,虽然逗号作为字符可以在C语言的许多不同位置用于完全不同的含义——例如函数声明、定义和调用——但在本例中,逗号字符是条件的一部分,因此它充当着类似于 +% 操作符的东西。)
expression1 , expression2

逗号运算符会先评估`expression1`,然后是`expression2`,并返回`expression2`的值。
每次评估条件时,我们都会评估这两个表达式(在本例中为操作数`i++`和`i<=8`),然后将右侧表达式的值视为逗号运算本身的结果以及我们条件的值。因此只要右侧操作数解析为`true`,循环就会一直重复。
通常我们使用条件来控制循环的执行,但是像这种情况一样,条件可能具有“副作用”(有意或无意)。在我们的例子中,每次评估条件都会影响变量`i`:它会增加一个。
我们的示例仅在`condition`的操作数顺序上有所不同,因此请注意真正控制循环执行的右操作数。
让我们首先检查第二个示例。在这种情况下,我们有条件`i ++,i <= 8`。这意味着在每次评估时,我们首先增加`i`,然后检查它是否小于或等于8。因此,在对条件进行第一次评估时,我们将`i`从0增加到1,并得出1 <= 8,因此循环继续。构建的循环将在`i`变为9时中断,即在第9次迭代时。
现在对于第一个示例,条件为`i <= 8,++i`。由于比较没有副作用,也就是说,我们可以以任何顺序执行任意数量的比较,如果那是我们所做的唯一事情,也就是说,如果我们没有按结果有序或顺序执行任何其他操作,那些比较将什么也不做。就像在我们的例子中一样,我们评估`i<=8`,它会评估为`true`或`false`,但我们不使用这个结果,只是继续评估右操作数。因此左操作数根本无关紧要。另一方面,右操作数既具有副作用,又成为整个条件的值。在每个循环迭代之前,我们都检查`i ++`是否评估为`true`或`false`。
`i ++`是后增量的一元运算符。它返回`i`的值,然后将其增加一(`i ++`和`++ i`之间的差异在这种情况下微妙但至关重要)。因此,发生的情况是我们首先检查`i`是否为`true`或`false`,然后将`i`增加一。
在C中,没有`boolean`类型。如果整数具有非零值,则认为它们为`true`。

当我们对i++进行第一次评估时,得到的是0,即false。这意味着循环甚至没有进行一次迭代就被打破了。但是它并不会中断对i++的评估,这会导致在我们完成循环并执行超出循环范围的代码之前,i增加1。因此,一旦我们完成while循环,i已经是1了。

如果我们想要非常准确地理解,在评估整个条件的结果之前,我们执行与此评估相关的任何代码后,时间上发生的部分是之后的。因此,我们首先记住在达到i++部分时i为0,然后将i增加1,然后我们完成执行condition,因此我们向决定是否应该执行另一个(在这种情况下是第一个)迭代或跳过循环部分并继续移动的代码提供值0。这正是为什么即使已经确定循环将结束,但在condition完成执行之前,循环内的所有内容实际上都会发生的确切原因:已确定,但尚未检查和执行,直到condition完成执行。


@bhass1,感谢您的编辑,但在这种特定情况下添加花括号会降低价值。花括号不是问题片段的一部分,您明白我的意思吗?花括号,正如您可能知道的那样,不是强制性的,它们的缺失可能会让一些新程序员感到困惑,并且需要成为正确答案的一部分。 - szpanczyk

1
表达式分隔符运算符,强制从左到右进行评估,也是一个排序点
程序1:考虑i <= 8, i++。评估并丢弃i <= 8,然后评估i++。整个表达式具有未递增的i值。由于i最初为0,所以while循环在第一次迭代时终止。输出将是仅递增的i值,即1。
程序2:i++被评估并丢弃结果,然后使用值评估i <= 8,因为,是一个排序点。因此,while循环运行,直到使用递增的i值不再true为止。输出将是9。

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