6.6 goto语句和标号
分支、循环都讲完了,现在只剩下最后一种影响控制流程的语句了,那就是goto语句,即实现无条件跳转。我们知道break只能跳出最内层的循环,如果在一个嵌套循环中遇到某个错误条件需要立即跳出最外层循环做出错处理,就可以用goto语句,例如:
- for (...)
- for (...) {
- ...
- if (出现错误条件)
- goto error;
- }
- error:
- 出错处理;
这里的error:叫作标号(Label),任何语句前面都可以加若干个标号,每个标号的命名也要遵循标识符的命名规则。
goto语句过于强大了,从程序中的任何地方都可以无条件跳转到任何其他地方,只要在那个地方定义一个标号就行,唯一的限制是goto只能跳转到同一个函数中的某个标号处,而不能跳到别的函数中 。滥用goto语句会使程序的控制流程非常复杂,可读性很差。著名的计算机科学家Edsger W. Dijkstra最早指出编程语言中goto语句的危害,提倡取消goto语句。goto语句不是必须存在的,显然可以用别的办法替代,比如上面的代码段可以改写为以下形式:
- int cond = 0;
- for (...) {
- for (...) {
- ...
- if (出现错误条件) {
- cond = 1;
- break;
- }
- }
- if (cond)
- break;
- }
- if (cond)
- 出错处理;
通常goto语句只用于这种场合,一个函数中任何地方出现了错误条件都可以立即跳转到函数末尾进行出错处理(例如释放先前分配的资源、恢复先前改动过的全局变量等),处理完之后函数返回。比较用goto和不用goto的两种写法,用goto语句还是方便很多。但是除此之外,在任何其他场合都不要轻易考虑使用goto语句。有些编程语言(如C++)中有异常(Exception)处理的语法,可以代替goto和setjmp/longjmp的这种用法。
回想一下,我们在第4.4节中学过case和default后面也要跟冒号(:号,Colon),事实上它们是两种特殊的标号。和标号有关的语法规则如下:
语句 → 标识符: 语句
语句 → case 常量表达式: 语句
语句 → default: 语句
反复应用这些语法规则进行组合可以在一条语句前面添加多个标号,例如在例4.2中,有些语句前面有多个case标号。现在我们再看switch语句的格式:
- switch (控制表达式) {
- case 常量表达式: 语句列表
- case 常量表达式: 语句列表
- ...
- default: 语句列表
- }
{}里面是一组语句列表,其中每个分支的第一条语句带有case或default标号,从语法上来说,switch的语句块和其他分支、循环结构的语句块没有本质区别:
语句 → switch (控制表达式) 语句
语句 → { 语句列表 }
有兴趣的读者可以在网上查找有关Duff 's Device的资料,Duff's Device是一段很有意思的代码,正是利用"switch的语句块和循环结构的语句块没有本质区别"这一点实现了一个巧妙的代码优化。