Golang的包 · Golang学习笔记 · 看云
### 包的概念
包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。
包里面是一个或多个以 .go 为扩展名的文件(以 .go 为扩展名的文件就是Go语言的源文件),而每一个 .go 文件都属于且仅属于一个包,并且在编写 .go 文件时,必须在非注释的第一行写明该文件属于哪一个包。
包可以看做一个类库或者命名空间,当在一个 .go 文件里导入一个包后,就可以使用该包里面的常量、变量、类型、函数名、结构字段等等。
### 关于包的理解
我们新建一个初始程序,来解释以上文字:

1. 上图左边的 doc.go 、main.go 就是我们的源文件。
1. 图右边非注释的第一行,也就是 package main 这句,表明 main.go 是属于 main 这个包的。另外,package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包,不然编译出来不是一个可执行程序。(当然,在该例中的 doc.go 文件里,也在非注释的第一行写了package main)
1. 接着往下,看到 import ( "fmt" ) ,这句就是将fmt这个包导入了 main.go里,这表示在 main.go 中可以使用fmt包中可见的所有方法、类型等。
1. 在func main (){} 中,调用了fmt包中的 Println() 这个函数,执行后将在控制台打印出“Hello World!”。
另外,main() 函数是整个程序的入口函数,每一个可执行程序所**必须包含**的,如果在没有 init()函数(初始化函数)时,它将在启动后第一个执行。如果你的 main 包的源代码没有包含 main 函数,则会引发构建错误 undefined: main.main。main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。
### 如何导入一个包
从上文的内容,可以得知,Go语言是通过 import 这个关键字来导入包的,写法有:
~~~
//一个包时:
import "fmt"
//多个包时:
import "fmt"; import "os"
//或者:
import "fmt"
import "os"
//最好的是:
import (
"fmt"
"os"
)
//也可以:
import ("fmt"; "os")
~~~
### 标准库
上图中的fmt包是来自于Go 的**标准库**。在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。

上图展示了fmt包里的源文件。fmt包是Go 的标准库中的包之一,你可以在安装目录里找到。我们上文所调用的函数 Println() 就是来自于这个文件目录下 print.go 源文件里的函数。
Go 的标准库,包含了大量的包(如:fmt 和 os)。如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。
属于同一个包的源文件必须全部被一起编译,一个包即是编译时的一个单元,因此根据惯例,每个目录都只包含一个包。
**另外,如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。**
### 自己建个简单的包
[Go入门指南](http://www.kancloud.cn/kancloud/the-way-to-go) 里面说:如果你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母。

上图中的 fmt.a 文件就是我们这里的导入的fmt包编译生成的。
那么我们现在自己建个包试试:
我们新建一个文件夹pack1,里面再建一个 add.go , add.go里面写了个两数相加的函数,注意第一行是 package pack1 。这样就建好一个名为pack1的包了。

然后,我们在test1文件里的main.go 文件里导入包pack1,并调用函数Add(),代码如下:
~~~
package main
import (
"fmt"
"pack1"
)
func main() {
fmt.Println("Hello World!")
pack1.Add(1, 2)
}
~~~
运行成功过后,我们看GOPATH目录里的pkg文件夹

从上图可以看到pack1包编译后生成的 pack1.a。
上述例子中,如果把文件夹pack1放在test1文件夹中,效果也是一样的。
我们再在,pack1文件夹中建一个sub.go,如下图:

注意到,声明所在的包是pack2,而不是pack1。编译运行看看是什么结果:

编译报错:在D:\GOPATH\src\pack1目录下找到pack1、pack2两个包。
我们在上文的**标准库**中提过“属于同一个包的源文件必须全部被一起编译,一个包即是编译时的一个单元,因此根据惯例,每个目录都只包含一个包。”
意思就是在一个文件夹里的源文件只能属于一个包。我们把sub.go里的pack2 改为pack1,程序就能正常编译运行。
### 包的可见性
包的可见性就是在该包里的内容是否能被外部调用。
举个例子,我们将 sub.go 中的 Sub 该为 sub ,并在 mian.go 里调用它,运行结果如下:

编译报错:没有pack1.sub,没有定义pack1.sub。
这说明 sub.go 中的函数 sub() 在 main.go 中不能被发现不能被调用,而一旦将sub的首字母变成大写,改成Sub,程序就不会报错了。
那么由此可知包的可见性规则:
标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Sub,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
#### **注意事项**
导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os,这正是遵循了 Go 的格言:“没有不必要的代码!“。
### 包的依赖关系
Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 .o 的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。
如果 A.go 依赖 B.go,而 B.go 又依赖 C.go:
编译 C.go, B.go, 然后是 A.go.
为了编译 A.go, 编译器读取的是 B.o 而不是 C.o.
这种机制对于编译大型的项目时可以显著地提升编译速度。
每一段代码只会被编译一次