Go 程式結構 作用域

2021-08-07 04:07:23 字數 3219 閱讀 2726

乙個宣告語句將程式中的實體和乙個名字關聯,比如乙個函式或乙個變數。宣告語句的作用域是指源**中刻意有效使用這個名字的範圍。

不要將作用域和宣告週期混為一談。作用域對應的是乙個源**的文字區域,它是編譯時屬性;生命週期是指程式中物件存在的有效時間段,在此時間段內,它可以被程式的其它部分引用,是乙個執行時的概念。

語法塊是由花括號所包含的一些列語句,就像函式體或迴圈體那樣。塊內部宣告的物件是無法被外部成員訪問的。有乙個語法塊可以包含整個源**中宣告的物件,稱為全域性語法塊;然後是每個包的包語法塊;再然後是函式內部的語法塊;再然後是for、if和switch語句的語法塊;最後是這些語句內部的由花括號顯示指定的語法塊。

整個源**》包》檔案》函式》迴圈/分支》迴圈/分支內部顯示花括號

宣告語句對應的詞法域決定了作用域範圍的大小。對於內建的型別、函式和常量,比如 int、len和true等是全域性作用域,因此可以在整個程式中直接使用。任何在函式外部(包級)宣告的名字可以在同乙個包的任何原始檔中訪問。對於匯入的包,例如tempconv匯入的fmt包,則是對應檔案級的作用域,因此只能在當前的檔案中訪問匯入的fmt包,當前包的其它原始檔無法訪問此原始檔匯入的包。還有許多宣告語句,比如tempconv.ctof函式中的變數c,則是區域性作用域,它只能在函式內部(甚至是函式內部的某些部分)訪問。

控制流標號,就是break、continu或goto語句後面跟著的名字,則是函式級的作用域。

乙個程式可能包含多個同名的宣告,只要它們在不同的詞法域就沒有關係。例如,你可以宣告乙個區域性變數和包級變數同名。或者像前面所寫將乙個函式引數的名字宣告為new,雖然內建的new是全域性作用域。但是如果濫用不同詞法域可同名的特性,可能導致程式很難閱讀。

當編譯器遇到乙個名字引用時,如果它看起來像乙個宣告,它會從最內層的區域性作用域向全域性作用域查詢。如果未找到,則報告「未宣告的名字」這樣的錯誤。如果該名字在內部和外部都宣告過,則首先在內部被找到。在這種情況下,內部宣告遮蔽了外部同名的宣告,讓外部宣告的名字無法被訪問:

func f() {}

var g = "g"

func main()

在函式中詞法域可以深度巢狀,因此內部的乙個宣告可能遮蔽外部的宣告。下面的**有三個不同的變數x,它們定義在不同的詞法域:

func main() 

}}

在 x[i ]和 x + 'a' - 'a'宣告語句的初始化表示式中,都引用了外部作用域宣告的

變數x,稍後我們會解釋這個。

正如上面例子所示,並不是所有的詞法域都顯示地對應到由花括號包含的語句,還有一些隱式的規則。上面的for語句建立了兩個詞法域:花括號包含的for迴圈體是顯示部分,

另乙個隱式部分則是迴圈的初始化部分,比如用於迭代變數的初始化。隱式的詞法域部分還包含條件測試語和i++。

下面的例子同樣有三個不同的變數x,每個宣告在不同的詞法域,乙個在函式體詞法域,乙個在for隱式的初始化詞法域,乙個在for迴圈體詞法域;只有兩個是顯示建立的。

func main() 

}

和for迴圈類似,if和switch語句也會在條件部分建立隱式詞法域,還有他們對應的執行體詞法域。下面的if-else 演示了x和y的有效作用域範圍。

if x := f(); x == 0  else

if y := g(x); x == y else

fmt.println(x, y) // compile error: x and y are not visible here

第二個if語句巢狀在第乙個內部,因此第乙個if語句條件初始化詞法域宣告的變數在第二個if中也可以訪問。switch語句的每個分支也有類似的詞法域規則:條件部分是乙個隱式詞法域,然後是每個分支的詞法域。

在包級別宣告的順序並不會影響作用域範圍,因此乙個先宣告的可以引用它自身或者是引用後面的乙個宣告,這可以讓我們定義一些互相巢狀或遞迴的型別或函式。但是如果乙個變數或常量遞迴引用了自身,則會產生編譯錯誤。

if f, err := os.open(fname); err != nil 

f.readbyte() // compile error: undefined f

f.close() // compile error: undefined f

變數f的作用域只有在if語句內,因此後面的語句將無法引用它,這將導致編譯錯誤。

通常需要在if之前宣告變數,這樣可以確保後面的語句依然可以訪問該變數:

f, err := os.open(fname)

if err != nil

f.readbyte()

f.close()

你可能會考慮通過將readbyte和close移動到if的else塊來解決這個問題:

if f, err := os.open(fname); err != nil  else
但這不是go語言推薦的做法,go語言的習慣是在if中處理錯誤然後直接返回,這樣可以確保正常執行的語句不需要**縮排。

要特別注意短變數宣告語句的作用域範圍,考慮下面的程式,它的目的是獲取當前的工作目錄然後儲存到乙個包級別的變數中。這本來可以直接通過呼叫os.getwd完成,但是將這個從主邏輯中分離出來可能會更好,特別是在需要處理錯誤的時候。函式log.fataif用於列印日誌資訊,然後呼叫os.exit(1)終止程式。

var cwd string

func init()

}

雖然cwd在外部已經生命果,但是:=語句還是將cwd重新宣告為新的區域性變數。因為內部宣告的cwd將遮蔽外部的宣告,因此上面的**並不會正確更新包級別的變數cwd。由於當前的編譯器會檢測到區域性變數cwd並沒有使用,所以會編譯失敗。但是這種檢測並不可靠。因為一些小的**變更,例如增加乙個區域性cwd的列印語句,就可能導致這種檢測失效。

var cwd string

func init()

log.printf("working directory = %s", cwd)

}

全域性的cwd變數依然沒有被正確地初始化,而且看似正常的日誌輸出更是讓這個bug更加隱晦。

有許多方式可以避免出現類似潛在的問題。最直接的方法是通過單獨宣告err變數,來避免使用:=的簡短宣告方式:

var cwd string

func init()

}

Go 語法 程式結構

背景 go 語言和其他的語言一樣,龐大的程式都是由小的基本元件構建而來 名稱go 語言有25 個關鍵字,不可用作名稱 go 語言還有 三十幾個內建的與宣告的常量 型別和函式 notice 宣告宣告給乙個程式實體命名,並設定其部分或全部屬性。有 4 個主要的宣告 example 以乙個例項分別介紹 4...

Go語言程式結構結構

4 賦值 5 型別 go語言的基礎組成包括 例如 package main import fmt func main go語言中的函式名 變數名 常量名 型別名 語句標號和包名等所有的命名,都遵循乙個簡單的命名規則 名字必須以乙個字母或下劃線開頭,後面可以跟任意數量的字母 數字或下劃線 區分大小寫 ...

Go語言的程式結構

go 語言的命名規則和c語言的命令沒什麼不同,都是由數字,下劃線,字母組成,且必須以下劃線或者字母開頭。大小寫敏感 var a int 和 var a int是不同變數 go語言的關鍵字主要包括25個 break default func inte ce select case defer goma...