七叶笔记 » golang编程 » Golang:导入声明

Golang:导入声明

南京鸡鸣寺

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.

相关文章