什么是goto語句?
goto 語句被稱為 C 語言中的跳轉語句。
用于無條件跳轉到其他標簽。它將控制權轉移到程序的其他部分。
goto 語句一般很少使用,因為它使程序的可讀性和復雜性變得更差。
語法
goto?label;
goto 語句示例
讓我們來看一個簡單的例子,演示如何使用 C 語言中的 goto 語句。
打開 Visual Studio 創建一個名稱為:goto 的工程,并在這個工程中創建一個源文件:goto-statment.c,其代碼如下所示:
#include??? void?main() { ??int?age; gotolabel: ??printf("You?are?not?eligible?to?vote! "); ??printf("Enter?you?age: "); ??scanf("%d",?&age); ??if?(age?18)? ??{ ???goto?gotolabel; ??} ??else ??{ ???printf("You?are?eligible?to?vote! "); ??} }
執行上面代碼,得到以下結果
You?are?not?eligible?to?vote! Enter?you?age: 12 You?are?not?eligible?to?vote! Enter?you?age: 18 You?are?eligible?to?vote!
為什么它這么不受待見?
二十幾年前,當計算機編程尚處于起步階段時,程序流程是由 “GOTO” 語句來控制。
該類語句允許程序員對當前代碼行斷行,而直接進入另一個不同的代碼段。
列表 1 為簡單的示例。
圖片
編程語言終究開始引入了函數的概念,即允許程序對代碼進行斷行。
如果已經完成,不再使用 goto 語句來表示代碼的斷行。
函數調用后,函數將回到下一條指令。列表2 為示例。
圖片
這一做法改善了程序結構,提高了可讀性。自此,這被視為編寫程序的正確方法。
只要看到或想到 goto 語句,就會讓軟件工程師退縮,產生本能的厭惡。
在 wikipedia 上的解釋就是;
GOTO語句一直是批評和爭論的目標,主要的負面影響是使用GOTO語句使程序的可讀性變差,甚至成為不可維護的「面條代碼」。
隨著結構化編程在二十世紀六十年代到七十年代變得越來越流行,許多計算機科學家得出結論,即程序應當總是使用被稱為「結構化」控制流程的命令,以及 if-then-else 語句來替代 GOTO。
甚至在今天,許多程序風格編碼標準禁止使用 GOTO 語句。
也有不少人為 GOTO 語句辯護,他們認為只要加以限制地使用 GOTO 語句不會導致低質量的代碼,并且在許多編程語言中,一些功能難以在不使用 GOTO 語句的情況下實現。
比如有限狀態機的實現、跳出嵌套循環以及異常處理等等。
大概最著名的對于 GOTO 的批評是艾茲格·迪杰斯特拉(Edsger Wybe Dijkstra)在1968年的一篇名為《GOTO陳述有害輪》的論文。
迪杰斯特拉認為不加限制地使用GOTO語句應當從高級語言中廢止,因為它使分析和驗證程序正確性(特別是涉及循環)的任務變得復雜。
另外一種觀點出現在高德納的Structured Programming with go to Statements [3]中,文章分析了許多常見編程任務,然后發現其中的一些使用GOTO將得到最理想的結構。
限制GOTO
許多語言,如 C 語言和 Java,提供了相關的控制流語句,如 break 和 continue,它們都是有效地被限制的 goto 語句。
它們的作用是無條件跳轉,但是只能夠跳到循環塊結束的位置——繼續進入下一循環(continue)或者結束循環(break)
switch/case結構
C 語言、C++ 和 Java 中的 switch 語句高效地實現了一個多路 goto,跳轉目標由表達式的值來選擇。
這也導致了我們沒有不得不使用 goto 的理由。
針對這些,導致目前 goto 的使用情況是這樣的:
goto 語句的結果:在C/C++等高級編程語言中保留了goto語句,但被建議不用或少用。
在一些更新的高級編程語言,如 Java 不提供 goto 語句,它雖然指定 goto 作為關鍵字,但不支持它的使 用,使程序簡潔易讀;
盡管如此后來的 c# 還是支持 goto 語句的,goto 語句一個好處就是可以保證程序存在唯一的出口,避免了過于龐大的 if 嵌套。
另一方面,goto 語句只是不提倡,當然不是禁用,那么在什么情況下可以使用 goto 語句呢?
可以考慮使用 goto 的情形:
從多重循環中直接跳出 ;
出錯時清除資源;
可增加程序的清晰度的情況。
不加限制地使用 goto:破壞了清晰的程序結構,使程序的可讀性變差,甚至成為不可維護的"面條代碼"。
經常帶來錯誤或隱患,比如它可能跳過了某些對象的構造、變量的初始化、重要的計算等語句。
下列關于使用 goto 語句的原則可以供讀者參考。
使用 goto 語句只能 goto 到同一函數內,而不能從一個函數里 goto 到另外一個函數里。
使用 goto 語句在同一函數內進行 goto 時,goto 的起點應是函數內一段小功能的結束處,goto 的目的 label 處應是函數內另外一段小功能的開始處。
不能從一段復雜的執行狀態中的位置 goto 到另外一個位置,比如,從多重嵌套的循環判斷中跳出去就是不允許的。
應該避免像兩個方向跳轉。這樣最容易導致"面條代碼"。
閱讀過 linux 內核代碼的同學應該注意到,linux 內核代碼里面其實有不少地方用了 goto 語句,
這是在/drivers/i2c/i2c-dev.c中的i2c_dev_init函數:
static?int?__init?i2c_dev_init(void) { ?int?res; ?pr_info("i2c?/dev?entries?driver "); ?res?=?register_chrdev_region(MKDEV(I2C_MAJOR,?0),?I2C_MINORS,?"i2c"); ?if?(res) ??goto?out; ?i2c_dev_class?=?class_create(THIS_MODULE,?"i2c-dev"); ?if?(IS_ERR(i2c_dev_class))? ?{ ??res?=?PTR_ERR(i2c_dev_class); ??goto?out_unreg_chrdev; ?} ?i2c_dev_class->dev_groups?=?i2c_groups; ?/*?Keep?track?of?adapters?which?will?be?added?or?removed?later?*/ ?res?=?bus_register_notifier(&i2c_bus_type,?&i2cdev_notifier); ?if?(res) ??goto?out_unreg_class; ?/*?Bind?to?already?existing?adapters?right?away?*/ ?i2c_for_each_dev(NULL,?i2cdev_attach_adapter); ?return?0; out_unreg_class: ?class_destroy(i2c_dev_class); out_unreg_chrdev: ?unregister_chrdev_region(MKDEV(I2C_MAJOR,?0),?I2C_MINORS); out: ?pr_err("Driver?Initialisation?failed "); ?return?res; }
但是你會發現,這些地方的goto語句,使用非常謹慎,基本都遵循上面提到的幾個原則。
審核編輯:黃飛
評論
查看更多