Go 中的 init 函數
[導讀] Go 語言 init 函數是什麼?怎麼合理使用 init 函數簡化代碼?本文做了詳細介紹。
正文
我們知道 Go 程序的入口是 main 函數,當 main 函數退出了,程序也就退出了。init 函數在 Go 程序中也扮演着重要的角色。這篇文章將會介紹 init 函數的特性以及如何使用它們。
init 函數的作用:
-
變量初始化
-
檢查和修復程序狀態
-
運行前註冊,例如 decoder,parser 的註冊
-
運行只需計算一次的模塊,像 sync.once 的作用
-
其他
包初始化
如果需要使用一個導入的包,首先要對這個包進行初始化,這一步在 main 函數執行之前,由 runtime 來完成,分以下步驟:
-
初始化導入的包;
-
初始化包作用域中的變量;
-
執行包中的 init 函數。
如果某個包被導入了多次,也只會執行一次包的初始化。
初始化順序
Go 一個包中可以包含很多文件,那麼變量的初始化順序與各個包的 init 函數執行順序又是怎樣的呢?
首先,runtime 的初始化依賴機制會啓動,當初始化依賴機制計算完成後,就需要決定 a.go 和 z.go 中的變量誰先初始化,這取決於呈現給編譯器的文件順序,一般來說是按文件名的字典序,但是變量間或各個包間有依賴需要另外討論。如果 z.go 先被傳到 build 系統,那麼 z.go 的變量初始化就比 a.go 先一步完成。
同一個包中,變量的初始化順序是按文件名的字典序,但同時 runtime 也會解析變量間依賴關係,沒有依賴的變量最先初始化,init 函數的執行順序也同理。
來看下面按文件名字典序初始化的例子:
sandbox.go
package main
import "fmt"
var _ int64 = s()
func init() {
fmt.Println("init in sandbox.go")
}
func s() int64 {
fmt.Println("calling s() in sandbox.go")
return 1
}
func main() {
fmt.Println("main")
}
a.go
package main
import "fmt"
var _ int64 = a()
func init() {
fmt.Println("init in a.go")
}
func a() int64 {
fmt.Println("calling a() in a.go")
return 2
}
z.go
package main
import "fmt"
var _ int64 = z()
func init() {
fmt.Println("init in z.go")
}
func z() int64 {
fmt.Println("calling z() in z.go")
return 3
}
程序輸出:
calling a() in a.go
calling s() in sandbox.go
calling z() in z.go
init in a.go
init in sandbox.go
init in z.go
main
下面是按依賴關係決定初始化順序的例子。
pack.go
package pack
import (
"fmt"
"test_util" // 引入test_util包
)
var Pack int = 6
func init() {
a := test_util.Util
fmt.Println("init pack ", a)
}
test_util.go
package test_util
import "fmt"
var Util int = 5
func init() {
fmt.Println("init test_util")
}
main.go
package main
import (
"fmt"
"pack"
"test_util"
)
func main() {
fmt.Println(pack.Pack)
fmt.Println(test_util.Util)
}
輸出:
init test_util
init pack 5
6
5
由於 pack 包的初始化依賴 test_util,因此運行時會先初始化 test_util 包再初始化 pack 包;
init 函數的特性
init 函數不需要傳入參數也沒有返回值,而且 init 函數是不能被其他函數調用的。
package main
import "fmt"
func init() {
fmt.Println("init")
}
func main() {
init()
}
上面的代碼會報編譯錯誤:undefined: init。
在一個文件中也可以有多個 init 函數,看下面代碼。
sandbox.go
package main
import "fmt"
func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}
utils.go
package main
import "fmt"
func init() {
fmt.Println("init 3")
}
輸出:
init 1
init 2
init 3
main
init 函數的也廣泛用在標準庫中,比如math
,bzip2
,image
。
最常用的是初始化不能使用初始化表達式的變量,也就是不能在變量聲明的時候初始化的變量,看以下例子。
var square [10]int
func init() {
for i := 0; i < 10; i++ {
square[i] = i * i
}
}
只是爲了執行 init 函數而導入包
我們經常會在開源代碼中見到有些導入的包中前面加了個下劃線”_“,這表示只是想執行包中的 init 函數。
import _ "image/png"
image/png
包裏的 init 函數作用是向image
包註冊 png 圖片的解碼器,見 src/image/png/reader.go
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
總結
小心並且不要濫用 init 函數,因爲對於複雜點的項目來說,init 函數的執行順序難以捉摸。
轉自:deletelazy
juejin.cn/post/6844903864555012104
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/6XmBqvfBMB5siA9_f-CrOQ