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

歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持
想加入Go學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群】
萬(wàn)水千山總是情,點(diǎn)個(gè)【在看】行不行
