Go中的程序由软件包组成。通常,软件包依赖于其他软件包,或者内置于标准库或第三方的软件包。包需要先导入才能使用其导出的标识符。本文将翻译一篇国外的文章,用于介绍包导入的原理以及几种常用的导入方式。

<>>==========>><>

Go 语言中的程序由软件包组成,一般来说,软件包会依赖于其他软件包,这些软件包可能是标准库或者是第三方的软件包。但是,无论是哪里的,包都需要先导入才能使用其导出的标识符,而这需要通过调用 import 语句:

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

上面我们有一个 导入声明 和两个 Import语句。每个 Import 语句都定义了单个包的导入。

名为 main 的软件包用于创建可执行二进制文件。程序执行通过调用在 package main 中的 main 的函数开始。

但是......还有其他一些不太为人所知的选项在各种情况下都很有用:

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

这四个导入规范中的每一个都有不同的表现,在本文中,我们将分析这些差异。

导入包只能引用导入包中的导出标识符。导出的标识符是以 Unicode 大写字母开头的 -  https://golang.org/ref/spec#Exported_identifiers

基础

Import 语句声明

ImportDeclaration = "import" ImportSpec
ImportSpec = [ "." | "_" | Identifier ] ImportPath
  • 标识符是将在合格标识符中使用的任何有效标识符
  • ImportPath 是字符串文字(原始或可解析的)

我们来看一些例子:

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

因式导入声明

导入两个或更多包可以用两种方式编写。我们可以编写多个导入声明:

import "io"
import "bufio"

或者我们可以使用因式导入声明(在单个导入声明中使用多个ImportSpec):

import (
"io"
"bufio"
)

第二个选项特别有用,如果包有很多导入,然后 import 多次重复关键字会降低可读性。如果您不使用 https://github.com/bradfitz/goimports 等自动修复导入的工具,它还可以节省一些击键。

(短)导入路径

在导入语句中使用的字符串字面量(每个导入声明包含一个或多个导入语句)指定要导入的包。这个字符串被称为导入路径。根据语言规范,它取决于实现如何解释导入路径(字符串),但在实际中它是路径相关包的 vendor 目录或 go env GOPATH/src(更多关于GOPATH)。

内置的软件包可以使用短路径进行导入,例如 "math" 和 "fmt"。

.go 文件剖析

每个.go文件的结构都是一样的。首先是包的描述,可选地在前面加注释,通常描述包的用途。然后是零个或多个导入声明。第三部分包含零个或多个顶级声明(源代码):

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

强制的组织形式不允许引入不必要的混乱,这简化了解析过程,并基本上导航了代码库(导入声明不能放在包子句之前,也不能与顶级声明交错,因此总是很容易找到)。

import 范围

导入的范围是文件块。这意味着它可以从整个文件中访问,但不在整个包中:

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

import 的类型

自定义包名称

按照惯例,导入路径的最后一个组件也是导入包的名称。当然,没有什么能阻止我们不遵循这个惯例:

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

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

import log "github.com/sirupsen/logrus"

如果我们只使用在内置日志包中找到的API,那么替换这种导入 import "log" 并不需要对源代码进行任何更改。它也稍短(但仍然有意义),因此可以节省一些击键。

将所有导出的标识符放入导入块

例如这样的导入语句:

import m "math"
import "fmt"

可以引用导出标识符与导入规范(m.Exp)中传递的包名称,也可以引用导入包(fmt.Println)的包子句中的名称。还有另一个选项允许访问导出的标识符而不需要合格的标识符:

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

什么时候可能有用?在测试中。假设我们已经打包了一个由包 b 导入的包。现在我们要添加测试来打包a。如果测试也将在包 a 中进行,并且测试也将导入包 b(因为那么需要在那里实现一些东西),那么我们将最终得到禁止的循环依赖。避免这种情况的一种方法是将测试放入单独的软件包,如 a_tests。然后,我们需要导入包 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 函数:init function in Go,我鼓励你从上到下阅读它,但本质上是像下面这样导入:

import _ "math"

不需要在导入文件中使用包数学,但是从导入包中初始化函数将被执行(包和它的依赖关系将被初始化)。如果我们只关注由导入的包完成的引导工作,但我们没有引用任何导出的标识符,这很有用。

如果程序包导入时没有空白标识符并且完全不使用,编译将会失败。

循环导入

Go规范明确禁止循环导入 - 当程序包间接导入时。最明显的例子是,当包一个进口包b和包b接着导入包一:

# 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。

Reference

  1. 原文:Import declarations in Go

Go 中包导入声明的更多相关文章

  1. java ->包的声明与访问

    包的声明与访问 包的概念 java的包,其实就是我们电脑系统中的文件夹,包里存放的是类文件. 当类文件很多的时候,通常我们会采用多个包进行存放管理他们,这种方式称为分包管理. 在项目中,我们将相同功能 ...

  2. java 匿名内部类与包的声明访问

    一 匿名内部类 1.匿名内部类的概念 内部类是为了应对更为复杂的类间关系.查看源代码中会涉及到,而在日常业务中很 难遇到,这里不做赘述. 最常用到的内部类就是匿名内部类,它是局部内部类的一种. 定义的 ...

  3. android中正确导入第三方jar包

    android中正确导入第三方jar包 andriod中如果引入jar包的方式不对就会出现一些奇怪的错误. 工作的时候恰好有一个jar包需要调用,结果用了很长时间才解决出现的bug. 刚开始是这样引用 ...

  4. Eclipse的Java开发中jar导入后无法使用包内class的解决方案

    请注意, 本方法只对于自己的包有效, 如果你的类内部互相调用, 此方法会失效, 需要每个类文件都进行一次CTRL+SHIFT+O进行包的导入. 如上图的一个结构, algs4.jar和stdlib.j ...

  5. 关于junit包导入不了但是maven本地库中却存在的问题

    导入项目的时候发现junit的类使用不了,于是就去看看包导入了没有 发现包是灰色的,于是猜想可能是maven本地库中包没下载过来 查询了本地库发现包是存在的,这就奇怪的,经过网上查询之后得到解决方案 ...

  6. Android studio中怎么导入android.support.v4包

    Android studio中怎么导入android.support.v4包 1.File点击选择projectStructure选择对应的APP然后点击Dependencies 2.点击+号,点击第 ...

  7. 解决IDEA中maven导入jar包

    查      看:   File------>Project Structure--------->Libraries如下面没有maven所引入的jar包则为该错误[1] 错误原因:  是 ...

  8. python中的模块以及包导入

    python中的导入关键字:import 以及from  import 1.import import一般用于导入包以及模块. 不过有个小问题: (1)当导入的是模块的时候是可以直接可以使用模块内的函 ...

  9. pycharm中无法导入pip安装的包

    https://blog.csdn.net/mdxiaohu/article/details/82430060 2020.1.20 练习通过python操作数据库的时候需要导入一个包,因为看代码写的是 ...

随机推荐

  1. 通过IntelliJ IDEA创建maven+springmvc+mybatis项目

    第一个springmvc+mybatis项目,通过学习极客学院视频(视频案例通过eclipse搭建,网址为http://www.jikexueyuan.com/course/1430.html),发现 ...

  2. TOMCAT开启APR模式

    Tomcat支持三种接收请求的处理方式:BIO.NIO.ARP. BIO模式:阻塞式I/O操作,表示Tomcat使用传统Java I/O操作.默认情况下,Tomcat7以下版本使用BIO模式运行,由于 ...

  3. 一个Unix内核级别漏洞(一)

    翻译原创稿件,prison整理翻译,首发ichunqiu,原地址:http://lsd-pl.net/kernelvuln.pdf 这是一篇关于Unix内核级别漏洞的paper,由某团队发布在一次黑客 ...

  4. ELASTICSEARCH健康red的解决

    今天惯例看统计报表, 才发现es集群悲剧了......昨天下午到今天早上, 持续报错, 写了1G的错误日志>_<#(暂无监控....) 当前状态: 单台机器, 单节点(空集群), 200W ...

  5. Flask从入门到精通之flask扩展

    Flask被设计成可扩展形式,因此并没有提供一些重要的功能,比如数据库和用户认证,所以开发者可以自由选择最适合程序的包,或者按需求自行开发.社区成员开发了大量不同用途的扩展,如果这还不能满足需求,你还 ...

  6. ElasticSearch的概念解析

    ElasticSearch是面向文档的,它不仅仅可以存储整个对象或则文档(document),还会索引(index)每个文档的内容使它可以被快速的检索.ElasticSearch和关系型数据库的对比如 ...

  7. JAVA并发编程学习笔记------多线程调优

    1. 多线程场景下尽量使用并发容器代替同步容器 (如ConcurrentHashMap代替同步且基于散列的Map, 遍历操作为主要操作的情况下用CopyOnWriteArrayList代替同步的Lis ...

  8. git 忽略某些文件的命令

    正常的,我们在提交项目版本的时候,经常会存在一些需要忽略的文件或者文件夹,那这个时候,我们就可以通过git的一些操作命令来实现! vim .gitignore 通过上面一句话进入编辑忽略文件/文件夹的 ...

  9. 【UOJ#435】【集训队作业2018】Simple Tree 分块+树链剖分

    题目大意: 有一棵有根树,根为 1 ,点有点权.现在有 m 次操作,操作有 3 种:1 x y w ,将 x 到 y 的路径上的点点权加上 w (其中 w=±1w=±1 ):2 x y ,询问在 x ...

  10. OS之进程管理---多处理器调度

    引言 之前我们所学习的操作系统进程调度策略的前提条件是单处理器系统下的CPU调度程序.如果系统中存在多个CPU,那么负载分配就成为可能,但是相应的调度问题就会更加复杂. 多处理器调度方法 对于多处理器 ...