申金鑫? 17101223365
轉(zhuǎn)載自知乎專欄 Go語言實戰(zhàn)筆記
【嵌牛導(dǎo)讀】:本教程詳細(xì)介紹了Go語言的一些包的使用
【嵌牛鼻子】: Go 語言
【嵌牛提問】:如何使用Go語言的包
【嵌牛正文】:
前兩天一直關(guān)注的《Go語言實戰(zhàn)》終于拿到手了,這本書期待了很久樟蠕,預(yù)售的時候就第一時間進(jìn)行了預(yù)定贮聂。昨天閑暇時間翻了前三章,覺得還不錯寨辩,所以打算針對該書籍吓懈,寫一個讀書筆記系列,這是對自己看書的一個總結(jié)靡狞,也是和大家一個分享耻警。
這本是In Action系列的書籍,這個系列做研發(fā)的都知道甸怕,在研發(fā)屆評價很多甘穿,很多新的技術(shù)、語言等都會有一本實戰(zhàn)的書籍梢杭。既然是實戰(zhàn)温兼,那么這本書假設(shè)了他的讀者有了一定的基礎(chǔ),比如這本書的讀者要有一定的Go語言基礎(chǔ)武契,比如Go開發(fā)環(huán)境搭建妨托,Go的內(nèi)置類型、Go的常用關(guān)鍵字等等吝羞。
不過我公眾號[flysnow_org]里兰伤,既然打算出一個《Go語言實戰(zhàn)》的讀書筆記,就不需要大家有Go基礎(chǔ)了钧排,書里沒講的知識點敦腔,我也會在我的讀書筆記里介紹,當(dāng)然還有深入恨溜。
什么是Go語言中的包
這里我們直接講Go語言實戰(zhàn)的筆記符衔,就不介紹Go語言的入門了找前,要入門,可以參考我的這篇文章 Go語言開發(fā)環(huán)境搭建詳解
我們在使用其他語言判族,比如Java躺盛,是有包的概念的,它是Java語言中組織我們的Java文件的一個概念形帮,比如java.lang這個包槽惫,他里面有很多我們常用的類,比如String辩撑。在Go語言中界斜,包也是類似的概念,它是把我們的go文件組織起來合冀,可以方便進(jìn)行歸類各薇、復(fù)用等目的。 比如Go內(nèi)置的net包
net
├── http
├── internal
├── rpc
├── smtp
├── testdata
├── textproto
└── url
以上是net包的一個目錄結(jié)構(gòu)君躺,net本身是一個包峭判,net目錄下的http又是一個包。從這個大家可以看到棕叫,go語言的包其實就是我們計算機里的目錄林螃,或者叫文件夾,通過它們進(jìn)行目錄結(jié)構(gòu)和文件組織谍珊,go只是對目錄名字做了一個翻譯治宣,叫【包】而已。比如這里的net包其實就是net目錄砌滞,http包其實就是http目錄侮邀,這也是go語言中的一個命名習(xí)慣,包名和文件所在的目錄名是一樣的贝润。
包的命名
go語言的包的命名绊茧,遵循簡潔、小寫打掘、和go文件所在目錄同名的原則华畏,這樣就便于我們引用,書寫以及快速定位查找尊蚁。
比如go自帶的http這個包亡笑,它這個http目錄下的所有g(shù)o文件都屬于這個http包,所以我們使用http包里的函數(shù)、接口的時候横朋,導(dǎo)入這個http包就可以了仑乌。
package main
import "net/http"
func main() {
? ? http.ListenAndServe("127.0.0.1:80",handler);
}
從這個例子可以看到,我們導(dǎo)入的是net/http,這在go里叫做全路徑,因為http包在net里面晰甚,net是最頂級的包衙传,所以必須使用全路徑導(dǎo)入,go編譯程序才能找到http這個包厕九,和我們文件系統(tǒng)的目錄路徑是一樣的蓖捶。
因為有了全路徑,所以命名的包名可以和其他庫的一樣扁远,只要它們的全路徑不同就可以了俊鱼,使用全路徑的導(dǎo)入,也增加了包名命名的靈活性穿香。
對于自己或者公司開發(fā)的程序而言亭引,我們一般采用域名作為頂級包名的方式绎速,這樣就不用擔(dān)心和其他開發(fā)者包名重復(fù)的問題了皮获,比如我的個人域名是飛雪無情的博客 | 專注于Android、Java纹冤、移動互聯(lián)網(wǎng)洒宝、項目管理、軟件架構(gòu),那么我自己開發(fā)的go程序都以flysnow.org作為全路徑中的最頂層部分萌京,比如導(dǎo)入我開發(fā)的一個工具包:
package main
import "flysnow.org/tools"
如果你沒有自己的域名雁歌,怎么辦呢?這時候可以使用http://github.com知残。干研發(fā)這一行的靠瞎,在github都會有個賬號,如果沒有趕緊申請一個求妹,這時候我們就可以使用http://github.com/<username>作為你的頂級路徑了乏盐,別人是不會和你重名的。
package main
import "github.com/rujews/tools"
這就是換成http://github.com命名的方式制恍。
main包
當(dāng)把一個go文件的包名聲明為main時父能,就等于告訴go編譯程序,我這個是一個可執(zhí)行的程序净神,那么go編譯程序就會嘗試把它編譯為一個二進(jìn)制的可執(zhí)行文件何吝。
一個main的包,一定會包含一個main()函數(shù)鹃唯,這種我們也不陌生爱榕,比如C和Java都有main()函數(shù),它是一個程序的入口,沒這個函數(shù)坡慌,程序就無法執(zhí)行黔酥。
在go語言里,同時要滿足main包和包含main()函數(shù),才會被編譯成一個可執(zhí)行文件絮爷。
我們看一個Hello World的Go語言版本趴酣,來說明main 包。
package main
import "fmt"
func main() {
? ? fmt.Println("Hello, 世界")
}
假設(shè)該go文件叫hello.go,放在$GOPATH/src/hello目錄下坑夯,那么我們在這個目錄下執(zhí)行g(shù)o build命令就會生成二進(jìn)制的可執(zhí)行文件岖寞,在window系統(tǒng)下生成的是hello.exe,在Unix柜蜈,MAC和Linux下生成的是hello,我們在CMD或者終端里執(zhí)行它仗谆,就可以看到控制臺打印的:
Hello, 世界
二進(jìn)制可執(zhí)行文件的名字,就是該main包的go文件所在目錄的名字淑履,因為hello.go在hello目錄下隶垮,所以生成的可執(zhí)行文件就是hello這個名字。
導(dǎo)入包
要想使用一個包秘噪,必須先導(dǎo)入它才可以使用狸吞,Go語言提供了import關(guān)鍵字來導(dǎo)入一個包,這個關(guān)鍵字告訴Go編譯器到磁盤的哪里去找要想導(dǎo)入的包指煎,所以導(dǎo)入的包必須是一個全路徑的包蹋偏,也就是包所在的位置。
import "fmt"
這就表示我們導(dǎo)入了fmt包至壤,也就等于告訴go編譯器威始,我們要使用這個包下面的代碼。如果要導(dǎo)入多個包怎么辦呢像街?Go語言還為我們提供的導(dǎo)入塊黎棠。
import (
? ? "net/http"
? ? "fmt"
)
使用一對括號包含的導(dǎo)入塊,每個包獨占一行镰绎。
對于多于一個路徑的包名脓斩,在代碼中引用的時候,使用全路徑最后一個包名作為引用的包名跟狱,比如net/http,我們在代碼使用的是http俭厚,而不是net。
現(xiàn)在我導(dǎo)入了包驶臊,那么編譯的時候挪挤,go編譯器去什么位置找他們呢?這里就要介紹下Go的環(huán)境變量了关翎。Go有兩個很重要的環(huán)境變量GOROOT和GOPATH,這是兩個定義路徑的環(huán)境變量扛门,GOROOT是安裝Go的路徑,比如/usr/local/go纵寝;GOPATH是我們自己定義的開發(fā)者個人的工作空間论寨,比如/home/flysnow/go。
編譯器會使用我們設(shè)置的這兩個路徑,再加上import導(dǎo)入的相對全路徑來查找磁盤上的包葬凳,比如我們導(dǎo)入的fmt包绰垂,編譯器最終找到的是/usr/local/go/fmt這個位置。
值得了解的是:對于包的查找火焰,是有優(yōu)先級的劲装,編譯器會優(yōu)先在GOROOT里搜索,其次是GOPATH,一旦找到昌简,就會馬上停止搜索占业。如果最終都沒找到,就報編譯異常了纯赎。
遠(yuǎn)程包導(dǎo)入
互聯(lián)網(wǎng)的時代谦疾,現(xiàn)在大家使用類似于Github共享代碼的越來越多,如果有的Go包共享在Github上犬金,我們一樣有辦法使用他們念恍,這就是遠(yuǎn)程導(dǎo)入包了,或者是網(wǎng)絡(luò)導(dǎo)入佑附,Go天生就支持這種情況樊诺,所以我們可以很隨意的使用Github上的Go庫開發(fā)程序仗考。
import "github.com/spf13/cobra"
這種導(dǎo)入音同,前提必須是該包托管在一個分布式的版本控制系統(tǒng)上,比如Github秃嗜、Bitbucket等权均,并且是Public的權(quán)限,可以讓我們直接訪問它們锅锨。
編譯在導(dǎo)入它們的時候叽赊,會先在GOPATH下搜索這個包,如果沒有找到必搞,就會使用go get工具從版本控制系統(tǒng)(GitHub)獲取必指,并且會把獲取到的源代碼存儲在GOPATH目錄下對應(yīng)URL的目錄里,以供編譯使用恕洲。
go get工具可以遞歸獲取依賴包塔橡,如果http://github.com/spf13/cobra也引用了其他的遠(yuǎn)程包,該工具可以一并下載下來霜第。
命名導(dǎo)入
我們知道葛家,在使用import關(guān)鍵字導(dǎo)入包之后,我們就可以在代碼中通過包名使用該包下相應(yīng)的函數(shù)泌类、接口等癞谒。如果我們導(dǎo)入的包名正好有重復(fù)的怎么辦呢?針對這種情況,Go語言可以讓我們對導(dǎo)入的包重新命名弹砚,這就是命名導(dǎo)入双仍。
package main
import (
? ? "fmt"
? ? myfmt "mylib/fmt"
)
func main() {
? ? fmt.Println()
? ? myfmt.Println()
}
如果沒有重新命名,那么對于編譯器來說桌吃,這兩個fmt它是區(qū)分不清楚的殊校。重命名也很簡單,在我們導(dǎo)入的時候读存,在包名的左側(cè)为流,起一個新的包名就可以了。
Go語言規(guī)定让簿,導(dǎo)入的包必須要使用敬察,否則會包編譯錯誤,這是一個非常好的規(guī)則尔当,因為這樣可以避免我們引用很多無用的代碼而導(dǎo)致的代碼臃腫和程序的龐大莲祸,因為很多時候,我們都不知道哪些包是否使用椭迎,這在C和Java上會經(jīng)常遇到锐帜,有時候我們不得不借助工具來查找我們沒有使用的文件、類型畜号、方法和變量等缴阎,把它們清理掉。
但是有時候简软,我們需要導(dǎo)入一個包蛮拔,但是又不使用它,按照規(guī)則痹升,這是不行的建炫,為此Go語言給我們提供了一個空白標(biāo)志符_,只需要我們使用_重命名我們導(dǎo)入的包就可以了。
package main
import (
? ? _ "mylib/fmt"
)
包的init函數(shù)
每個包都可以有任意多個init函數(shù)疼蛾,這些init函數(shù)都會在main函數(shù)之前執(zhí)行肛跌。init函數(shù)通常用來做初始化變量、設(shè)置包或者其他需要在程序執(zhí)行前的引導(dǎo)工作察郁。比如上面我們講的需要使用_空標(biāo)志符來導(dǎo)入一個包的目的衍慎,就是想執(zhí)行這個包里的init函數(shù)。
我們以數(shù)據(jù)庫的驅(qū)動為例绳锅,Go語言為了統(tǒng)一關(guān)于數(shù)據(jù)庫的訪問西饵,使用databases/sql抽象了一層數(shù)據(jù)庫的操作,可以滿足我們操作MYSQL鳞芙、Postgre等數(shù)據(jù)庫眷柔,這樣不管我們使用這些數(shù)據(jù)庫的哪個驅(qū)動期虾,編碼操作都是一樣的,想換驅(qū)動的時候驯嘱,就可以直接換掉镶苞,而不用修改具體的代碼。
這些數(shù)據(jù)庫驅(qū)動的實現(xiàn)鞠评,就是具體的茂蚓,可以由任何人實現(xiàn)的武翎,它的原理就是定義了init函數(shù)毕籽,在程序運行之前,把實現(xiàn)好的驅(qū)動注冊到sql包里溉躲,這樣我們就使用使用它操作數(shù)據(jù)庫了负乡。
package mysql
import (
? ? "database/sql"
)
func init() {
? ? sql.Register("mysql", &MySQLDriver{})
}
因為我們只是想執(zhí)行這個mysql包的init方法牍白,并不想使用這個包,所以我們在導(dǎo)入這個包的時候抖棘,需要使用_重命名包名茂腥,避免編譯錯誤。
import "database/sql"
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", "user:password@/dbname")
看非常簡潔切省,剩下針對的數(shù)據(jù)庫的操作最岗,都是使用的database/sql標(biāo)準(zhǔn)接口,如果我們想換一個mysql的驅(qū)動的話朝捆,只需要換個導(dǎo)入就可以了般渡,靈活方便,這也是面向接口編程的便利右蹦。
這里關(guān)于Go包管理使用的就結(jié)束了诊杆,后面會接著講Go的一些常用工具、文檔以及依賴管理何陆。