一、包(package)
一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。Go语言的包具有以下特点:
package是Go语言的基本复用模块单元。
注:以首字母大小写来区分是否可被包外代码访问。
大写代表,可以被包外引入。
小写代表,不可被包外引入。
package名可以与目录名不一致。(但建议一致)
同一目录下的Go文件,必须属于同一个package。
1. 可见性
包中的变量、函数和方法等以首字母大小写来区分是否可被包外代码访问。
大写代表,可以被包外引入。
小写代表,不可被包外引入。
比如我们新建一个文件 calc.go, main.go 平级,分别定义 add 和 main 方法。
calc.go
package main func add(num1 int, num2 int) int { return num1 + num2 }main.go
package main import "fmt" func main() { fmt.Println(add(3, 5)) // 8 }运行
go run main.go,会报错,add 未定义:./main.go:6:14: undefined: add因为
go run main.go仅编译 main.go 一个文件,所以命令需要换成$ go run main.go calc.go 8或
$ go run . 8Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。
2. init和main函数
2.1 init函数
go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。
有下面的特征:
- init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
- 每个包可以拥有多个init函数
- 包的每个源文件也可以拥有多个init函数
- 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
- 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
- init函数不能被其他函数调用,而是在main函数执行之前,自动被调用
2.2 main函数
Go语言程序的默认入口函数(主函数):func main()
函数体用{}一对括号包裹。
func main(){
//函数体
}
2.3 init函数和main函数的异同
相同点:
两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
不同点:
init可以应用于任意包中,且可以重复定义多个。
main函数只能用于main包中,且只能定义一个。
两个函数的执行顺序:
- 对同一个go文件的
init()调用顺序是从上到下的。 - 对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的
init()函数。 - 对于不同的
package,如果不相互依赖的话,按照main包中”先import的后调用”的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。 - 如果
init函数中使用了println()或者print()你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。
二、依赖管理
1. 为什么使用是依赖管理
最早的时候,Go所依赖的所有的第三方库都放在GOPATH这个目录下面。这就导致了同一个库只能保存一个版本的代码。如果不同的项目依赖同一个第三方的库的不同版本,应该怎么解决?
2. go module
go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。
2.1 GO111MODULE
要启用go module支持首先要设置环境变量GO111MODULE,通过它可以开启或关闭模块支持,它有三个可选值:off、on、auto,默认值是auto。
GO111MODULE=off禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。GO111MODULE=on启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据go.mod下载依赖。GO111MODULE=auto,当项目在$GOPATH/src外且项目根目录有go.mod文件时,开启模块支持。
简单来说,设置GO111MODULE=on之后就可以使用go module了,以后就没有必要在GOPATH中创建项目了,并且还能够很好的管理项目依赖的第三方包信息。
使用 go module 管理依赖后会在项目根目录下生成两个文件go.mod和go.sum。
2.2 go mod命令
常用的go mod命令如下:
go mod init 初始化当前文件夹, 创建go.mod文件
go mod tidy 增加缺少的module,删除无用的module
go mod download 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit 编辑go.mod文件
go mod graph 打印模块依赖图
go mod vendor 将依赖复制到vendor下
go mod verify 校验依赖
go mod why 解释为什么需要依赖
go init
go mod init example
完成上述操作之后,就会在当前目录下生成一个go.mod文件,这个文件中的结构如下:
//module 后面是当前项目的模块名称,一般与项目名称相同,但也可以不同
module example
//预设的go版本
go 1.13
//用于将一个模块版本替换成另一个模块版本,前提是当前模块为主模块时,例如:
replace k8s.io/api => k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
//也可以用于引入本地的其他模块到当前这个模块中,例如:
replace ecm-sdk-go => ./vendor/go-util
//当前项目中的依赖列表
require (
github.com/go-git/go-git/v5 v5.1.0
)
//当前项目中禁止依赖包列表
exclude (
github.com/go-git/go-git/v5 v5.1.0
)
go get
go get packageName:安装依赖go get -upackageName:将依赖升级到最新的次要版本或修订版本go get -u=patch packageName:将依赖升级到最新的修订版本go get package@version:升级到指定的版本
go get github.com/kataras/iris@master
如果下载所有依赖可以使用go mod download命令。
go.mod文件记录了项目所有的依赖信息,其结构大致如下:
module example
go 1.12
require (
github.com/go-sql-driver/mysql v1.4.1
github.com/golang/protobuf v1.3.2
github.com/grpc-ecosystem/grpc-gateway v1.11.3 // indirect
github.com/jmoiron/sqlx v1.2.0
github.com/micro/go-micro v1.12.0
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
go.uber.org/zap v1.10.0
google.golang.org/grpc v1.24.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)
其中,
module用来定义包名require用来定义依赖包及版本indirect表示间接引用
依赖的版本
go mod支持语义化版本号,比如go get foo@v1.2.3,也可以跟git的分支或tag,比如go get foo@master,当然也可以跟git提交哈希,比如go get foo@e3702bed2。关于依赖的版本支持以下几种格式:
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
gopkg.in/yaml.v2 <=v2.2.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
latest
replace
在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。
replace (
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)
整理依赖
我们在代码中删除依赖代码后,相关的依赖库并不会在go.mod文件中自动移除。这种情况下我们可以使用go mod tidy命令更新go.mod中的依赖关系。
go mod edit
格式化
因为我们可以手动修改go.mod文件,所以有些时候需要格式化该文件。Go提供了一下命令:
go mod edit -fmt
添加依赖项
go mod edit -require=golang.org/x/text
移除依赖项
如果只是想修改go.mod文件中的内容,那么可以运行go mod edit -droprequire=package path,比如要在go.mod中移除golang.org/x/text包,可以使用如下命令:
go mod edit -droprequire=golang.org/x/text
关于go mod edit的更多用法可以通过go help mod edit查看。
2.3 在项目中使用 go module
在一个空文件夹下,初始化一个 Module
$ go mod init example
go: creating new go.mod: module example
此时,在当前文件夹下生成了go.mod,这个文件记录当前模块的模块名以及所有依赖包的版本。
接着,我们在当前目录下新建文件 main.go,添加如下代码:
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Hello()) // Ahoy, world!
}
运行 go run .,将会自动触发第三方包 rsc.io/quote的下载,具体的版本信息也记录在了go.mod中:
module example
go 1.13
require rsc.io/quote v3.1.0+incompatible
go run命令会直接下载依赖并且运行项目文件,如果我们只想下载依赖可以使用go mod tidy命令。
go mod tidy
这时go编译器会去查找go mod和项目文件,增加缺少的module,删除无用的module。
我们在当前目录,添加一个子 package calc,代码目录如下:
demo/
|--calc/
|--calc.go
|--main.go
在 calc.go 中写入
package calc
func Add(num1 int, num2 int) int {
return num1 + num2
}
在 package main 中如何使用 package cal 中的 Add 函数呢?import 模块名/子目录名 即可,修改后的 main 函数如下:
package main
import (
"fmt"
"example/calc"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Hello())
fmt.Println(calc.Add(10, 3))
}
$ go run .
Ahoy, world!
13