這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
一、Package
Go語言中的包(Package)就像其它語言的庫(Library)或模組(Module)一樣,支援模組化,封裝性,可重用性,單獨編譯等特點。包的源碼是由數個.go檔案組成,這些檔案所在的目錄名是import路徑的最後一個詞,例如github.com/sunface/corego包的所有檔案都儲存在$GOPATH/src/github.com/sunface/corego底下。
每個包都有獨立的命名空間。例如,在image包中的Decode和unicode/utf16中的Decode是完全不同的函數。如果要引用第三方庫的函數,我們要使用package.Func的形式,例如image.Decode和utf16.Decode。
包也允許我們自己控制包內變數、函數的可見度。在Go語言中,變數、函數等的匯出只取決於一個因素:名字首字母的大小寫。
想象一下,如果我們的溫度轉換軟體開始流行了,然後希望貢獻給開源社區,應該怎麼做?
首先讓我們建立一個包github.com/sunface/temconv,在1.3節的例子基礎上做一些變化。這個包中包含了兩個檔案,示範了怎麼樣把資料聲明和資料訪問分開,在現實項目中,這個包實際只需要一個檔案。
temconv.go包含了型別宣告,常量,類型的method:
package tempconvimport "fmt"type Celsius float64type Fahrenheit float64const ( AbsoluteZeroC Celsius = -273.15 FreezingC Celsius = 0 BoilingC Celsius = 100)func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
conv.go包含了轉換函式:
//溫度轉換package tempconv// 將Celsius溫度轉換為Fahrenheitfunc CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }// 將Fahrenheit溫度轉換為Celsius.func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
每個.go檔案的第一行都是package tempconv的聲明,表示該檔案屬於哪個包。當包被匯入後,可以這樣調用它的成員:tempconv.CToF等等。包層級的變數,例如類型、常量等,對同一個包內的所有檔案都是可見的,就好像所有代碼定義在同一個檔案中一樣。注意這裡temconv.go匯入了fmt.但是conv.go沒有,因為它沒有用到fmt的任何成員。
上面代碼中包層級的const變數,都是大寫字母開頭的,因此可以在temconv包的外部使用,tempconv.AbsoluteZeroC:
fmt.Println(tempconv.CToF(tempconv.BoilingC)) // "212°F"
我們會選擇包內的某個.go檔案進行包層級的注釋,注釋寫在該檔案的package聲明前(見之前的conv.go)。一般來說,這裡的注釋是對檔案進行概述的。一個包只有一個檔案需要包層級的注釋。這些注釋一般會放在doc.go檔案中,後續可以通過go doc tempconv來查看包注釋。
二、包匯入(import)
在Go程式中,每一個包都是通過一個唯一的字串來標示的,被稱為匯入路徑,這些包是在import聲明中統一匯入的。Go語言規範不會對匯入的包名進行任何約定,這些是由Go的工具來完成解析的。當使用go的工具 (tool)時,一個匯入路徑代表了一個檔案夾,該檔案夾內包含了組成包的.go檔案。
在import聲明中,每個包都有自己的匯入包名,按照慣例,這個包名是匯入路徑的最後一個詞,例如github.com/sunface/tempconv的匯入包名是temconv:
package mainimport ( "fmt" "os" "strconv" "github.com/sunface/tempconv")func main() { for _, arg := range os.Args[1:] { t, err := strconv.ParseFloat(arg, 64) if err != nil { fmt.Fprintf(os.Stderr, "cf: %v\n", err) os.Exit(1) } f := tempconv.Fahrenheit(t) c := tempconv.Celsius(t) fmt.Printf("%s = %s, %s = %s\n", f, tempconv.FToC(f), c, tempconv.CToF(c)) }}因此可以直接調用tempconv.CToF。還可以使用別名機制避免包名衝突:
import ("github.com/sunface/tempconv"temp "github.com/sunfei/tempconv")調用github.com/sunfei/tempconv:temp.CToF;調用github.com/sunface/tempconv:tempconv.CToF。
上面這個程式將單獨的數字命令列參數轉換成Celsius和Fahrenhit的值。
$ go build github.com/sunface/corego/ch1.4/cf$ ./cf 3232°F = 0°C, 32°C = 89.6°F$ ./cf 212212°F = 100°C, 212°C = 413.6°F$ ./cf -40-40°F = -40°C, -40°C = -40°F
如果匯入一個包後不去使用,那麼就會報編譯錯誤,編譯器的這個檢查可以協助消除不需要的包引用,雖然在debug期間可能會比較蛋疼,例如注釋掉log.Print("hello")可能會消除程式對log包的引用,這個時候編譯器就會報錯。還好,我們可以使用golang.org/x/tools/cmd/goimports工具,它會自動插入和移除包引用,大多數ide都支援配置去使用goimports。
三、包的初始化
包的初始化時會按照聲明的順序初始化包層級的變數,除非變數間有依賴順序:
var a = b + c // a 第三個初始化 3var b = f() // b 第二個初始化,調用了fvar c = 1 // c 第一個初始化func f() int { return c + 1 }如果某個包有多個.go檔案,那這些檔案會按照提交給編譯器的順序來初始化,在喚醒編譯器前,go tool會通過檔案名稱對.go檔案進行排序。
任何檔案都可以包含任何數目的init函數:
func init() { /* ... */ }這種init函數不能被調用也不能被引用,在每個檔案內,init函數都會在程式剛啟動的時候自動運行,檔案內每個init函數會按照聲明的順序依次執行。
每個包只會被初始化一次,首先是初始化依賴包,如果包p匯入q,可以肯定的是:q在p初始化之前肯定會完全初始化。因為依賴包會先初始化,所以程式的初始化是自底向上的,main包肯定是最後一個初始化(包的組織類似一個樹形結構,main是根節點)。這樣在main函數開始時,所有的包都會初始化完畢!
文章所有權:Golang隱修會 連絡人:孫飛,CTO@188.com!