您好,登錄后才能下訂單哦!
我們在使用其他語言,比如 Java ,是有包的概念的。它是 Java 語言中組織我們的 Java 文件的一個概念,比如java.lang
這個包,它里面有很多我們常用的類,比如 String。在 Go 語言中,包也是類似的概念。它是把我們的 Go 文件組織起來,可以方便進行歸類、復用等, 比如 Go 內置的 net 包。
net ├── http ├── internal ├── mail ├── rpc ├── smtp ├── testdata ├── textproto └── url
以上是 net 包的一個目錄結構,net 本身是一個包,net 目錄下的 http 又是一個包。從這個大家可以看到,Go 語言的包其實就是我們計算機里的目錄,或者叫文件夾,通過它們進行目錄結構和文件組織。Go 只是對目錄名字做了一個翻譯,叫“包”而已。比如這里的 net 包其實就是 net 目錄,http 包其實就是 http 目錄,這也是 Go 語言中的一個命名習慣,包名和文件所在的目錄名是一樣的。
包的命名
Go 語言中包的命名,遵循簡潔、小寫以及與 Go 文件所在目錄同名的原則,這樣就便于我們引用、書寫和快速定位查找。
比如 Go 自帶的 http 這個包,這個 http 目錄下的所有 Go 文件都屬于這個 http 包,所以我們使用 http 包里的函數、接口的時候,導入這個 http 包就可以了。
package main import "net/http" func main() { http.ListenAndServe("127.0.0.1:80",handler); }
從這個例子可以看到,我們導入的是net/http
,這在 Go 語言里叫做全路徑。因為 http 包在 net 里面,net 是最頂級的包,所以必須使用全路徑導入,Go 編譯程序才能找到 http 這個包,和我們文件系統的目錄路徑是一樣的。
因為有了全路徑,所以命名的包名可以和其他庫的一樣,只要它們的全路徑不同就可以了。使用全路徑的導入,也增加了包名命名的靈活性。
對于個人或者公司開發的程序而言,我們一般采用域名作為頂級包名的方式,這樣就不用擔心和其他開發者包名重復的問題了,比如我的個人域名是www.flysnow.org
,那么我自己開發的 Go 程序都以flysnow.org
作為全路徑中的最頂層部分。例如,導入我開發的一個工具包:
package main import "flysnow.org/tools"
如果你沒有自己的域名,怎么辦呢?這時候可以使用 Github.com 。干研發這一行的,在 Github 都會有個賬號,如果沒有趕緊申請一個。這時候我們就可以使用github.com/<username>
作為你的頂級路徑了,別人是不會和你重名的。
package main import "github.com/rujews/tools"
這就是換成 Github.com 命名的方式。
main包
當把一個 Go 文件的包名聲明為main
時,就等于告訴 Go 編譯程序,我這個是一個可執行的程序,那么 Go 編譯程序就會嘗試把它編譯為一個二進制的可執行文件。
一個main
的包,一定會包含一個main()
函數,這種我們也不陌生,比如 C 和 Java 都有main()
函數,它是一個程序的入口,沒這個函數,程序就無法執行。
在 Go 語言里,同時要滿足main
包和包含main()
函數,才會被編譯成一個可執行文件。
我們看一個 Hello World 的 Go 語言版本,來說明main
包。
package main import "fmt" func main() { fmt.Println("Hello, 世界") }
假設該 Go 文件叫 hello.go,放在$GOPATH/src/hello
目錄下,那么我們在這個目錄下執行go build
命令就會生成二進制的可執行文件,在 window 系統下生成的是hello.exe
;在 UINX、MAC 和 Linux 下生成的是hello
,我們在 CMD 或者終端里執行它,就可以看到控制臺打印的:
Hello, 世界
二進制可執行文件的名字,就是該 main 包的 Go 文件所在目錄的名字,因為 hello.go 在 hello 目錄下,所以生成的可執行文件就是 hello 這個名字。
導入包
要想使用一個包,必須先導入它才可以使用。Go 語言提供了import
關鍵字來導入一個包,這個關鍵字告訴 Go 編譯器到磁盤的哪里去找要想導入的包,所以導入的包必須是一個全路徑的包,也就是包所在的位置。
import "fmt"
這就表示我們導入了fmt
包,也就等于告訴 Go 編譯器,我們要使用這個包下面的代碼。如果要導入多個包怎么辦呢?Go 語言還為我們提供了導入塊。
import ( "net/http" "fmt" )
使用一對括號包含導入塊,每個包獨占一行。
對于多于一個路徑的包名,在代碼中引用的時候,使用全路徑最后一個包名作為引用的包名,比如net/http
,我們在代碼使用的是http
,而不是net
。
現在我導入了包,那么編譯的時候,Go 編譯器去什么位置找它們呢?這里就要介紹一下 Go 的環境變量了。Go 有兩個很重要的環境變量GOROOT
和GOPATH
,這是兩個定義路徑的環境變量,GOROOT
是安裝 Go 的路徑,比如/usr/local/go
;GOPATH
是我們自己定義的開發者個人的工作空間,比如/home/flysnow/go
。
編譯器會使用我們設置的這兩個路徑,再加上import
導入的相對全路徑來查找磁盤上的包,比如我們導入的fmt
包,編譯器最終找到的是/usr/local/go/fmt
這個位置。
值得了解的是:首先,對于包的查找是有優先級的,編譯器會優先在GOROOT
里搜索;其次是GOPATH
,一旦找到,就會馬上停止搜索。如果最終都沒找到,那么就報編譯異常了。
遠程包導入
互聯網的時代,現在大家使用類似于 Github 共享代碼的越來越多,如果有的 Go 包共享在 Github 上,我們一樣有辦法使用它們,這就是遠程導入包了,或者是網絡導入,Go 天生就支持這種情況,所以我們可以很隨意地使用 Github 上的 Go 庫開發程序。
import "github.com/spf13/cobra"
這種導入,前提必須是該包托管在一個分布式的版本控制系統上,比如 Github、Bitbucket 等,并且是 Public 的權限,可以讓我們直接訪問它們。
編譯在導入它們的時候,會先在GOPATH
下搜索這個包,如果沒有找到,就會使用go get
工具從版本控制系統(GitHub)獲取,并且會把獲取到的源代碼存儲在GOPATH
目錄下對應 URL 的目錄里,以供編譯使用。
go get
工具可以遞歸獲取依賴包,如果github.com/spf13/cobra
也引用了其他的遠程包,該工具可以一并下載下來。
命名導入
我們知道,在使用import
關鍵字導入包之后,我們就可以在代碼中通過包名使用該包下相應的函數、接口等。如果我們導入的包名正好有重復的怎么辦呢?針對這種情況,Go 語言可以讓我們對導入的包重新命名,這就是命名導入。
package main import ( "fmt" myfmt "mylib/fmt" ) func main() { fmt.Println() myfmt.Println() }
如果沒有重新命名,那么對于編譯器來說,這兩個fmt
它是區分不清楚的。重命名也很簡單,在我們導入的時候,在包名的左側,起一個新的包名就可以了。
Go 語言規定,導入的包必須要使用,否則會包編譯錯誤,這是一個非常好的規則,因為這樣可以避免我們引用很多無用的代碼而導致的代碼臃腫和程序的龐大。很多時候,我們都不知道哪些包是否使用,這在 C 和 Java 上會經常遇到,因此我們不得不借助工具來查找我們沒有使用的文件、類型、方法和變量等,把它們清理掉。
但是有時候,我們需要導入一個包,但是又不使用它,按照規則,這是不行的,為此 Go 語言給我們提供了一個空白標志符 _ ,只需要我們使用 _ 重命名我們導入的包就可以了。
package main import ( _ "mylib/fmt" )
包的 init ()函數
每個包都可以有任意多個init()
函數,這些init()
函數都會在main()
函數之前執行。init()
函數通常用來做初始化變量、設置包或者其他需要在程序執行前的引導工作。比如上面我們講的需要使用 _ 空標志符來導入一個包的目的,就是想執行這個包里的init()
函數。
我們以數據庫的驅動為例,Go 語言為了統一關于數據庫的訪問,使用databases/sql
抽象了一層數據庫的操作,可以滿足我們操作 MYSQL、Postgre 等數據庫。這樣不管我們使用這些數據庫的哪個驅動,編碼操作都是一樣的,想換驅動的時候,就可以直接換掉,而不用修改具體的代碼。
這些數據庫驅動的實現,就是具體的,可以由任何人實現的,它的原理就是定義了init()
函數,在程序運行之前,把實現好的驅動注冊到 sql 包里,這樣我們就使用使用它操作數據庫了。
package mysql import ( "database/sql" ) func init() { sql.Register("mysql", &MySQLDriver{}) }
因為我們只是想執行這個 mysql 包的init()
方法,并不想使用這個包,所以我們在導入這個包的時候,需要使用_
重命名包名,避免編譯錯誤。
import "database/sql" import _ "github.com/go-sql-driver/mysql" db, err := sql.Open("mysql", "user:password@/dbname")
看非常簡潔,剩下針對的數據庫的操作都是使用的database/sql
標準接口。如果我們想換一個 mysql 的驅動的話,只需要換個導入就可以了,靈活方便,這也是面向接口編程的便利。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。