zoukankan      html  css  js  c++  java
  • Go 中包导入声明

    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
  • 相关阅读:
    UITableViewCell 获取当前位置
    iOS图片拉伸
    TCP/IP基础
    AFNetworking报错"_UTTypeCopyPreferredTagWithClass", referenced from: _AFContentTypeForPathExtens
    iOS 后台处理
    统计iOS项目的总代码行数的方法
    iOS自定义model排序
    iOS开发 适配iOS10
    中文 iOS/Mac 开发博客列表
    C#--静态构造函数
  • 原文地址:https://www.cnblogs.com/makor/p/import-declarations-in-go.html
Copyright © 2011-2022 走看看