导入路径
每个包是由一个全局唯一的字符串所标识的导入路径定位。出现在import语句中的导入路径也是字符串。
import (
"fmt"
"math/rand"
"encoding/json"
"golang.org/x/net/html"
"github.com/go-sql-driver/mysql"
)
包声明
在每个Go语言源文件的开头都必须有包声明语句。包声明语句的主要目的是确定当前包被其它包导入时默认的标识符(也称为包名)。
例如,math/rand包的每个源文件的开头都包含package rand包声明语句,所以当你导入这个包,你就可以用rand.Int、rand.Float64类似的方式访问包的成员。
func name(parameter-list) (result-list) {
body
}
//如果参数列表中若干个相邻的参数类型的相同,则可以在参数列表中省略前面变量的类型声明,如下所示:
func Add(a, b int)(ret int, err error) {
//...
}
//如果函数只有一个返回值,也可以这么写:
func Add(a, b int) int {
// ...
}
导入声明
可以在一个Go语言源文件包声明语句之后,其它非导入声明语句之前,包含零到多个导入包声明语句。每个导入声明可以单独指定一个导入路径,也可以通过圆括号同时导入多个导入路径。下面两个导入形式是等价的,但是第二种形式更为常见。
import "fmt"
import "os"
import (
"fmt"
"os"
)
如果想同时导入两个有着名字相同的包,例如math/rand包和crypto/rand包,那么导入声明必须至少为一个同名包指定一个新的包名以避免冲突。这叫做导入包的重命名。
import (
"crypto/rand"
mrand "math/rand" // alternative name mrand avoids conflict
)
匿名导入
import _ "image/png" // register PNG decoder
这个被称为包的匿名导入。
工作区结构
对于大多数的Go语言用户,只需要配置一个名叫GOPATH的环境变量,用来指定当前工作目录即可。go get 命令会下载到GOPATH下的bin/目录下。
GOPATH/
src/
gopl.io/
.git/
ch1/
helloworld/
main.go
dup/
main.go
...
golang.org/x/net/
.git/
html/
parse.go
node.go
...
bin/
helloworld
dup
pkg/
darwin_amd64/
...
GOPATH对应的工作区目录有三个子目录。其中src子目录用于存储源代码。每个包被保存在与$GOPATH/src的相对路径为包导入路径的子目录中,例如gopl.io/ch1/helloworld相对应的路径目录。一个GOPATH工作区的src目录中可能有多个独立的版本控制系统,例如gopl.io和golang.org分别对应不同的Git仓库。其中pkg子目录用于保存编译后的包的目标文件,bin子目录用于保存编译后的可执行程序,例如helloworld可执行程序。
第二个环境变量GOROOT用来指定Go的安装目录,还有它自带的标准库包的位置。GOROOT的目录结构和GOPATH类似,因此存放fmt包的源代码对应目录应该为$GOROOT/src/fmt。用户一般不需要设置GOROOT,默认情况下Go语言安装工具会将其设置为安装的目录路径。
构建包
go build命令编译命令行参数指定的每个包。如果包是一个库,则忽略输出结果;这可以用于检测包是可以正确编译的。如果包的名字是main,go build将调用链接器在当前目录创建一个可执行程序;以导入路径的最后一段作为可执行程序的名字。
由于每个目录只包含一个包,因此每个对应可执行程序或者叫Unix术语中的命令的包,会要求放到一个独立的目录中。
go install命令和go build命令很相似,但是它会保存每个包的编译成果,而不是将它们都丢弃。被编译的包会被保存到$GOPATH/pkg目录下,目录路径和 src目录路径对应,可执行程序被保存到$GOPATH/bin目录。(很多用户会将$GOPATH/bin添加到可执行程序的搜索列表中。)还有,go install命令和go build命令都不会重新编译没有发生变化的包,这可以使后续构建更快捷。为了方便编译依赖的包,go build -i命令将安装每个目标所依赖的包。
go mod
go 1.11开始可以使用更优雅灵活的module机制做包依赖管理,能直接感受到的优点如下:
- 项目路径可以脱离$GOPATH了,不需要必须放在$GOPATH/src下。
- 项目内部模块的引入是基于moduleName而不再死板的基于projectName了。
- 半自动维护依赖,如果你很懒,你甚至可以不需要使用get预先安装依赖,module在run test build时会检测未下载的依赖,并自动下载它们。
以前$GOPATH/src/projectName后,项目中各模块互相引用的话都是基于projectName(go 的包加载机制导致的,去 $GOROOT/src 和 $GOPATH/src 去寻址,所以projectName也需要作为包引入路径的一部分),别人使用你的项目时也必须是projectName,否则就得把项目内的所有projectName改为他们的项目名。
而module模式下,项目的包域是moduleName,和projectName无关,项目名称怎样都好,moduleName会注册到加载路径中去。
而module模式下,项目的包域是moduleName,和projectName无关,项目名称怎样都好,moduleName会注册到加载路径中去。
虽然module可以灵活到消除项目名作为项目模块引入路径的槽点,但如果后面要转为普通的vendor模式的话,我们还是建议moduleName同projetName保持一致。
新引入了一个包到项目中可以直接运行而不会报错(非gomod会报错),gomod会自动的检测未下载的包,自动下载,解决依赖问题。
GO111MODULE
mod模式下安装的依赖包都存放在$GOPATH/pkg/mod目录下,在 mod 模式下导入第三方包时会去$GOPATH/pkg/mod目录下加载,而不是普通模式下的$GOPATH/src | project/vendor目录。
mod模式是否开启由环境变量GO111MODULE决定,GO111MODULE 环境变量有三个值:
- on:强制开启mod模式,此模式下不会去 $GOPATH/src,project/vendor 下加载第三方依赖包,只会去$GOPATH/pkg/mod下载加载。
- auto(默认值):如果项目在 $GOPATH/src下,则转为普通模式,否则,转为mod模式。
- off:关闭mod模式,转为普通的$GOPATH/src,project/vendor模式。
init
初始化项目为module模式:go mod init moduleName
注意项目模块包的引入是以 moduleName 作为包域的
tidy
tidy主要用来手动维护项目的包依赖,会检测项目当前的依赖,做相应的依赖添加或移除:go mod tidy
当我们下载了别人的mod项目到本地时,可以直接run,mod会自动下载未安装的依赖,也可以执行一次tidy手动维护一次依赖,当我们要上传自己本地的项目到仓库时,应该先执行以下tidy命令,清理一下无用的依赖,再上传。
vendor
go mod vendor
将module项目转为普通的vendor项目,这时就需要将项目移至$GOPATH/src下,并要保证projectName同moduleName保持一致,否则要手动去修改项目模块包的加载路径了。
go test
go test命令是一个按照一定的约定和组织来测试代码的程序。在包目录内,所有以_test.go为后缀名的源文件在执行go build时不会被构建成包的一部分,它们是go test测试的一部分。
在_test.go文件中,有三种类型的函数:测试函数、基准测试(benchmark)函数、示例函数。一个测试函数是以Test为函数名前缀的函数,用于测试程序的一些逻辑行为是否正确;go test命令会调用这些测试函数并报告测试结果是PASS或FAIL。基准测试函数是以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能;go test命令会多次运行基准函数以计算一个平均的执行时间。
go test命令会遍历所有的_test.go文件中符合上述命名规则的函数,生成一个临时的main包用于调用相应的测试函数,接着构建并运行、报告测试结果,最后清理测试中生成的临时文件。
测试函数
每个测试函数必须导入testing包。测试函数有如下的签名:
func TestName(t *testing.T) {
// ...
}
测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头:
func TestSin(t *testing.T) { /* ... */ }
func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }
参数-run对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被go test命令运行
列如go test -v -run=”French|Canal”
基准测试
基准测试是测量一个程序在固定工作负载下的性能。在Go语言中,基准测试函数和普通测试函数写法类似,但是以Benchmark为前缀名,并且带有一个testing.B类型的参数;testing.B参数除了提供和*testing.T类似的方法,还有额外一些和性能测量相关的方法。它还提供了一个整数N,用于指定操作执行的循环次数。
下面是IsPalindrome函数的基准测试,其中循环将执行N次。
import "testing"
func BenchmarkIsPalindrome(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPalindrome("A man, a plan, a canal: Panama")
}
}
通过-bench命令行标志参数手工指定要运行的基准测试函数。该参数是一个正则表达式,用于匹配要执行的基准测试函数的名字,默认值是空的。其中“.”模式将可以匹配所有基准测试函数,但因为这里只有一个基准测试函数,因此和-bench=IsPalindrome参数是等价的效果。
结果中基准测试名的数字后缀部分,这里是16,表示运行时对应的GOMAXPROCS的值,这对于一些与并发相关的基准测试是重要的信息。
报告显示每次调用IsPalindrome函数花费68.436微秒,是执行161212次的平均时间。因为基准测试驱动器开始时并不知道每个基准测试函数运行所花的时间,它会尝试在真正运行基准测试前先尝试用较小的N运行测试来估算基准测试函数所需要的时间,然后推断一个较大的时间保证稳定的测量结果。
比较型的基准测试就是普通程序代码。它们通常是单参数的函数,由几个不同数量级的基准测试函数调用,就像这样:
func benchmark(b *testing.B, size int) { /* ... */ }
func Benchmark10(b *testing.B) { benchmark(b, 10) }
func Benchmark100(b *testing.B) { benchmark(b, 100) }
func Benchmark1000(b *testing.B) { benchmark(b, 1000) }
通过函数参数来指定输入的大小,但是参数变量对于每个具体的基准测试都是固定的。要避免直接修改b.N来控制输入的大小。除非你将它作为一个固定大小的迭代计算输入,否则基准测试的结果将毫无意义。