LV050-循环结构
一、while
1. 基本形式
while(表达式)
{
语句块...;
}先计算“表达式”的值,当值为真(非 0)时, 再执行“语句块”;执行完“语句块”,再次计算表达式的值,如果为真,继续执行“语句块”……这个过程会一直重复,直到表达式的值为假(0),就退出循环,执行 while 整体 后面的代码。

2. 使用实例
#include <stdio.h>
int main(int argc, char *argv[])
{
int n = 5;
int count = 0;
printf("------cycle start------\n");
while(n > 2)
{
printf("Before statement execution: count=%d, n=%d\n", count, n);
n--;
count++;
printf("After statement execution: count=%d, n=%d\n", count, n);
}
printf("------cycle end------\n");
printf("count = %d, n = %d \n", count, n);
return 0;
}
【注意】 while 循环是 先判断 表达式为真,然后才执行 循环,若为假,则直接结束循环,不会再执行循环体内的语句块。
二、do...while
1. 基本形式
do{
语句块...;
}while(表达式);先执行“语句块”,再计算“表达式”的值,当值为真(非 0)时,继续执行“语句块”……这个过程会一直重复,直到表达式的值为假(0),就退出循环,执行 do...while 整体后面的代码。
2. 使用实例
#include <stdio.h>
int main(int argc, char *argv[])
{
int n = 5;
int count = 0;
printf("------cycle start------\n");
do{
printf("Before statement execution: count=%d, n=%d\n", count, n);
n--;
count++;
printf("After statement execution: count=%d, n=%d\n", count, n);
}while(n > 2);
printf("------cycle end------\n");
printf("count = %d, n = %d \n", count, n);
return 0;
}
会发现这里和上面的 while 循环执行情况是一样的,当我们吧这里的 n > 2 改为 n > 5 的时候就会发现,这里会执行 1 次,但是前面的 wihle 循环一次都不会执行。
【注意】 do...while 循环是 先执行一次循环,然后才判断 表达式为真,然后才继续执行循环,若为假,则直接结束循环,不会再执行循环体内的语句块。所以当两个实例中的判断条件 n > 2 变为 n > 5 时 while 循环结构一次也不执行,而 do...while 循环结构会执行一次,这点需要注意。
三、for
1. 基本形式
for(表达式1; 表达式2; 表达式3)
{
语句块...;
}(1) 先执行“表达式 1”。
(2) 再执行“表达式 2”,如果它的值为真(非 0),则执行循环体,否则结束循环。
(3) 执行完循环体后再执行“表达式 3”。
(4) 重复执行步骤 (2) 和 (3),直到“表达式 2”的值为假,就结束循环。
上面的步骤中,(2) 和 (3) 是一次循环,会重复执行,for 语句的主要作用就是不断执行步骤 (2) 和 (3)。

【注意】
(1)“表达式 1”仅在第一次循环时执行,以后都不会再执行,可以认为这是一个初始化语句。“表达式 1”可省略, 但循环之前应给循环变量赋值。
(2)表达式 2”一般是一个 关系表达式,决定了是否还要继续下次循环,称为“循环条件”。“表达式 2”可省略, 将陷入死循环。
(3)“表达式 3”很多情况下是一个带有自增或自减操作的表达式,以 使循环条件逐渐变得“不成立”。“表达式 3”可省略, 但在循环体中增加使循环变量值改变的语句。
2. 使用实例
#include <stdio.h>
int main(int argc, char *argv[])
{
int n = 5;
int count = 0;
printf("------cycle start------\n");
for(n=5;n>2;n++)
{
count++;
printf("statement execution: count=%d, n=%d\n", count, n);
}
printf("------cycle end------\n");
printf("count = %d, n = %d \n", count, n);
return 0;
}四、死循环
在我们实际编程的时候,一般程序都不是只执行 1 次,经常需要一直运行,这个时候就需要死循环,上面三种循环结构都可以进入死循环,只要保证条件一直为真即可。
1. 三种死循环
// while 语句
while(1){
// 要执行的语句
}
// do...while 语句
do {
// 要执行的语句
}while(1)
// for 循环
for(;;) {
// 要执行的语句
}2. for(;;)与 while(1)
在内核源码中,我们会看到很多的 for 死循环,和 while(1)相比,有什么区别?
2.1 功能上等效
在实现无限循环(死循环)这个功能上,while(1) 和 for(;;)在 C 语言里是 100% 等效的。 它们都能让代码一遍又一遍、永无止境地执行下去,直到出现断电或者复位。
为什么可以等效?
while(1):while 后面括号里的表达式是条件。1 在 C 语言里代表真。所以 while(1) 的意思就是“当条件为真时,执行循环体”。因为 1 永远是真,所以循环就永远停不下来。
for(;😉:for 循环的标准结构是 for(初始化; 条件; 增量)。for(;;)的精妙之处就在于它故意省略了所有三个部分。在 C 语言标准里,for 循环如果省略条件部分,默认这个条件就是真,这就和 while(1) 的条件一样了。
2.2 编译器怎么看?
在 C 语言刚诞生那会儿,一些老旧的编译器,对 while(1) 和 for(;😉 的底层处理可能有那么一丁点不同。
while(1):编译器需要生成指令去检查条件 1 是否为真。虽然 1 永远是真,但这个检查是否为真的动作理论上还是存在的。
for(;😉:因为条件部分直接是空的,被视为真,可能被某些老旧编译器直接编译成一个无条件跳转指令,跳回到循环开头。理论上少了一个检查常数是否为真的冗余步骤。
而在新的编译器上, 两者的差别其实已经没有了, 现在的编译器已经能作出很智能的优化了。我们可以尝试一下:
// test_while.c
#include <stdio.h>
int main()
{
while(1)
{
sleep(1000);
}
}
// test_for.c
#include <stdio.h>
int main()
{
for(;;)
{
sleep(1000);
}
}我们直接生成汇编文件(-S 选项的 GCC 编译过程会为每个被编译的输入文件生成以.s 作为后缀的汇编语言文件):
gcc -S test_while.c
gcc -S test_for.c我们会得到以下两个汇编文件:

这里使用的环境是 ubuntu18.04,gcc 版本是 7.5.0。在这个环境中,可以看到两个循环对应的汇编其实是一模一样的。
2.2 为什么国外资料 for 更常见?
功能没区别,编译器下效率也没区别,那为啥国外资料里 for(;😉 似乎更常见呢?这主要是历史传统和编程风格偏好:
K&R C 的遗产:C 语言的两位祖师爷,Kernighan 和 Ritchie (K&R),在他们那本影响深远的圣经《The C Programming Language》中,就大量使用了 for(;😉 来表示无限循环。这本书奠定了 C 语言的风格基础。很多早期的 C 程序员、Unix 系统开发者都深受此书影响,习惯、延续了这一写法。这是一种祖师爷就这么写的的传统力量。
简洁性:在一些程序员看来,for(;😉 极其简洁,没有任何冗余信息。它一眼就能看出这是个无限循环,因为 for 循环的三个部分都省略了。而 while(1) 看起来像一个正常的条件循环,只是条件碰巧是 1。for(;;)更像是一个为无限循环量身定制的成语。
避免编译后的警告: 如上所述,虽然可以解决,但 for(;😉 天然避免了某些编译器关于常量条件的警告,对追求编译零警告的严格项目有一定吸引力。