包是 Go 語言中代碼組織和代碼編譯的主要方式。關(guān)于它們的很多基本信息已經(jīng)在 4.2 章節(jié)中給出,最引人注目的便是可見性?,F(xiàn)在我們來看看具體如何來使用自己寫的包。在下一節(jié),我們將回顧一些標(biāo)準(zhǔn)庫中的包,自定義的包和標(biāo)準(zhǔn)庫以外的包。
當(dāng)寫自己包的時候,要使用短小的不含有 _
(下劃線)的小寫單詞來為文件命名。這里有個簡單例子來說明包是如何相互調(diào)用以及可見性是如何實現(xiàn)的。
當(dāng)前目錄下(examples/chapter_9/book/)有一個名為 package_test.go 的程序, 它使用了自定義包 pack1 中 pack1.go 的代碼。這段程序(連同編譯鏈接生成的 pack1.a)存放在當(dāng)前目錄下一個名為 pack1 的文件夾下。所以鏈接器將包的對象和主程序?qū)ο箧溄釉谝黄稹?/p>
示例 9.4 pack1.go:
package pack1
var Pack1Int int = 42
var PackFloat = 3.14
func ReturnStr() string {
return "Hello main!"
}
它包含了一個整型變量 Pack1Int
和一個返回字符串的函數(shù) ReturnStr
。這段程序在運行時不做任何的事情,因為它不包含有一個 main 函數(shù)。
在主程序 package_test.go 中這個包通過聲明的方式被導(dǎo)入
import "./pack1/pack1"
import 的一般格式如下:
import "包的路徑或 URL 地址"
例如:
import "github.com/org1/pack1”
路徑是指當(dāng)前目錄的相對路徑。
示例 9.5 package_test.go:
package main
import (
"fmt"
"./pack1/pack1"
)
func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf("Integer from package1: %d\n", pack1.Pack1Int)
// fmt.Printf("Float from package1: %f\n", pack1.pack1Float)
}
輸出結(jié)果:
ReturnStr from package1: Hello main!
Integer from package1: 42
如果包 pack1 和我們的程序在同一路徑下,我們可以通過 "import ./pack1"
這樣的方式來引入,但這不被視為一個好的方法。
下面的代碼試圖訪問一個未引用的變量或者函數(shù),甚至沒有編譯。將會返回一個錯誤:
fmt.Printf("Float from package1: %f\n", pack1.pack1Float)
錯誤:
cannot refer to unexported name pack1.pack1Float
主程序利用的包必須在主程序編寫之前被編譯。主程序中每個 pack1 項目都要通過包名來使用:pack1.Item
。具體使用方法請參見示例 4.6 和 4.7。
因此,按照慣例,子目錄和包之間有著密切的聯(lián)系:為了區(qū)分,不同包存放在不同的目錄下,每個包(所有屬于這個包中的 go 文件)都存放在和包名相同的子目錄下:
Import with .
:
import . "./pack1"
當(dāng)使用.
來做為包的別名時,你可以不通過包名來使用其中的項目。例如:test := ReturnStr()
。
在當(dāng)前的命名空間導(dǎo)入 pack1 包,一般是為了具有更好的測試效果。
Import with _
:
import _ "./pack1/pack1"
pack1包只導(dǎo)入其副作用,也就是說,只執(zhí)行它的init函數(shù)并初始化其中的全局變量。
導(dǎo)入外部安裝包:
如果你要在你的應(yīng)用中使用一個或多個外部包,首先你必須使用 go install
(參見第 9.7 節(jié))在你的本地機器上安裝它們。
假設(shè)你想使用 http://codesite.ext/author/goExample/goex
這種托管在 Google Code、GitHub 和 Launchpad 等代碼網(wǎng)站上的包。
你可以通過如下命令安裝:
go install codesite.ext/author/goExample/goex
將一個名為 codesite.ext/author/goExample/goex
的 map 安裝在 $GOROOT/src/
目錄下。
通過以下方式,一次性安裝,并導(dǎo)入到你的代碼中:
import goex "codesite.ext/author/goExample/goex"
因此該包的 URL 將用作導(dǎo)入路徑。
在 http://golang.org/cmd/goinstall/
的 go install
文檔中列出了一些廣泛被使用的托管在網(wǎng)絡(luò)代碼倉庫的包的導(dǎo)入路徑
包的初始化:
程序的執(zhí)行開始于導(dǎo)入包,初始化 main 包然后調(diào)用 main 函數(shù)。
一個沒有導(dǎo)入的包將通過分配初始值給所有的包級變量和調(diào)用源碼中定義的包級 init 函數(shù)來初始化。一個包可能有多個 init 函數(shù)甚至在一個源碼文件中。它們的執(zhí)行是無序的。這是最好的例子來測定包的值是否只依賴于相同包下的其他值或者函數(shù)。
init 函數(shù)是不能被調(diào)用的。
導(dǎo)入的包在包自身初始化前被初始化,而一個包在程序執(zhí)行中只能初始化一次。
編譯并安裝一個包(參見第 9.7 節(jié)):
在 Linux/OS X 下可以用類似第 3.9 節(jié)的 Makefile 腳本做到這一點:
include $(GOROOT)/src/Make.inc
TARG=pack1
GOFILES=\
pack1.go\
pack1b.go\
include $(GOROOT)/src/Make.pkg
通過 chmod 777 ./Makefile
確保它的可執(zhí)行性。
上面腳本內(nèi)的include語引入了相應(yīng)的功能,將自動檢測機器的架構(gòu)并調(diào)用正確的編譯器和鏈接器。
然后終端執(zhí)行 make 或 gomake
工具:他們都會生成一個包含靜態(tài)庫 pack1.a 的 _obj 目錄。
go install(參見第 9.7 節(jié),從 Go1 的首選方式)同樣復(fù)制 pack1.a 到本地的 $GOROOT/pkg 的目錄中一個以操作系統(tǒng)為名的子目錄下。像 import "pack1"
代替 import "path to pack1"
,這樣只通過名字就可以將包在程序中導(dǎo)入。
當(dāng)?shù)?13 章我們遇到使用測試工具進行測試的時候我們將重新回到自己的包的制作和編譯這個話題。
問題 9.1
a)一個包能分成多個源文件么?
b)一個源文件是否能包含多個包?
練習(xí) 9.3
創(chuàng)建一個程序 main_greetings.go 能夠和用戶說 "Good Day" 或者 "Good Night"。不同的問候應(yīng)該放到 greetings 包中。
在同一個包中創(chuàng)建一個 ISAM 函數(shù)返回一個布爾值用來判斷當(dāng)前時間是 AM 還是 PM,同樣創(chuàng)建 IsAfternoon
和 IsEvening
函數(shù)。
使用 main_greetings 作出合適的問候(提示:使用 time 包)。
練習(xí) 9.4 創(chuàng)建一個程序 main_oddven.go 判斷前 100 個整數(shù)是不是偶數(shù),包內(nèi)同時包含測試的功能。
練習(xí) 9.5 使用第 6.6 節(jié)的斐波那契程序:
1)將斐波那契功能放入自己的 fibo 包中并通過主程序調(diào)用它,存儲最后輸入的值在函數(shù)的全局變量。
2)擴展 fibo 包將通過調(diào)用斐波那契的時候,操作也作為一個參數(shù)。實驗 "+" 和 “*”
main_fibo.go / fibonacci.go