Go 語言基礎(chǔ)-包
回復(fù)“Go語言”即可獲贈從入門到進階共10本電子書
新知遭薄俗,舊好隔良緣。
你好,我是四哥。
上一篇文章我們學(xué)習(xí)了函數(shù),這篇文章我們再來學(xué)習(xí)寫包的用法。
什么是包以及為什么我們要使用它?
到目前為止,我們看到的 Go 程序只有一個文件,其中包含一個 main 函數(shù)和幾個其他函數(shù)。在實際業(yè)務(wù)開發(fā)時,這種將所有源代碼寫入單個文件的方法是不可擴展的。這種代碼的重用性和可維護性將變得不可能,這個時候包就可以派上用場了。
包是位于同一目錄中的 .go 結(jié)尾的文件的集合,用來組織源代碼,實現(xiàn)更好的可重用性和可讀性。包提供了代碼劃分,使得維護 Go 項目更加容易。
例如,假設(shè)我們正在用 Go 編寫一個金融系統(tǒng),包含如下功能:簡單的利息計算、復(fù)利計算和貸款計算等。組織這個系統(tǒng)的的一個簡單辦法就是按照功能劃分,我們可以創(chuàng)建三個包 simpleinterest、compoundinterest 和 loan。如果 loan 包需要用到 simpleinterest 包的功能,只需要簡單導(dǎo)入 simpleinterest 包即可,這樣代碼就實現(xiàn)重用了。
我們將通過創(chuàng)建一個簡單的應(yīng)用程序來學(xué)習(xí)包,用于計算給定本金、利率和時間的情況下利息是多少。
main 函數(shù)和 main 包
每個可執(zhí)行的 Go 應(yīng)用程序都必須包含 main 函數(shù)。該函數(shù)是程序執(zhí)行的入口,主要功能應(yīng)該在 main 包內(nèi)。
package packagename
上面的語法用于指定源文件屬于 packagename 包,通常出現(xiàn)在文件的第一行。
讓我們開始為我們的應(yīng)用程序創(chuàng)建 main 函數(shù)和 main 包。
運行下面命令,在當前用戶的 Documents 目錄中創(chuàng)建一個名為 learnpackage 的目錄。
mkdir ~/Documents/learnpackage/
在 learnpackage 目錄中創(chuàng)建一個名為 main.go 的文件,其中包含以下代碼:
package main
import "fmt"
func main() {
fmt.Println("Simple interest calculation")
}
package main 代碼用于指定該文件屬于主包;
import "packagename" 語句用于導(dǎo)入已存在的包;
packagename.FunctionName() 是調(diào)用包中函數(shù);
上面代碼的第 3 行,我們導(dǎo)入 fmt 包以便能調(diào)用包里的 Println() 函數(shù),fmt 是 Go 語言標準庫中內(nèi)置的一個包。主函數(shù)會打印 Simple interest calculation。
cd 到 learnpackage 目錄并且編譯上面的代碼
cd ~/Documents/learnpackage/
go install
如果一切順利,二進制文件將被編譯并可以執(zhí)行。在終端中鍵入命令 learnpackage,將會看到如下輸出:
Simple interest calculation
Go Module
我們將會這樣構(gòu)建代碼,與計算利息相關(guān)的功能函數(shù)都放在 simpleinterest 包里。為此,我們需要創(chuàng)建一個自定義包 simpleinterest,其中包含計算利息的函數(shù)。在創(chuàng)建自定義包之前,我們需要先了解 Go Modules,創(chuàng)建自定義包需要 Go Modules。
簡單來說,Go Module 只不過是 Go 包的集合。你可能會有疑問,為什么我們需要 Go 模塊來創(chuàng)建自定義包呢?答案是我們創(chuàng)建的自定義包的導(dǎo)入路徑來源于 go 模塊的名稱。除此之外,我們的應(yīng)用程序使用的所有其他第三方包(例如來自 github 的源代碼)將與版本一起出現(xiàn)在 go.mod 文件中。當我們創(chuàng)建一個新模塊時,會創(chuàng)建出一個 go.mod 文件,下一小節(jié)會講到這點。
可能你又會疑惑了:為什么到現(xiàn)在我們還沒有創(chuàng)建 Go 模塊程序也能執(zhí)行成功?答案是,本系列教程到目前為止,我們從未創(chuàng)建過自定義包,因此不需要 Go 模塊。
理論知識學(xué)完了,讓我們來創(chuàng)建自己的 Go 模塊和自定義包。
創(chuàng)建 Go module
執(zhí)行下面命令確保在 learnpackage 目錄下,
cd ~/Documents/learnpackage/
在該目錄下輸入如下命令創(chuàng)建名為 learnpackage 的 go 模塊。
go mod init learnpackage
上面的命令將創(chuàng)建一個名為 go.mod 的文件,下面是該文件的內(nèi)容:
module learnpackage
go 1.13
代碼行 module learnpackage 指定模塊的名字為 learnpackage。正如之前提到的,learnpackage 是模塊的基礎(chǔ)路徑,想要導(dǎo)入模塊的任何一個包必須基于此路徑。最后一行指定此模塊中的文件使用 1.13 的 go 版本。
創(chuàng)建一個自定義包:計算利息
屬于一個包的源文件應(yīng)該放在它們自己的單獨文件夾中。Go 中的約定是文件夾名稱與包名相同。
讓我們在 learnpackage 文件夾中創(chuàng)建一個名為 simpleinterest 的文件夾。mkdir simpleinterest 將會創(chuàng)建該文件夾。
simpleinterest 文件夾中的所有文件都應(yīng)該以 package simpleinterest 開頭,因為它們都屬于 simpleinterest 包。
在 simpleinterest 文件夾中創(chuàng)建一個文件 simpleinterest.go。
下面是程序的結(jié)構(gòu)目錄:
├── learnpackage
│ ├── go.mod
│ ├── main.go
│ └── simpleinterest
│ └── simpleinterest.go
將以下代碼添加到 simpleinterest.go 文件中。
package simpleinterest
//Calculate calculates and returns the simple interest for a principal p, rate of interest r for time duration t years
func Calculate(p float64, r float64, t float64) float64 {
interest := p * (r / 100) * t
return interest
}
在上面的代碼中,我們創(chuàng)建了一個計算并返回利息的函數(shù) Calculate()。
導(dǎo)入自定義包
要使用自定義包,我們必須先導(dǎo)入它。導(dǎo)入路徑是模塊名加上包的子目錄和包名。在我們的例子中,模塊名稱是 learnpackage,包 simpleinterest 位于 learnpackage 下的 simpleinterest 文件夾中。
├── learnpackage
│ └── simpleinterest
所以 import "learnpackage/simpleinterest" 代碼將導(dǎo)入 simpleinterest 包。
如果我們的目錄結(jié)構(gòu)是這樣的:
learnpackage
│ └── finance
│ └── simpleinterest
那么導(dǎo)入包的語句將會是 mport "learnpackage/finance/simpleinterest"。
將下面的代碼加入 main.go
package main
import (
"fmt"
"learnpackage/simpleinterest"
)
func main() {
fmt.Println("Simple interest calculation")
p := 5000.0
r := 10.0
t := 1.0
si := simpleinterest.Calculate(p, r, t)
fmt.Println("Simple interest is", si)
}
上面的代碼導(dǎo)入了 simpleinterest 包并使用了 Calculate() 函數(shù)來計算利息。標準庫中的包不需要模塊名稱前綴,比如直接導(dǎo)入 fmt 包也是可以的。執(zhí)行代碼輸出:
Simple interest calculation
Simple interest is 500
關(guān)于 go install 更多的知識
現(xiàn)在我們了解了包的工作原理,是時候來討論關(guān)于 go install 的用法了。像 go install 這樣的 Go 工具在當前目錄的上下文中工作。我們來理解是什么意思,到目前為止,我們一直在目錄 ~/Documents/learnpackage/ 中運行 go install。如果我們嘗試在其他任何目錄執(zhí)行這個命令,將會報錯。
嘗試執(zhí)行命令 cd ~/Documents/ 然后運行 go install learnpackage,它將失敗并出現(xiàn)以下錯誤:
can't load package: package learnpackage: cannot find package "learnpackage" in any of:
/usr/local/Cellar/go/1.13.7/libexec/src/learnpackage (from $GOROOT)
/Users/nramanathan/go/src/learnpackage (from $GOPATH)
我們一起來分析下錯誤的原因,go install 需要一個可選的包名作為參數(shù)(在我們的例子中包名是 learnpackage),如果執(zhí)行命令的當前目錄或者其父級目錄下存在這個包,它會嘗試編譯 main 函數(shù)。
我們在 Documents 目錄中沒有 go.mod 文件,因此 go install 會提示找不到包 learnpackage。
當我們 cd 到 ~/Documents/learnpackage/ 目錄時,該目錄下存在 go.mod 文件,該文件中指定了模塊名稱 learnpackage。
所以 go install learnpackage 將在 ~/Documents/learnpackage/ 目錄中工作。
但是到目前為止,我們只是在使用 go install 并且沒有指定包名。如果沒有指定包名,go install 將默認為當前工作目錄中的模塊名,這就是我們沒有指定包名但仍然能執(zhí)行成功的原因。因此,在 ~/Documents/learnpackage/ 目錄下執(zhí)行下面 3 個命令是等效的:
go install
go install .
go install learnpackage
上面我們提到 go install 能夠遞歸地在父目錄中搜索 go.mod 文件,讓我們檢查一下這點是否正確。
cd ~/Documents/learnpackage/simpleinterest/
執(zhí)行上面的命令 cd 到 simpleinterest 目錄,在該目錄下執(zhí)行下面的命令:
go install learnpackage
Go install 將能在父目錄 learnpackage 中找到一個 go.mod 文件,該文件定義了模塊 learnpackage,因此它可以工作。
可導(dǎo)出
simpleinterest 包里的 Calculate() 函數(shù)開頭字母是大寫的。這在 Go 中有特別的定義,任何以大寫字母開頭的變量或函數(shù)都是可導(dǎo)出。只能訪問其他包中可導(dǎo)出的變量或者函數(shù),在我們這個例子中,我們能在 main 包里訪問 Calculate() 函數(shù),因為它是可導(dǎo)出的。
如果將 Calculate() 函數(shù)開頭字母小寫 calculate(),再次嘗試從 main 包訪問,將會報錯:
# learnpackage
./main.go:13:8: cannot refer to unexported name simpleinterest.calculate
./main.go:13:8: undefined: simpleinterest.calculate
因此,如果你想從其他包能訪問包內(nèi)的函數(shù),則應(yīng)將其首字母大寫。
init 函數(shù)
Go 里每個包都可以包含一個 init() 函數(shù)。init() 函數(shù)不能有任何返回值,也不能有任何參數(shù)。在代碼中不能顯式地調(diào)用 init() 函數(shù),包初始化時會自動調(diào)用 init() 函數(shù)。init() 函數(shù)語法如下:
func init() {
}
init() 函數(shù)可以用于執(zhí)行初始化任務(wù),也可用于程序運行前一些參數(shù)的驗證。
包的初始化順序如下:
1.首先初始化包級別的變量;2.接著會調(diào)用 init() 函數(shù),一個包可以有多個 init() 函數(shù)(在單個文件中或分布在多個文件中),它們按照呈現(xiàn)給編譯器的順序被調(diào)用;
如果一個包導(dǎo)入其他包,則首先初始化導(dǎo)入的包。
一個包即使被多個包導(dǎo)入,也只會被初始化一次。
我們對之前的程序做一些修改,以便能更好地學(xué)習(xí)了解 init() 函數(shù)。
首先,讓我們將 init 函數(shù)添加到 simpleinterest.go 文件中。
package simpleinterest
import "fmt"
/*
* init function added
*/
func init() {
fmt.Println("Simple interest package initialized")
}
//Calculate calculates and returns the simple interest for principal p, rate of interest r for time duration t years
func Calculate(p float64, r float64, t float64) float64 {
interest := p * (r / 100) * t
return interest
}
上面的代碼添加了一個簡單的 init 函數(shù),它只負責(zé)打印 Simple interest package initialized。
現(xiàn)在,我們來修改下 main 包,我們都知道計算利息時本金、利率和時間都應(yīng)該大于零,我們將在 main.go 文件里定義包級別的變量并且在 init() 函數(shù)校驗這些變量。
main.go 文件如下:
package main
import (
"fmt"
"learnpackage/simpleinterest" //importing custom package
"log"
)
var p, r, t = 5000.0, 10.0, 1.0
/*
* init function to check if p, r and t are greater than zero
*/
func init() {
println("Main package initialized")
if p < 0 {
log.Fatal("Principal is less than zero")
}
if r < 0 {
log.Fatal("Rate of interest is less than zero")
}
if t < 0 {
log.Fatal("Duration is less than zero")
}
}
func main() {
fmt.Println("Simple interest calculation")
si := simpleinterest.Calculate(p, r, t)
fmt.Println("Simple interest is", si)
}
main.go 修改如下:
1.p、r 和 t 變量從主函數(shù)級別移至包級別。2.添加了一個 init() 函數(shù),如果本金、利率或時間有一個小于 0,log.Fatal() 會打印日志并終止程序。
初始化順序如下:
1.導(dǎo)入的包首先被初始化,因此 simpleinterest 包首先被初始化并且它的 init 方法被調(diào)用。2.接下來初始化包級變量 p、r 和 t。3.接著調(diào)用 main.go 里的 init() 函數(shù)。4.最后調(diào)用 main() 函數(shù)。
執(zhí)行程序?qū)敵觯?/p>
Simple interest package initialized
Main package initialized
Simple interest calculation
Simple interest is 500
正如我們預(yù)期的那樣,simpleinterest 包的 init 函數(shù)首先被調(diào)用,然后是包級變量 p、r 和 t 的初始化,接下來調(diào)用 main 包的 init() 函數(shù),它檢查 p、r 和 t 是否小于零,如果 if 語句為真則會終止程序。關(guān)于 if 語句的使用我們將在另外章節(jié)介紹。在我們的程序里,所有的 if 條件都為 false,程序繼續(xù)執(zhí)行,最后調(diào)用 main() 函數(shù)。
我們來簡單修改下代碼,
將 main.go 文件的這行代碼:
var p, r, t = 5000.0, 10.0, 1.0
修改成:
var p, r, t = -5000.0, 10.0, 1.0
我們已將 p 初始化為負數(shù)。
現(xiàn)在如果執(zhí)行程序就會輸出:
Simple interest package initialized
Main package initialized
2020/02/15 21:25:12 Principal is less than zero
因為 p 是負數(shù),所有程序在輸出 Principal is less than zero 之后便終止了。
空白符 _
源代碼里導(dǎo)入包如果不適用是非法的,編譯器編譯時會提示,這樣做的原因是為了避免未使用的包太多,這將顯著增加編譯時間。將 main.go 中的代碼替換為以下內(nèi)容:
package main
import (
"learnpackage/simpleinterest"
)
func main() {
}
執(zhí)行上面的代碼將會報錯:
# learnpackage
./main.go:4:2: imported and not used: "learnpackage/simpleinterest"
但是在平時開發(fā)時,導(dǎo)入一個包可能現(xiàn)在不用,在之后代碼的某個地方使用是很常見的,這時候該怎么辦?
這時候空白符 _ 就能派上用場了,上面代碼的報錯可以使用空白符消除。
package main
import (
"learnpackage/simpleinterest"
)
var _ = simpleinterest.Calculate
func main() {
}
上面的代碼行 var _ = simpleinterest.Calculate 雖然能消除錯誤,但是不建議這種方式,我們應(yīng)該特別留意這類代碼,并在程序開發(fā)完成之后刪除,包括導(dǎo)入但未使用的包。因此,建議在使用 import 導(dǎo)入包時使用 _ 消除此類錯誤。
有時我們導(dǎo)入一個包只是為了確保初始化,即使我們不需要使用包中的任何函數(shù)或變量。例如,我們可能需要確保調(diào)用 simpleinterest 包的 init 函數(shù),即使我們不需要用到包里的任何函數(shù)或者變量。這種情況下也可以使用 _ 空白標識符,如下所示:
package main
import (
_ "learnpackage/simpleinterest"
)
func main() {
}
執(zhí)行上面的代碼會輸出 Simple Interest package initialized,我們已經(jīng)成功地初始化了 simpleinterest 包,即使它沒有在代碼中的任何地方被使用。
via: https://golangbot.com/go-packages/
作者:Naveen R
------------------- End -------------------
往期精彩文章推薦:

歡迎大家點贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持
想加入Go學(xué)習(xí)群請在后臺回復(fù)【入群】
萬水千山總是情,點個【在看】行不行
