C语言有三种循环结构:while、for和do while。下面依次讲解
一、while循环
1、while介绍
while(逻辑表达式) //布尔值为1 真
{
反复执行的语句
}
只要逻辑表达式结果为真就反复不停执行大括号里的语句,直到逻辑表达式结果为假循环结束
只要把逻辑表达式写成1则循环成为死循环,while(1)
while循环里可以使用break和continue;
break 关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到break 时,循环便终止。
如果把 break 换成 continue 会是什么样子呢? continue 表示终止本次(本轮) 循环。当代码执行到 continue 时,本轮循环终止,进入下一轮循环。while( 1)也有写成 while(true) 或者 while(1==1) 或者 while((bool) 1)等形式的,效果一样。
还需注意:任何非零值都是真.比如,while (-1) == while (true)
2、while复合语句
复合语句是使用花括号组织起来的两个或更过的语句,它也被称为一个代码块。
例如:
/*程序1*/
index = 0;
while (index++ < 10)
sam = 10 *index +2;
printf ("sam = %d\n", sam);
/*程序2*/
index = 0;
while (index++ < 10)
{
sam = 10 *index +2;
printf ("sam = %d\n", sam);
}
说明:在没有花括号的情况下,while循环语句的范围是从while到下一个分号。
3、技巧
A、假如你想要跳过输入直到第一个不为空格或者数字的字符,你可以使用这样的循环:
while (scanf ("%d", &num) == 1) {....}
只要scanf()输入一个整数,它就返回1,循环就会继续。
B、while ((ch = getchar ()) != '\n') {....}
只要输入不为换行符'\n',它就返回1,循环就会继续。
C、while循环内只执行一次的语句
int i = 0;
while (1)
{
if (0 == i)
{只执行一次该语句};
i == 1;
}
4、空语句
while ((ch = getchar ()) != EOF && ch != '\n')
;
while语句之后的单独一个分号称为空语句,它就是应用于目前这个场合,也就是语法要求这个地方出现一条语句但又无需执行任何任务的时候,这个分号独占一行,这是为了防止读者错误地以为接下来的语句也是循环的一部分。
二、do while循环
1、do while介绍
do{
} while(为真的逻辑);
do while为退出条件循环,判断条件在执行循环之后进行检查,这样就可以保证循环体中的语句至少被执行一次。
2、do while复合语句
例如:
do
scanf ("%d", &num);
while (num != 20); /*注意while后面有分号 ;*/
3、do while的常规用法
4、do {}while(0)的用法
参看:do while的使用
linux内核和其他一些开源的代码中,经常会遇到这样的代码:
do{
...
}while(0)
这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?
实际上,do{...}while(0)的作用远大于美化你的代码,while(0)的好处在于,在编译的时候,编译器基本都会为你做好优化,把这部分内容去掉,最终编译的结果不会因为这个do while而导致运行效率上的差异。。查了些资料,总结起来这样写主要有以下几点好处:
1、辅助定义复杂的宏,避免引用的时候出错:
举例来说,假设你需要定义这样一个宏:
#define DO_SOMETHING()\
foo1();\
foo2();
这个宏的本意是,当调用DO_SOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这写:
if(a>0)
DO_SOMETHING();
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:
if(a>0)
foo1();
foo2();
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。
那么仅仅使用{}将foo1()和foo2()包起来行么?
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:
if(a>0)
{
foo1();
foo2();
};
注意if{};后面的";",如果if后面还有else等语句则编译不会通过。所以,很多人才采用了do{...}while(0);
#define DO_SOMETHING() \
do{ \
foo1();\
foo2();\
}while(0)\
...
if(a>0)
DO_SOMETHING();
...
这样,宏被展开后,才会保留初始的语义。GCC提供了Statement-Expressions用以替代do{...}while(0); 所以你也可以这样定义宏:
#define DO_SOMETHING() ({\
foo1(); \
foo2(); \
})
http://www.spongeliu.com/
2、避免使用goto对程序流进行统一的控制:
有些函数中,在函数return之前我们经常会进行一些收尾的工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:
int foo()
{
somestruct* ptr = malloc(...);
dosomething...;
if(error)
{
goto END;
}
dosomething...;
if(error)
{
goto END;
}
dosomething...;
END:
free(ptr);
return 0;
}
由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:
int foo()
{
somestruct* ptr = malloc(...);
do{
dosomething...;
if(error)
{
break;
}
dosomething...;
if(error)
{
break;
}
dosomething...;
}while(0);
free(ptr);
return 0;
}
这里将函数主体使用do()while(0)包含起来,使用break来代替goto,后续的处理工作在while之后,就能够达到同样的效果。
3、避免空宏引起的warning
内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:
#define EMPTYMICRO do{}while(0)
4、定义一个单独的函数块来实现复杂的操作:
当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。
#define st(x) do { x } while (__LINE__ == -1)
1, __LINE__ 是个宏,它代表当前代码在源文件的行号,它是大于0的,所以__LINE__ == -1 等同于0,化简为:
#define st(x) do { x } while (0)
2,do {} while (0)通常用于宏中, 为的是避免如下情况:
#define st(x) x
那么我们在调用 if (0) st(a = b; b = c;) 时会被解释成
if(0)
a = b;
b = c;
可见 if 只对a = b;起作用。
三、for循环
1、for介绍
for(num=1; num <10; num++);
在关键字for之后的圆括号中包含了由两个分号分开的三个表达式。
第一个表达式进行初始化,它在for循环开始的时候执行一次。
第二个表达式是判断条件,在每次执行循环之前都要对它进行求值。当表达式为假时,循环结束。
第三个表达式进行改变或者称为更新,它在每次循环结束时进行计算。
2、for复合语句
例如:
for (n = 0; n < 10; n+)
printf ("%d %d\n", n, 2 * n + 1);
3、for循环的灵活性
A、可以让一个或多个表达式为空(但是不要遗漏分号)。只须确保在循环中包含一些能是循环最终结束的语句。
B、顺便说一句,中间的那个控制表达式为空会被认为是真,所以下面的循环会永远执行:
for (; ;)
printf ("hello world\n");
C、第一个表达式不必初始化一个变量,它也可是某种类型的printf()语句,要记住第一个表达式只在执行循环的其他部分之前被求值或执行一次。
D、for循环可使用逗号运算符把两个表达式链接为一个表达式,并保证最左边的表达式最先计算。
例如:
for (n = 2, m = 0; m < 1000; n *=2)
m +=n;
E、在for循环中使用数组,使用#define来指定数组大小
例如:
#define SIZE 10
for (int n = 0; n < SIZE; n++)
printf ("hello world\n");
4、嵌套循环
A、嵌套循环是指在另一个循环之内的循环。通常使用嵌套循环来按行按列显示数据。也就是说一个循环处理一行中的所有列,而另一个循环处理所有的行。
B、外层for循环为外部循环,按行显示数据;内层for循环为内部循环。按列显示数据。
在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。例如:
长循环在最内层,效率高
for (col=0; col<5; col++ )
{
for (row=0; row<100; row++)
{
sum = sum + a[row][col];
}
}
长循环在最外层,效率低
for (row=0; row<100; row++)
{
for ( col=0; col<5; col++ )
{
sum = sum + a[row][col];
}
}
C、建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。半开半闭区间写法和闭区间写法虽然功能是相同,但相比之下,半开半闭区间写法写法更加直观。
半开半闭区间写法:for (n = 0; n < 10; n++) {....}
闭区间写法:for (n = 0; n <= 9; n++) {....}
D、不能在 for 循环体内修改循环变量,防止循环失控。
for (n = 0; n < 10; n++)
{
…
n = 8;//不可,很可能违背了你的原意
…
}
E、循环要尽可能的短,要使代码清晰,一目了然。
如果你写的一个循环的代码超过一显示屏,那会让读代码的人发狂的。解决的办法由两个:第一,重新设计这个循环,确认是否这些操作都必须放在这个循环里;第二,将这些代码改写成一个子函数, 循环中只调用这个子函数即可。 一般来说循环内的代码不要超过 20行。
F、把循环嵌套控制在 3 层以内。
国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个字函数。
G、扩展
1、编程实现打印一个空心的菱形和实心的菱形:
*
*****
*********
*************
*********
*****
*
#include <stdio.h>
int main()
{
int i=0,j=0,k=0;
for(i=0;i<=3;i++)
{
for(j=0;j<=5-2*i;j++)
{
printf(" ");
}
for(k=0;k<=4*i;k++)
{
printf("*");
}
printf("\n");
}
for(i=0;i<=2;i++)
{
for(j=0;j<=2*i+1;j++)
{
printf(" ");
}
for(k=0;k<=8-4*i;k++)
{
printf("*");
}
printf("\n");
}
return 0;
}
2、请简述以下两个 for 循环的优缺点
// 第一个
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
优缺点:效率低但程序简洁
// 第二个
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
优缺点:效率高但程序不简洁
说明:第一个程序比第二个程序多执行了 N-1 次逻辑判断。并且由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。如果 N 非常大,最好采用第二个程序的写法,可以提高效率。如果 N 非常小,两者效率差别并不明显,采用第一个程序的写法比较好,因为程序更加简洁。
注意:
1、上面有提到,在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
2、建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
int a[10];
半开半闭区间写法:for (n = 0; n < 10; n++)
闭区间写法:for (n = 0; n <= 9; n++)
半开半闭区间写法和闭区间写法虽然功能是相同,但考虑到边界计算,例如我们要在C语言中定义一个拥有 10 个元素的数组,那么 0 就是数组下标的第一个 “入界点”(指处于数组下标范围以内的点,包括边界点),而 10 就是数组下标中的第一个“出界点”(指不在数组下标范围以内的点,不含边界点)。因此写成半开半闭区间的写法更加直观。
3、不能在 for 循环体内修改循环变量,防止循环失控。
for (n = 0; n < 10; n++)
{
…
n = 8;//不可,很可能违背了你的原意
…
}
4、循环要尽可能的短,要使代码清晰,一目了然。
如果你写的一个循环的代码超过一显示屏,那会让读代码的人发狂的。解决的办法由两个:第一,重新设计这个循环,确认是否这些操作都必须放在这个循环里;第二,将这些代码改写成一个子函数, 循环中只调用这个子函数即可。 一般来说循环内的代码不要超过 20行。
5、把循环嵌套控制在 3 层以内。
国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个字函数。
4、do {}while(0)的用法
参看:do while的使用
linux内核和其他一些开源的代码中,经常会遇到这样的代码:
do{
...
}while(0)
这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?
实际上,do{...}while(0)的作用远大于美化你的代码,while(0)的好处在于,在编译的时候,编译器基本都会为你做好优化,把这部分内容去掉,最终编译的结果不会因为这个do while而导致运行效率上的差异。。查了些资料,总结起来这样写主要有以下几点好处:
1、辅助定义复杂的宏,避免引用的时候出错:
举例来说,假设你需要定义这样一个宏:
#define DO_SOMETHING()\
foo1();\
foo2();
这个宏的本意是,当调用DO_SOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这写:
if(a>0)
DO_SOMETHING();
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:
if(a>0)
foo1();
foo2();
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。
那么仅仅使用{}将foo1()和foo2()包起来行么?
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:
if(a>0)
{
foo1();
foo2();
};
注意if{};后面的";",如果if后面还有else等语句则编译不会通过。所以,很多人才采用了do{...}while(0);
#define DO_SOMETHING() \
do{ \
foo1();\
foo2();\
}while(0)\
...
if(a>0)
DO_SOMETHING();
...
这样,宏被展开后,才会保留初始的语义。GCC提供了Statement-Expressions用以替代do{...}while(0); 所以你也可以这样定义宏:
#define DO_SOMETHING() ({\
foo1(); \
foo2(); \
})
http://www.spongeliu.com/
2、避免使用goto对程序流进行统一的控制:
有些函数中,在函数return之前我们经常会进行一些收尾的工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:
int foo()
{
somestruct* ptr = malloc(...);
dosomething...;
if(error)
{
goto END;
}
dosomething...;
if(error)
{
goto END;
}
dosomething...;
END:
free(ptr);
return 0;
}
由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:
int foo()
{
somestruct* ptr = malloc(...);
do{
dosomething...;
if(error)
{
break;
}
dosomething...;
if(error)
{
break;
}
dosomething...;
}while(0);
free(ptr);
return 0;
}
这里将函数主体使用do()while(0)包含起来,使用break来代替goto,后续的处理工作在while之后,就能够达到同样的效果。
3、避免空宏引起的warning
内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:
#define EMPTYMICRO do{}while(0)
4、定义一个单独的函数块来实现复杂的操作:
当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。
4、do {}while(0)的用法
linux内核和其他一些开源的代码中,经常会遇到这样的代码:
do{
...
}while(0) |
这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?
实际上,do{...}while(0)的作用远大于美化你的代码,while(0)的好处在于,在编译的时候,编译器基本都会为你做好优化,把这部分内容去掉,最终编译的结果不会因为这个do while而导致运行效率上的差异。。查了些资料,总结起来这样写主要有以下几点好处:
1、辅助定义复杂的宏,避免引用的时候出错:
举例来说,假设你需要定义这样一个宏:
#define DO_SOMETHING()\
foo1();\
foo2(); |
这个宏的本意是,当调用DO_SOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这写:
if(a>0)
DO_SOMETHING(); |
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:
if(a>0)
foo1();
foo2(); |
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。
那么仅仅使用{}将foo1()和foo2()包起来行么?
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:
if(a>0)
{
foo1();
foo2();
}; |
注意if{};后面的";",如果if后面还有else等语句则编译不会通过。所以,很多人才采用了do{...}while(0);
#define DO_SOMETHING() \
do{ \
foo1();\
foo2();\
}while(0)\
...
if(a>0)
DO_SOMETHING();
... |
这样,宏被展开后,才会保留初始的语义。GCC提供了Statement-Expressions用以替代do{...}while(0); 所以你也可以这样定义宏:
#define DO_SOMETHING() ({\
foo1(); \
foo2(); \
}) |
http://www.spongeliu.com/
2、避免使用goto对程序流进行统一的控制:
有些函数中,在函数return之前我们经常会进行一些收尾的工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:
int foo()
{
somestruct* ptr = malloc(...);
dosomething...;
if(error)
{
goto END;
}
dosomething...;
if(error)
{
goto END;
}
dosomething...;
END:
free(ptr);
return 0;
} |
由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:
int foo()
{
somestruct* ptr = malloc(...);
do{
dosomething...;
if(error)
{
break;
}
dosomething...;
if(error)
{
break;
}
dosomething...;
}while(0);
free(ptr);
return 0;
} |
这里将函数主体使用do()while(0)包含起来,使用break来代替goto,后续的处理工作在while之后,就能够达到同样的效果。
3、避免空宏引起的warning
内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:
#define EMPTYMICRO do{}while(0) |
4、定义一个单独的函数块来实现复杂的操作:
当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。
#define st(x) do { x } while (__LINE__ == -1)
1, __LINE__ 是个宏,它代表当前代码在源文件的行号,它是大于0的,所以__LINE__ == -1 等同于0,化简为:#define st(x) do { x } while (0)
2,do {} while (0)通常用于宏中, 为的是避免如下情况:
#define st(x) x
那么我们在调用 if (0) st(a = b; b = c;) 时会被解释成
if(0)
a = b;
b = c;
可见 if 只对a = b;起作用。
for (n = 0; n < 10; n+)
printf ("%d %d\n", n, 2 * n + 1);
for (n = 2, m = 0; m < 1000; n *=2)
m +=n;
#define SIZE 10
for (int n = 0; n < SIZE; n++)
printf ("hello world\n");
长循环在最内层,效率高
for (col=0; col<5; col++ )
{
for (row=0; row<100; row++)
{
sum = sum + a[row][col];
}
}
长循环在最外层,效率低
for (row=0; row<100; row++)
{
for ( col=0; col<5; col++ )
{
sum = sum + a[row][col];
}
}
for (n = 0; n < 10; n++)
{
…
n = 8;//不可,很可能违背了你的原意
…
}
*****
*********
*************
*********
*****
*
#include <stdio.h>
int main()
{
int i=0,j=0,k=0;
for(i=0;i<=3;i++)
{
for(j=0;j<=5-2*i;j++)
{
printf(" ");
}
for(k=0;k<=4*i;k++)
{
printf("*");
}
printf("\n");
}
for(i=0;i<=2;i++)
{
for(j=0;j<=2*i+1;j++)
{
printf(" ");
}
for(k=0;k<=8-4*i;k++)
{
printf("*");
}
printf("\n");
}
return 0;
}
2、请简述以下两个 for 循环的优缺点
// 第一个
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
// 第二个
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
优缺点:效率高但程序不简洁
int a[10];
闭区间写法:for (n = 0; n <= 9; n++)
半开半闭区间写法和闭区间写法虽然功能是相同,但考虑到边界计算,例如我们要在C语言中定义一个拥有 10 个元素的数组,那么 0 就是数组下标的第一个 “入界点”(指处于数组下标范围以内的点,包括边界点),而 10 就是数组下标中的第一个“出界点”(指不在数组下标范围以内的点,不含边界点)。因此写成半开半闭区间的写法更加直观。
for (n = 0; n < 10; n++)
{
…
n = 8;//不可,很可能违背了你的原意
…
}
如果你写的一个循环的代码超过一显示屏,那会让读代码的人发狂的。解决的办法由两个:第一,重新设计这个循环,确认是否这些操作都必须放在这个循环里;第二,将这些代码改写成一个子函数, 循环中只调用这个子函数即可。 一般来说循环内的代码不要超过 20行。
国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个字函数。