長沙網(wǎng)站制作收費明細表網(wǎng)站宣傳文案
Go包介紹與初始化:搞清Go程序的執(zhí)行次序
文章目錄
- Go包介紹與初始化:搞清Go程序的執(zhí)行次序
- 一、main.main 函數(shù):Go 應用的入口函數(shù)
- 1.1 main.main 函數(shù)
- 1.2 main.main 函數(shù)特點
- 二、包介紹
- 2.1 包介紹與聲明
- 2.2 非 main包的 main 函數(shù)
- 2.3 包的命名規(guī)則
- 三、包的導入
- 3.1 包的導入介紹
- 3.2 導入多個包
- 3.2 包的別名
- 四、神器的下劃線
- 4.1 下劃線的作用
- 4.2 下劃線在代碼中
- 4.3 下劃線在import中
- 五、init 函數(shù):Go 包的初始化函數(shù)
- 5.1 init 函數(shù) 介紹
- 5.2 init 函數(shù) 特點
- 六、Go 包的初始化次序
- 6.1 包的初始化次序探究
- 6.2 包的初始化原則
- 七、init 函數(shù)的常用用途
- 7.1 用途一:重置包級變量值
- 7.2 用途二:實現(xiàn)對包級變量的復雜初始化
- 7.3 用途三:在 init 函數(shù)中實現(xiàn)“注冊模式”
一、main.main 函數(shù):Go 應用的入口函數(shù)
1.1 main.main 函數(shù)
在Go語言中,main
函數(shù)是任何Go應用的入口函數(shù)–用戶層入口。當你運行一個Go程序時,操作系統(tǒng)會首先調(diào)用main
函數(shù),然后程序開始執(zhí)行。main
函數(shù)的函數(shù)原型是這樣的:
package mainfunc main() {// 用戶層執(zhí)行邏輯... ...
}
你的程序的執(zhí)行會從main
函數(shù)開始,會在這個函數(shù)內(nèi)按照它的調(diào)用順序展開。
1.2 main.main 函數(shù)特點
main.main
函數(shù)是Go應用程序的入口函數(shù),它具有一些特點和規(guī)定,使得Go程序的執(zhí)行流程有一定的規(guī)范性。以下是關(guān)于main.main
函數(shù)的特點:
- 唯一入口點: 在一個Go應用程序中,只能有一個
main.main
函數(shù)。這是整個程序的唯一入口點,程序的執(zhí)行將從這里開始。如果存在多個main
函數(shù),編譯時會報錯。 - 不接受參數(shù):
main.main
函數(shù)不接受任何參數(shù),它沒有輸入?yún)?shù),也沒有返回值。這是Go語言的規(guī)定,而程序的命令行參數(shù)通常通過os.Args
等方式獲取。
二、包介紹
2.1 包介紹與聲明
在Go中,包(Package)是組織和管理代碼的基本單元。包包括一組相關(guān)的函數(shù)、類型和變量,它們可以被導入到其他Go文件中以便重復使用。Go標準庫以及第三方庫都是以包的形式提供的。
每個Go文件都屬于一個包,你可以使用package
關(guān)鍵字來指定聲明一個文件屬于哪個包。例如:
package main
2.2 非 main包的 main 函數(shù)
除了 main 包外,其他包也可以擁有自己的名為 main 的函數(shù)或方法。但按照 Go 的可見性規(guī)則(小寫字母開頭的標識符為非導出標識符),**非 main 包中自定義的 main 函數(shù)僅限于包內(nèi)使用,**就像下面代碼這樣,這是一段在非 main 包中定義 main 函數(shù)的代碼片段:
package pkg1import "fmt"func Main() {main()
}func main() {fmt.Println("main func for pkg1")
}
你可以看到,這里 main 函數(shù)就主要是用來在包 pkg1
內(nèi)部使用的,它是沒法在包外使用的。
2.3 包的命名規(guī)則
- 在Go語言中,包的名稱通常使用小寫字母,具有簡潔的、描述性的名稱。這有助于提高代碼的可讀性和可維護性。標準庫中的包通常具有非常清晰的包名,例如
fmt
、math
、strings
等。 - 在Go語言中,包級別的標識符(變量、函數(shù)、類型等)的可見性是由其首字母的大小寫來決定的。如果一個標識符以大寫字母開頭,它就是可導出的(公有的),可以被其他包訪問。如果以小寫字母開頭,它就是包內(nèi)私有的,只能在包內(nèi)部使用。
三、包的導入
3.1 包的導入介紹
要在Go程序中使用其他包的功能,你需要導入這些包。使用import
關(guān)鍵字來導入包,導入語句通常放在文件的頂部。一個典型的包導入語句的格式如下:
import "包的導入路徑"
其中,包的導入路徑
是指被導入包的唯一標識符,通常是包的名稱或路徑,它用于告訴Go編譯器去哪里找到這個包的代碼。
例如,導入標準庫中的fmt
包可以這樣做:
import "fmt"
然后,你就可以在你的程序中使用fmt
包提供的函數(shù)和類型。
3.2 導入多個包
在Go程序中,你可以一次導入多個包,只需在import
語句中列出這些包的導入路徑,用括號()括起來并以括號內(nèi)的方式分隔包的導入路徑。
示例:
import ("fmt""math""net/http"
)
這個示例中導入了fmt
、math
和net/http
三個包。這種方式使你可以更清晰地組織你的導入語句,以便程序更易讀。
注意:Go語言的編譯器會自動檢測哪些導入的包是真正被使用的,未使用的導入包不會引起編譯錯誤,但通常被視為不良實踐。在Go中,未使用的導入包可能會引起代碼不清晰,因此應該避免導入不需要的包。
3.2 包的別名
在Go語言中,你可以使用包的別名(package alias)來為一個導入的包賦予一個不同的名稱,以便在代碼中引用它。包的別名通常用于以下情況:
- 避免包名沖突:當你導入多個包時,有可能出現(xiàn)包名沖突,此時你可以為一個或多個包使用別名來解決沖突。
- 簡化包名:有時,包的導入路徑可能很長,為了減少代碼中的冗長,你可以為包使用一個短的別名。
使用包的別名是非常簡單的,只需在導入語句中使用as
關(guān)鍵字為包指定一個別名。以下是示例:
import fm "fmt"
在上面的示例中,fm
是fmt
包的別名?,F(xiàn)在,你可以在代碼中使用fm
來代替fmt
,例如:
fm.Println("Hello, World!")
這樣,你就可以使用更短的fm
來調(diào)用fmt
包的函數(shù),以減少代碼中的冗長。
包的別名可以根據(jù)需要自定義,但通常建議選擇一個有意義的別名,以使代碼更易讀。使用別名時要注意避免產(chǎn)生混淆,要確保別名不與其他標識符(如變量名或函數(shù)名)發(fā)生沖突。
四、神器的下劃線
4.1 下劃線的作用
下劃線 _
在Go語言中用于以下幾個不同的場景:
- 匿名變量:
_
可以用作匿名變量,用于忽略某個值。當你希望某個值返回但又不需要使用它時,可以將其賦值給_
。 - 空標識符:
_
也被稱為空標識符,它用于聲明但不使用變量或?qū)氚皇褂冒臉俗R符。這是為了確保代碼通過編譯,但不會產(chǎn)生未使用變量或包的警告。
4.2 下劃線在代碼中
在代碼中,下劃線 _
可以用作匿名變量,用于忽略某個值。這通常在函數(shù)多返回值中使用,如果你只關(guān)心其中的某些值而不需要其他返回值,可以將其賦值給 _
。
示例:
x, _ := someFunction() // 忽略第二個返回值
在上面的示例中,_
用于忽略 someFunction
函數(shù)的第二個返回值。
4.3 下劃線在import中
- 當導入一個包時,該包下的文件里所有
init()
函數(shù)都會被執(zhí)行,然而,有些時候我們并不需要把整個包都導入進來,僅僅是是希望它執(zhí)行init()
函數(shù)而已。 - 這個時候就可以使用
import _
引用該包。即使用 import _ 包路徑 只是引用該包,僅僅是為了調(diào)用init()函數(shù),所以無法通過包名來調(diào)用包中的其他函數(shù)。
以下是一個示例,演示如何使用 import _
引用一個包以執(zhí)行其 init()
函數(shù):
項目結(jié)構(gòu):
src
|
+--- main.go
|
+--- hello|+--- hello.go
main.go
文件
package mainimport _ "./hello"func main() {// hello.Print() //編譯報錯:./main.go:6:5: undefined: hello
}
hello.go
文件
package helloimport "fmt"func init() {fmt.Println("imp-init() come here.")
}func Print() {fmt.Println("Hello!")
}
輸出結(jié)果:
imp-init() come here.
五、init 函數(shù):Go 包的初始化函數(shù)
5.1 init 函數(shù) 介紹
init
函數(shù)是在Go包的初始化階段自動調(diào)用的函數(shù)。它的目的是執(zhí)行一些包級別的初始化工作,例如設(shè)置變量、初始化數(shù)據(jù)、連接數(shù)據(jù)庫等。init
函數(shù)沒有參數(shù),也沒有返回值,它的定義形式如下:
func init() {// 包初始化邏輯... ...
}
5.2 init 函數(shù) 特點
init
函數(shù)有以下特點:
- 自動執(zhí)行:
init
函數(shù)不需要手動調(diào)用,它會在程序啟動時自動執(zhí)行。這確保了包的初始化工作在程序開始執(zhí)行之前完成。 - 包級別:
init
函數(shù)是包級別的,因此它只能在包的內(nèi)部定義。不同包中的init
函數(shù)互不影響,它們獨立執(zhí)行。 - 多個
init
函數(shù): 一個包可以包含多個init
函數(shù),它們按照定義的順序依次執(zhí)行。被導入的包的init
函數(shù)會在導入它的包的init
函數(shù)之前執(zhí)行。 - 沒有參數(shù)和返回值: 和前面
main.main
函數(shù)一樣,init
函數(shù)也是一個無參數(shù)無返回值的函數(shù),它只用于執(zhí)行初始化工作,而不與其他函數(shù)交互。 - 順序執(zhí)行: 由于
init
函數(shù)的執(zhí)行順序是根據(jù)包的導入順序確定的,因此在編寫代碼時應該謹慎考慮包的依賴關(guān)系,以確保正確的初始化順序。 - 可用于注冊和初始化:
init
函數(shù)通常用于執(zhí)行包的初始化工作,也可用于在導入包時注冊一些功能,例如數(shù)據(jù)庫驅(qū)動程序的注冊。
這里要特別注意的是,在 Go 程序中我們不能手工顯式地調(diào)用 init
,否則就會收到編譯錯誤,就像下面這個示例,它表示的手工顯式調(diào)用 init 函數(shù)的錯誤做法:
package mainimport "fmt"func init() {fmt.Println("init invoked")
}func main() {init()
}
構(gòu)建并運行上面這些示例代碼之后,Go 編譯器會報下面這個錯誤:
$go run call_init.go
./call_init.go:10:2: undefined: init
接著,我們將代碼修改如下:
package mainimport "fmt"func init() {fmt.Println("init invoked")
}func main() {fmt.Println("this is main")
}
Go 編譯器運行結(jié)果如下:
init invoked
this is main
我們看到,在初始化 Go 包時,Go 會按照一定的次序,逐一、順序地調(diào)用這個包的 init 函數(shù)。一般來說,先傳遞給 Go 編譯器的源文件中的 init 函數(shù),會先被執(zhí)行;而同一個源文件中的多個 init 函數(shù),會按聲明順序依次執(zhí)行。所以說,在Go中,main.main
函數(shù)可能并不是第一個被執(zhí)行的函數(shù)。
六、Go 包的初始化次序
6.1 包的初始化次序探究
我們從程序邏輯結(jié)構(gòu)角度來看,Go 包是程序邏輯封裝的基本單元,每個包都可以理解為是一個“自治”的、封裝良好的、對外部暴露有限接口的基本單元。一個 Go 程序就是由一組包組成的,程序的初始化就是這些包的初始化。每個 Go 包還會有自己的依賴包、常量、變量、init
函數(shù)(其中 main
包有 main
函數(shù))等。
在平時開發(fā)中,我們在閱讀和理解代碼的時候,需要知道這些元素在在程序初始化過程中的初始化順序,這樣便于我們確定在某一行代碼處這些元素的當前狀態(tài)。
下面,我們就通過一張流程圖,來了解 Go 包的初始化次序:
這里,我們來看看具體的初始化步驟。
首先,main
包依賴 pkg1
和 pkg4
兩個包,所以第一步,Go 會根據(jù)包導入的順序,先去初始化 main 包的第一個依賴包 pkg1。
第二步,Go 在進行包初始化的過程中,會采用“深度優(yōu)先”的原則,遞歸初始化各個包的依賴包。在上圖里,pkg1 包依賴 pkg2 包,pkg2 包依賴 pkg3 包,pkg3 沒有依賴包,于是 Go 在 pkg3 包中按照“常量 -> 變量 -> init 函數(shù)”的順序先對 pkg3 包進行初始化;
緊接著,在 pkg3
包初始化完畢后,Go 會回到 pkg2
包并對 pkg2 包進行初始化,接下來再回到 pkg1
包并對 pkg1
包進行初始化。在調(diào)用完 pkg1
包的 init
函數(shù)后,Go 就完成了 main
包的第一個依賴包 pkg1
的初始化。
接下來,Go 會初始化 main
包的第二個依賴包 pkg4
,pkg4
包的初始化過程與 pkg1
包類似,也是先初始化它的依賴包 pkg5
,然后再初始化自身;
然后,當 Go 初始化完 pkg4
包后也就完成了對 main
包所有依賴包的初始化,接下來初始化 main
包自身。
最后,在 main 包中,Go 同樣會按照“常量 -> 變量 -> init 函數(shù)”的順序進行初始化,執(zhí)行完這些初始化工作后才正式進入程序的入口函數(shù) main
函數(shù)。
現(xiàn)在,我們可以通過一段代碼示例來驗證一下 Go 程序啟動后,Go 包的初始化次序是否是正確的,示例程序的結(jié)構(gòu)如下:
prog-init-order
├── main.go
├── pkg1
│ └── pkg1.go
├── pkg2
│ └── pkg2.go
└── pkg3└── pkg3.go
這里我只列出了 main
包的代碼,pkg1
、pkg2
和 pkg3
可可以到代碼倉庫中查看。
package mainimport ("fmt"_ "gitee.com/tao-xiaoxin/study-basic-go/syntax/prog-init-order/pkg1"_ "gitee.com/tao-xiaoxin/study-basic-go/syntax/prog-init-order/pkg2"
)var (_ = constInitCheck()v1 = variableInit("v1")v2 = variableInit("v2")
)const (c1 = "c1"c2 = "c2"
)func constInitCheck() string {if c1 != "" {fmt.Println("main: const c1 has been initialized!")}if c2 != "" {fmt.Println("main: const c2 has been initialized!")}return ""
}func variableInit(name string) string {fmt.Printf("main: var %s has been initialized\n", name)return name
}func init() {fmt.Println("main: first init function invoked")
}func init() {fmt.Println("main: second init function invoked")
}func main() {//
}
我們可以看到,在 main 包中其實并沒有使用 pkg1 和 pkg2 中的函數(shù)或方法,而是直接通過空導入的方式“觸發(fā)”pkg1 包和 pkg2 包的初始化(pkg1
包和和 pkg2
包都通過空導入的方式依賴 pkg3 包的,),下面是這個程序的運行結(jié)果:
$go run main.go
pkg3: const c has been initialized
pkg3: var v has been initialized
pkg3: init func invoked
pkg1: const c has been initialized
pkg1: var v has been initialized
pkg1: init func invoked
pkg2: const c has been initialized
pkg2: var v has been initialized
pkg2: init func invoked
main: const c1 has been initialized
main: const c2 has been initialized
main: var v1 has been initialized
main: var v2 has been initialized
main: first init func invoked
main: second init func invoked
正如我們預期的那樣,Go 運行時是按照“pkg3 -> pkg1 -> pkg2 -> main
”的順序,來對 Go 程序的各個包進行初始化的,而在包內(nèi),則是以“常量 -> 變量 -> init 函數(shù)”的順序進行初始化。此外,main 包的兩個 init 函數(shù),會按照在源文件 main.go
中的出現(xiàn)次序進行調(diào)用。根據(jù) Go 語言規(guī)范,**一個被多個包依賴的包僅會初始化一次,**因此這里的 pkg3 包僅會被初始化了一次。
6.2 包的初始化原則
根據(jù)以上,包的初始化按照依賴關(guān)系的順序執(zhí)行,遵循以下規(guī)則:
- 依賴包按照 “深度優(yōu)先” 的方式進行初始化,即先初始化最底層的依賴包。
- 在每個包內(nèi)部以“常量 -> 變量 -> init 函數(shù)”的順序進行初始化。
- 包內(nèi)的多個
init
函數(shù)按照它們在代碼中的出現(xiàn)順序依次自動調(diào)用。
七、init 函數(shù)的常用用途
Go 包初始化時,init
函數(shù)的初始化次序在變量之后,這給了開發(fā)人員在 init
函數(shù)中對包級變量進行進一步檢查與操作的機會。
7.1 用途一:重置包級變量值
init
函數(shù)就好比 Go 包真正投入使用之前唯一的“質(zhì)檢員”,負責對包內(nèi)部以及暴露到外部的包級數(shù)據(jù)(主要是包級變量)的初始狀態(tài)進行檢查。在 Go 標準庫中,我們能發(fā)現(xiàn)很多 init
函數(shù)被用于檢查包級變量的初始狀態(tài)的例子,標準庫 flag 包對 init 函數(shù)的使用就是其中的一個,這里我們簡單來分析一下。
flag
包定義了一個導出的包級變量 CommandLine
,如果用戶沒有通過 flag.NewFlagSet
創(chuàng)建新的代表命令行標志集合的實例,那么 CommandLine
就會作為 flag
包各種導出函數(shù)背后,默認的代表命令行標志集合的實例。
而在 flag 包初始化的時候,由于 init 函數(shù)初始化次序在包級變量之后,因此包級變量 CommandLine
會在 init
函數(shù)之前被初始化了,可以看如下代碼:
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {f := &FlagSet{name: name,errorHandling: errorHandling,}f.Usage = f.defaultUsagereturn f
}func (f *FlagSet) defaultUsage() {if f.name == "" {fmt.Fprintf(f.Output(), "Usage:\n")} else {fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)}f.PrintDefaults()
}
我們可以看到,在通過 NewFlagSet
創(chuàng)建 CommandLine
變量綁定的 FlagSet
類型實例時,CommandLine
的 Usage
字段被賦值為 defaultUsage
。
也就是說,如果保持現(xiàn)狀,那么使用 flag
包默認 CommandLine
的用戶就無法自定義 usage
的輸出了。于是,flag
包在 init
函數(shù)中重置了 CommandLine
的 Usage
字段:
func init() {CommandLine.Usage = commandLineUsage // 重置CommandLine的Usage字段
}func commandLineUsage() {Usage()
}var Usage = func() {fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])PrintDefaults()
}
這個時候我們會發(fā)現(xiàn),CommandLine
的 Usage
字段,設(shè)置為了一個 flag
包內(nèi)的未導出函數(shù) commandLineUsage
,后者則直接使用了 flag
包的另外一個導出包變量 Usage
。這樣,就可以通過 init
函數(shù),將 CommandLine
與包變量 Usage
關(guān)聯(lián)在一起了。
然后,當用戶將自定義的 usage
賦值給了 flag.Usage
后,就相當于改變了默認代表命令行標志集合的 CommandLine
變量的 Usage
。這樣當 flag
包完成初始化后,CommandLine
變量便處于一個合理可用的狀態(tài)了。
7.2 用途二:實現(xiàn)對包級變量的復雜初始化
有些包級變量需要一個比較復雜的初始化過程。有些時候,使用它的類型零值(每個 Go 類型都具有一個零值定義)或通過簡單初始化表達式不能滿足業(yè)務邏輯要求,而 init
函數(shù)則非常適合完成此項工作。標準庫 http
包中就有這樣一個典型示例:
var (http2VerboseLogs bool // 初始化時默認值為falsehttp2logFrameWrites bool // 初始化時默認值為falsehttp2logFrameReads bool // 初始化時默認值為falsehttp2inTests bool // 初始化時默認值為false
)func init() {e := os.Getenv("GODEBUG")if strings.Contains(e, "http2debug=1") {http2VerboseLogs = true // 在init中對http2VerboseLogs的值進行重置}if strings.Contains(e, "http2debug=2") {http2VerboseLogs = true // 在init中對http2VerboseLogs的值進行重置http2logFrameWrites = true // 在init中對http2logFrameWrites的值進行重置http2logFrameReads = true // 在init中對http2logFrameReads的值進行重置}
}
我們可以看到,標準庫 http
包定義了一系列布爾類型的特性開關(guān)變量,它們默認處于關(guān)閉狀態(tài)(即值為 false
),但我們可以通過 GODEBUG
環(huán)境變量的值,開啟相關(guān)特性開關(guān)。
可是這樣一來,簡單地將這些變量初始化為類型零值,就不能滿足要求了,所以 http
包在 init
函數(shù)中,就根據(jù)環(huán)境變量 GODEBUG
的值,對這些包級開關(guān)變量進行了復雜的初始化,從而保證了這些開關(guān)變量在 http
包完成初始化后,可以處于合理狀態(tài)。
7.3 用途三:在 init 函數(shù)中實現(xiàn)“注冊模式”
首先我們來看一段使用 lib/pq
包訪問 PostgreSQL 數(shù)據(jù)庫的代碼示例:
import ("database/sql"_ "github.com/lib/pq"
)func main() {db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")if err != nil {log.Fatal(err)}age := 21rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)...
}
其實,這是一段“神奇”的代碼。你可以看到示例代碼是以空導入的方式導入 lib/pq
包的,main
函數(shù)中沒有使用 pq
包的任何變量、函數(shù)或方法,這樣就實現(xiàn)了對 PostgreSQL 數(shù)據(jù)庫的訪問。而這一切的奧秘,全在 pq
包的 init
函數(shù)中:
func init() {sql.Register("postgres", &Driver{})
}
這個奧秘就在,我們其實是利用了用空導入的方式導入 lib/pq
包時產(chǎn)生的一個“副作用”,也就是 lib/pq
包作為 main 包的依賴包,它的 init
函數(shù)會在 pq
包初始化的時候得以執(zhí)行。
從上面的代碼中,我們可以看到在 pq
包的 init
函數(shù)中,pq
包將自己實現(xiàn)的 SQL 驅(qū)動注冊到了 database/sql
包中。這樣只要應用層代碼在 Open 數(shù)據(jù)庫的時候,傳入驅(qū)動的名字(這里是“postgres”),那么通過 sql.Open
函數(shù),返回的數(shù)據(jù)庫實例句柄對數(shù)據(jù)庫進行的操作,實際上調(diào)用的都是 pq
包中相應的驅(qū)動實現(xiàn)。
實際上,這種通過在 init 函數(shù)中注冊自己的實現(xiàn)的模式,就有效降低了 Go 包對外的直接暴露,尤其是包級變量的暴露,從而避免了外部通過包級變量對包狀態(tài)的改動。
另外,從標準庫 database/sql
包的角度來看,這種“注冊模式”實質(zhì)是一種工廠設(shè)計模式的實現(xiàn),sql.Open
函數(shù)就是這個模式中的工廠方法,它根據(jù)外部傳入的驅(qū)動名稱“生產(chǎn)”出不同類別的數(shù)據(jù)庫實例句柄。
這種“注冊模式”在標準庫的其他包中也有廣泛應用,比如說,使用標準庫 image
包獲取各種格式圖片的寬和高:
package mainimport ("fmt""image"_ "image/gif" // 以空導入方式注入gif圖片格式驅(qū)動_ "image/jpeg" // 以空導入方式注入jpeg圖片格式驅(qū)動_ "image/png" // 以空導入方式注入png圖片格式驅(qū)動"os"
)func main() {// 支持png, jpeg, gifwidth, height, err := imageSize(os.Args[1]) // 獲取傳入的圖片文件的寬與高if err != nil {fmt.Println("get image size error:", err)return}fmt.Printf("image size: [%d, %d]\n", width, height)
}func imageSize(imageFile string) (int, int, error) {f, _ := os.Open(imageFile) // 打開圖文文件defer f.Close()img, _, err := image.Decode(f) // 對文件進行解碼,得到圖片實例if err != nil {return 0, 0, err}b := img.Bounds() // 返回圖片區(qū)域return b.Max.X, b.Max.Y, nil
}
你可以看到,上面這個示例程序支持 png、jpeg、gif 三種格式的圖片,而達成這一目標的原因,正是 image/png
、image/jpeg
和 image/gif
包都在各自的 init
函數(shù)中,將自己“注冊”到 image
的支持格式列表中了,你可以看看下面這個代碼:
// $GOROOT/src/image/png/reader.go
func init() {image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}// $GOROOT/src/image/jpeg/reader.go
func init() {image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}// $GOROOT/src/image/gif/reader.go
func init() {image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
}
那么,現(xiàn)在我們了解了 init
函數(shù)的常見用途。init
函數(shù)之所以可以勝任這些工作,恰恰是因為它在 Go 應用初始化次序中的特殊“位次”,也就是 main
函數(shù)之前,常量和變量初始化之后。