Skip to content

包管理工具

概念

包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。

你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package mainpackage main 表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包。如果你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母。

标准库:

在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。一般情况下,标准包会存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目录下。

Go 的标准库包含了大量的包(如:fmtos),如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。

如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。

依赖关系:

Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 .go 的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。

如果 A.go 依赖 B.go,而 B.go 又依赖 C.go

  • 先编译 C.go, B.go, 然后是 A.go.
  • 为了编译 A.go, 编译器读取的是 B.o 而不是 C.o.

这种机制对于编译大型的项目时可以显著地提升编译速度。

导入包

一个 Go 程序是通过 import 关键字将一组包链接在一起,导入包即等同于包含了这个包的所有的代码对象。

import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。包名被封闭在半角双引号 "" 中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。

完整的引入声明语句形式

go
import importname "path/to/package"

其中引入名importname是可选的,它的默认值为被引入的包的包名(不是目录名)。

如果需要多个包,它们可以被分别导入:

go
import "fmt"
import "os"

// 或者
import "fmt"; import "os"

但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 constvartype 的声明或定义):

go
import (
   "fmt"
   "os"
)

当你导入多个包时,最好按照字母顺序排列包名,这样做更加清晰易读。

一个完整引入声明语句形式的引入名importname可以是一个句点(.)。 这样的引入称为句点引入。使用被句点引入的包中的导出代码要素时,限定标识符的前缀必须省略。

go
import (
 . "fmt"
 . "time"
)

func main() {
 Println("Current time:", Now())
}

在上面这个例子中,PrintlnNow函数调用不需要带任何前缀。

一般来说,句点引入不推荐使用,因为它们会导致较低的代码可读性。

一个完整引入声明语句形式的引入名importname可以是一个空标识符(_)。 这样的引入称为匿名引入。一个包被匿名引入的目的主要是为了加载这个包,从而使得这个包中的代码要素得以初始化。 被匿名引入的包中的init函数将被执行并且仅执行一遍。

在下面这个例子中,net/http/pprof标准库包中的所有init函数将在main入口函数开始执行之前全部执行一遍。

go
import _ "net/http/pprof"

func main() {
 ... // 做一些事情
}

WARNING

如果包名不是以 ./ 开头,如 "fmt" 或者 "container/list",则 Go 会在全局文件进行查找;如果包名以 ./ 开头,则 Go 会在相对目录中查找;如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。

可见性规则

TIP

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是它们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

(大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)。

因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。

假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不可以省略的)。

因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于它们的包名,例如 pack1.Thingpack2.Thing

你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:import fm "fmt"。下面的代码展示了如何使用包的别名:

go
package main

import fm "fmt" // alias3

func main() {
   fm.Println("hello, world")
}

WARNING

如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os,这正是遵循了 Go 的格言:“没有不必要的代码!”。

包的分级声明和初始化:

你可以在使用 import 导入包之后定义或声明 0 个或多个常量 (const)、变量 (var) 和类型 (type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用,然后声明一个或多个函数 (func)。