淺析Go語言中包的介紹與初始化
一、main.main 函數(shù):Go 應(yīng)用的入口函數(shù)
1.1 main.main 函數(shù)
在Go語言中,main
函數(shù)是任何Go應(yīng)用的入口函數(shù)--用戶層入口。當(dāng)你運(yùn)行一個Go程序時(shí),操作系統(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ù)特點(diǎn)
main.main
函數(shù)是Go應(yīng)用程序的入口函數(shù),它具有一些特點(diǎn)和規(guī)定,使得Go程序的執(zhí)行流程有一定的規(guī)范性。以下是關(guān)于main.main
函數(shù)的特點(diǎn):
- 唯一入口點(diǎn): 在一個Go應(yīng)用程序中,只能有一個
main.main
函數(shù)。這是整個程序的唯一入口點(diǎn),程序的執(zhí)行將從這里開始。如果存在多個main
函數(shù),編譯時(shí)會報(bào)錯。 - 不接受參數(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語言中,包的名稱通常使用小寫字母,具有簡潔的、描述性的名稱。這有助于提高代碼的可讀性和可維護(hù)性。標(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)入包不會引起編譯錯誤,但通常被視為不良實(shí)踐。在Go中,未使用的導(dǎo)入包可能會引起代碼不清晰,因此應(yīng)該避免導(dǎo)入不需要的包。
3.2 包的別名
在Go語言中,你可以使用包的別名(package alias)來為一個導(dǎo)入的包賦予一個不同的名稱,以便在代碼中引用它。包的別名通常用于以下情況:
- 避免包名沖突:當(dāng)你導(dǎo)入多個包時(shí),有可能出現(xiàn)包名沖突,此時(shí)你可以為一個或多個包使用別名來解決沖突。
- 簡化包名:有時(shí),包的導(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ù)需要自定義,但通常建議選擇一個有意義的別名,以使代碼更易讀。使用別名時(shí)要注意避免產(chǎn)生混淆,要確保別名不與其他標(biāo)識符(如變量名或函數(shù)名)發(fā)生沖突。
四、神器的下劃線
4.1 下劃線的作用
下劃線 _
在Go語言中用于以下幾個不同的場景:
- 匿名變量:
_
可以用作匿名變量,用于忽略某個值。當(dāng)你希望某個值返回但又不需要使用它時(shí),可以將其賦值給_
。 - 空標(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)入一個包時(shí),該包下的文件里所有
init()
函數(shù)都會被執(zhí)行,然而,有些時(shí)候我們并不需要把整個包都導(dǎo)入進(jìn)來,僅僅是是希望它執(zhí)行init()
函數(shù)而已。 - 這個時(shí)候就可以使用
import _
引用該包。即使用 import _ 包路徑 只是引用該包,僅僅是為了調(diào)用init()函數(shù),所以無法通過包名來調(diào)用包中的其他函數(shù)。
以下是一個示例,演示如何使用 import _
引用一個包以執(zhí)行其 init()
函數(shù):
項(xiàng)目結(jié)構(gòu):
src
|
+--- main.go
|
+--- hello
|
+--- hello.go
main.go
文件
package main import _ "./hello" func main() { // hello.Print() //編譯報(bào)錯:./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ù) 特點(diǎn)
init
函數(shù)有以下特點(diǎn):
- 自動執(zhí)行:
init
函數(shù)不需要手動調(diào)用,它會在程序啟動時(shí)自動執(zhí)行。這確保了包的初始化工作在程序開始執(zhí)行之前完成。 - 包級別:
init
函數(shù)是包級別的,因此它只能在包的內(nèi)部定義。不同包中的init
函數(shù)互不影響,它們獨(dú)立執(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)入順序確定的,因此在編寫代碼時(shí)應(yīng)該謹(jǐn)慎考慮包的依賴關(guān)系,以確保正確的初始化順序。 - 可用于注冊和初始化:
init
函數(shù)通常用于執(zhí)行包的初始化工作,也可用于在導(dǎo)入包時(shí)注冊一些功能,例如數(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)建并運(yùn)行上面這些示例代碼之后,Go 編譯器會報(bào)下面這個錯誤:
$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 編譯器運(yùn)行結(jié)果如下:
init invoked
this is main
我們看到,在初始化 Go 包時(shí),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ù))等。
在平時(shí)開發(fā)中,我們在閱讀和理解代碼的時(shí)候,需要知道這些元素在在程序初始化過程中的初始化順序,這樣便于我們確定在某一行代碼處這些元素的當(dāng)前狀態(tài)。
下面,我們就通過一張流程圖,來了解 Go 包的初始化次序:
這里,我們來看看具體的初始化步驟。
首先,main
包依賴 pkg1
和 pkg4
兩個包,所以第一步,Go 會根據(jù)包導(dǎo)入的順序,先去初始化 main 包的第一個依賴包 pkg1。
第二步,Go 在進(jìn)行包初始化的過程中,會采用“深度優(yōu)先”的原則,遞歸初始化各個包的依賴包。在上圖里,pkg1 包依賴 pkg2 包,pkg2 包依賴 pkg3 包,pkg3 沒有依賴包,于是 Go 在 pkg3 包中按照“常量 -> 變量 -> init 函數(shù)”的順序先對 pkg3 包進(jìn)行初始化;
緊接著,在 pkg3
包初始化完畢后,Go 會回到 pkg2
包并對 pkg2 包進(jìn)行初始化,接下來再回到 pkg1
包并對 pkg1
包進(jìn)行初始化。在調(diào)用完 pkg1
包的 init
函數(shù)后,Go 就完成了 main
包的第一個依賴包 pkg1
的初始化。
接下來,Go 會初始化 main
包的第二個依賴包 pkg4
,pkg4
包的初始化過程與 pkg1
包類似,也是先初始化它的依賴包 pkg5
,然后再初始化自身;
然后,當(dāng) Go 初始化完 pkg4
包后也就完成了對 main
包所有依賴包的初始化,接下來初始化 main
包自身。
最后,在 main 包中,Go 同樣會按照“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化,執(zhí)行完這些初始化工作后才正式進(jìn)入程序的入口函數(shù) main
函數(shù)。
現(xiàn)在,我們可以通過一段代碼示例來驗(yà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 包中其實(shí)并沒有使用 pkg1 和 pkg2 中的函數(shù)或方法,而是直接通過空導(dǎo)入的方式“觸發(fā)”pkg1 包和 pkg2 包的初始化(pkg1
包和和 pkg2
包都通過空導(dǎo)入的方式依賴 pkg3 包的,),下面是這個程序的運(yùn)行結(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 運(yùn)行時(shí)是按照“pkg3 -> pkg1 -> pkg2 -> main
”的順序,來對 Go 程序的各個包進(jìn)行初始化的,而在包內(nèi),則是以“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化。此外,main 包的兩個 init 函數(shù),會按照在源文件 main.go
中的出現(xiàn)次序進(jìn)行調(diào)用。根據(jù) Go 語言規(guī)范,一個被多個包依賴的包僅會初始化一次,因此這里的 pkg3 包僅會被初始化了一次。
6.2 包的初始化原則
根據(jù)以上,包的初始化按照依賴關(guān)系的順序執(zhí)行,遵循以下規(guī)則:
- 依賴包按照 "深度優(yōu)先" 的方式進(jìn)行初始化,即先初始化最底層的依賴包。
- 在每個包內(nèi)部以“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化。
- 包內(nèi)的多個
init
函數(shù)按照它們在代碼中的出現(xiàn)順序依次自動調(diào)用。
七、init 函數(shù)的常用用途
Go 包初始化時(shí),init
函數(shù)的初始化次序在變量之后,這給了開發(fā)人員在 init
函數(shù)中對包級變量進(jìn)行進(jìn)一步檢查與操作的機(jī)會。
7.1 用途一:重置包級變量值
init
函數(shù)就好比 Go 包真正投入使用之前唯一的“質(zhì)檢員”,負(fù)責(zé)對包內(nèi)部以及暴露到外部的包級數(shù)據(jù)(主要是包級變量)的初始狀態(tài)進(jìn)行檢查。在 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)志集合的實(shí)例,那么 CommandLine
就會作為 flag
包各種導(dǎo)出函數(shù)背后,默認(rèn)的代表命令行標(biāo)志集合的實(shí)例。
而在 flag 包初始化的時(shí)候,由于 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
類型實(shí)例時(shí),CommandLine
的 Usage
字段被賦值為 defaultUsage
。
也就是說,如果保持現(xiàn)狀,那么使用 flag
包默認(rèn) 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() }
這個時(shí)候我們會發(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)于改變了默認(rèn)代表命令行標(biāo)志集合的 CommandLine
變量的 Usage
。這樣當(dāng) flag
包完成初始化后,CommandLine
變量便處于一個合理可用的狀態(tài)了。
7.2 用途二:實(shí)現(xiàn)對包級變量的復(fù)雜初始化
有些包級變量需要一個比較復(fù)雜的初始化過程。有些時(shí)候,使用它的類型零值(每個 Go 類型都具有一個零值定義)或通過簡單初始化表達(dá)式不能滿足業(yè)務(wù)邏輯要求,而 init
函數(shù)則非常適合完成此項(xiàng)工作。標(biāo)準(zhǔn)庫 http
包中就有這樣一個典型示例:
var ( http2VerboseLogs bool // 初始化時(shí)默認(rèn)值為false http2logFrameWrites bool // 初始化時(shí)默認(rèn)值為false http2logFrameReads bool // 初始化時(shí)默認(rèn)值為false http2inTests bool // 初始化時(shí)默認(rèn)值為false ) func init() { e := os.Getenv("GODEBUG") if strings.Contains(e, "http2debug=1") { http2VerboseLogs = true // 在init中對http2VerboseLogs的值進(jìn)行重置 } if strings.Contains(e, "http2debug=2") { http2VerboseLogs = true // 在init中對http2VerboseLogs的值進(jìn)行重置 http2logFrameWrites = true // 在init中對http2logFrameWrites的值進(jìn)行重置 http2logFrameReads = true // 在init中對http2logFrameReads的值進(jìn)行重置 } }
我們可以看到,標(biāo)準(zhǔn)庫 http
包定義了一系列布爾類型的特性開關(guān)變量,它們默認(rèn)處于關(guān)閉狀態(tài)(即值為 false
),但我們可以通過 GODEBUG
環(huán)境變量的值,開啟相關(guān)特性開關(guān)。
可是這樣一來,簡單地將這些變量初始化為類型零值,就不能滿足要求了,所以 http
包在 init
函數(shù)中,就根據(jù)環(huán)境變量 GODEBUG
的值,對這些包級開關(guān)變量進(jìn)行了復(fù)雜的初始化,從而保證了這些開關(guān)變量在 http
包完成初始化后,可以處于合理狀態(tài)。
7.3 用途三:在 init 函數(shù)中實(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) ... }
其實(shí),這是一段“神奇”的代碼。你可以看到示例代碼是以空導(dǎo)入的方式導(dǎo)入 lib/pq
包的,main
函數(shù)中沒有使用 pq
包的任何變量、函數(shù)或方法,這樣就實(shí)現(xiàn)了對 PostgreSQL 數(shù)據(jù)庫的訪問。而這一切的奧秘,全在 pq
包的 init
函數(shù)中:
func init() { sql.Register("postgres", &Driver{}) }
這個奧秘就在,我們其實(shí)是利用了用空導(dǎo)入的方式導(dǎo)入 lib/pq
包時(shí)產(chǎn)生的一個“副作用”,也就是 lib/pq
包作為 main 包的依賴包,它的 init
函數(shù)會在 pq
包初始化的時(shí)候得以執(zhí)行。
從上面的代碼中,我們可以看到在 pq
包的 init
函數(shù)中,pq
包將自己實(shí)現(xiàn)的 SQL 驅(qū)動注冊到了 database/sql
包中。這樣只要應(yīng)用層代碼在 Open 數(shù)據(jù)庫的時(shí)候,傳入驅(qū)動的名字(這里是“postgres”),那么通過 sql.Open
函數(shù),返回的數(shù)據(jù)庫實(shí)例句柄對數(shù)據(jù)庫進(jìn)行的操作,實(shí)際上調(diào)用的都是 pq
包中相應(yīng)的驅(qū)動實(shí)現(xiàn)。
實(shí)際上,這種通過在 init 函數(shù)中注冊自己的實(shí)現(xiàn)的模式,就有效降低了 Go 包對外的直接暴露,尤其是包級變量的暴露,從而避免了外部通過包級變量對包狀態(tài)的改動。
另外,從標(biāo)準(zhǔn)庫 database/sql
包的角度來看,這種“注冊模式”實(shí)質(zhì)是一種工廠設(shè)計(jì)模式的實(shí)現(xiàn),sql.Open
函數(shù)就是這個模式中的工廠方法,它根據(jù)外部傳入的驅(qū)動名稱“生產(chǎn)”出不同類別的數(shù)據(jù)庫實(shí)例句柄。
這種“注冊模式”在標(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) // 對文件進(jìn)行解碼,得到圖片實(shí)例 if err != nil { return 0, 0, err } b := img.Bounds() // 返回圖片區(qū)域 return b.Max.X, b.Max.Y, nil }
你可以看到,上面這個示例程序支持 png、jpeg、gif 三種格式的圖片,而達(dá)成這一目標(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ù)之所以可以勝任這些工作,恰恰是因?yàn)樗?Go 應(yīng)用初始化次序中的特殊“位次”,也就是 main
函數(shù)之前,常量和變量初始化之后。
以上就是淺析Go語言中包的介紹與初始化的詳細(xì)內(nèi)容,更多關(guān)于Go包的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang正整數(shù)指定規(guī)則排序算法問題分析
這篇文章主要介紹了Golang正整數(shù)指定規(guī)則排序算法問題,結(jié)合實(shí)例形式分析了Go語言排序算法操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01Windows上安裝Go并配置環(huán)境變量(圖文步驟)
開始使用Go創(chuàng)建應(yīng)用程序之前,需要設(shè)置開發(fā)環(huán)境,本文主要介紹了Windows上安裝Go并配置環(huán)境變量,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08Golang并發(fā)讀取文件數(shù)據(jù)并寫入數(shù)據(jù)庫的項(xiàng)目實(shí)踐
本文主要介紹了Golang并發(fā)讀取文件數(shù)據(jù)并寫入數(shù)據(jù)庫的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06用Go語言標(biāo)準(zhǔn)庫實(shí)現(xiàn)Web服務(wù)之創(chuàng)建路由
在上一節(jié)中創(chuàng)建了項(xiàng)目,這篇文章主要介紹如何用Go語言標(biāo)準(zhǔn)庫創(chuàng)建路由,文中有詳細(xì)的代碼示例,對大家的學(xué)習(xí)或工作有一定的幫助,感興趣的同學(xué)可以參考下2023-05-05