Golang教程:(七)包
2017-07-15 08:33
281 查看
原文:https://golangbot.com/packages/
这是本Golang系列教程的第七篇。
包用于组织Go源代码,以获得更好的重用性和可读性。包提供了代码封装的机制从而使得Go应用程序易于维护。例如,假设我们正在开发一个图像处理应用,它提供了诸如图像裁剪,锐化,模糊和增色等功能。一种组织代码的方式是将所有实现同一功能的代码放在一个独立的包中。例如裁剪功能可以放在一个单独包中,而锐化功能可以放在另一个包中。这种做法的优点是:增色功能可能需要做一些锐化的处理,那么增色代码中可以简单地导入(我们即将讨论导入)锐化包,使用其中提供的功能即可。这种方式使得代码变得更容易重用。
我们将逐步创建一个计算矩形面积和对角线的应用程序。
通过构建该程序,我们将更好的理解包。
指定一个特定源文件属于一个包的语法为:
下面让我们开始创建
编写
执行
执行
属于同一个包的源文件应该放在独立的文件夹中,按照Go的惯例,该文件夹的名字应该与包名相同。
因此让我们在
在
在上面的代码中我们实现了两个函数
注意上面实现的两个函数的函数名
语句
在
上面的代码导入了
如果将
一个包的初始化顺序如下:
包级别的变量首先被初始化
接着
如果一个包导入了另一个包,被导入的包先初始化。
尽管一个包可能被包含多次,但是它只被初始化一次。
下面让我们对我们的程序做一些修改来理解
首先在
我们添加了一个简单的
现在我们来修改
修改
我们对
rectLen 和 rectWidth 变量从
添加
首先初始化被导入的包。因此
然后初始化包级别的变量: rectLen 和 rectWidth 。
init 函数被调用。
最后
运行该程序,输出如下:
正如预期的那样,
让我们再次修改程序来学习
将
现在运行程序,得到如下结果:
像上面一样,
代码可以在 github 上下载。
上面的程序将会报错:
但是在开发过程中,导入包却不立即使用它是很常见的。可以用空指示符(
下面的代码可以避免抛出上面的错误:
有时我们导入一个包只是为了确保该包初始化的发生,而我们不需要使用包中的任何函数或变量。例如,我们也许需要确保
运行上面的程序,将会得到输出:
包的介绍到此结束。感谢阅读。
目录
上一篇:Golang教程:(六)函数
下一篇:Golang教程:(八)if else 语句
这是本Golang系列教程的第七篇。
什么是包?为什么使用包?
到目前为止我们见到的 Go 程序都只有一个文件,文件中包含了一个main函数和几个其他函数。在实际中这种将所有代码都放在一个文件里的组织方式是不可行的。这样的组织方式使得代码变得无法重用和维护困难。包(package)用于解决这样的问题。包用于组织Go源代码,以获得更好的重用性和可读性。包提供了代码封装的机制从而使得Go应用程序易于维护。例如,假设我们正在开发一个图像处理应用,它提供了诸如图像裁剪,锐化,模糊和增色等功能。一种组织代码的方式是将所有实现同一功能的代码放在一个独立的包中。例如裁剪功能可以放在一个单独包中,而锐化功能可以放在另一个包中。这种做法的优点是:增色功能可能需要做一些锐化的处理,那么增色代码中可以简单地导入(我们即将讨论导入)锐化包,使用其中提供的功能即可。这种方式使得代码变得更容易重用。
我们将逐步创建一个计算矩形面积和对角线的应用程序。
通过构建该程序,我们将更好的理解包。
main函数与main包
每个可执行的Go程序都必须包含一个main函数。这个函数是执行程序的入口点。
main函数应该包含在
main包中。
指定一个特定源文件属于一个包的语法为:
package packagename,这条语句应该放在源文件的第一行。
下面让我们开始创建
main函数和
main包。在
[工作空间目录]/src目录下新建一个子目录,命名为
geometry。在该目录下新建
geometry.go。
编写
geometry.go代码如下:
//geometry.go package 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。找到后编译该文件并在
[工作空间目录]/bin目录下生成二进制文件
geometry(在Windows下是
geometry.exe)。 现在的目录结构如下所示:
src/ geometry/ geometry.go bin/ geometry
执行
[工作空间目录]/bin/geometry运行该程序,其中 [工作空间目录] 需要换成自己的实际目录。这条命令运行在
bin目录下的
geometry二进制文件。你应该可以看到如下输出:
Geometrical shape properties
创建自定义包
下面我们将创建一个rectangle包,将与矩形相关的功能(计算矩形的面积和对角线)都放在这个包里。
属于同一个包的源文件应该放在独立的文件夹中,按照Go的惯例,该文件夹的名字应该与包名相同。
因此让我们在
geometry目录下创建一个
rectangle子目录。所有放在该目录下的源文件都应该以
package rectangle开头,用以表示这些源文件都属于
rectangle包。
在
rectangle目录下新建
rectangle.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包中的
Sqrt函数来计算平方根。
注意上面实现的两个函数的函数名
Area和
Diagonal都是以大写字母开头的。这是必要的,我们将很快解释为什么需要这样做。
导入自定义包
为了使用自定义包我们必须先导入它。用来导入自定义包的语法为:import path。我们必须指定
path为相对于
[工作空间目录]/src目录的相对路径。我们当前的目录结构如下所示:
src/ geometry/ geometry.go rectangle/ rectangle.go
语句
import "geometry/rectangle"表示我们要导入 rectangle 包。
在
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包并且使用
Area和
Diagonal函数计算矩形的面积和对角线。在
Printf中的
%.2f格式化指示符表示仅保留浮点数的两位小数。程序输出如下:
Geometrical shape properties area of rectangle 42.00 diagonal of the rectangle 9.22
导出名字
我们将rectangle包中的两个函数名称
Area和
Diagonal的首字母大写,这在 Go 中有特殊的意义。在 Go 中,任何以大写字母开头的变量名、函数名都是被导出的名字(exported name)。只有被导出的名字才能被其它包访问。在这里我们需要在
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函数形式如下:
func init() { }
init函数可用于执行初始化任务,也可用于在执行开始之前验证程序的正确性。
一个包的初始化顺序如下:
包级别的变量首先被初始化
接着
init函数被调用。一个包可以有多个
init函数(在一个或多个文件中),它们的调用顺序为编译器解析它们的顺序。
如果一个包导入了另一个包,被导入的包先初始化。
尽管一个包可能被包含多次,但是它只被初始化一次。
下面让我们对我们的程序做一些修改来理解
init函数。
首先在
rectprops.go中添加一个
init函数:
//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 initialized。
现在我们来修改
main包。我们知道矩形的
length和
width应该大于
0。我们将在
geometry.go中添加
init函数和包级别的变量来做此检查。
修改
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函数中移到了外面,成为了包级别的变量。
添加
init函数。当
rectLen或
rectWidth小于
0时,该函数利用 log.Fatal 打印一条日志并终止程序。
main包的初始化顺序为:
首先初始化被导入的包。因此
rectangle包先被初始化。
然后初始化包级别的变量: 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包的
init函数首先被调用,接着是包级别的变量 rectLen 和 rectWidth 被初始化,接着是
main包的
init函数被调用,该函数检测
rectLen和
rectWidth是否小于
0,如果小于
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初始化。接着调用
main包的
init函数,因为
rectLen是小于
0的,因此程序打印
length is less than zero后退出。
代码可以在 github 上下载。
使用空指示符
在 Go 中只导入包却不在代码中使用它是非法的。如果你这么做了,编译器会报错。这样做的原因是为了避免引入过多未使用的包而导致编译时间的显著增加。将geometry.go中的代码替换为如下代码:
//geometry.go package main import ( "geometry/rectangle" //importing custom package ) func main() { }
上面的程序将会报错:
geometry.go:6: imported and not used: "geometry/rectangle"
但是在开发过程中,导入包却不立即使用它是很常见的。可以用空指示符(
_)来处理这种情况。
下面的代码可以避免抛出上面的错误:
package main import ( "geometry/rectangle" ) var _ = rectangle.Area //error silencer func main() { }
var _ = rectangle.Area这一行屏蔽了错误。我们应该跟踪这些“错误消音器”(error silencer), 在开发结束时,我们应该去掉这些“错误消音器”,并且如果没有使用相应的包,这些包也应该被一并移除。因此,建议在
import语句之后的包级别中写“错误消音器”。
有时我们导入一个包只是为了确保该包初始化的发生,而我们不需要使用包中的任何函数或变量。例如,我们也许需要确保
rectangle包的
init函数被调用而不打算在代码中的任何地方使用这个包。空指示符仍然可以处理这种情况,像下面的代码一样:
package main import ( _ "geometry/rectangle" ) func main() { }
运行上面的程序,将会得到输出:
rectangle package initialized。我们成功地初始化了这个包,即使在代码中的任何地方都没有使用它。
包的介绍到此结束。感谢阅读。
目录
上一篇:Golang教程:(六)函数
下一篇:Golang教程:(八)if else 语句
相关文章推荐
- Golang教程:(一)介绍和安装
- Golang教程:(二十二)信道
- golang模板语法简明教程
- Golang初级系列教程-控制结构-switch-case
- Golang读写Excel的方法教程
- Golang教程:(八)if else 语句
- Golang教程:(二)Hello World
- Golang教程:(四)类型
- Golang教程:(十五)指针
- Golang对MongoDB数据库的操作简单封装教程
- golang 的glide包管理使用技巧教程
- Golang教程:(九)循环语句
- Golang教程:(七)包
- Golang教程:(十六)结构体
- Golang教程:(二十六)go面向对象编程--结构体代替类
- Golang初级系列教程-接口
- golang教程汇总
- Golang教程:(八)if else 语句
- Golang教程:(十七)方法
- Golang教程:(二十五)Mutex