Go程序由各种包(package)组成。通常包会依赖于其他的包,不论是内置(built-in)的还是第三方的。如果需要使用某个包中的导出标识(exported identifiers),就需要导入(import)这个包。今儿就来讲讲这个“import”声明:

package mainimport (    "fmt"    "math")func main() {    fmt.Println(math.Exp2(10))  // 1024}

上述导入声明中包含2条导入条目,每个导入条目定义一个包导入。

“main”包用于创建可执行程序,Go程序由这个包中的“main”函数发起。

上面介绍了一个简单而通用的导入声明,但是还有几种大家不是太了解的使用场景:

import (    "math"    m "math"    . "math"    _ "math")

这四种导入语句行为各不相同,后面会逐一解释。

被导入包中只有导出标识才能被使用,导出标识指的是大写字母开头的标识——https://golang.org/ref/spec#Exported_identifiers.

导入声明

ImportDeclaration = "import" ImportSpecImportSpec        = [ "." | "_" | Identifier ] ImportPath
  • “Identifier”指的是任意合法标识符
  • “ImportPath”是字符串字面量

我们来看一些例子:

import . "fmt"import _ "io"import log "github.com/sirupsen/logrus"import m "math"

联合导入声明(“Factored import declaration”)

导入两个及两个以上的包可以写成2种方式。既可以通过多条导入声明:

import "io"import "bufio"

也可以使用联合导入声明的方式:

import (    "io"    "bufio")

如果需要导入很多包的时候,第二种方式更有优势,不但减少了重复的“import”,还提高了代码可读性。

(短)导入路径

导入描述(import specification)中的字符串字面量(每个导入声明包含一个或者多个导入描述)告诉编译器引入哪些包。这里的字符串字面量就是导入路径。导入路径包含基于“GOPATH”的绝对路径和相对路径。

导入内置包时我们使用短导入路径,如:“math”或“fmt”。

解构".go"文件

每个“.go”文件的结构是一样的,首先是一个可选的描述部分,紧接着是一个或多个导入声明,第三部分包含0个或多个顶级声明:

// description...package main // package clause// zero or more import declarationsimport (    "fmt"    "strings")import "strconv"// top-level declarationsfunc main() {    fmt.Println(strings.Repeat(strconv.FormatInt(15, 16), 5))}

导入包作用域

导入包的作用域限制在这个文件代码块,我们可以在这整个文件中访问导入包中的导出标识符,但是仅限于这个文件,而非整个包:

// github.com/mlowicki/a/main.gopackage mainimport "fmt"func main() {    fmt.Println(a)}// github.com/mlowicki/a/foo.gopackage mainvar a int = 1func hi() {    fmt.Println("Hi!")}

这段代码无法通过编译:

> go build# github.com/mlowicki/a./foo.go:6:2: undefined: fmt

自定义包名

通常来说导入路径最后部分就是包的名称,但是这只是一个习惯做法,Go语言本身没有强制要求这么做:

# github.com/mlowicki/main.gopackage mainimport (    "fmt"    "github.com/mlowicki/b")func main() {    fmt.Println(c.B)}# github.com/mlowicki/b/b.gopackage cvar B = "b"

这段代码的输出是“b”。但是这样的写法看起来很怪,所以一般来说我们建议使用习惯用法。

如果导入描述中不包含自定义包名,那么就用包子句中的名称来引用导入包中的导出标识符:

package mainimport "fmt"func main() {    fmt.Println("Hi!")}

也可以在导入包的时候,自定义包名称:

# github.com/mlowicki/b/b.gopackage bvar B = "b"package mainimport (    "fmt"    c "github.com/mlowicki/b")func main() {    fmt.Println(c.B)}

这段代码的输出和之前一样。当多个包有相同的接口(导出标识符)时这种导入包的方式是非常有用的。比如,我们使用包“https://github.com/sirupsen/logrus”时,因为它的接口和内置的“log”接口一致,所以使用这种导入方式可以保证我们的调用代码不变:

import log "github.com/sirupsen/logrus"

导入所有导出标识符(点导入)

使用如下导入方式:

import m "math"import "fmt"

我们可以通过这两种方式访问导出标识符:

  • m.Exp
  • fmt.Println

有一种方式可以让我们直接访问所有的导出标识符:

package mainimport (    "fmt"    . "math")func main() {    fmt.Println(Exp2(6))  // 64}

什么情况下这种导入方式有用?在测试中。假设我们有一个包“a”被另一个包“b”导入。现在我们想要在“b”中测试包“a”,如果测试写在包“a”中,且需要引用包“b”(因为我们需要其中的一些实现),那么这里就会存在一个循环依赖的问题。一种解法是将所有的测试单独放在另外一个包“a_tests”中,那么我们需要引用“a”中所有的导出标识符:

import . "a"

然后就像在同一个包中使用这些标识符一样,而无需使用包名来访问这些导出标识符。

使用这种方式有一个问题,如果通过“.”导入多个包,且这些包中同名导出标识符,那编译器会报错:

# github.com/mlowicki/cpackage cvar V = "c"# github.com/mlowkci/bpackage bvar V = "b"# github.com/mlowicki/apackage mainimport (    "fmt"    . "github.com/mlowicki/b"    . "github.com/mlowicki/c")func main() {    fmt.Println(V)}> go run main.go# command-line-arguments./main.go:6:2: V redeclared during import "github.com/mlowicki/c"    previous declaration during import "github.com/mlowicki/b"./main.go:6:2: imported and not used: "github.com/mlowicki/c"

使用空标识符(blank identifier)导入(空导入)

如果一个包被导入进来,但是从未使用,Go编译器会报错:

package mainimport "fmt"func main() {}

使用空标识符导入可以达到这种效果,使用这种导入方式时,“init”方法会被使用到,关于这块内容,可以参考这个:

init functions in GoIdentifier main is ubiquitous. Every Go program starts in a package main by calling identically named function. When…http://medium.com

上述文章最好看一下,但这里必须知道的是:

import _ "math"

使用这种方式导入包,“math”不必要被导入它的包使用,但是“math”包中“init”函数总是会被执行(同时它所依赖的包的“init”函数也会被相继执行)。

循环导入

Go语言禁止循环导入——包间接导入它自身。最常见的情况是包“a”导入包“b”,然后包“b”又导入了包“a”:

# github.com/mlowicki/a/main.gopackage aimport "github.com/mlowicki/b"var A = b.B# github.com/mlowicki/b/main.gopackage bimport "github.com/mlowicki/a"var B = a.A

编译无法通过:

> go buildcan't load package: import cycle not allowedpackage github.com/mlowicki/a    imports github.com/mlowicki/b    imports github.com/mlowicki/a

当然,更复杂的情况“a” → “b” → “c” → “d” → “a”,依然循环导入了,无法编译通过:

package mainimport (    "fmt"    "github.com/mlowicki/a")var A = "a"func main() {    fmt.Println(a.A)}

编译报错: can’t load package: import cycle not allowed.