淺析Go語言中包的介紹與初始化
一、main.main 函數(shù):Go 應(yīng)用的入口函數(shù)
1.1 main.main 函數(shù)
在Go語言中,main函數(shù)是任何Go應(yīng)用的入口函數(shù)--用戶層入口。當(dāng)你運行一個Go程序時,操作系統(tǒng)會首先調(diào)用main函數(shù),然后程序開始執(zhí)行。main 函數(shù)的函數(shù)原型是這樣的:
package main
func main() {
// 用戶層執(zhí)行邏輯
... ...
}
你的程序的執(zhí)行會從main函數(shù)開始,會在這個函數(shù)內(nèi)按照它的調(diào)用順序展開。
1.2 main.main 函數(shù)特點
main.main函數(shù)是Go應(yīng)用程序的入口函數(shù),它具有一些特點和規(guī)定,使得Go程序的執(zhí)行流程有一定的規(guī)范性。以下是關(guān)于main.main函數(shù)的特點:
- 唯一入口點: 在一個Go應(yīng)用程序中,只能有一個
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ù)、類型和變量,它們可以被導(dǎo)入到其他Go文件中以便重復(fù)使用。Go標(biāo)準(zhǔn)庫以及第三方庫都是以包的形式提供的。
每個Go文件都屬于一個包,你可以使用package關(guān)鍵字來指定聲明一個文件屬于哪個包。例如:
package main
2.2 非 main包的 main 函數(shù)
除了 main 包外,其他包也可以擁有自己的名為 main 的函數(shù)或方法。但按照 Go 的可見性規(guī)則(小寫字母開頭的標(biāo)識符為非導(dǎo)出標(biāo)識符),非 main 包中自定義的 main 函數(shù)僅限于包內(nèi)使用,就像下面代碼這樣,這是一段在非 main 包中定義 main 函數(shù)的代碼片段:
package pkg1
import "fmt"
func Main() {
main()
}
func main() {
fmt.Println("main func for pkg1")
}
你可以看到,這里 main 函數(shù)就主要是用來在包 pkg1 內(nèi)部使用的,它是沒法在包外使用的。
2.3 包的命名規(guī)則
- 在Go語言中,包的名稱通常使用小寫字母,具有簡潔的、描述性的名稱。這有助于提高代碼的可讀性和可維護性。標(biāo)準(zhǔn)庫中的包通常具有非常清晰的包名,例如
fmt、math、strings等。 - 在Go語言中,包級別的標(biāo)識符(變量、函數(shù)、類型等)的可見性是由其首字母的大小寫來決定的。如果一個標(biāo)識符以大寫字母開頭,它就是可導(dǎo)出的(公有的),可以被其他包訪問。如果以小寫字母開頭,它就是包內(nèi)私有的,只能在包內(nèi)部使用。
三、包的導(dǎo)入
3.1 包的導(dǎo)入介紹
要在Go程序中使用其他包的功能,你需要導(dǎo)入這些包。使用import關(guān)鍵字來導(dǎo)入包,導(dǎo)入語句通常放在文件的頂部。一個典型的包導(dǎo)入語句的格式如下:
import "包的導(dǎo)入路徑"
其中,包的導(dǎo)入路徑是指被導(dǎo)入包的唯一標(biāo)識符,通常是包的名稱或路徑,它用于告訴Go編譯器去哪里找到這個包的代碼。
例如,導(dǎo)入標(biāo)準(zhǔn)庫中的fmt包可以這樣做:
import "fmt"
然后,你就可以在你的程序中使用fmt包提供的函數(shù)和類型。
3.2 導(dǎo)入多個包
在Go程序中,你可以一次導(dǎo)入多個包,只需在import語句中列出這些包的導(dǎo)入路徑,用括號()括起來并以括號內(nèi)的方式分隔包的導(dǎo)入路徑。
示例:
import (
"fmt"
"math"
"net/http"
)
這個示例中導(dǎo)入了fmt、math和net/http三個包。這種方式使你可以更清晰地組織你的導(dǎo)入語句,以便程序更易讀。
注意:Go語言的編譯器會自動檢測哪些導(dǎo)入的包是真正被使用的,未使用的導(dǎo)入包不會引起編譯錯誤,但通常被視為不良實踐。在Go中,未使用的導(dǎo)入包可能會引起代碼不清晰,因此應(yīng)該避免導(dǎo)入不需要的包。
3.2 包的別名
在Go語言中,你可以使用包的別名(package alias)來為一個導(dǎo)入的包賦予一個不同的名稱,以便在代碼中引用它。包的別名通常用于以下情況:
- 避免包名沖突:當(dāng)你導(dǎo)入多個包時,有可能出現(xiàn)包名沖突,此時你可以為一個或多個包使用別名來解決沖突。
- 簡化包名:有時,包的導(dǎo)入路徑可能很長,為了減少代碼中的冗長,你可以為包使用一個短的別名。
使用包的別名是非常簡單的,只需在導(dǎo)入語句中使用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)生混淆,要確保別名不與其他標(biāo)識符(如變量名或函數(shù)名)發(fā)生沖突。
四、神器的下劃線
4.1 下劃線的作用
下劃線 _ 在Go語言中用于以下幾個不同的場景:
- 匿名變量:
_可以用作匿名變量,用于忽略某個值。當(dāng)你希望某個值返回但又不需要使用它時,可以將其賦值給_。 - 空標(biāo)識符:
_也被稱為空標(biāo)識符,它用于聲明但不使用變量或?qū)氚皇褂冒臉?biāo)識符。這是為了確保代碼通過編譯,但不會產(chǎn)生未使用變量或包的警告。
4.2 下劃線在代碼中
在代碼中,下劃線 _ 可以用作匿名變量,用于忽略某個值。這通常在函數(shù)多返回值中使用,如果你只關(guān)心其中的某些值而不需要其他返回值,可以將其賦值給 _。
示例:
x, _ := someFunction() // 忽略第二個返回值
在上面的示例中,_ 用于忽略 someFunction 函數(shù)的第二個返回值。
4.3 下劃線在import中
- 當(dāng)導(dǎo)入一個包時,該包下的文件里所有
init()函數(shù)都會被執(zhí)行,然而,有些時候我們并不需要把整個包都導(dǎo)入進來,僅僅是是希望它執(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 main
import _ "./hello"
func main() {
// hello.Print()
//編譯報錯:./main.go:6:5: undefined: hello
}
hello.go 文件
package hello
import "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í)行。被導(dǎo)入的包的init函數(shù)會在導(dǎo)入它的包的init函數(shù)之前執(zhí)行。 - 沒有參數(shù)和返回值: 和前面
main.main函數(shù)一樣,init函數(shù)也是一個無參數(shù)無返回值的函數(shù),它只用于執(zhí)行初始化工作,而不與其他函數(shù)交互。 - 順序執(zhí)行: 由于
init函數(shù)的執(zhí)行順序是根據(jù)包的導(dǎo)入順序確定的,因此在編寫代碼時應(yīng)該謹慎考慮包的依賴關(guān)系,以確保正確的初始化順序。 - 可用于注冊和初始化:
init函數(shù)通常用于執(zhí)行包的初始化工作,也可用于在導(dǎo)入包時注冊一些功能,例如數(shù)據(jù)庫驅(qū)動程序的注冊。
這里要特別注意的是,在 Go 程序中我們不能手工顯式地調(diào)用 init,否則就會收到編譯錯誤,就像下面這個示例,它表示的手工顯式調(diào)用 init 函數(shù)的錯誤做法:
package main
import "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 main
import "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ā)中,我們在閱讀和理解代碼的時候,需要知道這些元素在在程序初始化過程中的初始化順序,這樣便于我們確定在某一行代碼處這些元素的當(dāng)前狀態(tài)。
下面,我們就通過一張流程圖,來了解 Go 包的初始化次序:

這里,我們來看看具體的初始化步驟。
首先,main 包依賴 pkg1 和 pkg4 兩個包,所以第一步,Go 會根據(jù)包導(dǎo)入的順序,先去初始化 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,然后再初始化自身;
然后,當(dāng) 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 main
import (
"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ù)或方法,而是直接通過空導(dǎo)入的方式“觸發(fā)”pkg1 包和 pkg2 包的初始化(pkg1 包和和 pkg2 包都通過空導(dǎo)入的方式依賴 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
正如我們預(yù)期的那樣,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ì)檢員”,負責(zé)對包內(nèi)部以及暴露到外部的包級數(shù)據(jù)(主要是包級變量)的初始狀態(tài)進行檢查。在 Go 標(biāo)準(zhǔn)庫中,我們能發(fā)現(xiàn)很多 init 函數(shù)被用于檢查包級變量的初始狀態(tài)的例子,標(biāo)準(zhǔn)庫 flag 包對 init 函數(shù)的使用就是其中的一個,這里我們簡單來分析一下。
flag 包定義了一個導(dǎo)出的包級變量 CommandLine,如果用戶沒有通過 flag.NewFlagSet 創(chuàng)建新的代表命令行標(biāo)志集合的實例,那么 CommandLine 就會作為 flag 包各種導(dǎo)出函數(shù)背后,默認的代表命令行標(biāo)志集合的實例。
而在 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.defaultUsage
return 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)的未導(dǎo)出函數(shù) commandLineUsage,后者則直接使用了 flag 包的另外一個導(dǎo)出包變量 Usage。這樣,就可以通過 init 函數(shù),將 CommandLine 與包變量 Usage 關(guān)聯(lián)在一起了。
然后,當(dāng)用戶將自定義的 usage 賦值給了 flag.Usage 后,就相當(dāng)于改變了默認代表命令行標(biāo)志集合的 CommandLine 變量的 Usage。這樣當(dāng) flag 包完成初始化后,CommandLine 變量便處于一個合理可用的狀態(tài)了。
7.2 用途二:實現(xiàn)對包級變量的復(fù)雜初始化
有些包級變量需要一個比較復(fù)雜的初始化過程。有些時候,使用它的類型零值(每個 Go 類型都具有一個零值定義)或通過簡單初始化表達式不能滿足業(yè)務(wù)邏輯要求,而 init 函數(shù)則非常適合完成此項工作。標(biāo)準(zhǔn)庫 http 包中就有這樣一個典型示例:
var (
http2VerboseLogs bool // 初始化時默認值為false
http2logFrameWrites bool // 初始化時默認值為false
http2logFrameReads bool // 初始化時默認值為false
http2inTests 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的值進行重置
}
}
我們可以看到,標(biāo)準(zhǔn)庫 http 包定義了一系列布爾類型的特性開關(guān)變量,它們默認處于關(guān)閉狀態(tài)(即值為 false),但我們可以通過 GODEBUG 環(huán)境變量的值,開啟相關(guān)特性開關(guān)。
可是這樣一來,簡單地將這些變量初始化為類型零值,就不能滿足要求了,所以 http 包在 init 函數(shù)中,就根據(jù)環(huán)境變量 GODEBUG 的值,對這些包級開關(guān)變量進行了復(fù)雜的初始化,從而保證了這些開關(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 := 21
rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
...
}
其實,這是一段“神奇”的代碼。你可以看到示例代碼是以空導(dǎo)入的方式導(dǎo)入 lib/pq 包的,main 函數(shù)中沒有使用 pq 包的任何變量、函數(shù)或方法,這樣就實現(xiàn)了對 PostgreSQL 數(shù)據(jù)庫的訪問。而這一切的奧秘,全在 pq 包的 init 函數(shù)中:
func init() {
sql.Register("postgres", &Driver{})
}
這個奧秘就在,我們其實是利用了用空導(dǎo)入的方式導(dǎo)入 lib/pq 包時產(chǎn)生的一個“副作用”,也就是 lib/pq 包作為 main 包的依賴包,它的 init 函數(shù)會在 pq 包初始化的時候得以執(zhí)行。
從上面的代碼中,我們可以看到在 pq 包的 init 函數(shù)中,pq 包將自己實現(xiàn)的 SQL 驅(qū)動注冊到了 database/sql 包中。這樣只要應(yīng)用層代碼在 Open 數(shù)據(jù)庫的時候,傳入驅(qū)動的名字(這里是“postgres”),那么通過 sql.Open 函數(shù),返回的數(shù)據(jù)庫實例句柄對數(shù)據(jù)庫進行的操作,實際上調(diào)用的都是 pq 包中相應(yīng)的驅(qū)動實現(xiàn)。
實際上,這種通過在 init 函數(shù)中注冊自己的實現(xiàn)的模式,就有效降低了 Go 包對外的直接暴露,尤其是包級變量的暴露,從而避免了外部通過包級變量對包狀態(tài)的改動。
另外,從標(biāo)準(zhǔn)庫 database/sql 包的角度來看,這種“注冊模式”實質(zhì)是一種工廠設(shè)計模式的實現(xiàn),sql.Open 函數(shù)就是這個模式中的工廠方法,它根據(jù)外部傳入的驅(qū)動名稱“生產(chǎn)”出不同類別的數(shù)據(jù)庫實例句柄。
這種“注冊模式”在標(biāo)準(zhǔn)庫的其他包中也有廣泛應(yīng)用,比如說,使用標(biāo)準(zhǔn)庫 image 包獲取各種格式圖片的寬和高:
package main
import (
"fmt"
"image"
_ "image/gif" // 以空導(dǎo)入方式注入gif圖片格式驅(qū)動
_ "image/jpeg" // 以空導(dǎo)入方式注入jpeg圖片格式驅(qū)動
_ "image/png" // 以空導(dǎo)入方式注入png圖片格式驅(qū)動
"os"
)
func main() {
// 支持png, jpeg, gif
width, 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 三種格式的圖片,而達成這一目標(biāo)的原因,正是 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 應(yīng)用初始化次序中的特殊“位次”,也就是 main 函數(shù)之前,常量和變量初始化之后。
以上就是淺析Go語言中包的介紹與初始化的詳細內(nèi)容,更多關(guān)于Go包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go Excelize API源碼閱讀GetPageLayout及SetPageMargins
這篇文章主要為大家介紹了Go Excelize API源碼閱讀GetPageLayout及SetPageMargins的方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
go+react實現(xiàn)遠程vCenter虛擬機管理終端方式
基于Go和React實現(xiàn)遠程vSphere vcenter虛擬機終端console頁面,提供與vcenter管理中的LaunchWebConsole相同的功能,項目包括前端、后端配置,以及vCenter宿主機的Nginx代理設(shè)置2026-04-04
Golang 實現(xiàn)分片讀取http超大文件流和并發(fā)控制
這篇文章主要介紹了Golang 實現(xiàn)分片讀取http超大文件流和并發(fā)控制,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12

