一、概述
想学习Golang,包肯定是绕不过去的,就像C语言的标准库,这篇文章里,我将为大家介绍关于Go语言包机制的使用、注意事项和原理
二、package
package遵循以下原则:
package
是最基本的分发单位和工程管理中依赖关系的体现- 每个Go语言源代码文件开头都必须要有一个
package
声明,表示源代码文件所属包 - 要生成Go语言可执行程序,必须要有名为
main
的package
包,且在该包下必须有且只有一个main
函数 - 同一个路径下只能存在一个
package
,一个package
可以由多个源代码文件组成
按照我个人的理解,一个目录就是一个包,该目录下的所有文件必须属于同一个包。
三、import
1. 作用与写法
import
语句的唯一作用就是导入源码文件所依赖的包,而且导入的包必须在源码文件里用到,否则会报错(在VS Code里如果输入一个没有导入的包函数,VS Code会自动导入)
import
的写法有两种格式,建议使用第二种,导入包方法如下:
//第一种
import "package1"
import "package2"
import "package3"
//第二种
improt (
"package1",
"package2",
"package3"
)
2. 原理
跟package
类似,import
原理遵守以下几个原则:
- 如果一个
main
导入其他的包,包会被顺序导入 - 如果导入的包(
pkg1
)依赖其他的包(包pkg2
),会首先导入pkg2
,然后初始化pkg2
中的常量与变量,如果pkg2中有init函数,会自动执行init - 所有包导入完成后才会对
main
的常量和变量进行初始化,然后执行main
中的init
函数(如果有的话),最后执行main
函数 - 如果一个包被导入多次实际上只会导入一次
关于上面第三条规的import
多重依赖的流程如下图所示
3. import别名
import iii "fmt"
// 此处省略一些代码...
iii.Println("hello")
如果别名是.
的话,则可以这样调用(一般不建议这样使用)
import . "fmt"
// 此处省略一些代码...
Println("hello")
有一个最特别的情况,如果别名是 _
的话,表示只注册该包(初始化全局常量和变量,且执行其init函数),并不会实际引入该包。
当导入一个包时,它所有的 init() 函数就会被执行。有些时候我们并非真的需要使用这些包,仅仅是希望它的 init() 函数被执行而已。
举个例子,如果我们需要处理图像,通常会导入 Go 标准库支持的所有相关的包,但是并不会用到这些包的任何函数。
import (
"fmt"
"image"
"os"
"path/filepath"
"runtime”
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
这里导入了 image/gif、image/jpeg 和 image/png 包,纯粹是为了让它们的 init() 函数被执行(这些 init() 函数注册了各自的图像格式),所有这些包都以下划线作为别名,所以 Go语言不会发出导入了某个包但是没有使用的警告。
四、创建自定义的包
我们创建的自定义的包最好就放在 GOPATH 的 src 目录下(或者 GOPATH src 的某个子目录),如果这个包只属于某个应用程序,可以直接放在应用程序的子目录下,但如果我们希望这个包可以被其他的应用程序共享,那就应该放在 GOPATH 的 src 目录下,每个包单独放在一个目录里,如果两个不同的包放在同一个目录下,会出现名字冲突的编译错误。
作为惯例,包的源代码应放在一个同名的文件夹下面。同一个包可以有任意多个文件,文件的名字也没有任何规定(但后续名必须是 .go),这里我们假设包名就是 .go 的文件名(如果一个包有多个 .go 文件,则其中会有一个 .go 文件的文件名和包名相同)。
例如:
aGoPath/src/stacker/stacker.go
aGoPath/src/stacker/stack/stack.go
五、理解包导入后的init()函数初始化顺序
Go 语言包会从 main 包开始检查其引用的所有包,每个包也可能包含其他的包。Go 编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。
在运行时,被最后导入的包会最先初始化并调用 init() 函数。
通过下面的代码理解包的初始化顺序。
main函数:
package main
import "chapter08/code8-2/pkg1"
func main() {
pkg1.ExecPkg1()
}
pkg1包对应的函数:
package pkg1
import (
"chapter08/code8-2/pkg2"
"fmt"
)
func ExecPkg1() {
fmt.Println("ExecPkg1")
pkg2.ExecPkg2()
}
func init() {
fmt.Println("pkg1 init")
}
pkg2包对应的函数:
package pkg2
import "fmt"
func ExecPkg2() {
fmt.Println("ExecPkg2")
}
func init() {
fmt.Println("pkg2 init")
}
执行代码,输出如下:
pkg2 init
pkg1 init
ExecPkg1
ExecPkg2