Go 系列教程 —— 7. 包

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。這是 [Golang 系列教程](/subject/2)的第 7 個教程。### 什麼是包,為什麼使用包?到目前為止,我們看到的 Go 程式都只有一個檔案,檔案裡包含一個 main [函數](https://studygolang.com/articles/11892)和幾個其他的函數。在實際中,這種把所有原始碼編寫在一個檔案的方法並不好用。以這種方式編寫,代碼的重用和維護都會很困難。而包(Package)解決了這樣的問題。**包用於組織 Go 原始碼,提供了更好的可重用性與可讀性**。由於包提供了代碼的封裝,因此使得 Go 應用程式易於維護。例如,假如我們正在開發一個 Go 影像處理程式,它提供了映像的裁剪、銳利化、模糊和彩色增強等功能。一種組織程式的方式就是根據不同的特性,把代碼放到不同的包中。比如裁剪可以是一個單獨的包,而銳利化是另一個包。這種方式的優點是,由於彩色增強可能需要一些銳利化的功能,因此彩色增強代碼只需要簡單地匯入(我們會在隨後討論)銳利化功能的包,就可以使用銳利化的功能了。這樣的方式使得代碼易於重用。我們會逐步構建一個計算矩形的面積和對角線的應用程式。通過這個程式,我們會更好地理解包。### main 函數和 main 包所有可執行檔 Go 程式都必須包含一個 main 函數。這個函數是程式啟動並執行入口。main 函數應該放置於 main 包中。**`package packagename` 這行代碼指定了某一源檔案屬於一個包。它應該放在每一個源檔案的第一行。**下面開始為我們的程式建立一個 main 函數和 main 包。**在 Go 工作區內的 src 檔案夾中建立一個檔案夾,命名為 `geometry`**。在 `geometry` 檔案夾中建立一個 `geometry.go` 檔案。在 geometry.go 中編寫下面代碼。```go// geometry.gopackage 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`。接下來,它編譯併產生一個名為 `geometry` (在 windows 下是 `geometry.exe`)的二進位檔案,該二進位檔案放置於工作區的 bin 檔案夾。現在,工作區的目錄結構會是這樣:```src geometry gemometry.gobin geometry```鍵入 `workspacepath/bin/geometry`,運行該程式。請用你自己的 Go 工作區來替換 `workspacepath`。這個命令會執行 bin 檔案夾裡的 `geometry` 二進位檔案。你應該會輸出 `Geometrical shape properties`。### 建立自訂的包我們將組織代碼,使得所有與矩形有關的功能都放入 `rectangle` 包中。我們會建立一個自訂包 `rectangle`,它有一個計算矩形的面積和對角線的函數。**屬於某一個包的源檔案都應該放置於一個單獨命名的檔案夾裡。按照 Go 的慣例,應該用包名命名該檔案夾。**因此,我們在 `geometry` 檔案夾中,建立一個命名為 `rectangle` 的檔案夾。在 `rectangle` 檔案夾中,所有檔案都會以 `package rectangle` 作為開頭,因為它們都屬於 rectangle 包。在我們之前建立的 rectangle 檔案夾中,再建立一個名為 `rectprops.go` 的檔案,添加下列代碼。```go// rectprops.gopackage rectangleimport "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"` 這一行會匯入 rectangle 包。在 `geometry.go` 裡面添加下面的代碼:```go// geometry.gopackage main import ( "fmt" "geometry/rectangle" // 匯入自訂包)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```### 匯出名字(Exported Names)我們將 rectangle 包中的函數 Area 和 Diagonal 首字母大寫。在 Go 中這具有特殊意義。在 Go 中,任何以大寫字母開頭的變數或者函數都是被匯出的名字。其它包只能訪問被匯出的函數和變數。在這裡,我們需要在 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` 函數。init 函數不應該有任何傳回值類型和參數,在我們的代碼中也不能顯式地調用它。init 函數的形式如下:```gofunc init() { }```init 函數可用於執行初始化任務,也可用於在開始執行之前驗證程式的正確性。包的初始化順序如下:1. 首先初始化包層級(Package Level)的變數2. 緊接著調用 init 函數。包可以有多個 init 函數(在一個檔案或分佈於多個檔案中),它們按照編譯器解析它們的順序進行調用。如果一個包匯入了另一個包,會先初始化被匯入的包。儘管一個包可能會被匯入多次,但是它只會被初始化一次。為了理解 init 函數,我們接下來對程式做了一些修改。首先在 `rectprops.go` 檔案中添加了一個 init 函數。```go// rectprops.gopackage rectangleimport "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 包。我們知道矩形的長和寬都應該大於 0,我們將在 `geometry.go` 中使用 init 函數和包層級的變數來檢查矩形的長和寬。修改 `geometry.go` 檔案如下所示:```go// geometry.gopackage main import ( "fmt" "geometry/rectangle" // 匯入自訂包 "log")/* * 1. 包層級變數*/var rectLen, rectWidth float64 = 6, 7 /**2. init 函數會檢查長和寬是否大於0*/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. 變數 **rectLen** 和 **rectWidth** 從 main 函數層級移到了包層級。2. 添加了 init 函數。當 rectLen 或 rectWidth 小於 0 時,init 函數使用 **log.Fatal** 函數列印一條日誌,並終止了程式。main 包的初始化順序為:1. 首先初始化被匯入的包。因此,首先初始化了 rectangle 包。2. 接著初始化了包層級的變數 **rectLen** 和 **rectWidth**。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 函數,然後,會初始化包層級的變數 **rectLen** 和 **rectWidth**。接著調用 main 包裡的 init 函數,該函數檢查 rectLen 和 rectWidth 是否小於 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。rectLen 為負數,因此當運行 init 函數時,程式在列印 `length is less than zero` 後終止。本代碼可以在 [github](https://github.com/golangbot/geometry) 下載。### 使用空白標識符(Blank Identifier)匯入了包,卻不在代碼中使用它,這在 Go 中是非法的。當這麼做時,編譯器是會報錯的。其原因是為了避免匯入過多未使用的包,從而導致編譯時間顯著增加。將 `geometry.go` 中的代碼替換為如下代碼:```go// geometry.gopackage main import ( "geometry/rectangle" // 匯入自定的包)func main() {}```上面的程式將會拋出錯誤 `geometry.go:6: imported and not used: "geometry/rectangle"`。然而,在程式開發的活躍階段,又常常會先匯入包,而暫不使用它。遇到這種情況就可以使用空白標識符 `_`。下面的代碼可以避免上述程式的錯誤:```gopackage mainimport ( "geometry/rectangle" )var _ = rectangle.Area // 錯誤屏蔽器func main() {}````var _ = rectangle.Area` 這一行屏蔽了錯誤。我們應該瞭解這些錯誤屏蔽器(Error Silencer)的動態,在程式開發結束時就移除它們,包括那些還沒有使用過的包。由此建議在 import 語句下面的包層級範圍中寫上錯誤屏蔽器。有時候我們匯入一個包,只是為了確保它進行了初始化,而無需使用包中的任何函數或變數。例如,我們或許需要確保調用了 rectangle 包的 init 函數,而不需要在代碼中使用它。這種情況也可以使用空白標識符,如下所示。```gopackage main import ( _ "geometry/rectangle" )func main() {}```運行上面的程式,會輸出 `rectangle package initialized`。儘管在所有代碼裡,我們都沒有使用這個包,但還是成功初始化了它。包的介紹到此結束。希望您喜歡本篇,請留下您的寶貴的反饋和評論。**上一教程 - [函數](https://studygolang.com/articles/11892)****下一教程 - [if else 語句](https://studygolang.com/articles/11902)**

via: https://golangbot.com/packages/

作者:Nick Coghlan 譯者:Noluye 校對:rxcai

本文由 GCTT 原創編譯,Go語言中文網 榮譽推出

本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽

4381 次點擊  ∙  2 贊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.