目录
前言
莫比乌斯环大家并不陌生,其所具有无限循环的特性,使其被赋予了远超其数学观念的意义。那么C语言中是否存在与莫比乌斯环具有类似特点的东西?如果有,那么我想应该是C语言中的循环结构。循环结构顾名思义就是程序在一定条件下会在一段代码里反复执行,以满足一些重复性任务·的需要。而本篇就将对C语言实现循环结构的while、do……while和for语句进行介绍。
一、while循环
1、while基本结构
while(exp)
语句
while(exp)
{
语句1
……
语句n
}
以上为while循环的基本结构。咋一看while循环的结构与我们的if语句有些类似,它们同样要靠判断表达式值的真假(0为假,非0为真)来决定是否要进入它们的语句中。而循环结构里的语句们有了一个特别的名称叫循环体,也就是程序进行循环时所要被反复执行的代码部分。接下来将通过用while循环实现打印数字1 - 10的例子来说明while循环的运行逻辑。代码如下:
#include<stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
printf("%d ",i++);
}
printf("\n");
return 0;
}
while循环首先是执行用来判断的表达式,如果表达式不成立,其值为0,循环将会结束也就是不执行;如果表达式成立,其值为非0,就会执行循环体里的内容,执行完并经过循环体里的调整后会回到表达式再次进行判断。
像上面的例子里 i 初始值为1,符合i<=10的条件,表达式为真执行循环体输出i的值,在执行循环体的过程中,经过i++这个先使用i的值再i=i+1的调整后,i的值变为了2,再回到表达式进行判断,反复这个过程直到i的值为11时,不符合条件,表达式为假,结束循环。
2、while循环之小身手大用处
在了解while循环的运行机制后,让我们通过一个将数字逆序输出的例子(如输入1234输出4321)来深度理解while循环与其作用。代码如下:
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d",&n);
while(n)
{
printf("%d",n%10);
n/=10;
}
printf("\n");
return 0;
}
为了实现逆序输出,我们要从低位开始分别取一位数并将其输出这样最终结果就符合逆序输出的要求。取一位数可以通过n%10来实现,但这只能取最低位,所以我们通过n/=10将n从n位数变为n-1位数,用while循环反复实现这个过程,直到n上每一位数被取下,即n为0终止循环。
像这样在相对简单的条件下要反复进行某一段代码进程的情况,我们可以使用while循环来即快速又简单地实现。
二、do……while循环
1、do……while基本结构
do
{
循环体(语句)
}while(exp);
以上就是do……while的基本结构。在名称上我们可以看出其与前一个while循环有一定的相似性,都包含循环体与判断是否继续循环的表达式,而不同的是它们循环体与判断表达式所在的位置与执行的顺序。通过do……while来求一个整数有几位数有以下代码:
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d",&n);
int count = 0;
do
{
n/=10;
count++;
}while(n);
printf("%d\n",count);
return 0;
}
可知do……while的执行逻辑是其会先执行一遍循环体里的内容,再进行判断是否继续循环。向上面的例子里如果n即使是0,它也是一位数,也要执行count++这条语句。而利用do……while的逻辑来实现能比用while更为直接与简单。
2、while与do……while长得像却不一样
说到while与do……while的区别,下面我们分别通过用while和do……while的两个例子所输出的a值来进行具体说明。代码如下:
#include<stdio.h>
int main()
{
int i = 0;
int a = 0;
while(i)
{
a++;
}
printf("%d\n",a);
return 0;
}
#include<stdio.h>
int main()
{
int a = 0;
int i = 0;
do
{
a++;
}while(i);
printf("%d\n",a);
return 0;
}
通过上面的例子我们更进一步理解了do……while跟while的不同在于do……while会先执行一遍循环体里的内容,再进行判断是否继续循环;而while是先判断是否继续循环,再执行循环体里的内容。所以while那段代码输出的a值为0,而do……while的输出的a值为1。
三、for循环
1、for基本结构
for(exp1;exp2;exp3)
{
循环体
}
上面就是for循环的基本结构。其中exp1用于初始化循环变量,exp2就是循环条件判断部分,而exp3用于循环变量的调整。下面用for实现打印1 - 10的数,代码如下:
#include<stdio.h>
int main()
{
int i = 0;
for(i=1;i<=10;i++)
{
printf("%d ",i);
}
printf("\n");
return 0;
}
通过上面的例子,我们知道for的执行逻辑是:先执行exp1,也就是上面的i=1将循环变量i的值初始化为1;然后执行exp2,判断是否进行循环,exp2为真就执行循环体,exp2为假则不执行循环体;然后就到exp3进行循环变量i的调整。值得注意的是for的exp1在整个过程中只被执行一次,而exp2和exp3都在循环,并且每次执行完exp2后无论exp2为真还是为假,都会执行exp3。也就是说在上面的例子里,当for被执行完后,i的值是11。
2、for与while的结构对比
#include<stdio.h>
int main()
{
int i = 0;
for(i=1;i<=10;i++)
{
printf("%d ",i);
}
printf("\n");
return 0;
}
#include<stdio.h>
int main()
{
int i = 1;//初始化
while(i<=10)//条件判断
{
printf("%d ",i);
i++;//调整
}
ptintf("\n");
return 0;
}
还记得for里面三个表达式的作用吗,exp1用于初始化循环变量,exp2就是循环条件判断部分,而exp3用于循环变量的调整。在while的里结构其实也存在初始化、条件判断和调整这三个过程(代码中已注释标明),只不过while里条件判断为假时且调整部分在{ }而不是条件表达式里时,它不会去执行调整部分。
既然for和while结构那么类似,为什么我们还用for呢?这时因为for的初始化、条件判断和调整三个部分非常集中,在循环体很长的情况下,for比while更易维护我们的代码。
3、for不同样子的初始化
int i = 0;
for(i=0;i<n;i++)
{
循环体
}
for(int i = 0;i<n;i++)
{
循环体
}
相信有人见过for上面两种关于 i 的初始化形式。或许有人想问它们有什么不一样呢?其实它们作用一样的,不同在于在较早期C99标准之前C语言是不支持在for里直接进行int i这样的变量声明,所以int i得写到for外面;在C99标准之后为了增强代码的可读性,C语言就允许在for里声明变量,但这样声明出来的变量 i 的作用域只局限于这个for以内,而for之前声明的变量 i 作用域就不会。所以根据你的需要以及你所用的编译器是否支持C99以及之后的标准,你可以自由选择这两种初始化方式。如果你不清楚所用编译系统是否支持,那建议用第一种。
四、循环里的break和continue
1、再遇break
对于break看过我上篇分支语句的朋友们可能不会陌生,但break此次与我们的再次相遇,它的用作可不一样了。让我们再重新认识在循环里的break。
#include<stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
printf("%d ",i);
if(i==5)
break;
i++;
}
printf("\n");
return 0;
}
#include<stdio.h>
int main()
{
int i = 1;
for(i=1;i<=10;i++)
{
printf("%d ",i);
if(i==5)
break;
}
printf("\n");
return 0;
}
#include<stdio.h>
int main()
{
int i = 1;
do
{
printf("%d ",i);
if(i==5)
break;
i++;
}while(i<=10);
printf("\n");
return 0;
}
运行上面三个不同循环的程序,我们发现他们都只输出1 - 5的数字,这是因为我们通过if在i==5时执行了break。可见,在循环里break的作用是终止本次其所在的循环,无论循环是否嵌套。
2、continue
#include<stdio.h>
int main()
{
int a = 1;
int i = 1;
while(i<=10)
{
printf("%d ",i);
i++;
if(i==5)
continue;
a++;
}
printf("\n");
printf("a = %d\n",a);
return 0;
}
#include<stdio.h>
int main()
{
int a = 1;
int i = 1;
for(i=1;i<=10;i++)
{
printf("%d ",i);
if(i==5)
continue;
a++;
}
printf("\n");
printf("a = %d\n",a);
return 0;
}
#include<stdio.h>
int main()
{
int a = 1;
int i = 1;
do
{
printf("%d ",i);
i++;
if(i==5)
continue;
a++;
}while(i<=10);
printf("\n");
printf("a = %d\n");
return 0;
}
通过观察上面的程序的结果,我们发现输出了1 - 10的数字,但a的值只有5,这是因为continue的作用是跳过本次循环里continue后面部分,无论循环是否嵌套。在for里执行continue后将直接到调整部分,而其他则直接到条件判断,值得注意的是如果continue后面有关于循环变量的调整部分,使用不当的话很可能会导致死循环的出现。
五、嵌套循环的使用
下面我们通过找出100 - 200之间的素数的经典例子来展示嵌套循环的作用。素数我们知道,其除了1和它本身以外就没有其他因数,也就是说我们可以通过用n是否能整除以2到n-1的数来判断n是否是素数,如果能被整除及说明其不是素数。代码如下:
#include<stdio.h>
int main()
{
int n = 0;
for(n=100;n<=200;n++)//用for生成100到200的数
{
int i = 0;
int flag = 1;//用于标志n为素数的真假
for(i=2;i<n-1;i++)//生成2到n-1的数
{
if(n%i==0)
{
printf("%d不是素数\n",n);
flag = 0;//n为素数为假
break;
}
}
if(flag)
{
printf("%d是素数\n",n);
}
}
return 0;
}
在上面的例子里我们嵌套了两个for来分别实现多个数字的生成,以及用break在已知n不是素数,不用在生成2到n-1的数情况下,终止其所在的本次循环即生成2到n-1的循环。我们知道合理使用循环的嵌套以及break、continue能使我们处理多条件下的循环更加灵活。
既然说到了找素数,我们上个例子其实不是最优解,还可以优化一下算法。且看优化后的版本:
#include<stdio.h>
#include<math.h>
int main()
{
int n = 0;
for(n=101;n<=200;n+=2)
{
int i = 0;
int flag = 1;//用于标志n为素数的真假
for(i=2;i<sqrt(n);i++)//生成2到sqrt(n)的数
{
if(n%i==0)
{
printf("%d不是素数\n",n);
flag = 0;//n为素数为假
break;
}
}
if(flag)
{
printf("%d是素数\n",n);
}
}
return 0;
}
我们知道素数不会是除了2以外的偶数,所以我们可以直接生成100到200之间的奇数;同时一个数以它的算术平方根为界,其因数呈现出对称性,以16为例:1*16、2*8、4*4、8*2、16*1,所以我们通过找算术平方根之前的数是否为因数,就可以省去后半部分的过程。这里我们引用sqrt()这个数学函数,其作用就是求一个数的算术平方根,使用它之前要包含math.h这个头文件。通过以上步骤,我们极大地减少了不必要的循环次数,优化了我们的时间复杂度。
六、处境尴尬的goto语句
goto语句包含goto和跳转标号,其能实现在同一个函数内无论多少重循环都能跳到设置好的标号处,可见其功能的强大。如下面例子:
for(……)
{
there:
for(……)
{
for(……)
{
if(……)
{
goto there;//代码执行跳到there标号处
}
for(……)
{
if(……)
goto here;//代码执行跳到here标号处
}
}
}
}
here:
……
使用goto能快速跳出多重循环,实现更深层的循环。但如果goto使用不当,会使代码执行在函数内到处跳转,使代码执行逻辑混乱,可维护性差。所以goto语句即使功能强大,但并不被推荐使用,所以有了它的尴尬处境。
结语
本篇我们触碰C语言中的“莫比乌斯环”——循环结构,了解while、for和do……while的基本知识,并能用循环结构实现许多要求反复执行的部分的场景。以上就是本期的分享,如果喜欢我的内容,请给我这一位代码小白多一点支持,点赞和关注,以及欢迎各位对本篇内容的不足之处在评论区补充说明与批评指正啊。