淺談golang package中init方法的多處定義及運行順序問題

在不瞭解這個問題之前,在網上搜索一下竟然搜出瞭兩個完全相反的結果,所以打算自己測試下這個問題。

首先給出結論:

在同一個package中,可以多個文件中定義init方法

在同一個go文件中,可以重復定義init方法

在同一個package中,不同文件中的init方法的執行按照文件名先後執行各個文件中的init方法

在同一個文件中的多個init方法,按照在代碼中編寫的順序依次執行不同的init方法

下面看下測試的代碼:

在當前目錄下新建main.go及testinit目錄,在testinit目錄下共有三個文件:123.go、ini1.go、ini2.go,各個源碼文件分別如下:

123.go

package testinit
import "fmt"
func init(){
    fmt.Println("123init")
}

ini1.go

package testinit
import "fmt"
func init(){
    fmt.Println("init1")
}
func init(){
    fmt.Println("init1-2")
}

ini2.go

package testinit
import "fmt"
func init(){
    fmt.Println("init2")
}

main.go

package main
import (
    _ "./testinit"
    "fmt"
)
func main(){
    fmt.Println("main")
}

如上main.go中導入testinit package,然後go build main.go,執行顯示如下:

這裡寫圖片描述

從運行的結構就能很清晰的看到,123、ini1、ini2三個文件按照文件名執行,對於ini1.go中的兩個ini方法按照init方法編寫的先後順序執行,最後才執行的main方法!

補充:Golang中defer、return、返回值和main、init函數的陷阱

Go語言中延遲函數defer充當著 cry…catch 的重任,使用起來也非常簡便,然而在實際應用中,很多gopher並沒有真正搞明白defer、return和返回值之間的執行順序。他們的特點:

多個defer的執行順序為“後進先出”;

defer、return、返回值三者的執行邏輯應該是:return最先執行,return負責將結果寫入返回值中;接著defer開始執行一些收尾工作;最後函數攜帶當前返回值退出。

如何解釋兩種結果的不同:

上面兩段代碼的返回結果之所以不同,其實從上面第2條結論很好理解。

a()int 函數的返回值沒有被提前聲名,其值來自於其他變量的賦值,而defer中修改的也是其他變量,而非返回值本身,因此函數退出時返回值並沒有被改變。

b()(i int) 函數的返回值被提前聲名,也就意味著defer中是可以調用到真實返回值的,因此defer在return賦值返回值 i 之後,再一次地修改瞭 i 的值,最終函數退出後的返回值才會是defer修改過的值。

package main 
import (
 "fmt"
)
 
func main() { 
 fmt.Println("c return:", *(c())) // 打印結果為 c return: 2
 
}
 
func c() *int {
 var i int
 defer func() {
  i++
  fmt.Println("c defer2:", i) // 打印結果為 c defer: 2
 }()
 
 defer func() {
  i++
  fmt.Println("c defer1:", i) // 打印結果為 c defer: 1
 }()
 return &i
}

雖然 c()*int 的返回值沒有被提前聲明,但是由於 c()*int 的返回值是指針變量,那麼在return將變量 i 的地址賦給返回值後,defer再次修改瞭 i 在內存中的實際值,因此函數退出時返回值雖然依舊是原來的指針地址,但是其指向的內存實際值已經被成功修改瞭。

Go裡面有兩個保留的函數:init函數(能夠應用於所有的package)和main函數(隻能應用於package main)。這兩個函數在定義時不能有任何的參數和返回值。雖然一個package裡面可以寫任意多個init函數,但這無論是對於可讀性還是以後的可維護性來說,我們都強烈建議用戶在一個package中每個文件隻寫一個init函數。

Go程序會自動調用init()和main(),所以你不需要在任何地方調用這兩個函數。每個package中的init函數都是可選的,但package main就必須包含一個main函數。

程序的初始化和執行都起始於main包。如果main包還導入瞭其它的包,那麼就會在編譯時將它們依次導入。有時一個包會被多個包同時導入,那麼它隻會被導入一次(例如很多包可能都會用到fmt包,但它隻會被導入一次,因為沒有必要導入多次)。

當一個包被導入時,如果該包還導入瞭其它的包,那麼會先將其它包導入進來,然後再對這些包中的包級常量和變量進行初始化,接著執行init函數(如果有的話),依次類推。

等所有被導入的包都加載完畢瞭,就會開始對main包中的包級常量和變量進行初始化,然後執行main包中的init函數(如果存在的話),最後執行main函數。

下圖詳細地解釋瞭整個執行過程:

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。

推薦閱讀: