go 包
package
package pkg_name
定义程序属于哪个包,每个 go 文件第一行- package 用于组织 go 的源码改善可用性和易读性,提供代码的模块化
- 代码包的导入路径是相对于 Go 语言自身的源码目录(即
$GOROOT/src
)或在环境变量GOPATH
中指定的某个目录的src
子目录下的子路径,使用/
分隔路径 - 导入的包名使用双引号包围,习惯上将文件夹的最后一个元素命名与包名一致。例外的情况
- 如果某包定义一条命令(可执行的 Go 程序),那么总是使用
main
。这是告诉go build
必须调用链接器生成可执行文件 - 目录中一些文件以
test.go
结尾,包名会以_test
结尾,这是外部测试包。其他的文件是普通包。_test
后缀告诉go test
两个包都需要构建,并指明文件属于哪个包 - 外部测试包用于避免测试所依赖的导入图中的循环依赖
- 一些依赖管理工具会在包导入路径末尾追加版本号,包名仍然不包含版本号后缀
- 如果某包定义一条命令(可执行的 Go 程序),那么总是使用
默认导入
- 单行导入:
import pkg_name
导入包 - 多行导入:导入多个包时,建议使用
()
将导入的包放在一起,即分组导入 - 包分组:导入的包之间通过添加空行分组。通常将来自不同组织的包独立分组。包的导入顺序无关紧要,但是在每个分组中一般会根据字符串顺序排列
导出包内标识符
- 导出名:首字母大写的名字是导出的名字,首字母小写只能包内使用。导入包之后只能使用包导出的名字
导入包的重命名
- 导入包后可以自定义引用的包名
导入包的重命名:如果同时导入两个名字相同的包,那么导入声明必须至少为一个同名包指定一个新的包名避免冲突
import ( "crypto/rand" mrand "math/rand" )
导入包的重命名只影响当前源文件。其它的源文件如果导入了相同的包,可以用导入包原本默认的名字或重命名为另一个完全不同的名字
导入包重命名不仅仅只是为了解决名字冲突。如果导入的一个包名很笨重,特别是在一些自动生成的代码中,这时候用一个简短名称会更方便
- 选择用简短名称重命名导入包时候最好统一,以避免包名混乱
- 选择另一个包名称还可以帮助避免和本地普通变量名产生冲突
每个导入声明语句都明确指定了当前包和被导入包之间的依赖关系。如果遇到包循环导入的情况,Go语言的构建工具将报告错误
Go 编译速度快
- 有三个主要原因
- 所有的导入必须在每一个源文件的开头进行显示列出,这样编译器不需要读取和处理整个文件来确定依赖性
- 包的依赖性形成有向无环图,因为没有环,包可以独立甚至并行编译
- Go 包编译输出的目标文件不仅记录它自己的导出信息,也记录所依赖包的导出信息。当编译一个包时,编译器对于每一个导入必须读取一个目标文件,但是不需要超出这些文件
导入匿名包
- go 导入一个包之后不在代码中使用是不合法的
- 导入包,只需要调用包中的 init 函数:在包名之前加下划线和空格
import _ pkg_name
- 暂时导入包,之后才会需要,建议在 import 之后紧跟语句
var _ = pkg_name.SomeFunc // error silencer
,此语句可避免编译错误 - 在真正使用包中的代码之后就删掉此语句
- 导入包,只需要调用包中的 init 函数:在包名之前加下划线和空格
包的初始化入口 init
- init 函数用于执行初始化任务,或者在执行之前验证程序的正确性
- init 函数的特性
- 每个源码可以使用 1 个 init 函数
- init 函数会在程序执行前(main 函数执行前)被自动调用
- 调用顺序为 main 中引用的包,以深度优先顺序初始化
- 假设包的引用关系
main->A->B->C
,那么这些包的 init 函数调用顺序为C.init->B.init->A.init->main
- 同一个包中的多个 init 函数的调用顺序不可预期
- init 函数不应有返回值,不应包括任何参数,不能在源码中显式调用
- 包的初始化顺序
- 初始化包级别的变量
- 调用 init 函数,如果有多个 init 函数(在一个或多个文件中),按照编译器接收顺序调用:go 会从 main 包开始检查其引用的所有包,每个包也可能包含其他的包。编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。在运行时,被最后导入的包会最先初始化并调用 init 函数
- 导入的包先初始化
每个包只初始化一次
geometry.go
package main import ( "fmt" "geometry/rectangle" "log" ) var recLen, recWidth float64 = 3, -4 func init() { fmt.Println("Geometry init func") if recLen < 0 { log.Fatal("length is less than zero") } if recWidth < 0 { log.Fatal("width is less than zero") } } func main() { fmt.Println("Geometry main func") fmt.Println("rectangle area: %.2f", rectangle.Area(recLen, recWidth)) fmt.Println("rectangle diagonal: %.2f", rectangle.Diagonal(recLen, recWidth)) }
rectangle.go
package rectangle import ( "fmt" "math" ) func init() { fmt.Println("Rectangle init func") } func Area(len, width float64) float64 { area := len * width return area } func Diagonal(len, width float64) float64 { diagonal := math.Sqrt(len*len + width*width) return diagonal }
内部包
- 内部包只能被另一个包导入。这个包位于以 internal 目录的父目录为根目录的树中
- 内部包可以不需要导出标识符就可以被满足条件的包访问
- 例如有下面的文件夹
net/http
net/http/internal/chunked
net/http/httputil
net/url
net/http/httputil
和net/http
可以导入net/http/internal/chunked
net/url
不可以导入net/http/internal/chunked
net/url
可以导入net/http/httputil
包的文档化
- 文档注释是完整的语句,使用声明的包名作为开头的第一句注释通常是总结
- 可以出现在任何文件,但是必须只有一个
- 文件名通常是
doc.go
- 函数参数和其他的标识符不用括号或特别标注