# 包package

1. Go使用包来组织源代码
2. 内置包源码位于`$GOROOT/src`下
3. 定义包：
   1. 包名一般是小写的，使用一个简短且有意义的名称
   2. 包名一般要和所在的目录同名，也可以不同，包名中不能包含`-` 等特殊符号
   3. 一个文件夹下的所有源码文件只能属于同一个包，同样属于同一个包的源码文件不能放在多个文件夹下
   4. 包的定义不包含路径，比如定义包：`package myp`，不会定义`package ppkg/myp`
   5. 包名为 main 的包为应用程序的入口包，编译不包含 main 包的源码文件时不会得到可执行文件
   6. 包一般使用域名作为目录名称，这样能保证包名的唯一性，比如 GitHub 项目的包一般会放到`GOPATH/src/github.com/userName/projectName` 目录下
4. 导入包：
   1. 导入包需要带上路径`import ppkg/myp`
   2. 包名称是从`$GOPATH/src/`下面进行计算的，使用`/`分割，`import ppkg/myp`的路径实际为`$GOPAT/src/ppkg/myp`

## 导入包

```go
// 单行导入
import "包 1. 入门 的路径"
import "包 2 的路径"

// 多行导入
import (
    "包 1. 入门 的路径"
    "包 2 的路径"
)

// 全路径导入
// 全路径是指GOROOT/src/或GOPATH/src/后面包的存放路径
import "lab/test"
import "database/sql/driver"
import "database/sql

// 相对路径导入
import "../a"
```

## 引用包

导入包后，需要对包内的成员进行引用，有以下几种引用方式

* 标准引用：

  ```go
  package main
  import "fmt" // 不使用包会报错
  func main() {
  	fmt.Println("hello") // 使用包名.成员名称
  }
  ```
* 自定义别名引用，当包名称有冲突时使用

  ```go
  package main
  import F "fmt" // 不使用包会报错
  func main() {
  	F.Println("hello") // 使用包别名.成员名称
  }
  ```
* 省略引用格式，将包内的内容合并到当前源文件中

  ```go
  package main
  import . "fmt" // 不使用包会报错
  func main() {
  	Println("hello") // 不需要任何前缀，直接使用
  }
  ```
* 匿名引用格式，如果只是希望执行包初始化的 init 函数，而不使用包内部的数据时，可以使用匿名引用格式，即使不使用包也不会报错

  ```go
  package main
  import (
  	_ "database/sql" // 即使不使用包，也不会报错
  	"fmt"
  )
  func main() {
  	fmt.Println("C语言中文网")
  }

  ```

## init函数

* 一个包可以有多个 init 函数，包加载时会执行全部的 init 函数，但并不能保证执行顺序，所以不建议在一个包中放入多个 init 函数，将需要初始化的逻辑放到一个 init 函数里面。
* 包不能出现环形引用的情况，比如包 a 引用了包 b，包 b 引用了包 c，如果包 c 又引用了包 a，则编译不能通过。
* 包的重复引用是允许的，比如包 a 引用了包 b 和包 c，包 b 和包 c 都引用了包 d。这种场景相当于重复引用了 d，这种情况是允许的，并且 Go 编译器保证包 d 的 init 函数只会执行一次。
* main包中的init函数将会先于main函数运行。

  ```go
  package main

  import "fmt"

  func init() {
  	fmt.Println("init")
  }
  func main() {
  	fmt.Println("Hello")
  }
  // 运行结果
  // init
  // Hello
  ```

## 包加载

![image-20210910155832935](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-54535654e971b5c33303e8fe37aeed9b701fe20c%2Fimage-20210910155832935.png?alt=media)

Go语言包的初始化有如下特点：

* 包初始化程序从 main 函数引用的包开始，逐级查找包的引用，直到找到没有引用其他包的包，最终生成一个包引用的有向无环图。
* Go 编译器会将有向无环图转换为一棵树，然后从树的叶子节点开始逐层向上对包进行初始化。
* 单个包的初始化过程如上图所示，先初始化常量，然后是全局变量，最后执行包的 init 函数。

## 导出标识符

标识符包含变量、常量、方法等，只需要将其首字母大写即可导出：

```go
package mypkg

var myVar = 100
const MyConst = "hello"
type MyStruct struct {
}
```

如果结构体或者函数中的值也想导出，也将标识符首字母大写即可：

```go
type MyStruct struct {
    // 包外可以访问的字段
    ExportedField int
    // 仅限包内访问的字段
    privateField int
}

type MyInterface interface {
    // 包外可以访问的方法
    ExportedMethod()
    // 仅限包内访问的方法
    privateMethod()
}
```
