Go語言核心技術分析 卷一 之1 5 作用域

2021-12-29 20:43:14 字數 3005 閱讀 3636

變數的作用域是指程式**中可以有效使用這個變數的範圍。不要將作用域和生命期混在一起。作用域是**中的一塊區域,是乙個編譯期的屬性;生命期是程式執行期間變數存活的時間段,在此時間段內,變數可以被程式的其它部分所引用,是執行期的概念。

語法塊是包含在花括號內的一系列語句,例如函式體或者迴圈體。語法塊內部宣告的變數是無法被語法塊外部**訪問的。我們可以擴充套件區域性語法塊的概念,在某些場景下,是不需要花括號的,這種形式稱之為詞法塊。詞法塊分為幾種:全域性詞法塊,包含所有源**;包 詞法塊,包含整個package;檔案詞法塊,包含整個檔案;for、if、switch語句的詞法塊;switch或select中的case分支的詞法塊;當然也包含之前提到的語法塊

宣告語句的詞法塊決定了變數的作用域。go語言的內建型別、內建函式、內建常量都是全域性詞法塊,因此它們都是全域性作用域的,例如int、len、true等,可以在整個程式直接使用;對於匯入的包,例如temconv匯入的fmt包,是檔案詞法塊,因此只能在當前檔案中訪問fmt包,這裡fmt是全檔案範圍的作用域;tempconv.ctof函式中的變數c,則是區域性詞法塊(語法塊)的,因此它的作用域是函式的內部。

控制語句後面的標籤(label),例如break、continue或goto後的標籤,它們的作用域是在控制語句所在的函式內部。

乙個程式可能會有多個相同的變數名,只要它們的宣告在不同的詞法塊就好。例如,你可以在函式內宣告乙個區域性變數x,同時再宣告乙個包級的變數x,這是在函式內部,區域性變數x就會替代後者,這裡稱之為shadow,意味著在函式作用域內區域性變數將包變數隱藏了。

當編譯器遇到乙個變數名的引用時,會去搜尋該變數的宣告語句,首先從最內部的詞法塊開始,然後直到全域性詞法塊。如果編譯期找不到變數名的宣告語句,那麼就會報錯:undeclared name。如果變數名在內部的詞法塊和外部的詞法塊同時宣告,那麼根據編譯期的搜尋規則,內部的詞法塊會先找到。在這種情況下,內部的宣告會隱藏外部的宣告(shadow),此時,外部宣告的變數是無法訪問的:

func f() {}

var g = "g"

func main() 在函式內部,詞法塊可以巢狀任意層數,因此本地變數可以隱藏外部變數。大多數的語法塊(花括號)是通過控制語句if、for等建立的,下面的程式有三個不同的x變數,每個變數都是宣告在不同的詞法塊中(這段**主要是為了說明作用域的規則,這種編碼風格並不提倡!):func main()

}}表示式x[i]和x + 'a' - 'a' 分別引用了不同的x變數,後面會解釋。

就像之前提到的那樣,不是所有的詞法塊都有顯式的花括號。上面的for迴圈建立了兩個詞法塊:帶花括號的迴圈主體,顯式詞法塊;還有不帶花括號的隱式詞法塊,例如for迴圈的條件語句中宣告乙個變數i。這裡i的作用域包含for的條件語句和for的主體。

下面的例子也建立了三個變數x,每個都在不同的詞法塊中宣告,乙個在函式主體中,乙個在for的隱式詞法塊中,還有乙個在顯式詞法塊-迴圈主體中,這其中只有1和3是顯式詞法塊:

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中也有類似的規則:除了條件詞法塊外,每個case也有自己的詞法塊。

對於包級變數來說,宣告的順序和作用域是無關的,所有乙個包級變數宣告時可以引用它自身也可以引用在它之後宣告的包級變數,然而,如果乙個變數或者常量在宣告時引用了它自己,編譯器會報錯。

看下面的程式:

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

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

f.close() // compile error: undefined ff的作用域僅僅是if語句,因此在if之外的詞法塊是不可訪問的,報編譯錯誤。

這裡可以更改**,提前宣告f變數:

f, err := os.open(fname)

if err != nil

f.readbyte()

f.close()如果不想在外部詞法塊宣告變數,可以這麼寫:if f, err := os.open(fname); err != nil else 但是第三種不是go推薦的寫法,第二種比較合適,將正常邏輯和錯誤處理分離。

短宣告變數的作用域是要特別注意的,考慮下面的程式,開始時會獲取當前的工作目錄,儲存在乙個包級變數中。這個本來可以通過在main函式中呼叫os.getwd來完成,但是用init函式將這塊兒邏輯從主邏輯中分離是乙個更好的選擇,特別是因為獲取目錄的操作可能會是失敗的,這個時候需要處理返回的錯誤。函式log.fatalf會列印一條資訊,然後呼叫os.exit(1)終結程式:

var cwd string

func init()

}這裡cwd和err在init的詞法塊中都沒有宣告過,因此 := 語句會將它們兩宣告為本地變數。init內部的cwd宣告會隱藏外部的,因此這個程式沒有達到更新包級變數cwd的目的。

當前版本,go編譯器會檢測到本地變數cwd從未使用,因此會報錯,但是這種檢查並不是很嚴格,例如,如果在log.fatalf中列印cwd的值(這時本地變數cwd會被使用),那麼這種錯誤就會被隱藏!!!

var cwd string

func init()

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

}這裡全域性變數cwd沒有得到正確的初始化,同時,log函式使用了cwd本地變數,隱藏了這個bug。

有一些辦法可以處理這種潛在的錯誤,最直接的就是避免使用:=,通過var來宣告err變數:

var cwd string

func init()

}在這一章裡,我們簡單學習了包,檔案,宣告,語句等。在接下來的兩章,我們會學習資料結構

Java核心技術卷之位操作

處理整型型別時,可以對組成整型數值的各個位完成操作。位運算子包括 and or xor not 這些運算子按位模式處理 基本用法 public static void main string args 26的二進位制表示為11010,與1000進行 操作,第四位為1,其餘為零,轉化為十進位制是8 當...

c語言核心技術 一

c語言編譯分析記號,有乙個原則是盡可能靠左合併符號使其得到符合語法的記號,所以 a b 會被解釋為 a b 而不是 a b 函式作用域和語句塊作用域 一直以為函式內部申明的識別符號其作用域就叫做函式作用域,其實應該是語句塊作用域,它和函式作用域還是有區別的,語句塊作用域的作用範圍是從申明處開始,到包...

Docker 核心技術之映象

映象是乙個docker的可執行檔案,其中包括執行應用程式所需的所有 內容 依賴庫 環境變數和配置檔案等。通過映象可以建立乙個或多個容器。作用 命令格式 命令引數 options 只搜尋官方的 centos docker search f is official true centos作用 命令格式 ...