https://waterflow.link/articles/
創(chuàng)新互聯(lián)建站長期為成百上千客戶提供的網(wǎng)站建設(shè)服務(wù),團隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為大姚企業(yè)提供專業(yè)的成都網(wǎng)站制作、成都做網(wǎng)站,大姚網(wǎng)站改版等技術(shù)服務(wù)。擁有10余年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
init 函數(shù)是用于初始化應(yīng)用程序狀態(tài)的函數(shù)。 它不接受任何參數(shù)并且不返回任何結(jié)果(一個 func() 函數(shù))。 初始化包時,將初始化包中的所有常量和變量聲明。 然后,執(zhí)行初始化函數(shù)。 下面是一個初始化主包的例子:
package main
import "fmt"
// 1
var a = func() int {
fmt.Println("var a")
return 0
}()
// 2
func init() {
fmt.Println("init")
}
// 1
var b = func() int {
fmt.Println("var b")
return 1
}()
// 3
func main() {
fmt.Println("main")
}
上面代碼的初始化順序是:
我們看下打印的結(jié)果:
go run 2.go
var a
var b
init
main
初始化包時會執(zhí)行一個 init 函數(shù)。 在下面的例子中,我們定義了兩個包,main 和 redis,其中 main 依賴于 redis。 首先, 2 .go 是主包:
package main
import (
"fmt"
"go-demo/100gomistakes/2/redis"
)
// 2
func init() {
fmt.Println("main init")
}
// 3
func main() {
err := redis.Store("ni", "hao")
fmt.Println(err)
}
我們可以看到main包中調(diào)用了redis包的方法。
我們再看下redis包中的內(nèi)容:
package redis
import "fmt"
// 1
func init() {
fmt.Println("redis init")
}
func Store(key, value string) error {
return nil
}
因為main依賴redis,所以先執(zhí)行redis包的init函數(shù),然后是main包的init,然后是main函數(shù)本身。上面的代碼中標(biāo)明了執(zhí)行順序。
我們可以為每個包定義多個初始化函數(shù)。 當(dāng)我們這樣做時,包內(nèi)的 init 函數(shù)的執(zhí)行順序是基于源文件的字母順序。 例如,如果一個包包含一個 a.go 文件和一個 b.go 文件,并且都有一個 init 函數(shù),則首先執(zhí)行 a.go init 函數(shù)。
但是如果我們把文件a.go改為ca.go,則會先執(zhí)行b.go的init函數(shù)。
所以我們不應(yīng)該依賴包中初始化函數(shù)的順序。 實際上,這可能很危險,因為可以重命名源文件,從而可能影響執(zhí)行順序。
下圖說明了這個問題:
當(dāng)然,我們還可以在同一個源文件中定義多個初始化函數(shù):
package main
import (
"fmt"
"go-demo/100gomistakes/2/redis"
)
func init() {
fmt.Println("main init1")
}
func init() {
fmt.Println("main init2")
}
func main() {
err := redis.Store("ni", "hao")
fmt.Println(err)
}
執(zhí)行順序是按照源文件順序執(zhí)行的,我們看下打印的結(jié)果:
go run 2.go
redis2 init
redis init
main init1
main init2
我們開發(fā)的時候經(jīng)常會使用gorm查詢數(shù)據(jù)庫,所以我們經(jīng)常會看到在初始化db連接的時候會有下面的代碼:
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
這種情況就是說,我們沒有強依賴mysql包(沒有直接使用mysql的公共函數(shù))。但是我們需要初始化mysql包里的一些數(shù)據(jù),也就是執(zhí)行mysql包里的init函數(shù)。這個時候我們就可以在這個包的前面加上_
。
需要注意的是init函數(shù)不能被當(dāng)作普通函數(shù)調(diào)用,會編譯報錯。
首先,讓我們看一個使用 init 函數(shù)可能被認為不合適的示例:持有數(shù)據(jù)庫連接池。 在示例的 init 函數(shù)中,我們使用 sql.Open 打開一個數(shù)據(jù)庫。 這個全局變量db會被其他函數(shù)調(diào)用:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
)
var db *sql.DB
func init() {
d, err := sql.Open("mysql",
"root:liufutian@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Panic(err)
}
err := db.Ping()
if err != nil {
log.Panic(err)
}
db = d
}
func main() {
}
在本例中,我們打開數(shù)據(jù)庫,檢查是否可以 ping 通,然后將其分配給全局變量。
但是這種實現(xiàn)會帶來一些問題:
在大多數(shù)情況下,我們應(yīng)該傾向于封裝一個變量而不是讓它保持全局。
由于這些原因,之前的初始化可能應(yīng)該封裝到一個函數(shù)中處理,如下所示:
func createDB() (*sql.DB, error) {
d, err := sql.Open("mysql",
"root:liufutian@tcp(127.0.0.1:3306)/test")
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return d, nil
}
這樣寫的話,我們解決了之前討論的主要缺點:
但是這樣就是不能使用init函數(shù)了么?在我們上面的引入mysql驅(qū)動的例子中,說明使用init還是有幫助的:
func init() {
sql.Register("mysql", &MySQLDriver{})
}
上面的例子,通過注冊提供的驅(qū)動名稱使數(shù)據(jù)庫驅(qū)動程序可用。