[譯] part 7: golang 包

咔嘰咔嘰發表於2019-04-22

什麼是包以及為什麼要使用它

到目前為止,我們看到的 go 程式碼只有一個檔案,其中有一個 main 函式和其他幾個函式。在實際場景中,將所有原始碼寫入單個檔案不是一個好方法。複用和維護這種程式碼將變得非常艱難,包就是用來解決這些問題的。

包可以使程式碼更好的複用和可讀,也可以使程式碼解耦,因此使得應用程式很容易維護。

例如,假設我們正在建立一個 go 影象處理的應用程式,它提供了影象裁剪,銳化,模糊和色彩增強等功能。組織此應用程式的一種方法是將與功能相關的所有程式碼分組到自己的包中。例如,裁剪可以是單個包,銳化可以是另一個包。這樣做的優點是,色彩增強功能可能需要一些銳化功能。色彩增強程式碼可以簡單地匯入(我們將馬上討論包匯入)銳化包並使用其功能。這樣程式碼就變得易於複用。

我們將逐步建立一個計算矩形區域和對角線的應用程式,藉此應用程式讓我們更好地瞭解包的作用。

main 函式和 main 包

每個可執行的 go 應用程式都必須包含 main 函式。此函式是執行的入口。 main 函式應該放在 main 包中。

每個 go 原始碼檔案的第一行是package packagename,該語法用來指定該檔案屬於哪個包。

讓我們開始為我們的應用程式建立 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 "packagename"語句用於匯入現有的包。在該例子中,我們匯入了包含Println方法的 fmt 包。然後有一個 main 函式,列印了Geometrical shape properties

通過輸入go install geometry來編譯上面的程式。此命令在 geometry 資料夾中搜尋具有 main 函式的檔案。在該例子中,它會找到 geometry.go。然後編譯它並在工作區的bin資料夾內生成一個名為 geometry 的二進位制檔案(在 windows 的情況下為 geometry.exe)。現在工作區結構將是

src  
    geometry
            gemometry.go
bin  
    geometry
複製程式碼

我們輸入workspacepath/bin/geometry來執行程式。將 workspacepath 替換為 go 工作區的路徑。此命令執行 bin 資料夾中的 geometry 二進位制檔案,你可以看到輸出為Geometrical shape properties

建立自定義包

我們以這樣的方式來組織程式碼,讓與矩形相關的所有功能都在 rectangle 包中。

我們來建立一個自定義包rectangle,它擁有計算矩形區域和對角線長度的兩個函式。

包的原始檔應放在自己的單獨資料夾中,Go 中的一個約定是包的名稱和此資料夾的名稱相同。

因此,我們在 geometry 資料夾中建立一個名為 rectangle 的資料夾。rectangle 資料夾中的所有檔案都應以package rectangle開頭,因為它們都屬於rectangle包。

在 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" 是匯入 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  
複製程式碼

可匯出命名

將 rectangle 包中的函式AreaDiagonal首字母大寫,這在 Go 中有特殊意義。任何以大寫字母開頭的變數或函式都是 go 中的可匯出命名,用這種命名方式的函式和變數能夠被其他包訪問。在該例子中,我們通過這種方式在 main 包中了訪問AreaDiagonal行數。

如果把 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 函式。init 函式不應該有任何返回型別,也不應該有任何引數,在我們的原始碼中也無法顯式呼叫 init 函式。 init 函式如下所示

func init() {  
}
複製程式碼

init 函式用於執行初始化任務,也可用於在執行開始之前驗證程式的正確性。

包的初始化順序如下

  1. 首先初始化包級別變數
  2. 接下來呼叫 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 包,眾所周知矩形的長度和寬度應該大於零。我們可以使用 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 所做的更改

  1. rectLenrectWidth變數從 main 函式級別移動到包級別。
  2. 新增了一個 init 函式。如果rectLenrectWidth小於零則使用log.Fatal函式列印日誌並終止。

main 包的初始化順序是

  1. 首先初始化匯入的包。因此 rectangle 包首先被初始化
  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  
複製程式碼

正如所料,首先呼叫 rectangle 包的 init 函式,然後初始化包級變數rectLenrectWidth,接下來呼叫 main 包的 init 函式,它檢查rectLenrectWidth是否小於零,如果小於零則列印日誌並終止。我們會在單獨的教程中詳細瞭解 if 語句。現在你可以假設如果rectLen < 0就是判斷rectLen是否小於 0,如果是,則程式將被終止。我們為rectWidth也寫了一樣的條件。在該例子中,兩個條件都為false,程式繼續執行。最後呼叫 main 函式。

再稍微修改一下該程式,以瞭解 init 函式的用法。

將 geometry.go 的var rectLen, rectWidth float64 = 6, 7更改為var rectLen, rectWidth float64 = -6, 7rectLen被初始化為負數。

修改後執行會輸出,

rectangle package initialized  
main package initialized  
2017/04/04 00:28:20 length is less than zero  
複製程式碼

正常情況下,先初始化 rectangle 包,然後是 main 包中的包級別變數rectLenrectWidthrectLen是負數。因此,當 init 函式執行時,程式在列印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會使程式不會報錯。如果我們不使用這個包,我們應該關注這些程式碼,並在應用程式開發結束時刪除它們。因此,在程式開發階段,可以在 import 語句之後用該方式避免報錯。(譯者注:一般用_儲存不使用的變數等等,畢竟宣告瞭如果不使用編譯器會報錯的)

有時我們匯入一個包只是為了使用初始化功能,除此之外我們不需要使用包中的任何函式或變數。例如,我們可能需要呼叫 rectangle 包的 init 函式,即使在程式碼中的任何位置都不使用該包。在這種情況下也可以使用_符號,如下所示。

package main 

import (   

     _ "geometry/rectangle" 

)
func main() {

}
複製程式碼

執行上面的程式將輸出rectangle package initialized。我們已經成功初始化了包,即使它沒有在程式碼中的任何地方被使用。(譯者注:包匯入還有.操作可以省略字首包名直接呼叫該包的函式或者變數。別名操作也比較好理解,就是把字首包名重新命名,語法是 import alias package

相關文章