互联网上已经发布了非常多的Go语言开源包,可以通过 http://godoc.org 检索。
每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问。
每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包API的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。
当我们修改了一个源文件,我们必须重新编译该源文件对应的包和所有依赖该包的其他包。
即使是从头构建,Go语言编译器的编译速度也明显快于其它编译语言。
Go语言的闪电般的编译速度主要得益于三个语言特性。
- 第一点,所有导入的包必须在每个文件的开头显式声明,这样的话编译器就没有必要读取和分析整个源文件来判断包的依赖关系。
- 第二点,禁止包的环状依赖,因为没有循环依赖,包的依赖关系形成一个有向无环图,每个包可以被独立编译,而且很可能是被并发编译。
- 第三点,编译后包的目标文件不仅仅记录包本身的导出信息,目标文件同时还记录了包的依赖关系。因此,在编译一个包的时候,编译器只需要读取每个直接导入包的目标文件,而不需要遍历所有依赖的的文件。
每个包是由一个全局唯一的字符串所标识的导入路径定位。出现在import语句中的导入路径也是字符串。
import (
"fmt"
"math/rand"
"github.com/go-sql-driver/mysql"
)
Go语言的规范并没有指明包的导入路径字符串的具体含义,导入路径的具体含义是由构建工具来解释的。
如果计划分享或发布包,那么导入路径最好是全球唯一的。为了避免冲突,所有非标准库包的导入路径建议以所在组织的互联网域名为前缀;而且这样也有利于包的检索。
在每个Go语言源文件的开头都必须有包声明语句。包声明语句的主要目的是确定当前包被其它包导入时默认的标识符(也称为包名)。
默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名。
如果我们想同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。这叫做导入包的重命名。
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
每个导入声明语句都明确指定了当前包和被导入包之间的依赖关系。如果遇到包循环导入的情况,Go语言的构建工具将报告错误。
如果只是导入一个包而并不使用导入的包将会导致一个编译错误。
有时候我们只是想利用导入包而产生的副作用:它会计算包级变量的初始化表达式和执行导入包的init初始化函数。这时候我们需要抑制“unused import”编译错误,我们可以用下划线_
来重命名导入的包。像往常一样,下划线_
为空白标识符,并不能被访问。
import _ "image/png" // register PNG decoder
这个被称为包的匿名导入。它通常是用来实现一个编译时机制,然后通过在main主程序入口选择性地导入附加的包。
Go语言的工具箱集合了一系列功能的命令集。它可以看作是一个包管理器(类似于Linux中的apt和rpm工具),用于包的查询、计算包的依赖关系、从远程版本控制系统下载它们等任务。它也是一个构建系统,计算文件的依赖关系,然后调用编译器、汇编器和链接器构建程序,虽然它故意被设计成没有标准的make命令那么复杂。
对于大多数的Go语言用户,只需要配置一个名叫GOPATH的环境变量,用来指定当前工作目录即可。当需要切换到不同工作区的时候,只要更新GOPATH就可以了。
go env
命令用于查看Go语言工具涉及的所有环境变量的值,包括未设置环境变量的默认值。
GOOS环境变量用于指定目标操作系统(例如android、linux、darwin或windows),GOARCH环境变量用于指定处理器的类型,例如amd64、386或arm等。虽然GOPATH环境变量是唯一必须要设置的,但是其它环境变量也会偶尔用到。
使用Go语言工具箱的go命令,不仅可以根据包导入路径找到本地工作区的包,也至可以从互联网上找到和更新包。
使用命令go get
可以下载一个单一的包或者用...
下载整个子目录里面的每个包。
一旦go get
命令下载了包,然后就是安装包或包对应的可执行的程序。
go get
命令支持当前流行的托管网站GitHub、Bitbucket和Launchpad,可以直接向它们的版本控制系统请求代码。
go get
命令获取的代码是真实的本地存储仓库,而不仅仅只是复制源文件,因此你依然可以使用版本管理工具比较本地代码的变更或者切换到其它的版本。例如golang.org/x/net包目录对应一个Git仓库:
$ cd $GOPATH/src/golang.org/x/net
$ git remote -v
origin https://go.googlesource.com/net (fetch)
origin https://go.googlesource.com/net (push)
如果指定-u
命令行标志参数,go get
命令将确保所有的包和依赖的包的版本都是最新的,然后重新编译和安装它们。如果不包含该标志参数的话,而且如果包已经在本地存在,那么代码将不会被自动更新。
go get -u
命令只是简单地保证每个包是最新版本,如果是第一次下载包则是比较方便的;但是对于发布程序则可能是不合适的,因为本地程序可能需要对依赖的包做精确的版本依赖管理。通常的解决方案是使用vendor的目录用于存储依赖包的固定版本的源代码,对本地依赖的包的版本更新也是谨慎和持续可控的。
go build
命令编译命令行参数指定的每个包。如果包是一个库,则忽略输出结果;这可以用于检测包是可以正确编译的。如果包的名字是main,go build
将调用链接器在当前目录创建一个可执行程序;以导入路径的最后一段作为可执行程序的名字。
由于每个目录只包含一个包,因此每个对应可执行程序或者叫Unix术语中的命令的包,会要求放到一个独立的目录中。
默认情况下,go build
命令构建指定的包和它依赖的包,然后丢弃除了最后的可执行文件之外所有的中间编译结果。依赖分析和编译过程虽然都是很快的,但是随着项目增加到几十个包和成千上万行代码,依赖关系分析和编译时间的消耗将变的可观,有时候可能需要几秒种,即使这些依赖项没有改变。
go install
命令和go build
命令很相似,但是它会保存每个包的编译成果,而不是将它们都丢弃。被编译的包会被保存到$GOPATH/pkg
目录下,目录路径和 src目录路径对应,可执行程序被保存到$GOPATH/bin目录。
go install
命令和go build
命令都不会重新编译没有发生变化的包,这可以使后续构建更快捷。为了方便编译依赖的包,go build -i
命令将安装每个目标所依赖的包。
因为编译对应不同的操作系统平台和CPU架构,go install
命令会将编译结果安装到GOOS和GOARCH对应的目录。
Go语言的编码风格鼓励为每个包提供良好的文档。包中每个导出的成员和包声明前都应该包含目的和用法说明的注释。
Go语言中的文档注释一般是完整的句子,第一行通常是摘要说明,以被注释者的名字开头。注释中函数的参数或其它的标识符并不需要额外的引号或其它标记注明。
go doc
命令,该命令打印其后所指定的实体的声明与文档注释。该命令并不需要输入完整的包导入路径或正确的大小写。
godoc的在线服务 https://godoc.org ,包含了成千上万的开源包的检索工具。
在Go语言程序中,包是最重要的封装机制。没有导出的标识符只在同一个包内部可以访问,而导出的标识符则是面向全宇宙都是可见的。
Go语言的构建工具对包含internal名字的路径段的包导入路径做了特殊处理。这种包叫internal包,一个internal包只能被和internal目录有同一个父目录的包所导入。