Go程序由各种包(package)组成。通常包会依赖于其他的包,不论是内置(built-in)的还是第三方的。如果需要使用某个包中的导出标识(exported identifiers),就需要导入(import)这个包。今儿就来讲讲这个“import”声明:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Exp2(10)) // 1024
}
上述导入声明中包含2条导入条目,每个导入条目定义一个包导入。
“main”包用于创建可执行程序,Go程序由这个包中的“main”函数发起。
上面介绍了一个简单而通用的导入声明,但是还有几种大家不是太了解的使用场景:
import (
"math"
m "math"
. "math"
_ "math"
)
这四种导入语句行为各不相同,后面会逐一解释。
被导入包中只有导出标识才能被使用,导出标识指的是大写字母开头的标识—— #Exported_identifiers.
基础概念
导入声明
ImportDeclaration = "import" ImportSpec
ImportSpec = [ "." | "_" | 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 declarations
import (
"fmt"
"strings"
)
import "strconv"
// top-level declarations
func main() {
fmt.Println(strings.Repeat(strconv.FormatInt(15, 16), 5))
}
导入包作用域
导入包的作用域限制在这个文件代码块,我们可以在这整个文件中访问导入包中的导出标识符,但是仅限于这个文件,而非整个包:
// github.com/mlowicki/a/main.go
package main
import "fmt"
func main() {
fmt.Println(a)
}
// github.com/mlowicki/a/foo.go
package main
var a int = 1
func hi() {
fmt.Println("Hi!")
}
这段代码无法通过编译:
> go build
# github.com/mlowicki/a
./foo.go:6:2: undefined: fmt
导入的类型
自定义包名
通常来说导入路径最后部分就是包的名称,但是这只是一个习惯做法,Go语言本身没有强制要求这么做:
# github.com/mlowicki/main.go
package main
import (
"fmt"
"github.com/mlowicki/b"
)
func main() {
fmt.Println(c.B)
}
# github.com/mlowicki/b/b.go
package c
var B = "b"
这段代码的输出是“b”。但是这样的写法看起来很怪,所以一般来说我们建议使用习惯用法。
如果导入描述中不包含自定义包名,那么就用包子句中的名称来引用导入包中的导出标识符:
package main
import "fmt"
func main() {
fmt.Println("Hi!")
}
也可以在导入包的时候,自定义包名称:
# github.com/mlowicki/b/b.go
package b
var B = "b"
package main
import (
"fmt"
c "github.com/mlowicki/b"
)
func main() {
fmt.Println(c.B)
}
这段代码的输出和之前一样。当多个包有相同的接口(导出标识符)时这种导入包的方式是非常有用的。比如,我们使用包“”时,因为它的接口和内置的“log”接口一致,所以使用这种导入方式可以保证我们的调用代码不变:
import log "github.com/sirupsen/logrus"
导入所有导出标识符(点导入)
使用如下导入方式:
import m "math"
import "fmt"
我们可以通过这两种方式访问导出标识符:
- m.Exp
- fmt.Println
有一种方式可以让我们直接访问所有的导出标识符:
package main
import (
"fmt"
. "math"
)
func main() {
fmt.Println(Exp2(6)) // 64
}
什么情况下这种导入方式有用?在测试中。假设我们有一个包“a”被另一个包“b”导入。现在我们想要在“b”中测试包“a”,如果测试写在包“a”中,且需要引用包“b”(因为我们需要其中的一些实现),那么这里就会存在一个循环依赖的问题。一种解法是将所有的测试单独放在另外一个包“a_tests”中,那么我们需要引用“a”中所有的导出标识符:
import . "a"
然后就像在同一个包中使用这些标识符一样,而无需使用包名来访问这些导出标识符。
使用这种方式有一个问题,如果通过“.”导入多个包,且这些包中同名导出标识符,那编译器会报错:
# github.com/mlowicki/c
package c
var V = "c"
# github.com/mlowkci/b
package b
var V = "b"
# github.com/mlowicki/a
package main
import (
"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 main
import "fmt"
func main() {}
使用空标识符导入可以达到这种效果,使用这种导入方式时,“init”方法会被使用到,关于这块内容,可以参考这个:
init functions in Go
Identifier main is ubiquitous. Every Go pro gram starts in a package main by calling identically named function. When…
上述文章最好看一下,但这里必须知道的是:
import _ "math"
使用这种方式导入包,“math”不必要被导入它的包使用,但是“math”包中“init”函数总是会被执行(同时它所依赖的包的“init”函数也会被相继执行)。
循环导入
Go语言禁止循环导入——包间接导入它自身。最常见的情况是包“a”导入包“b”,然后包“b”又导入了包“a”:
# github.com/mlowicki/a/main.go
package a
import "github.com/mlowicki/b"
var A = b.B
# github.com/mlowicki/b/main.go
package b
import "github.com/mlowicki/a"
var B = a.A
编译无法通过:
> go build
can't load package: import cycle not allowed
package github.com/mlowicki/a
imports github.com/mlowicki/b
imports github.com/mlowicki/a
当然,更复杂的情况“a” → “b” → “c” → “d” → “a”,依然循环导入了,无法编译通过:
package main
import (
"fmt"
"github.com/mlowicki/a"
)
var A = "a"
func main() {
fmt.Println(a.A)
}
编译报错: can’t load package: import cycle not allowed.