7.4. 导入包-import

要引用其他包的标识符,可以使用import关键字,导入的包名使用双引号包围,包名是从GOPATH开始计算的路径,使用“/”进行路径分隔。

7.4.1. 1.默认导入的写法

1.1 单行导入

import "包1"
import "包2"

1.2 多行导入

import(
    "包1"
    "包2"
    …
)

7.4.2. 2 理解import的机制

目录结构如下

.
└── src
    └── chapter08
        └── importadd
            ├── main.go
            └── mylib
                └── add.go

加函数(具体文件:…/chapter08/importadd/mylib/add.go)

package mylib
func Add(a, b int) int {
    return a + b
}

add.go 在 mylib 文件夹下,习惯上将文件夹的命名与包名一致,命名为 mylib 包。

导入包(具体文件:…/chapter08/importadd/main.go)

package main
import (
    "chapter08/importadd/mylib"
    "fmt"
)
func main() {
    fmt.Println(mylib.Add(1, 2))
}

// 运行代码,输出结果如下:
// 3

导入的包之间可以通过添加空行来分组;通常将来自不同组织的包独自分组。 包的导入顺序无关紧要,但是在每个分组中一般会根据字符串顺序排列。

import (
    "fmt"
    "html/template"
    "os"
    "golang.org/x/net/html"
    "golang.org/x/net/ipv4"
)

7.4.3. 3.自定义引用的包名

如果我们想同时导入两个有着名字相同的包, 例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。

这叫做导入包的重命名。

import (
    "crypto/rand"
    mrand "math/rand" // 将名称替换为mrand避免冲突
)

导入包的重命名只影响当前的源文件。 其它的源文件如果导入了相同的包,可以用导入包原本默认的名字或重命名为另一个完全不同的名字。

导入包重命名是一个有用的特性,它不仅仅只是为了解决名字冲突。

如果导入的一个包名很笨重,特别是在一些自动生成的代码中,这时候用一个简短名称会更方便。

选择用简短名称重命名导入包时候最好统一,以避免包名混乱。

选择另一个包名称还可以帮助避免和本地普通变量名产生冲突。
例如,如果文件中已经有了一个名为 path 的变量,那么我们可以将"path"标准包重命名为 pathpkg。

7.4.4. 4. 匿名导入包

如果只希望导入包,而不使用任何包内的结构和类型,也不调用包内的任何函数时,可以使用匿名导入包,格式如下:

import (
    _ "path/to/package"
)

其中,path/to/package表示要导入的包名,下画线_表示匿名导入包。

匿名导入的包与其他方式导入包一样会让导入包编译到可执行文件中,同时,导入包也会触发init()函数调用。

7.4.5. 5. 包初始化入口

在某些需求的设计上需要在程序启动时统一调用程序引用到的所有包的初始化函数, 如果需要通过开发者手动调用这些初始化函数,那么这个过程可能会发生错误或者遗漏。 我们希望在被引用的包内部,由包的编写者获得代码启动的通知,在程序启动时做一些自己包内代码的初始化工作。

例如,为了提高数学库计算三角函数的执行效率,可以在程序启动时,将三角函数的值提前在内存中建成索引表,外部程序通过查表的方式迅速获得三角函数的值。 但是三角函数索引表的初始化函数的调用不希望由每一个外部使用三角函数的开发者调用, 如果在三角函数的包内有一个机制可以告诉三角函数包程序何时启动, 那么就可以解决初始化的问题。

Go语言为以上问题提供了一个非常方便的特性:init()函数。

init() 函数的特性如下:

  • 每个源码可以使用1个init()函数。

  • init()函数会在程序执行前(main()函数执行前)被自动调用。

  • 调用顺序为 main()中引用的包,以深度优先顺序初始化。

例如,假设有这样的包引用关系:main→A→B→C,那么这些包的 init() 函数调用顺序为:

C.init→B.init→A.init→main

说明:

  • 同一个包中的多个 init() 函数的调用顺序不可预期。

  • init() 函数不能被其他函数调用。

7.4.6. 6. 包导入后初始化顺序

Go 语言包会从 main 包开始检查其引用的所有包,每个包也可能包含其他的包。 Go 编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。

在运行时,被最后导入的包会最先初始化并调用init()函数。

├── pkginit
│   ├── mian.go
│   ├── pkg1
│   │   └── pkg1.go
│   └── pkg2
│       └── pkg2.go

pkginit/pkg2/pkg2.go

package pkg2

import "fmt"

func ExecPkg2() {
    fmt.Println("ExecPkg2")
}

func init() {
    fmt.Println("pkg2 init")
}

pkginit/pkg1/pkg1.go

package pkg1

import (
    "fmt"
    "github.com/go_study02/02.package/pkginit/pkg2"
)

func ExecPkg1() {
    fmt.Println("ExecPkg1")
    pkg2.ExecPkg2()
}

func init() {
    fmt.Println("pkg1 init")
}

pkginit/main.go

package main

import "github.com/go_study02/02.package/pkginit/pkg1"

func main() {
    pkg1.ExecPkg1()
}

/*
pkg2 init
pkg1 init
ExecPkg1
ExecPkg2
 */