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.