網(wǎng)站首頁 編程語言 正文
包 package
- Go 包是 Go 語言的基本組成單元,一個(gè) Go 程序就是一組包的集合,所有 Go 代碼都位于包中
- Go 源碼可以導(dǎo)入其他 Go 包,并使用其中的導(dǎo)出語法元素,包括類型、變量、函數(shù)、方法等,而且 main 函數(shù)是整個(gè) Go 應(yīng)用的入口函數(shù)
- Go 語言提供了很多內(nèi)置包,如 fmt、os、io 等
- 任何源代碼文件必須屬于某個(gè)包,同時(shí)源碼文件的第一行有效代碼必須是
package 包名
語句,通過該語句聲明源碼文件所在的包
main.main 函數(shù):Go 應(yīng)用的入口函數(shù)
- Go 語言中有一個(gè)特殊的函數(shù):main 包中的 main 函數(shù),也就是
main.main
,它是所有 Go 可執(zhí)行程序的用戶層執(zhí)行邏輯的入口函數(shù) - Go 程序在用戶層面的執(zhí)行邏輯,會(huì)在這個(gè)函數(shù)內(nèi)按照它的調(diào)用順序展開
package main
- 整個(gè) Go 可執(zhí)行程序中僅允許存在一個(gè)名為 main 的包
-
package main
想要引用別的包的代碼,必須同樣以包的方式進(jìn)行引用
package main func main() { // 用戶層執(zhí)行邏輯 ... ... }
Go 語言要求:可執(zhí)行程序的 main 包必須定義 main 函數(shù),否則 Go 編譯器會(huì)報(bào)錯(cuò)
注意
main 包是不可以像標(biāo)準(zhǔn)庫 fmt 包那樣被導(dǎo)入(Import)的
其他包也可以擁有 main 函數(shù)或方法
按照 Go 的可見性規(guī)則(小寫字母卡頭的標(biāo)識(shí)符為非導(dǎo)出標(biāo)識(shí)符),非 main包中自定義的 main 函數(shù)僅限于包內(nèi)使用
package pkg1 import "fmt" func Main () { main() } func main(){ fmt.Println("main func for pkg1") }
重點(diǎn)
- 一個(gè)文件夾下的所有源碼文件只能屬于同一個(gè)包,不要求同名,但還是建議包名和所在目錄同名,這樣結(jié)構(gòu)更清晰,包名中不能包含特殊符號(hào)
- 給結(jié)構(gòu)定義的方法必須放在同一個(gè)包內(nèi),可以是不同文件
- 包名為 main 的包為應(yīng)用程序的入口包,編譯不包含 main 包的源碼文件時(shí)不會(huì)得到可執(zhí)行文件。
- 一個(gè)文件夾下的所有源碼文件只能屬于同一個(gè)包,屬于同一個(gè)包的源碼文件不能放在多個(gè)文件夾下
引子
不過對于 main 包的main 函數(shù)來說,還需要明確一點(diǎn),就是它雖然是用戶層邏輯的入口函數(shù),但它卻不一定是用戶層第一個(gè)被執(zhí)行的函數(shù)。這是為什么呢?這跟 Go 語言的另一個(gè)函數(shù) init 有關(guān)
init 函數(shù):Go 包的初始化函數(shù)
和 main.main 函數(shù)一樣,init 函數(shù)也是一個(gè)無參數(shù)無返回值的函數(shù)
func init() { // 包初始化邏輯 ... ... }
- Go 程序會(huì)在這個(gè)包初始化的時(shí)候,自動(dòng)調(diào)用它的 init 函數(shù),所以 init 函數(shù)的執(zhí)行會(huì)發(fā)生在 main 函數(shù)之前
- 在 Go 程序中不能手工顯式地調(diào)用 init,否則會(huì)收到編譯錯(cuò)誤
和 main 函數(shù)不一樣
- init 函數(shù)在一個(gè)包中可以有多個(gè),每個(gè) Go 源文件都可以定義多個(gè) init 函數(shù)
init 函數(shù)的執(zhí)行順序
- 在初始化 Go 包時(shí),Go 會(huì)按照一定的順序,逐一、順序地調(diào)用這個(gè)包的 init 函數(shù)
- 一般來說,先傳遞給 Go 編譯器的源文件中的 init 函數(shù),會(huì)先被執(zhí)行;而同一個(gè)源文件中的多個(gè) init 函數(shù),會(huì)按聲明順序依次執(zhí)行
Go 包的初始化次序
- 從程序邏輯結(jié)構(gòu)角度來看,Go 包是程序邏輯封裝的基本單元
- 每個(gè)包都可以理解為是一個(gè)“自治”的、封裝良好的、對外部暴露有限接口的基本單元
- 一個(gè) Go 程序就是由一組包組成的,程序的初始化就是這些包的初始化
- 每個(gè) Go 包還會(huì)有自己的依賴包、常量、變量、init 函數(shù)(其中 main 包有 main 函數(shù))等
三步走
- 依賴包按“深度優(yōu)先”的次序進(jìn)行初始化
- 每個(gè)包內(nèi)按以“常量 -> 變量 -> init 函數(shù)”的順序進(jìn)行初始化
- 包內(nèi)的多個(gè) init 函數(shù)按出現(xiàn)次序進(jìn)行自動(dòng)調(diào)用
init 函數(shù)的特點(diǎn)
- 如上圖所示,執(zhí)行順位排在包內(nèi)其他語法元素(常量、變量)的后面
- 每個(gè) init 函數(shù)在整個(gè) Go 程序生命周期內(nèi)僅會(huì)被執(zhí)行一次
- init 函數(shù)是順序執(zhí)行的,只有當(dāng)一個(gè) init 函數(shù)執(zhí)行完畢后,才會(huì)去執(zhí)行下一個(gè) init 函數(shù)
init 函數(shù)的用途
重置包級變量值
init 函數(shù)就好比 Go 包真正投入使用之前唯一的“質(zhì)檢員”,負(fù)責(zé)對包內(nèi)部以及暴露到外部的包級數(shù)據(jù)(主要是包級變量)的初始狀態(tài)進(jìn)行檢查
實(shí)現(xiàn)對包級變量的復(fù)雜初始化
有些包級變量需要一個(gè)比較復(fù)雜的初始化過程,有些時(shí)候,使用它的類型零值或通過簡單初始化表達(dá)式不能滿足業(yè)務(wù)邏輯要求,而 init 函數(shù)則非常適合完成此項(xiàng)工作,標(biāo)準(zhǔn)庫 http 包中就有這樣一個(gè)典型示例
package main import ( "os" "strings" ) var ( http2VerboseLogs bool // 初始化默認(rèn)值 false http2logFrameWrites bool http2logFrameReads bool http2inTests bool ) func init() { e := os.Getenv("GODEBUG") if strings.Contains(e, "http2debug=1") { http2VerboseLogs = true // 在 init 中對 http2VerboseLogs 的值進(jìn)行重置 } if strings.Contains(e, "http2debug=2") { http2logFrameWrites = true http2logFrameReads = true http2inTests = true } }
http 包在init 函數(shù)中,就根據(jù)環(huán)境變量 GODEBUG 的值,對這些包級開關(guān)變量進(jìn)行了復(fù)雜的初始化,從而保證了這些開關(guān)變量在 http 包完成初始化后,可以處于合理狀態(tài)
在 init 函數(shù)中實(shí)現(xiàn)“注冊模式”
來看一段使用 lib/pq 包訪問 PostgreSQL 數(shù)據(jù)庫的代碼 ??
package main import ( "database/sql" "log" _ "github.com/lib/pq" ) func main() { db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=ver) if err != nil { log.Fatal(err) } age := 21 rows, err := db.Query("SELECT name FROM users WHERE age = $1", age) } 復(fù)制代碼
這里是以空導(dǎo)入_
的方式導(dǎo)入 lib/pq 包的,main 函數(shù)中沒有使用 pq 包的任何變量、函數(shù)或方法,這樣就實(shí)現(xiàn)了對 PostgreSQL數(shù)據(jù)庫的訪問
實(shí)際原因
在 pq 包的 conn.go 源碼文件中的 init 函數(shù)
func init() { sql.Register("postgres", &Driver{}) }
- 利用了空導(dǎo)入的特性,將 lib/pq 包作為 main 包的依賴包,在包初始化時(shí),會(huì)先執(zhí)行 lib/pq 包里面的 init 函數(shù)
- pq 包的 init 函數(shù)將自己實(shí)現(xiàn)的 sql 驅(qū)動(dòng)注冊到了 sql 包中
- 這樣在實(shí)際應(yīng)用代碼中,Open 數(shù)據(jù)庫時(shí),傳入驅(qū)動(dòng)名字(這里是 postgres),就能得到數(shù)據(jù)庫實(shí)例,然后對數(shù)據(jù)庫進(jìn)行操作,實(shí)際上是因?yàn)檎{(diào)用了 pq 包中相應(yīng)的驅(qū)動(dòng)實(shí)現(xiàn)的
好處:這種通過在 init 函數(shù)中注冊自己的實(shí)現(xiàn)的模式,就有效降低了 Go 包對外的直接暴露,尤其是包級變量的暴露,從而避免了外部通過包級變量對包狀態(tài)的改動(dòng)
工廠設(shè)計(jì)模式
從標(biāo)準(zhǔn)庫 database/sql 包的角度來看,這種“注冊模式”實(shí)質(zhì)是一種工廠設(shè)計(jì)模式的實(shí)現(xiàn),sql.Open
函數(shù)就是這個(gè)模式中的工廠方法,它根據(jù)外部傳入的驅(qū)動(dòng)名稱“生產(chǎn)”出不同類別的數(shù)據(jù)庫實(shí)例句柄
通過注冊模式實(shí)現(xiàn)獲取各種格式圖片的寬、高
Go 源碼
package main import ( "fmt" "image" _ "image/gif" _ "image/jpeg" _ "image/png" "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() // 對文件進(jìn)行解碼,得到圖片實(shí)例 img, _, err := image.Decode(f) if err != nil { return 0, 0, err } // 返回圖片區(qū)域 b := img.Bounds() return b.Max.X, b.Max.Y, nil }
- 上面的源碼支持 png、jpeg、gif 三種格式的圖片
- 但并不需要手動(dòng)支持圖片格式
- 是因?yàn)?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) }
原文鏈接:https://juejin.cn/post/7055106841545539592
相關(guān)推薦
- 2023-03-11 CefSharp過濾圖片RequestHandler問題_C#教程
- 2024-01-15 Stream流 - 獲取Stream和轉(zhuǎn)換操作(含基本數(shù)據(jù)類型流)
- 2022-03-15 has been blocked by CORS policy: Response to prefl
- 2023-02-23 Rust個(gè)人學(xué)習(xí)小結(jié)之Rust的循環(huán)_Rust語言
- 2022-10-19 Python?變量教程私有變量詳解_python
- 2022-07-30 Oracle鎖表解決方法的詳細(xì)記錄_oracle
- 2023-02-09 最新解決'nvidia-smi'?不是內(nèi)部或外部命令也不是可運(yùn)行的程序_python
- 2022-04-22 Element UI 使用表單校驗(yàn),正確輸入后,仍然有提示信息
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支