Golang系列教程第 7 部分
什麼是包和爲什麼要使用它們
到目前爲止,我們看到的程序都只有一個文件,它有一個 main
函數和一些其他函數。在真實的場景中,這種把所有源代碼都放到一個文件中的途徑並不適用。這樣寫程序讓代碼重用和代碼維護變得不可能。這就是包出現的地方。
包被用來組織 go 源代碼以帶來更好的重用性和可讀性。包提供了代碼的劃分,因此可以輕鬆維護應用程序。
例如,我們創建一個go的圖片處理應用程序,它提供了圖像裁剪,銳化,模糊和色彩增強等特性。組織這個程序的一個方式是根據特性將所有代碼分組到相應的包中。例如裁剪可以單獨放一個包中,銳化在另一個包中。這樣做的優勢是色彩增強特性可能需要銳化的一些功能。色彩增強代碼可以僅引入(我們將在下面面討論導入)銳化包並使用它的功能。這樣代碼就變得更容易被重用。
我們將逐步創建一個計算矩形區域和對角線的應用程序。
我們可以通過這個程序更好地理解包
main 函數和 main 包
每一個可執行的 go
應用程序必須包含一個 main
函數。這個函數是執行的入口點。main
函數應該放到 main
包中。
指定特定源文件屬於包的代碼行是 package packagename
。這應該是每一個go源文件的第一行。
我們通過爲我們的應用程序創建一個 main
函數和 main
包來開始。在go
工程下的 src
文件夾中創建一個名爲 geometry
的文件夾。 在 geometry
文件夾中創建一個 geometry.go
文件。
在 geometry.go
文件中寫下如下代碼
//geometry.go
package main
import "fmt"
func main() {
fmt.Println("Geometrical shape properties")
}
代碼行 package main
指定了這個文件屬於 main
包。import "package"
語句被用來導入一個存在的包。在這個例子中我們導入 fmt
包。它包含 Println
方法。然後有一個 main
函數打印
Geometrical shape properties
通過輸入 go install geometry
編譯上面的程序。這個命令在 geometry
文件夾中搜索包含 main
函數的文件。在這個例子中,它找到 geometry.go
文件,然後編譯器編譯它在項目的 bin
文件夾下並生成一個名爲 geometry
(在 windows 平臺下是 geometry.exe
)的二進制文件。現在工作空間結構是
src
geometry
geometry.go
bin
geometry
讓我們通過輸入 workspacepath/bin/geometry
來運行這個程序。使用你的 go
工作空間替換 workspacepath
。這個命令執行 bin
文件夾下的 geometry
二進制。你會得到輸出 Geometrical shape properties
。
創建自定義包
我們將以這種方式來組織我們的包,所有與對角線相關的功能都位於 rectangle
包中。
我們創建一個自定義包 rectangle
,它包含有決定對角線和區塊的函數。
屬於一個包的源文件應該被放在一個屬於它們自己的單獨文件夾中。這在 go
中是很方便的,使用和包名同樣的名字來命令這個文件夾。
所以我們在 geometry
文件夾中創建一個名爲 rectangle
文件夾。所以位於 rectangle
文件夾中的文件都應該以 package rectangle
開始,因爲他們都屬於矩形包。
在我們剛創建的矩形文件夾中創建 rectprops.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
是導入一個自定義包的語法。我們必須指定自定義包的路徑,它相對於工作空間下的 src
文件夾。我們當前的文件夾結構是
src
geometry
geometry.go
rectangle
rectprops.go
import "geometry/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
可導出名字
我們將矩形包的函數 Area
和 Diagonal
以大寫開頭。這在 Go
中有特定我意義。任何以大寫字母開頭的變量或字母是可導出的。只有可導出的函數和變量可以在其他包中被訪問。在這個例子中,我們需要在 main
包中訪問 Area
和 Diagonal
函數。因此它們以大寫字母開頭。
如果 geometry.go
文件中的函數名從 Area(len, wid float64)
變爲 area(len, width float64)
,從 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
函數(或者在一個文件中,或者他們跨文件分佈),它們按照它們呈現給編譯器的順序被調用。
如果一個包導入其他包,被導入的包首先被初始化。
一個包僅被初始化一次,即使他們被多個包導入。
我們來對我們的應用程序做些修改以更好地理解初始化函數
我們以在 rectprops.go
文件中加入初始化函數開始。
//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 initialised
。
現在我們修改 main
包。我們知道,矩形的長和寬應該大於0,我們將使用 init
函數和 geometry.go
文件的包級變量來檢查這個特性。
修改 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
中做出的修改
- rectLen 和 rectWidth 變量從
main
函數級別提升到包級。 - 添加一個
init
函數。如果 rectLen 或 rectWidth 中的任何一個小於0,init
函數使用 log.Fatal 函數打印日誌並終止程序執行。
main
函數初始化的順序是
- 導入的包首先被初始化。因此矩形包首先被初始化。
- 接下來是包級別的變量 rectLen 和 rectWidth 被初始化。
- init 函數被調用
- 最後是
main
函數被調用
如果你運行程序,會得到如下的輸出
rectangle package initialized
main package initialized
Geometrical shape properties
area of rectangle 42.00
diagonal of the rectangle 9.22
正如期望的那樣, 矩形包的 init
函數首先被初始化,接下來初始化包級變量 rectLen 和 rectWidth。下面是 main
包中的 init
函數被調用。它檢查 rectLen
和 rectWidth
是否小於零,並在條件爲真是終止程序。我們將在一個單獨的教程中討論 if
語句細節。現在你可以假設 if rectLen < 0
檢查 rectLen
是否小於0,如果它小於0,程序將終止。我們對 rectWidth
也做了同樣的條件判斷。在這個例子中,兩個入條件都是 false ,程序繼續執行。最後調用 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
使用空白標示符
在 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
使錯誤沉默。在程序開發結束時,如果包未使用,我們應該找到這些讓錯誤沉默的包並刪除它們。因此推薦的做法是在導入語法中的包級使用錯誤沉默。
有時候我們需要導入一個包僅爲了確保初始化任務起作用,甚至我們不需要使用包中的任何函數和變量。例如,我們或許需要確保矩形包中的初始化函數被調用並沒有在任何代碼中使用這個包。空白標示符 _
也可以在這處狀態下被使用。展示如下
package main
import (
_ "geometry/rectangle"
)
func main() {
}
**下一教程 - if else 語句 **