关闭

C++深入体验之旅四:循环结构

标签: C++interface设计C++开发循环结构
1528人阅读 评论(0) 收藏 举报
分类:

1.for循环语句

大家看过赛车的话都知道,赛车就是围绕着一个固定的跑道跑一定数量的圈数,如果没有发生意外,那么跑完了指定数量的圈数,比赛就结束了。
我们来设想一下赛车的实际情况,当比赛开始,赛车越出起跑线的时候,车子跑了0圈,然后车子开到赛道的某个地方,会看到车迷举着一块标牌。过一会儿,赛车跑完了一圈,这时候已跑圈数还没有达到比赛指定的圈数,所以比赛还要继续,车子还要继续跑……开到刚才那个地方,又看到一次车迷举的标牌……当赛车跑完第60圈,也就是最后一圈时,已跑圈数已经等于比赛所要求的圈数,比赛就结束了。
问车手一共看到了几次粉丝举的标牌呢?很显然,答案是60次。
如果我们把粉丝的标牌换成了语句cout <<”加油!” <<endl;,那么很显然,屏幕上应该会显示60次“加油!”。于是我们有了重复多次输出字符串的基本想法。可是,我们现在还缺少赛车呢,在C++中,是如何造出一辆赛车来的呢?
赛车里最有名的是Formular 1(一级方程式赛车),于是我们取Formular的前三个字母for作为造赛车的语句,其具体语法格式为:
for (比赛前的准备;比赛继续的条件;每跑一圈后参数的变化)
   语句块;
for语句称为循环语句,大括号内的语句块称为循环体,而这种赛车的结构在C++中称为循环结构。根据上面的语法格式,我们来描述一下前面所说的输出60次“加油!”的情况:

for (int i=0;i<60;i=i+1) 
{ 
   cout <<”加油!” <<endl; 
} 

我们在比赛开始前,创建一个整型变量i用于存放赛车已跑的圈数,并且为它赋初值为0,即比赛开始前已跑了0圈。比赛继续的条件是赛车还没跑到60圈,即当i>=60的时候,比赛应该立即中止。(设想如果将此处改成i<=60,赛车实际要跑几圈?)每跑完一圈以后,已跑圈数要增加1,所以i=i+1。而语句块中的内容相当于在跑道中看到的各种情况……(参见图5.1.1)
下面我们来看一个完整的for语句构成的程序:(程序5.1.1)
#include "iostream.h" 
int main() 
{ 
   int sum=0; 
   for (int i=1;i<=100;i=i+1) 
   { 
      sum=sum+i; 
   } 
   cout <<sum <<endl; 
   return 0; 
} 
运行结果:
5050 
我们在循环之前,创建了两个变量,分别为sum和i。在循环语句中,我们习惯用诸如i,j,k之类的字母作为变量名,来控制循环的执行次数。这些变量又称为循环控制变量。而sum则表示和的意思,其作用是把一点一点的数值累加起来。我们来通过手工来模拟一下程序5.1.1的前三次循环:
创建变量sum=0→遇到for语句,创建变量i=1→判断i是否小于等于100→满足(i=1<100),于是执行循环→sum=sum+i,即sum=0+i=1→第一次循环完毕,i=i+1,即i=1+1=2→判断i是否小于等于100→满足(i=2<100),于是执行循环→sum=sum+i,即sum=1+i=3→第二次循环完毕,i=i+1,即i=2+1=3→判断i是否小于等于100→满足(i=3<100),于是执行循环→sum=sum+i,即sum=3+i=6→第三次循环完毕,i=i+1,即i=3+1=4……
通过三次循环,我们不难发现sum里存放的是1+2+3……的和。所以,循环100次以后输出了结果5050也在意料之中了。


算法时间:累加与循环控制变量
在循环结构中,累加是很常用的一种方法。累加分两种:常量累加和变量累加。常量累加就是类似i=i+1,即在自身的数值上每次递增1。这种方法一般用来记数,然后利用这个计数器作为条件帮助循环语句或分支语句做一些判断。变量累加一般是用于保存结果的,不管是1+2+3……+100还是1*2+2*3+3*4……+99*100都需要用到变量累加。变量累加一般和循环控制变量是有关系的,比如程序5.1.1中的累加值就是循环控制变量i,而1*2+2*3……中的累加值就是i*(i+1)了。

 

2.自增(++)与自减(—)

我们发现,在for语句中,会经常用到i=i+1之类的语句。于是为了方便表示,C++中有了增量表达式和减量表达式。增量的操作符为++,减量的操作符为--。增减量运算的优先级和逻辑非运算处在同一级。所以我们现在可以记作“不曾(增)算关羽活”,但是要注意,逻辑非和增减量操作符应该依次从左向右计算,而没有谁优先的说法(因为它们同级)。
在实际使用中,我们会遇到两种增(减)量操作。一种是++i,称为前增量操作,另一种是i++,称为后增量操作。那么这两种操作有什么不同呢?应该如何记忆呢?
我们刚才说了,增量和减量是表达式,既然是表达式就应该有一个结果。而前增量和后增量的结果是不同的。++i是先去做i=i+1,然后再把i作为表达式的结果;而i++是先把i作为表达式的结果,然后再去做i=i+1。说到这里,可能有些读者要糊涂了,要是i=1,执行完了i++和++i的结果不都是i=2么?怎么叫结果不一样呢?那么我们来看段程序:(程序5.1.2)

#include "iostream.h"  
int main() 
{ 
   int a,i=1; 
   a=i++; 
   int b,j=1; 
   b=++j; 
   cout <<a <<' ' <<b <<' ' <<i <<' ' <<j <<endl; 
   return 0; 
} 
运行结果:
1 2 2 2 
我们发现,当i和j同时为1分别执行前后自增以后,i和j的值都由1变成了2。但是a和b的值却是不同的。不同的原因就是在于前面讲的赋值与做加法的顺序不同。a=i++是先把没有做过加法的i值赋给了a,所以a的值为1;而b=++i是先做加法,即i=2了以后,再把i的值赋给b,所以此时b的值为2。我们记忆的时候可以按照增量操作符和变量的位置来记,加号在变量前面的称为“先加后赋”,即先做加法在赋值;加号在变量后面的称为“先赋后加”,即先赋值后做加法。由于增减量操作符有赋值操作,所以操作的对象(又称操作数,Operand)必须是左值。比如3++就是不允许的。

试试看:
1、修改程序5.1.1,使之输出以下结果:
①1+2+3……+50 ②1*2*3……*20 ③1/1+1/2+1/3……1/50
2、分别使用增量操作和减量操作修改程序5.1.1,使其运行结果不变。并考察使用前增量和使用后增量是否影响循环程序的运行结果。

 

3.for语句的使用技巧

  我们知道在for语句括号内的语句一共有三条,分别是循环前准备、循环继续的条件和每次循环后参数变化。那么这三条内容是不是必需的呢?如果缺少某一句的话,for语句还能否正常运行呢?

首先要了解,如果省略了某句语句,分号仍然是不能省略的。这里的分号起着分割的作用,如果省略了分号,那么电脑将无法判断到底是省略了哪句语句。

情况一:省略循环前准备

我们以程序5.1.1为例,在保证运行结果不变的情况下,可以做这样的修改:

#include "iostream.h" 
int main() 
{ 
   int sum=0; 
   int i=1;//创建循环控制变量,并赋初值为1 
   for (;i<=100;i=i+1) 
   { 
      sum=sum+i; 
   } 
   cout <<sum <<endl; 
   return 0; 
} 

实际上,我们并不是没有做准备工作,而是早就把准备工作在for语句之前就做好了。因此for括号内的准备工作就可以省略了。

情况二:省略循环继续的条件

事实上,循环继续的条件也是能够被省略的,但是却不推荐那样做。因为这将使得程序的可读性变差(即不容易让自己或别人看懂),程序的运行变得混乱。如果循环继续的条件被省略,那么for语句就会认为循环始终继续,直到用其他方式将for语句的循环打断。至于如何打断for循环我们将在下一节作介绍。

情况三:省略每次循环后的参数变化

我们知道,循环后的参数变化是等到每次循环结束以后才发生的。因此,我们把参数变化放在语句块的最后即可。如下是省略了参数变化的程序5.1.1:

#include "iostream.h" 
int main() 
{ 
   int sum=0; 
   for (int i=1;i<=100;)//省略参数变化 
   { 
      sum=sum+i; 
      i++;//在语句块最后补上参数的变化 
   } 
   cout <<sum <<endl; 
   return 0; 
} 

虽然省略for语句中的成分是允许的,但是在实际使用过程中这种方法却显得比较鸡肋。所以建议不要随意地将for语句的成分省略掉,以免给理解程序带来麻烦。

试试看:
1、试输出以下图形:
********
********
********
2、改写程序5.1.1,要求只改写for语句括号内一处,使其输出1+3+5……+99的结果。

 

4.用break和continue跳出循环

不知道大家有没有注意到,在上一节讲述赛车问题的时候有这样一句话:如果没有发生意外的话,那么跑完了指定数量的圈数,比赛就结束了。实际上,赛车比赛是会发生各种情况的,比如要进维修站进行维修,或者引擎突然损坏不得不退出比赛。那么C++的“赛车比赛”会不会进维修站或者退出比赛了呢?

上一节向大家介绍了for可以省略循环继续的条件而使其不断循环,但如果我们放任这种无止尽的循环,则可能会导致电脑死机。所以我们必须强制停止比赛。这条语句就是break语句,其实我们在4.4的switch语句中已经遇到过了。下面我们还是在程序5.1.1的基础上作修改,看看break在for语句中是如何使用的。

#include "iostream.h" 
int main() 
{ 
   int sum=0; 
   for (int i=1;;i++) 
   { 
      if (i>100) //若i大于100则退出循环 
      { 
         break; 
      } 
      sum=sum+i; 
   } 
   cout <<sum <<endl; 
   return 0; 
} 

这段代码的意思是,当i<=100的时候一直执行循环;一旦i>100了,则会运行到if语句里的break语句,于是强行中止了循环。以上这段代码可以由图5.2.1来表示。我们也不难发现,修改后的程序运行结果应该和程序5.1.1的运行结果一样。
那么,进维修站又是怎么一回事呢?
实际上进维修站并不是退出比赛,而是暂时绕开一段,然后重新进入赛道继续下一圈的比赛。那么绕开的赛道上的标牌是无法看到的。在C++的“赛车比赛”中,进维修站是绕开一些语句,重新开始下一次的循环。进维修站的语句是continue,下面我们来看一个程序:
(程序5.2.1)
#include "iostream.h" 
int main() 
{ 
   for (int i=0;i<12;i++) 
   { 
      cout <<'*';//输出星号 
      if (i%2==0) 
      { 
         continue; 
      } 
      cout <<' ';//输出空格 
   } 
   cout <<endl; 
   return 0; 
} 

运行结果: 
** ** ** ** ** ** 

在循环的执行过程中,如果i%2不等于0,即i为奇数的时候,则完成整个循环,输出一个星号和一个空格;如果i是个偶数,则跳过输出空格的语句,进行下一次循环。这个程序的运行情况可以由图5.2.2来描述。

试试看:
1、改写程序5.1.1,要求使用continue语句,使其输出1+3+5……+99的结果。
2、思考break和continue语句是否可能会影响循环的次数?为什么?
结论:break可能影响循环次数,而continue不会影响。

 

5.for语句的嵌套

在上一章,我们讲到“如果里的如果”,是利用if……else……语句的嵌套来描述多分支的情况。那么圈圈里的圈圈——for语句的嵌套又是怎么样的一种情况呢?
下面先让我们来看一个程序:(程序5.3.1)


运行结果:
0 1 2 3 4 5 6 7 8 9 
10 11 12 13 14 15 16 17 18 19 
20 21 22 23 24 25 26 27 28 29 
30 31 32 33 34 35 36 37 38 39 

我们把最先遇到的循环语句称为外循环,后遇到的循环语句称为内循环。根据运行结果,我们知道这段程序能够输出0~39这一些整数。虽然使用一个for语句也能够做到这个效果,但是它们的原理是不同的。下面我们就来分析一下这两个for是如何做到输出这些数字的。
创建变量number→遇到第一个for语句,创建变量i=0,进行循环→遇到第二个for语句,创建变量j=0,进行循环→number=0*10+0=0→输出0→继续第二个for语句的循环,j++,number=0*10+1=1→输出1→……输出9→第二个for语句的循环结束,输出换行,i++→i=1,小于3,第一个for语句的循环继续→再次遇到第二个for语句,j=0→number=1*10+0=10→
输出10→继续第二个for语句的循环,j++,number=1*10+1=11→输出11→……输出19……
如果你还对for语句嵌套的运行方法不能理解,那么我们可以找出一个生活中的例子。我们都知道,时钟的运行方式:分针走完一圈,时针走一大格,分针走完12圈,时针才走完一圈。在for语句的嵌套中,内循环就像分针,而外循环就像是时针,它走得很慢,要等到内循环走完一圈它才走一格。

算法时间:什么时候要用循环的嵌套?
循环的嵌套往往是用在由多样东西通过不同搭配而组成一样东西的情况下。比如由一个个位数和一个十位数组成一个两位数就要用到循环的嵌套,输出处在x轴和y轴不同位置的点组成的二维图形也要用到循环的嵌套。

 

6.域宽设置--让输出更规范

  看了程序5.3.1的运行结果,你可能会觉得输出的数字不太整齐。第一行的一位数都挤在了一起,而第二行开始的两位数都是整整齐齐的。那么,我们有什么办法让他们排排整齐么?大家自然就先想到空格了。不过如果为了个这么简单的功能,还要去编写一段判断一下这个数是几位的,要加几个空格之类代码就有点麻烦了。其实C++早已经为我们准备好了更方便的方法。这种方法就是设置域宽。

所谓域宽,就是输出的内容(数值或字符等等)需要占据多少个字符的位置,如果位置有空余则会自动补足。比如我们要设置域宽为2,那么当输出一位数1的时候输出的就是“ 1”,即在1前面加了一个空格。空格和数字1正好一共占用了两个字符的位置。
那有些时候我们不想在1前面补上空格,而是希望1前面补上0可不可以?当然也是可以的。我们可以设置填充字符,如果我们把0设置为填充字符,那么1前面就变成0了。
设置域宽的具体语法格式为:
    cout <<setw(int n) <<被设置的输出内容1 [<<setw(int m) <<被设置的输出内容2 …];
设置填充字符的具体语法格式为:
    cout <<setfill(char n) <<被设置的输出内容 [<<setfill(char m) <<被设置的输出内容12 …];

我们在设置域宽和填充字符的时候要注意几点:

  1. 设置域宽的时候应该填入整数,设置填充字符的时候应该填入字符。
  2. 我们可以对一个要输出的内容同时设置域宽和填充字符,但是设置好的属性仅对下一个输出的内容有效,对以后输出要再次设置。即cout <<setw(2) <<a <<b;语句中域宽设置仅对a有效,对b无效。
  3. setw和setfill被称为输出控制符,使用时需要在程序开头写上#include "iomanip.h",否则无法使用。

下面我们来看一段有关输出图形的循环嵌套程序:(程序5.3.2)
#include "iostream.h" 
#include "iomanip.h" 
int main() 
{ 
   int a,b; 
   cout <<"请输入长方形的长和宽:" <<endl; 
   cin >>a >>b; 
   for (int i=1;i<=b;i++)//控制长方形的宽度 
   { 
      for (int j=1;j<=a;j++)//控制长方形的长度 
      { 
         cout <<setw(2) <<'*'; 
      } 
      cout <<endl; 
   } 
   return 0; 
} 

运行结果:

请输入长方形的长和宽: 
5 3 
* * * * * 
* * * * * 
* * * * * 

7.do…while语句

  我们已经学习了for语句的循环,并且知道for语句习惯上是用在已知循环次数的情况下的。但是,人不具有先知的能力,有些时候我们无法预知一个循环要进行几次,那我们该怎么办呢?

一个循环,最不可缺少的就是开始和终止。如果一个程序的循环只有开始没有终止,那么这个程序是不会有结果的。所以,我们必须知道什么时候让循环终止,即循环继续或循环终止的条件。
于是,一个只包含循环继续条件的循环语句产生了,那就是while语句,具体语法格式为:
while (循环继续的条件)
   语句块;
while语句要比for语句简练很多,它只负责判断循环是否继续。所以,我们必须人为地在语句块中改变参数,使得循环最终能够被终止。由于while循环是在循环语句块之前判断是否继续循环,所以又被称为“当型循环”。
下面让我们来看一段简单的程序:(程序5.4.1)

#include "iostream.h" 
#include "iomanip.h" 
int main() 
{ 
   int password; 
   cout <<"请设置一个四位数密码(首位不能是0):" <<endl; 
   cin >>password; 
   int i=0; 
   while (i!=password)//如果密码没猜中就继续猜 
   { 
      i++; 
   } 
   cout <<"破解成功!密码是" <<i <<endl; 
   return 0; 
} 

运行结果:
请设置一个四位数密码(首位不能是0): 
1258 
破解成功!密码是1258 
可能有些读者还没看懂,上面这段程序到底是什么意思。其实上面这段程序就是暴力破解密码的基本原理。假设某台电脑内设置了一个四位整数的密码,我们就可以通过循环语句让它不断地去尝试猜测,但是我们无法预知这个密码是多少,也就无法知道循环里的语句块要执行多少次,所以我们应该使用while循环,而循环继续的条件就是密码没有被猜中。


算法时间:电脑的猜测
很多人认为,电脑没有思维,怎么能猜测呢?其实这样就大错特错了。电脑自己是无法猜测的,但是我们可以使用循环语句教它如何猜测,更确切地说是教它如何找到。这种使用循环来查找结果的方法我们称为穷举法。即把所有可能的结果都去试试看,如果哪个能对上号了,就是我们所要的答案。但是在使用它的时候我们要注意严密性,如果自己考虑时漏掉了可能的结果,那么电脑自然不会猜出完美的答案来。穷举法在程序设计中使用十分广泛,甚至很多人脑难以解决的问题,它都能很快地给出答案。
在实际使用中,我们发现while语句就像是只有循环条件的for语句。所以,在某些场合下,while语句和for语句是可以互相转化的。而while语句也有着和for语句类似的嵌套,在这里不作赘述。

导火索——do

在实际生活中会有这样的问题,比如今天是星期一,我们以一周作为一个循环,那么循环结束的条件还是“今天是星期一”。如果我们写while (今天!=星期一),那么这个循环压根儿就不会运行。因为“今天是星期一”不符合循环继续的条件,已经直接使循环结束了。
其实我们只要让第一次的循环运行起来就是了,然后再写上while (今天!=星期一),就能达到我们的目的。如果我们把后面可以发生的循环比作能发生连锁反应的炸药,那么我们缺少的只是一根导火索。而在C++中,就有这么一根导火索——do。它能够搭配while语句,使得第一次的循环一定能运行起来。它的语法格式是:
do
   语句块;
while (循环继续的条件);
要注意,这里的while后面是有一个分号的,如果缺少了这个分号,则会导致错误。下面就让我们来看一个do……while的程序:(程序5.4.2)

#include "iostream.h" 
int main() 
{ 
   char inquiry; 
   do 
   { 
      int n; 
      cout <<"你要输出几个星号?" <<endl; 
      cin >>n; 
      for (int i=0;i<n;i++)//输出n个星号 
      { 
         cout <<'*'; 
      } 
      cout <<endl; 
      cout <<"还要再输出一行吗?(n表示不要)" <<endl; 
      cin >>inquiry; 
   }while (inquiry!='n' && inquiry!='N'); 
   return 0; 
} 

运行结果:
你要输出几个星号? 
3 
*** 
还要再输出一行吗?(n表示不要) 
y 
你要输出几个星号? 
2 
** 
还要再输出一行吗?(n表示不要) 
y 
你要输出几个星号? 
1 
* 
还要再输出一行吗?(n表示不要) 
n 

在这段程序中,由for语句来控制输出星号的个数。而do…while语句则是提供了一个用户交流的方式,一旦用户回答n,则退出程序。


算法时间:命令行下的人机交流
我们现在所使用的Windows系统称为图形用户界面(GUI——Graphic User Interface),它是一种可以由鼠标控制的直观的操作系统(OS——Operating System)。然而,在图形用户界面的操作系统被开发出来之前,我们只好在DOS环境下面对着冷冰冰的电脑,没有好看的图标,也没有方便的鼠标。这种在黑乎乎的屏幕上给电脑下命令的操作模式叫做命令行(Command Line)模式。很显然,这种模式给用户很不友好的感觉。所以,我们在设计一个完美的命令行程序时,不仅要求它在功能上质量上的完美,还要求它能够提供更好的人机交流。而程序5.4.2中do……while语句的用法便是高级语言中简单而常用的提供人机交流的方法。
至此,我们学完了所有常用的的分支语句和循环语句。这些语句称为过程化语句。我们可以发现,除了do……while语句以外,所有的过程化语句的末尾是没有分号的,而分号都属于大括号内的语句或者语句块。
过程化语句是一个程序的骨骼。程序的大多数功能都要依赖过程化语句来实现。因此,掌握并且能够灵活运用过程化语句对程序设计来说非常重要。在以后的章节中,我们还会继续学习过程化话语句一些更多的使用方法。





0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:350001次
    • 积分:4393
    • 等级:
    • 排名:第6796名
    • 原创:137篇
    • 转载:32篇
    • 译文:13篇
    • 评论:28条
    个人经历
    爱编程,爱晚起,偶尔也忙到深夜; 喜欢学习,努力工作,也享受生活; 我酷爱技术,崇尚简单的快乐和幸福; 我不是码农,我是程序员; 我和你一样,为理想而奋斗.
    文章分类
    博客专栏