1. 循环的忌讳
表达循环结构
goto语句是低级语言的表征。它很灵活,灵活到没有任何拘束,在函数体内直来直往。函数体内可能含有一些嵌套的循环,这就意味着goto可以跳进跳出循环而无所顾忌。
例如,求1~100的和,由于不断的累积,使得用goto语句也可以直观表达(低级语言通过goto来表示循环结构),但是与for语句对循环结构的明确表达相比较,goto完全处于劣势:
//goto语句模拟do-while循环
int i=1,sum=0;
Loop://
sum += i;
i = i+1;
if(i<=100)
goto Loop;//有条件跳转
cout<<"the sum is "<<sum<<"\n";
//goto语句模拟while循环
int i=1,sum=0;
Loop:
if(i>100)
goto END;//有条件跳转
sum +=i;
i = i+1;
goto Loop;//无条件跳转
END:
cout<<"the sum is "<<sum<<"\n";
极端的goto跳转
goto可以向前跳,像上面的代码所示,也可以向后跳,但跳前调后都是为了从一个语句块跳到另外一个语句块。由于入口与出口的随意性使得过程结构遭到毁灭性的破坏!
例如,下面的程序是一个使用goto的极端例子,小小一个程序,理解起来却很费神:
#include<stdio.h>
int main(){
int a;
goto Init;
Forward:
a = a+1;
Print:
printf("%d\n",a);
goto Down;
Init:
a = 1;
goto Print;
Down:
if(a<100)
goto Forward;
}
虽然已经体现出良好的代码书写格式,但是因为可读性差,甚至连改写成不含goto的程序都很困难。在程序运行后,测试到它的功能,才知道它原来只是简单地打印1~100的整数。如果该程序在运行中出了错,可以想象要找出其中的错误肯定比for循环结构要复杂得多。
破坏声明规则
在C++中,声明和定义语句是穿插在过程体中的,如果跳过声明和定义语句,则还能造成过程体的编译错误,因为后面引用的变量没有经过声明或定义是非法的。例如:
goto Loop;//错误,企图跳过下面的定义语句
int a =1;
Loop:
a = a+1;
//...
跳出多重循环
现代程序设计不能容忍goto在过程中容易穿梭而破坏过程提的结构。没有goto语句跳过循环语句反应的过程体结构更清晰,程序更易读。
在c++中只有一个地方还有使用goto的价值:当从多重循环深处直接跳转到某个外循环之外时。如果用break,就只能一重一重的退,而且还要边退边做记号,若用goto则显得更为直观。例如:
#include<iostream>
using namespace std;
int main(){
const int C = 571;
for (int X,Y; cin>>X>>Y; )
{
for (int i = 1;i<=1000; i++)
for (int j = 1;j<=1000; j++)
if((i*i*X+j*j*Y)%817==C)
{
cout<<i<<" "<<j<<"\n";
goto A;
}
A:
}
}
上述代码中,“A:”为标记,用于给goto语句提升标志。
这里要注意的是,代码中,if语句是二重循环中内循环的循环体语句。“A:”所标记的位置的在整体二重循环语句的外面,并不是if语句的下一条语句位置。因此,if语句中的goto语句从循环内一下可跳到二重循环的外面。
另外要指出的是,goto标记的位置需要进一步的时间才能理解。例如,上述代码等价于下列代码:
#include<iostream>
using namespace std;
int main(){
const int C = 571;
A: for(int X,Y; cin>>X>>Y; )
for(int i=1; i<=1000; i++)
for(int j=1; j<=1000; j++)
if((i*i*X+j*j*Y)%817==c){
cout<<i<<" "<<j<<"\n";
goto A;
}
}
在省略了循环中不必要的花括号后,外循环开始处所设立的标号“A”,才是退出两重循环,继续最外层循环的唯一准确位置。
用了goto语句之后,退出两重循环就不需要break语句设置标记变量了,结构上变得清楚。而且,goto语句没有循环层数的障碍,可以从任意内层往某个外层跳去。
我们不提倡用goto去构筑循环,但也不要谈goto色变,需要从循环深处跳出时,可以毫不犹豫用goto去实现。