到目前爲止咱們見到的 Go 程序都只有一個文件,文件中包含了一個main函數和幾個其餘函數。在實際中這種將全部代碼都放在一個文件裏的組織方式是不可行的。這樣的組織方式使得代碼變得沒法重用和維護困難。包(package)用於解決這樣的問題。git
包用於組織Go源代碼,以得到更好的重用性和可讀性。包提供了代碼封裝的機制從而使得Go應用程序易於維護。例如,假設咱們正在開發一個圖像處理應用,它提供了諸如圖像裁剪,銳化,模糊和增色等功能。一種組織代碼的方式是將全部實現同一功能的代碼放在一個獨立的包中。例如裁剪功能能夠放在一個單獨包中,而銳化功能能夠放在另外一個包中。這種作法的優勢是:增色功能可能須要作一些銳化的處理,那麼增色代碼中能夠簡單地導入(咱們即將討論導入)銳化包,使用其中提供的功能便可。這種方式使得代碼變得更容易重用。github
咱們將逐步建立一個計算矩形面積和對角線的應用程序。golang
經過構建該程序,咱們將更好的理解包。less
每一個可執行的Go程序都必須包含一個 main
函數。這個函數是執行程序的入口點。main
函數應該包含在 main
包中。函數
指定一個特定源文件屬於一個包的語法爲:package packagename
,這條語句應該放在源文件的第一行。學習
下面讓咱們開始建立 main
函數和 main
包。在 [工做空間目錄]/src
目錄下新建一個子目錄,命名爲 geometry
。在該目錄下新建 geometry.go
。spa
編寫 geometry.go
代碼以下:.net
//geometry.go package main import "fmt" func main() { fmt.Println("Geometrical shape properties") }
package main
這一行指定了該文件屬於 main
包。import "packagename"
語句用來導入一個包,這裏咱們導入 fmt
包,該包導出了 Println
方法(譯者注:該方法用於打印文本到標準輸出)。而後是 main
函數,在這裏僅打印 Geometrical shape properties
。日誌
執行 go install geometry
編譯上面的程序。該命令在 geometry
目錄下查找包含 main
函數的文件,在這裏就是 geometry.go
。找到後編譯該文件並在 [工做空間目錄]/bin
目錄下生成二進制文件geometry
(在Windows下是 geometry.exe
)。 如今的目錄結構以下所示:code
src/ geometry/ geometry.go bin/ geometry
執行 [工做空間目錄]/bin/geometry
運行該程序,其中 [工做空間目錄] 須要換成本身的實際目錄。這條命令運行在 bin
目錄下的 geometry
二進制文件。你應該能夠看到以下輸出:
Geometrical shape properties
下面咱們將建立一個 rectangle
包,將與矩形相關的功能(計算矩形的面積和對角線)都放在這個包裏。
屬於同一個包的源文件應該放在獨立的文件夾中,按照Go的慣例,該文件夾的名字應該與包名相同。
所以讓咱們在 geometry
目錄下建立一個 rectangle
子目錄。全部放在該目錄下的源文件都應該以 package rectangle
開頭,用以表示這些源文件都屬於 rectangle
包。
在 rectangle
目錄下新建 rectangle.go
,編寫以下代碼:
//rectprops.go package rectangle import "math" func Area(len, wid float64) float64 { area := len * wid return area } func Diagonal(len, wid float64) float64 { diagonal := math.Sqrt((len * len) + (wid * wid)) return diagonal }
在上面的代碼中咱們實現了兩個函數 Area
和 Diagonal
分別用於計算矩形的面積和對角線。矩形的面積爲長與寬的積。矩形的對角線爲長與寬的平方和再開根號。這裏調用 math
包中的 Sqrt
函數來計算平方根。
注意上面實現的兩個函數的函數名 Area
和 Diagonal
都是以大寫字母開頭的。這是必要的,咱們將很快解釋爲何須要這樣作。
爲了使用自定義包咱們必須先導入它。用來導入自定義包的語法爲:import path
。咱們必須指定 path
爲相對於 [工做空間目錄]/src
目錄的相對路徑。咱們當前的目錄結構以下所示:
src/ geometry/ geometry.go rectangle/ rectangle.go
語句 import "geometry/rectangle"
表示咱們要導入 rectangle 包。
在 geometry.go
中添加以下代碼:
//geometry.go package main import ( "fmt" "geometry/rectangle" //importing custom package ) func main() { var rectLen, rectWidth float64 = 6, 7 fmt.Println("Geometrical shape properties") /*Area function of rectangle package used */ fmt.Printf("area of rectangle %.2f\n", rectangle.Area(rectLen, rectWidth)) /*Diagonal function of rectangle package used */ fmt.Printf("diagonal of the rectangle %.2f ",rectangle.Diagonal(rectLen, rectWidth)) }
上面的代碼導入了 rectangle
包而且使用 Area
和 Diagonal
函數計算矩形的面積和對角線。在 Printf
中的 %.2f
格式化指示符表示僅保留浮點數的兩位小數。程序輸出以下:
Geometrical shape properties area of rectangle 42.00 diagonal of the rectangle 9.22
咱們將 rectangle
包中的兩個函數名稱 Area
和 Diagonal
的首字母大寫,這在 Go 中有特殊的意義。在 Go 中,任何以大寫字母開頭的變量名、函數名都是被導出的名字(exported name)。只有被導出的名字才能被其它包訪問。在這裏咱們須要在 main
包中訪問 Area
和 Diagonal
函數,所以將它們的首字母大寫。
若是將 rectprops.go
中的 Area(len, wid float64)
改爲 area(len, wid float64)
,而且將 geometry.go
中的 rectangle.Area(rectLen, rectWidth)
改爲 rectangle.area(rectLen, rectWidth)
,那麼運行程序時編譯器將會報錯:geometry.go:11: cannot refer to unexported name rectangle.area
。所以,若是想訪問包外的函數,必須將其首字母大寫。
每個包均可以包含一個 init
函數。該函數不該該有任何參數和返回值,而且在咱們的代碼中不能顯式調用它。init
函數形式以下:
func init() { }
init
函數可用於執行初始化任務,也可用於在執行開始以前驗證程序的正確性。
一個包的初始化順序以下:
init
函數被調用。一個包能夠有多個 init
函數(在一個或多個文件中),它們的調用順序爲編譯器解析它們的順序。若是一個包導入了另外一個包,被導入的包先初始化。
儘管一個包可能被包含屢次,可是它只被初始化一次。
下面讓咱們對咱們的程序作一些修改來理解 init
函數。
首先在 rectprops.go
中添加一個 init
函數:
//rectprops.go package rectangle import "math" import "fmt" /* * init function added */ func init() { fmt.Println("rectangle package initialized") } func Area(len, wid float64) float64 { area := len * wid return area } func Diagonal(len, wid float64) float64 { diagonal := math.Sqrt((len * len) + (wid * wid)) return diagonal }
咱們添加了一個簡單的 init
函數,它僅打印:rectangle package initialized
。
如今咱們來修改 main
包。咱們知道矩形的 length
和 width
應該大於 0
。咱們將在 geometry.go
中添加 init
函數和包級別的變量來作此檢查。
修改 geometry.go
以下:
//geometry.go package main import ( "fmt" "geometry/rectangle" //importing custom package "log" ) /* * 1. package variables */ var rectLen, rectWidth float64 = 6, 7 /* *2. init function to check if length and width are greater than zero */ func init() { println("main package initialized") if rectLen < 0 { log.Fatal("length is less than zero") } if rectWidth < 0 { log.Fatal("width is less than zero") } } func main() { fmt.Println("Geometrical shape properties") fmt.Printf("area of rectangle %.2f\n", rectangle.Area(rectLen, rectWidth)) fmt.Printf("diagonal of the rectangle %.2f ",rectangle.Diagonal(rectLen, rectWidth)) }
咱們對 geometry.go
作了以下修改:
main
函數中移到了外面,成爲了包級別的變量。init
函數。當 rectLen
或 rectWidth
小於 0
時,該函數利用 log.Fatal 打印一條日誌並終止程序。main
包的初始化順序爲:
rectangle
包先被初始化。main
函數被調用。運行該程序,輸出以下:
rectangle package initialized main package initialized Geometrical shape properties area of rectangle 42.00 diagonal of the rectangle 9.22
正如預期的那樣,rectangle
包的 init
函數首先被調用,接着是包級別的變量 rectLen 和 rectWidth 被初始化,接着是 main
包的 init
函數被調用,該函數檢測 rectLen
和 rectWidth
是否小於 0
,若是小於 0
,則終止程序。咱們將會在單獨的教程裏介紹 if
語句。如今你能夠假定 if rectLen < 0
將會檢測 rectLen
是否小於 0
,若是是,則終止程序。rectWidth
也是一樣的處理。也就是說兩個條件都爲假程序才繼續執行。最後,main
函數被調用。
讓咱們再次修改程序來學習 init
函數的使用。
將 geometry.go
中的 var rectLen, rectWidth float64 = 6, 7
這一行改成 var rectLen, rectWidth float64 = -6, 7
。這裏將 rectLen
改成負值。
如今運行程序,獲得以下結果:
rectangle package initialized main package initialized 2017/04/04 00:28:20 length is less than zero
像上面同樣, rectangle
包首先被初始化,而後是 main
包中的包級別變量 rectLen
和 rectWidth
初始化。接着調用 main
包的 init
函數,由於 rectLen
是小於 0
的,所以程序打印 length is less than zero
後退出。
代碼能夠在 github 上下載。
在 Go 中只導入包卻不在代碼中使用它是非法的。若是你這麼作了,編譯器會報錯。這樣作的緣由是爲了不引入過多未使用的包而致使編譯時間的顯著增長。將 geometry.go
中的代碼替換爲以下代碼:
//geometry.go package main import ( "geometry/rectangle" //importing custom package ) func main() { }
上面的程序將會報錯:geometry.go:6: imported and not used: "geometry/rectangle"
可是在開發過程當中,導入包卻不當即使用它是很常見的。能夠用空指示符(_
)來處理這種狀況。
下面的代碼能夠避免拋出上面的錯誤:
package main import ( "geometry/rectangle" ) var _ = rectangle.Area //error silencer func main() { }
var _ = rectangle.Area
這一行屏蔽了錯誤。咱們應該跟蹤這些「錯誤消音器」(error silencer), 在開發結束時,咱們應該去掉這些「錯誤消音器」,而且若是沒有使用相應的包,這些包也應該被一併移除。所以,建議在 import
語句以後的包級別中寫「錯誤消音器」。
有時咱們導入一個包只是爲了確保該包初始化的發生,而咱們不須要使用包中的任何函數或變量。例如,咱們也許須要確保 rectangle
包的 init
函數被調用而不打算在代碼中的任何地方使用這個包。空指示符仍然能夠處理這種狀況,像下面的代碼同樣:
package main import ( _ "geometry/rectangle" ) func main() { }
運行上面的程序,將會獲得輸出:rectangle package initialized
。咱們成功地初始化了這個包,即便在代碼中的任何地方都沒有使用它。