现在不建议使用本教程。 请访问 https://golangbot.com/go-packages/ 获取更新的版本。
欢迎来到 Golang 系列教程第七篇教程。
什么是 packages,为什么要使用他们?
到目前为止,我们已经看过的 go 程序只有一个文件,其中有一个主函数和其他几个函数。在实际场景中,通常不会将所有源代码编写在一个文件中,因为这样代码不易于复用和维护。这就是 packages 节省时间的地方。
packages 可以获得更好的可重用性和可读性。 packages 提供了代码的划分,因此可以轻松维护应用程序。
举个例子,假设我们正在创建一个用 go 编写的图像处理的应用程序,它提供了图像裁剪,锐化,模糊和颜色增强等功能。管理这个应用程序代码的一种方式就是将与功能相关的所有代码拆分到自己的包中。比如裁剪功能可以为单个 package,锐化也可以为单独一个 package,这样的好处就是颜色增加可能需要一些锐化的功能。颜色增强的代码可以简单地导入锐化的 package 并开始使用其功能。这样代码就变得易于复用。
我们将逐步创建一个计算矩形面积和对角线的应用程序。
我们将通过此应用程序更好地了解 packages。
main 函数和 main package
每个能运行的 go 程序都必须包含 main 函数。此函数是程序的入口点。 main 函数应该在 main package 中。
指定特定源文件属于某个包的代码是 **package packagename
**。这应该是每个 go 源文件的第一行。
让我们开始为我们的应用程序创建主要函数和 main package。在 go 工作区的 src 文件夹中创建一个文件夹,并将其命名为 geometry
。**在 geometry
文件夹中创建文件 geometry.go
。
在 geometry.go 中编写以下代码
//geometry.go
package main
import "fmt"
func main() {
fmt.Println("Geometrical shape properties")
}
代码 package main 指定此文件属于main package。import “packagename” 语句用于导入现有包。在这个文件中,我们导入含有 Println 方法的 fmt
包。然后代码有一个主函数,可以输出 Geometrical shape properties
通过输入 go install geometry
来编译上面的程序。此命令在 geometry
文件夹中搜索具有 main 函数的文件。在这种情况下,它找到 geometry.go。然后它编译并生成一个名为 geometry 的二进制文件(在 windows 的情况下为 geometry.exe)在工作区的 bin 文件夹中。现在工作区结构将是
src
geometry
gemometry.go
bin
geometry
让我们通过输入 workspacepath/bin/geometry
来运行程序。将 workspacepath
替换为你的 go 工作区的路径。此命令执行bin文件夹中的 geometry
二进制。你应该会得到 Geometrical shape properties 作为输出。
创建自定义包
我们将与矩形相关的所有功能都放在 rectangle
包中这样的方式构造代码。
让我们创建一个自定义 rectangle
包,它具有计算矩形面积和对角线的函数。
属于 package 的源文件应放在自己的单独文件夹中。 Go 规定使用与 package 名字相同的名称命名此文件夹。
**
因此,我们在 geometry 文件夹中创建一个名为 rectangle
的文件夹。
rectangle
文件夹中的所有文件都应以 package rectangle 开头,因为它们都属于 rectagle package
在我们刚刚创建的 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
}
在上面的代码中,我们创建了两个计算 Area
和 Diagonal
的函数。矩形的面积是长度和宽度的乘积。矩形的对角线是长度和宽度的平方和的平方根。math
package中的Sqrt
函数用于计算平方根。
请注意,函数 Area 和 Diagonal 以大写字母开头。这是一定要注意的,我们接下来会解释为什么需要这样做。
导入自定义包
要使用自定义 package,我们必须先导入它。import path 是导入自定义 package 的语法。我们必须指定 workspace 内的 src 文件夹的自定义 package 的相对路径。我们现在的文件夹结构是:
src
geometry
geometry.go
rectangle
rectprops.go
import “geometry/rectangle” 将导入 rectangle package。
将以下代码添加到 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
package ,并使用它的 Area 和 Diagonal 函数来计算矩形的面积和对角线。Printf 中的 %.2f
是将浮点数截断为两位小数。应用程序的输出是
Geometrical shape properties
area of rectangle 42.00
diagonal of the rectangle 9.22
导出名
我们将 rectangle package 中的函数 Area
和 Diagonal
大写。这在 Go 中有特殊意义。任何以大写字母开头的变量或函数都是 go 中的导出名称,只有这种方式才能从其他 package 导出函数和变量。这样我们在 main package 才能访问 Area
和 Diagonal
函数,因为他们都大写了。
在 rectprops.go
文件中如果函数名 Area(len, wid float64)
变成 area(len, wid float64)
, rectangle.Area(rectLen, rectWidth)
变成 rectangle.area(rectLen, rectWidth)
。如果程序运行, 编译器会抛出错误 geometry.go:11: cannot refer to unexported name rectangle.area
。因此,如果要访问 package 外的函数,则应将其大写。
init 函数
每个包都可以包含 init 函数。init 函数不应该有任何返回类型,不应该有任何参数。在我们的源代码中无法显式调用 init 函数。 init 函数如下所示
func init() {
}
init 函数可用于执行初始化任务,也可用于在执行开始之前验证程序的正确性。
package 的初始化顺序如下:
- 首先初始化 package 级别的变量
- 接下来调用 init 函数。一个 package 可以有多个 init 函数(在单个文件中或分布在多个文件中),并按照它们呈现给编译器的顺序调用它们。
如果 package 导入其他 package,则首先初始化导入的 package。即使某个 package 从多个 package导入,也只会初始化一次这个 package。让我们对我们的应用程序进行一些修改以理解 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 package。我们知道矩形的长度和宽度应该大于零。我们将使用geometry.go 文件中的 init 函数和 package 级别变量来定义此检查。
修改 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 所做的更改
rectLen 和 rectWidth 变量从 main 函数级别变成 package 级别。
添加了一个 init 函数。如果使用 log.Fatal 函数,rectLen 或 rectWidth 小于零,则 init 函数打印日志并终止程序执行。
main package 的初始化顺序是
- 最先初始化的是被导入的 package,因此 rectangle package 是最先被初始化的。
- 接下来初始化的是 Pakcage 级别变量 rectLen 和 rectWidth。
- 调用 init 函数
- 最后调用 main 函数
如果运行该程序,将获得以下输出。
rectangle package initialized
main package initialized
Geometrical shape properties
area of rectangle 42.00
diagonal of the rectangle 9.22
和预期一致,首先调用 rectangle package 的 init 函数,然后初始化 package 级变量rectLen 和 rectWidth。接下来调用主包的 init 函数。它检查 rectLen 和 rectWidth 是否小于零,如果条件为 True 则终止。我们将在单独的教程中详细了解 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 package,然后是 main package 中的 package 级别变量 rectLen 和 rectWidth。rectLen 为负数。因此,当 init 函数下次运行时,程序在打印 length is less than zero
后结束。
该代码可在 github 上下载
使用空白标识符
Go 中导入 package 之后,但是不在代码中的任何地方使用它,这是不被允许的。因为这将会增加编译时间。用以下代码替换 geometry.go 中的代码,
//geometry.go
package main
import (
"geometry/rectangle" //importing custom package
)
func main() {
}
上面的程序将抛出错误 geometry.go:6: imported and not used: “geometry/rectangle”
但是,当应用程序处于正在开发时导入 package 很常见,并且在之后会在某处使用它们。 _ 标识符可以在这些情况下拯救我们。
以下代码可以使上述程序中的错误无效,
package main
import (
"geometry/rectangle"
)
var _ = rectangle.Area //error silencer
func main() {
}
var = rectangle.Area 使错误无效。如果不使用包,我们应该跟踪这些 error silencers,并在程序开发结束后删除它们,包括导入的 package。因此,建议在 import 语句之后在 package 中编写 error silencers。有时候我们需要导入一个package 来确保初始化发生,即使我们不需要使用 package 中任何函数或变量。例如,我们可能需要确保调用 rectangle package 的 init 函数,即使我们在代码中的任何地方都没有使用这个 package。在这种情况下也可以使用 ,如下所示。
package main
import (
_ "geometry/rectangle"
)
func main() {
}
运行上述程序将输出 rectangle package initialized
。我们已经成功地初始化了package,尽管它在代码中的任何地方都没有使用。