Part 7 包(package)

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
}

在上面代碼中, 我們已經創建兩個計算 AreaDiagonal 的函數。矩形的面積是長和寬的積。矩形的對角線是長和寬的平方和的平方根。math 包中的 Sqrt 函數被用來計算平方根。

注意名爲 AreaDiagonal 的函數都有一個帽子。這是必要的,我們將會簡短地解釋爲什麼它是必要的

導入自定義包

爲了使用自定義包,我們首先必須導入它。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 包並使用它的 AreaDiagonal 函數來獲得矩形的面積和對角線。Printf 中的 %.2f 格式化符裁剪浮點數爲兩位有效數字。程序輸出

Geometrical shape properties  
area of rectangle 42.00  
diagonal of the rectangle 9.22  

可導出名字

我們將矩形包的函數 AreaDiagonal 以大寫開頭。這在 Go 中有特定我意義。任何以大寫字母開頭的變量或字母是可導出的。只有可導出的函數和變量可以在其他包中被訪問。在這個例子中,我們需要在 main 包中訪問 AreaDiagonal 函數。因此它們以大寫字母開頭。

如果 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 函數被用來做些初始化任務,也可以被用來在程序運行前驗證包的正確性。

一個包的初始化順序如下

  1. 包級別的變量首先被初始化。
  2. 接下來是 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 中做出的修改

  1. rectLenrectWidth 變量從 main 函數級別提升到包級。
  2. 添加一個 init 函數。如果 rectLen 或 rectWidth 中的任何一個小於0,init 函數使用 log.Fatal 函數打印日誌並終止程序執行。

main 函數初始化的順序是

  1. 導入的包首先被初始化。因此矩形包首先被初始化。
  2. 接下來是包級別的變量 rectLenrectWidth 被初始化。
  3. init 函數被調用
  4. 最後是 main 函數被調用

如果你運行程序,會得到如下的輸出

rectangle package initialized  
main package initialized  
Geometrical shape properties  
area of rectangle 42.00  
diagonal of the rectangle 9.22  

正如期望的那樣, 矩形包的 init 函數首先被初始化,接下來初始化包級變量 rectLenrectWidth。下面是 main 包中的 init 函數被調用。它檢查 rectLenrectWidth 是否小於零,並在條件爲真是終止程序。我們將在一個單獨的教程中討論 if 語句細節。現在你可以假設 if rectLen < 0檢查 rectLen 是否小於0,如果它小於0,程序將終止。我們對 rectWidth 也做了同樣的條件判斷。在這個例子中,兩個入條件都是 false ,程序繼續執行。最後調用 main 函數。

我們再對程序做一些小改動來學習 init 函數的使用。

修改 geometry.go 文件中的代碼行 var rectLen, rectWidth float64 = 6, 7var 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 語句 **

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章