什么是包,为什么要使用它们?
到目前为止,我们看到的Go程序只有一个文件,其中有一个主函数和几个其他函数。在现实世界中,这种将所有源代码写在一个文件中的方法是不可扩展的。这样写出来的代码就不可能被重用和维护。这就是包的作用。
包用于组织 Go 源代码,以提高重用性和可读性。包是驻扎在同一目录下的Go源代码的集合。包提供了代码的分隔,因此,维护Go项目变得很容易。
例如,假设我们正在用Go编写一个金融应用程序,其中的一些功能是简单的利息计算、复利计算和贷款计算。组织这个应用程序的一个简单方法就是按照功能来组织。我们可以创建包simpleinterest,compoundinterest 和loan 。如果loan 包需要计算简单利息,它可以简单地通过导入simpleinterest 包来实现。这样一来,代码就被重复使用了。
我们将通过创建一个简单的应用程序来学习包,以确定给定本金、利率和以年为单位的时间长度的单利。
主函数和主包
每个可执行的Go程序都必须包含主函数。这个函数是执行的入口。主函数应该位于main包中。
package packagename 指定一个特定的源文件属于包 。这应该是每个go源文件的第一行。packagename
让我们开始为我们的应用程序创建主函数和主包。
运行下面的命令,在当前用户的Documents 目录内创建一个名为learnpackage 的目录。
mkdir ~/Documents/learnpackage/
在我们的learnpackage 目录下创建一个名为main.go 的文件,内容如下。
package main
import "fmt"
func main() {
fmt.Println("Simple interest calculation")
}
这行代码package main ,指定这个文件属于主包。import "packagename" 语句用于导入一个现有的包。packagename.FunctionName() 是调用一个包中的函数的语法。
在第3行,我们导入了3,我们导入fmt 包,以使用Println 函数。fmt 是一个标准包,作为Go标准库的一部分,可以内置。然后是主函数,它打印出Simple interest calculation
编译上述程序的方法是:使用learnpackage 目录,并输入以下命令。
cd ~/Documents/learnpackage/
并键入以下命令
go install
如果一切顺利,我们的二进制文件将被编译并准备好执行。在终端输入命令learnpackage ,你将看到以下输出。
Simple interest calculation
如果你不了解go install 的工作原理,或者如果你遇到错误
go install: no install location for directory /home/naveen/Documents/learnpackage outside GOPATH
For more details see: 'go help gopath'
请访问golangbot.com/hello-world…以了解更多。
Go模块
我们将以这样的方式组织代码,即所有与简单利息有关的功能都在simpleinterest 包中。要做到这一点,我们需要创建一个自定义包simpleinterest ,其中将包含计算单利的功能。在创建自定义包之前,我们需要先了解**Go模块,因为创建自定义包需要用到Go模块**。
Go模块只不过是Go包的一个集合。现在你可能会想到这个问题。为什么我们需要Go模块来创建一个自定义包?答案是我们创建的自定义包的导入路径是由Go模块的名称衍生出来的。除此之外,我们的应用程序所使用的所有其他第三方包(例如来自github的源代码)都会和版本一起出现在go.mod 文件中。这个go.mod 文件是在我们创建一个新模块时创建的。你将在下一节中更好地理解这一点。
另一个问题可能会在我们脑海中出现。为什么我们到现在还没有创建一个Go模块?答案是,在本教程系列中,我们直到现在都没有创建自己的自定义包,因此不需要Go模块。
理论讲够了:)。让我们开始行动,创建我们的Go模块和自定义包。
创建一个Go模块
通过输入cd ~/Documents/learnpackage/ ,确保你在目录learnpackage 内。在这个目录下运行以下命令,创建一个名为learnpackage的Go模块。
go mod init learnpackage
上述命令将创建一个名为go.mod 的文件。以下将是该文件的内容。
module learnpackage
go 1.13
module learnpackage 这一行指定了模块的名称为learnpackage 。正如我们前面提到的,learnpackage 将是导入该模块内创建的任何包的基本路径。go 1.13 这一行指定本模块中的文件使用go版本1.13 。
创建简单兴趣的自定义包
属于一个包的源文件应该放在各自独立的文件夹中。Go中的一个惯例是用与包相同的名称来命名这个文件夹。
让我们在learnpackage 文件夹内创建一个名为simpleinterest 的文件夹。mkdir simpleinterest 将为我们创建这个文件夹。
simpleinterest文件夹内的所有文件都应该以package simpleinterest 行开始,因为它们都属于simpleinterest 包。
在simpleinterest文件夹内创建一个文件simpleinterest.go 。
以下将是我们应用程序的目录结构。
├── 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
}
在上面的代码中,我们创建了一个函数Calculate ,计算并返回单利。这个函数是不言自明的。它计算并返回单利。
请注意,函数名称Calculate以大写字母开头。这一点至关重要,我们很快就会解释为什么需要这样做。
导入自定义包
要使用一个自定义包,我们必须首先导入它。导入路径是模块的名称加上包的子目录和包的名称。在我们的例子中,模块名称是learnpackage ,包simpleinterest 在simpleinterest 文件夹中,直接在learnpackage 。
├── learnpackage
│ └── simpleinterest
因此,import "learnpackage/simpleinterest" 行将导入simpleinterest包。
如果我们有一个像这样的目录结构
learnpackage
│ └── finance
│ └── simpleinterest
那么导入语句将是import "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)
}
上面的代码导入了simpleinterest 包,并使用Calculate 函数来查找简单利息。 标准库中的包不需要模块名称前缀,因此 "fmt "不需要模块前缀也能工作。当应用程序运行时,输出将是
Simple interest calculation
Simple interest is 500
关于go安装的更多信息
现在我们了解了包是如何工作的,现在是时候多谈一点关于go install 。像go install 这样的Go工具是在当前目录下工作的。让我们来理解这意味着什么。到现在为止,我们一直在从目录~/Documents/learnpackage/ 中运行go install 。如果我们试图从其他目录运行它,将会失败。
试着进入cd ~/Documents/ ,然后运行go install learnpackage 。它将失败,出现以下错误。
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 需要一个可选的包名作为参数(在我们的例子中,包名是learnpackage ),如果包存在于运行它的当前目录或父目录或它的父目录中,它就会尝试编译主函数,等等。
我们在Documents 目录下,那里没有go.mod 文件,因此go install 抱怨说它找不到包learnpackage 。
当我们移动到~/Documents/learnpackage/ 时,那里有一个go.mod 文件,我们的模块名称是go.mod 文件中的learnpackage 。
因此,go install learnpackage 将在~/Documents/learnpackage/ 目录内工作。
但是到目前为止,我们只是使用了go install ,我们没有指定包的名称。如果没有指定软件包名称,go install 将默认为当前工作目录下的模块名称。这就是为什么当go install ,而不使用~/Documents/learnpackage/ 的任何包名时,它可以工作。因此,以下3条命令在运行时是等效的。~/Documents/learnpackage/
go install
go install .
go install learnpackage
我还提到,go install 有递归搜索父目录中go.mod文件的能力。让我们检查一下这是否有效。
cd ~/Documents/learnpackage/simpleinterest/
上述命令将把我们带到simpleinterest 目录。在该目录下运行
go install learnpackage
Go安装将成功地在父目录learnpackage 中找到一个go.mod file ,该目录已经定义了模块learnpackage ,因此它可以工作:)。
输出的名称
我们在Simple interest包中把函数Calculate 大写。这在Go中有着特殊的含义。任何以大写字母开头的变量或函数在Go中都是导出的名字。只有导出的函数和变量可以从其他包中访问。在我们的例子中,我们想从主包中访问Calculate 函数。因此,它被大写了。
如果在simpleinterest.go 中把函数名从Calculate 改为calculate ,如果我们试图在main.go中用simpleinterest.calculate(p, r, t) 来调用这个函数,编译器会出错
# learnpackage
./main.go:13:8: cannot refer to unexported name simpleinterest.calculate
./main.go:13:8: undefined: simpleinterest.calculate
因此,如果你想访问一个包之外的函数,它应该被大写。
init函数
Go中的每个包都可以包含一个init 函数。 init 函数不能有任何返回类型,也不能有任何参数。init函数不能在我们的源代码中明确调用。它将在包被初始化时被自动调用。init函数的语法如下
func init() {
}
init 函数可用于执行初始化任务,也可用于在执行开始前验证程序的正确性。
一个包的初始化顺序如下
- 包级变量首先被初始化
- 接下来调用init函数。一个包可以有多个init函数(可以在一个文件中,也可以分布在多个文件中),它们按照呈现给编译器的顺序被调用。
如果一个包导入了其他的包,导入的包会先被初始化。
一个包将只被初始化一次,即使它是从多个包中导入的。
让我们对我们的应用程序做一些修改,以了解init 函数。
首先,让我们在simpleinterest.go 文件中添加init 函数。
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函数,它只是打印出Simple interest package initialised
现在让我们修改一下主包。我们知道,在计算单利时,本金、利率和时间长度应大于零。我们将在main.go 文件中使用init函数和包级变量定义这个检查。
将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 所做的修改
- p、r和t变量从主函数级移到包级。
- 增加了一个init函数。如果本金、利率或时间长度小于零,init函数会打印日志并使用log.Fatal函数终止程序的执行。
初始化的顺序如下。
- 首先对导入的包进行初始化。因此,simpleinterest包首先被初始化,它的init方法被调用。
- 包级变量p、r和t接下来被初始化。
- init函数在main中被调用。
- main函数最后被调用。
如果你运行该程序,你将得到以下输出。
Simple interest package initialized
Main package initialized
Simple interest calculation
Simple interest is 500
正如预期的那样,simpleinterest 包的init函数首先被调用,然后是包级变量的初始化p,r 和t 。接下来,主包的init函数被调用。它检查p,r 和t 是否小于零,如果条件为真则终止。我们将在另一个教程中详细学习if 语句。现在你可以假设,if p < 0 将检查p 是否小于0,如果是,程序将被终止。我们为r 和t 写了一个类似的条件。在这种情况下,所有这些条件都是假的,程序继续执行。最后,主函数被调用。
让我们稍微修改一下这个程序,以学习init函数的使用。
将行
var p, r, t = 5000.0, 10.0, 1.0
中的 "main.go "改为
var p, r, t = -5000.0, 10.0, 1.0
我们已经将p 初始化为负数。
现在如果你运行这个程序,你会看到
Simple interest package initialized
Main package initialized
2020/02/15 21:25:12 Principal is less than zero
p是负数。因此,当init函数运行时,程序在打印完Principal is less than zero 后就终止了。
使用空白标识符
在Go中,导入一个包而不在代码中使用它是非法的。如果你这样做,编译器会抱怨。这样做的原因是为了避免未使用的包的膨胀,这将大大增加编译的时间。将main.go 中的代码替换为以下内容。
package main
import (
"learnpackage/simpleinterest"
)
func main() {
}
上述程序将出现错误
# learnpackage
./main.go:4:2: imported and not used: "learnpackage/simpleinterest"
但是,当应用程序正在积极开发时,导入包是很常见的,如果不是现在,以后会在代码的某个地方使用它们。_ 空白标识符在这些情况下为我们节省了时间。
上述程序中的错误可以通过以下代码消音。
package main
import (
"learnpackage/simpleinterest"
)
var _ = simpleinterest.Calculate
func main() {
}
这一行var _ = simpleinterest.Calculate ,使错误消音。我们应该跟踪这些类型的错误消音器,并在应用程序开发结束时删除它们,包括导入的包,如果该包不被使用。因此,我们建议在导入语句之后,在包的层次上写上错误消音器。
有时我们需要导入一个包,以确保初始化的发生,即使我们不需要使用该包的任何函数或变量。init 例如,我们可能需要确保simpleinterest 包的函数被调用,尽管我们不打算在代码中使用该包。在这种情况下也可以使用_空白标识符,如下图所示。
package main
import (
_ "learnpackage/simpleinterest"
)
func main() {
}
运行上述程序将输出Simple interest package initialized 。我们已经成功地初始化了simpleinterest 包,尽管它在代码中没有任何地方被使用。
关于包的问题就到此为止。希望你喜欢阅读。请留下您的宝贵意见和反馈:)。