Go 中的 import 声明

栏目: Go · 发布时间: 6年前

内容简介:Go 中的程序由各种包组成。通常,包依赖于其它包,这些包内置于标准库或者第三方。包首先需要被导入才能使用包中的导出标识符。这是通过结构体调用上面我们有一个 import 导入的例子,其中包含了两行导入声明。每行声明定义了单个包的导入。命名为

Go 中的程序由各种包组成。通常,包依赖于其它包,这些包内置于标准库或者第三方。包首先需要被导入才能使用包中的导出标识符。这是通过结构体调用 import 声明 来实现的:

package main

import (
    "fmt"
    "math"
)

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

上面我们有一个 import 导入的例子,其中包含了两行导入声明。每行声明定义了单个包的导入。

命名为 main 的包,是用来创建可执行的二进制文件。程序的执行是从包 main 开始,通过调用包中也叫做 main 的函数开始。

但是,还有其它一些鲜为人知的导入用法,这些用法在各种场景下都很实用:

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

这四个导入格式都有各自不同的行为,在这篇文章中我们将分析这些差异。

导入包只能引用导入包中的导出标识符。 导出标识符是以Unicode大写字母开头的

基础

Import 声明剖析

ImportDeclaration = "import" ImportSpec
ImportSpec        = [ "." | "_" | Identifier ] ImportPath
  • Identifier 是将在限定标识符中使用的任何有效标识符。
  • ImportPath 是一个字符串(原始或解释字符串,译注:例如 \n 和 "\n" 的区别,原始字符串或回车)

让我们看一些例子:

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

合并 Import 声明

导入两个或者更多的包可以有两种写法。一个是,我们可以写多个 import 声明:

import "io"
import "bufio"

或者,我们可以将多个 import 声明合并(将多个导入放在一条导入声明中):

import (
    "io"
    "bufio"
)

第二种导入方式在导入很多个包的时候非常实用,然后多次重复的用 import 关键字导入包会降低可读性。如果你不使用自动导入之类的工具,例如: https://github.com/bradfitz/goimports ,这种方式还可以减少按键次数。

(短)导入路径

导入规范中使用的字符串文字(每个导入声明包含一个或多个导入规范)告诉导入哪个包。这个字符串称为导入路径。根据语言规范,它取决于如何解释导入路径(字符串)的实现方式,但在现实运用中它的路径相对包的第三方库目录或 go env GOPATH / src 目录(更多内容参考 GOPATH )。

内置的包导入使用 “math” 或 “fmt” 等短导入路径。

.go 文件剖析

每个 .go 文件的结构是相同的。首先是 package 语句,可选地在其前面加上通常是描述包的作用的注释。然后零个或多个导入声明。 接着包含零个或多个顶级声明。

// 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))
}

强制组织 (Enforced organisation) 不允许引入不必要的混乱,这简化了解析和基本的代码库跳转(导入声明不能放在 package 子句之前,也不能与顶级声明交错,所以它总是很容易找到)。

导入作用域

导入的作用域是文件块级别。这意味着它可以从整个文件中访问,但不能在整个包中被访问:

// 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

更多的关于作用域的内容参考之前发表的文章: Scopes in 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"

// github.com/mlowicki/main.go (依据原文含义,译者添加)
package main

import (
    "fmt"
    c "github.com/mlowicki/b"
)

func main() {
    fmt.Println(c.B)
}

这个输出结果和之前一样。如果我们的包具有与其它包相同的接口(导出的标识符),则这种导入形式非常有用。 一个这样的例子是 https://github.com/sirupsen/logrus ,它有一个与 log 兼容的 API :

import log "github.com/sirupsen/logrus"

如果我们只使用内置日志包中的 API ,那么用导入 log 替换这样的导入不需要对源代码进行任何更改。它也有点短(但仍然有意义)所以可能会节省一些按键次数。

导入所有的导出标识符

例如:

import m "math"
import "fmt"

可以使用指定的包的别名 (m.Exp) 或者导入的包名 (fmt.Prinln) 实现引用导出标识符。还有另一个方式不用通过限定标识符就可以访问导出标识符:

package main

import (
    "fmt"
    . "math"
)

func main() {
    fmt.Println(Exp2(6))  // 64
}

什么时候这种用法有用呢?在测试中。假设我们有一个包 b 导入包 a。现在我们想给包 a 添加测试。如果测试也在包 a 中,并且测试也会导入包 b (因为到时需要在那实现一些东西),那么我们将最终将会变成循环依赖,这是禁止的。绕过它的一种方法是将测试放入单独的包中,如 a_tests。然后我们需要导入包 a 并使用限定标识符引用每个导出的标识符。为了让我们的实现的更轻松,我们可以用点来导入包 a:

import . "a"

然后引用包 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"

使用空标识符

如果导入了包但是不使用,Golang的编译器将无法编译通过。

package main

import "fmt"

func main() {}

使用点导入,其中所有导出的标识符都直接添加到导入文件块中,在编译时也会出现失败。唯一的绕过方式是使用空白标识符。需要知道init函数是什么,以便理解为什么我们需要导入空白标识符。参考之前init的介绍文章 https://medium.com/golangspec/init-functions-in-go-eac191b3860a 我鼓励从上到下阅读这篇文章,但本质上,像如下的导入方式:

import _ "math"

不需要在导入文件中使用包 math,但是无论如何都将执行导入包中的 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 这种情况更加的复杂(x -> y 指包 x 导入包 y)。

包也是不能导入自己的:

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

(完)


以上所述就是小编给大家介绍的《Go 中的 import 声明》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创新者的解答

创新者的解答

【美】克莱顿•克里斯坦森、【加】迈克尔·雷纳 / 中信出版社 / 2013-10-10 / 49.00

《创新者的解答》讲述为了追求创新成长机会,美国电信巨子AT&T在短短10年间,总共耗费了500亿美元。企业为了保持成功记录,会面对成长的压力以达成持续获利的目标。但是如果追求成长的方向出现偏误,后果往往比没有成长更糟。因此,如何创新,并选对正确方向,是每个企业最大的难题。 因此,如何创新,并导向何种方向,便在于创新结果的可预测性─而此可预测性则来自于正确的理论依据。在《创新者的解答》中,两位......一起来看看 《创新者的解答》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具