目录
一、什么是包
- 和python类似,包就是一个个包含某种功能的代码文件,为了代码以后的可重用性与可读性更高,维护更方便,才引入的一个概念。
- 与python不同的是,python中的包就是一个文件,导包就是导文件,而go中,包以文件夹为基础,导包为导文件夹
- 没有包的概念,就会把所有代码、方法全写在一个文件中,这对于一个项目来说,想想是不是就很可怕
- 属于某一个包的源文件都应该放置于一个单独命名的文件夹里。且按照 Go 的惯例,应该用包名命名这个文件夹,在这个目录下的所有文件都属于这个包
二、main函数和main包
-
所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中,main 包是go自带的包。而python是没有这种限制的
-
package packagename 这行代码指定了某一源文件属于一个包。它应该放在每一个源文件的第一行
三、包管理
以下总体的目录结构:
src
geometry
geometry.go
rectangle
rectprops.go
bin
geometry.exe
// 执行 go build geometry 命令来编译上述程序。该命令会在 geometry 文件夹内搜索拥有 main 函数的文件。在这里,它找到了 geometry.go。接下来,它编译并产生一个名为 geometry (在 windows 下是 geometry.exe)的二进制文件,该二进制文件放置于工作区的 bin 文件夹
1. main包
// 在 geometry.go 中
package main
import "fmt"
func main() {
fmt.Println("Geometrical shape properties")
}
2. 创建自定义包
-
创建一个自定义包
rectangle
,它有一个计算矩形的面积和对角线的函数。属于某一个包的源文件都应该放置于一个单独命名的文件夹里。按照 Go 的惯例,应该用包名命名该文件夹。
因此,我们在
geometry
文件夹中,创建一个命名为rectangle
的文件夹。在rectangle
文件夹中,所有文件都会以package rectangle
作为开头,因为它们都属于 rectangle 包 -
注意:在 Go 中,任何以大写字母开头的变量或者函数才是能被导出的名字。其它包 只能 访问这些指定的被导出的函数和变量。在这里,我们需要在 main 包中访问 Area 和 Diagonal 函数,因此会将它们的首字母大写
-
在创建的 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 }
3. 导包
-
为了使用自定义包,我们必须要先导入它。导入自定义包的语法为
import path
。我们必须指定自定义包相对于工作区内src
文件夹的相对路径。我们目前的文件夹结构是: -
1. 目录结构 src geometry geometry.go rectangle rectprops.go 2. **********在geometry.go中导入这个自定义的包*********** // geometry.go package main import ( "fmt" "geometry/rectangle" // 导入自定义包,只导入目录 ) 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)) }
四、init 函数和包的初始化
-
所有包都可以包含一个
init
函数。init 函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它。init
函数的形式如下 -
func init() { }
-
init
函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性。包的初始化顺序如下:
- 首先初始化包级别(Package Level)的变量
- 紧接着调用
init
函数。包可以有多个init
函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。
-
如果一个包导入了另一个包,会先初始化被导入的包。
-
尽管一个包可能会被导入多次,但是它只会被初始化一次
1. 实例-验证包的初始化顺序
(1)修改 rectprops.go
- 首先在
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
}
(2)修改geometry.go
- 其次对
geometry.go
做了如下修改:- 变量 rectLen 和 rectWidth 从 main 函数级别移到了包级别。
- 添加了 init 函数。当 rectLen 或 rectWidth 小于 0 时,init 函数使用 log.Fatal 函数打印一条日志,并终止了程序。
// geometry.go
package main
import (
"fmt"
"geometry/rectangle" // 导入自定义包
"log"
)
// 1. 包级别变量
var rectLen, rectWidth float64 = 6, 7
// 2. init 函数会检查长和宽是否大于0
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))
}
(3)运行结果
-
理论上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
(4)再修改 geometry.go
将 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。rectLen 为负数,因此当运行 init 函数时,程序在打印 length is less than zero 后终止
*/
五、实现导包而不用
-
在go中,导入了包或变量,却不在代码中使用它们,这在 Go 中都是非法的。当这么做时,编译器是会报错的。其原因是为了避免导入过多未使用的包或定义了过多的变量,从而导致编译时间显著增加
-
变量还好,但是再开发之前,我们都会习惯先将要用的包导好,再进行后续开发,但是这就与上面说的相矛盾,此时就可以使用错误屏蔽器来避免
1. 错误导包
// geometry.go
package main
import (
"geometry/rectangle" // 导入自定的包
)
func main() {
}
// 导了而不用,会报错 geometry.go:6: imported and not used: "geometry/rectangle"
2. 错误屏蔽器
// geometry.go
package main
import (
"geometry/rectangle"
)
var _ = rectangle.Area // 错误屏蔽器
func main() {
}
/*
var _ = rectangle.Area 这一行屏蔽了错误。我们应该了解这些错误屏蔽器(Error Silencer)的动态,在程序开发结束时就移除它们,包括那些还没有使用过的包。由此建议在 import 语句下面的包级别范围中写上错误屏蔽器
*/
3. 空白标识符避错
-
有时候我们导入一个包,只是为了确保它进行了初始化,而无需使用包中的任何函数或变量。例如,我们或许需要确保调用了 rectangle 包的 init 函数,而不需要在代码中使用它。这种情况也可以使用空白标识符
-
// geometry.go package main import ( _ "geometry/rectangle" ) func main() { } // 运行这个的程序,会输出 rectangle package initialized。尽管在所有代码里,我们都没有使用这个包,但还是成功初始化了它