浅析C语言中的goto语句

作者:未知

  摘要:本文介紹了goto语句之争的历史背景,举例分析了C语言中goto语句在处理的某些问题上的优势,并提出灵活替换goto语句的一个思路,反驳了传统观点中对goto语句的偏见。
  关键词:goto语句;do-while(0);错误处理;循环跳出;程序可读性
  Goto语句,又称为无条件转移语句,一直以来都饱受编程从业者的诟病,似乎成了行业内的敏感词。人们总是谈goto色变,上课时老师也会告诫你不要轻易去用goto语句。但是真的是这样吗?为什么大家都如此避讳goto语句呢?
  1 历史上的两大争论
  要了解人们回避goto语句的原因就必须了解goto语句的发展历史。
  争论起源:
  1968年,E.W.Dijkstra发表了有名的《Go to Statement Considered Harmful》,提出“goto语句是有害的”论点,激起了人们对传统程序设计方法的讨论。
  争论出现两种声音。主张从高级程序语言中废除goto语句的人觉得,goto语句百害而无一利,他们认为:goto语句会造成程序静态结构和动态执行之间巨大的差异,并且破坏了程序单入口单出口的基本结构,难以查错。而去掉goto则能使程序更加易读和理解。
  而另一类人认为,若废除goto语句,某些情况下反而会增加许多程序不必要的计算量,使程序变得复杂,而灵活地使用goto语句会提高程序的效率。
  争论平息:
  1974年,D.E.Knuth发表了文章《Structured Programming With GOTO Statement》,发表了客观的观点:在一些使用goto会使程序变得难以理解的情况下,要避免使用goto语句,因此还限制goto语句不能从一个模块跳转到另一模块;但在不破坏程序良好结构的前提下,有控制地使用一些goto语句来提高程序的效率是可以的。这种观点的提出才平息了这场长达10年之久的争论。
  随着后来Dijkstra和他的研究团队证明了所有程序都可以用顺序、分支和循环结构来描述,结构化程序设计思想深入人心,goto语句渐渐淡出了人们的视线。
  2 考虑使用goto语句的一些情形
  了解了goto语句的历史,我们就知道了goto语句为何受大部分编程相关人员所“唾弃”,并不是goto语句本身有悖于程序本身,究其根本,使用goto语句会加大程序员们的阅读难度。这直接导致代码难以理解和发现错误。所以,在根本上程序员们所“唾弃”的是难以理解的程序而不是goto语句,如果合理使用goto语句使程序易于理解,那么使用goto语句不但不会被编程人员所禁忌甚至可能会很受欢迎。下面介绍几种适合使用goto的情形。
  (1) 一个函数需要多次执行同一程序片段
  当在一个函数中的几个地方要用到一个程序片段,而又没有将其包装成函数的必要,但把该片段在每个需要的地方复制粘贴又会使代码变的冗余时,这时候使用goto就很有价值。 最常见的例子就是进行错误处理。当程序查错成功时,函数需要返回进行错误处理,常见的处理方法是跳转到程序结尾,释放资源。而如果在一个函数中有多个错误处理,若不使用goto语句,就不可避免地会在每处错误下展开处理内容,例如下面这段代码:
  可见,goto语句在这里不但没有造成麻烦反而提高了程序的质量,使程序变得更条理更易于理解。
  (2) 从多层循环中跳出到循环外部
  众所周知,在C语言中,要想从一个循环内部跳出到循环外部,最常用的方法是使用break关键字。但是break一次只能跳出一层循环,当需要从多层循环直接跳出到循环外部时,break就显得有点捉襟见肘了。你只能在每层循环里都写一次break,这无疑降低了程序的效率。这时候goto的作用便显现出来了,使用goto语句可以轻而易举地从重重循环嵌套中脱身。以下代码作为一个例子:
  上述代码只是较为简单的三层循环,在规模较复杂的程序中多层循环应用得更加广泛,这时使用goto语句更胜一筹。但要注意一步到位,避免重复使用对其他功能造成影响。
  (3)提高程序的可读性
  尽管goto语句被人诟病最多的就是使程序难以被阅读和查错,但它某些情况下反而可以用来增加程序的清晰度。在上述前两对例子里我们就能够从中看出这一点。在第一个示例代码中,程序中的错误处理过程显得十分凌乱。而在使用goto语句后,无论从视觉还是逻辑上,整个程序都显得十分条理,井然有序;另一对例子也反映了该问题,使用break的那段代码明显不如使用goto语句的代码更容易看出程序的意图。由此看来,goto语句反而使程序变得更加容易阅读和理解。
  另一方面,goto语句还可以充当“注释”。例如我们在编写程序的时候有时会遇到一大段比较乱的代码,比如说字符集,不能优化的功能性片段等等。遇到这样的情况,就可以利用goto语句的标签将这段标记出来,易于编程者明确该片段的功能,方便其阅读代码。这种情况下,goto语句的使用不再是为了跳转到某处,而是起一个“注释”的作用,使程序变得更加清晰。
  3 goto语句的一个替代思路
  对goto语句有了如上的认识后就会明白,无条件反对goto语句根本不是明智之举,在一些情况下使用goto能够使编程事半功倍而并不破坏程序原有的结构性。goto的使用不仅仅是使用一个语句,而是一种解决问题的思路和编程习惯。有这样思考方式的人,即使不用goto也能探索出类似goto的逻辑来。以我在第二部分的第一个例子来讲,使用goto能够更好地解决错误处理问题,但有一个前提就是所有的变量定义必须要在goto之前,所以一般将变量全部放在函数头部定义。可是当函数规模较大时,有很多临时需要的变量并不能预期定义在goto语句之前,这就需要用新的机制来替换goto。在C++、JAVA等中我们可以考虑用异常处理机制来替换,而在C语言中,可以使用do-while(0)的方法替换goto,从而消除对变量的限制。例如第二部分第一个例子的程序可以改写成:
  4 总结
  综上所述,goto语句不应该随波逐流地被认为是一种“异类”,它本身并没有错,Dijkstra以及之后的人极力反对它是因为泛滥地使用goto将会导致软件难以理解和跟踪。我们要抵制的是难以理解和跟踪的程序而并不是goto语句本身。从上面的论述也可以知道,在恰当的情况下使用goto语句是会让程序更易于理解和跟踪的。如果不能够理解这一点,即使不使用goto语句,也有可能写出更为糟糕的代码。我们的出发点是:构造清晰易懂的代码。goto和其它语句及机制一样,都只是为我们这个思想服务的手段或工具。
  参考文献
  [1]于延,周国辉.C语言程序设计案例教程[M].清华大学出版社,2016.
  [2]侯卫周,郭浩,王娟玲.C语言结构化程序设计与GOTO语句之间的关系[J].平顶山学院学报,2006,21(2):32-34.
  [3]Dijkstra E W.Go to statement considered harmful[J].Communications of the Acm,1968,11(3):147-148.
  [4]Knuth,Donald E .Structured Programming with go to Statements[J].ACM Computing Surveys,1974,6(4):261-301.
转载注明来源:https://www.xzbu.com/1/view-14850403.htm

服务推荐