开发环境
GOPATH和GO MODULES¶
GOPATH: 想象一个巨大的公共图书馆. 所有的书 (项目代码和依赖) 都必须按照一个非常严格的系统存放在这个图书馆里. 你要写新书 (新项目), 必须去图书馆里指定的房间 ($GOPATH/src) 写. 你需要参考别人的书 (依赖), 就从图书馆的书架上拿. 问题: 如果两个项目需要同一本书的不同版本 (比如
Go Modules: 想象每个项目都是一个独立的书房. 每个书房里都有一个私人的小书架. 当你的项目需要一本书时, 你会去中央仓库"复印"一本, 然后放到你自己的小书架上. 你的书房里有一张清单 (go.mod), 上面精确记录了你需要的是哪本书的哪个版本 (例如:
| 特性 | GOPATH (旧方式, 不再推荐) | Go Modules (现代方式, 主流) |
|---|---|---|
| 核心理念 | 集中式工作区. 所有代码共享一个巨大的空间. | 去中心化, 项目独立. 每个项目都是一个自包含的模块. |
| 项目位置 | **必须**存放在 $GOPATH/src 目录下. 非常死板. |
可以存放在**任何位置**. 自由灵活. |
| 版本管理 | 没有版本管理. go get 总是拉取依赖的最新代码, 无法锁定版本. |
精确的版本管理. go.mod 文件记录了每个依赖的精确版本号. |
| 依赖存放 | 全部存放在 $GOPATH/src. 多个项目共享同一份依赖代码. |
依赖下载后缓存在 $GOPATH/pkg/mod, 但每个项目根据自己的 go.mod 文件"引用"特定版本, 逻辑上是隔离的. |
| 如何定义项目 | 依靠在$GOPATH/src下的目录路径. |
依靠项目根目录下的 go.mod 文件. |
| 构建可靠性 | 低. 任何依赖的更新都可能导致你的项目构建失败. | 高 (可复现构建). 只要 go.mod 和 go.sum 文件不变, 任何人在任何时间构建, 得到的结果都是完全一样的. |
| 典型命令 | go get <package> |
go mod init, go mod tidy, go get <package@version> |
GOPATH和GO MODULES的工作方式
- 你设置一个环境变量
GOPATH(例如~/go). - 你的所有项目代码, 比如
my-project, 必须放在~/go/src/github.com/my-name/my-project. - 当你
go get一个依赖, 例如github.com/gin-gonic/gin, 它的代码会被下载到~/go/src/github.com/gin-gonic/gin. - 你的项目和别人的项目都引用这同一个
gin库.
主要缺点: 版本冲突和环境污染.
- 你在任何地方创建一个项目目录, 例如
~/Desktop/my-new-project. - 进入该目录, 运行
go mod init github.com/my-name/my-new-project. 这会创建一个go.mod文件, 标志着这里是一个独立的Go模块. - 在你的代码中
import "github.com/gin-gonic/gin". - 运行
go mod tidy或go build. Go会自动:- 找到
gin库的最新稳定版本. - 将版本号 (例如
v1.9.1) 记录到你的go.mod文件里. - 下载
gin@v1.9.1的代码并缓存起来. - 生成
go.sum文件来校验代码完整性.
- 找到
主要优点: 项目独立, 版本锁定, 构建可复现.
版本管理¶
- 主角:
go.mod(用于版本选择) 和go.sum(用于安全验证) - 流程: 当你运行
go build,go test或go mod tidy时:- Go命令首先读取
go.mod来确定需要哪些依赖以及它们的 最低版本. - Go使用一个叫做 "最小版本选择" (Minimal Version Selection) 的算法来决定最终要下载哪个版本.
- 下载完模块后, Go计算其哈希值.
- Go在
go.sum文件中查找该模块和版本的哈希记录, 并进行 比对. 如果匹配, 继续. 如果不匹配, 报错.
- Go命令首先读取
- 结论:
go.sum是一个安全 校验列表 (Checksum List). Go 的首要目标是根据go.mod的规则来选择版本, 然后用go.sum来验证下载内容的真伪.
main和init函数¶
init 函数¶
init 函数是 Go 语言中一个非常特殊的函数, 主要用于**程序执行前对包(package)进行初始化**.
主要特点:
- 自动执行:
init函数会在main函数执行之前, 由 Go 运行时自动调用. 你不能手动调用它. - 多重定义:
- 同一个包(package)可以有多个
init函数. - 同一个源文件中也可以有多个
init函数.
- 同一个包(package)可以有多个
- 无参数无返回: 和
main函数一样,init函数不能有任何参数和返回值. - 执行顺序:
- 在同一个文件中,
init函数按其定义的顺序从上到下执行. - 在同一个包的不同文件中, Go 会按照文件名的字典顺序来执行
init函数. - 如果包之间有依赖关系, 会先执行被依赖的包的
init函数. 例如, 如果main包导入了pkgA, 那么会先执行pkgA的init函数, 再执行main包的init函数.
- 在同一个文件中,
main 函数¶
main 函数是 Go 语言程序的**入口点**, 是程序的开始.
主要特点:
- 程序入口: 每个可执行的 Go 程序都必须有一个
main包, 并且该包中必须包含一个main函数. - 唯一性: 在一个程序中,
main函数只能有一个. - 无参数无返回:
func main()的定义是固定的, 不能有参数和返回值. - 最后执行:
main函数在所有相关的init函数都执行完毕后才会开始执行.
init 和 main 的异同点¶
- 相同点:
- 都由系统自动调用.
- 都不能有参数和返回值.
- 不同点:
init函数可以定义在任何包中, 且可以有多个.main函数只能定义在main包中, 并且只能有一个.init函数在main函数之前执行.
总的来说, init 函数是为程序运行准备环境的, 而 main 函数则是程序的主体和入口.
Go语言命令¶
go build: 编译指定的源码文件或代码包及其依赖项.go clean: 删除执行其他命令时产生的临时文件和目录.go doc: 显示代码包或符号的文档.go env: 打印Go语言相关的环境信息.go fix: 修正指定代码包中所有Go源码文件的旧版本代码到新版本.go fmt: 格式化指定代码包的源码文件(使用gofmt工具).go generate: 通过处理源码生成Go文件.go get: 下载并安装指定的代码包及其依赖项.go install: 编译并安装指定的代码包及其依赖项.go list: 列出指定的代码包信息.go run: 编译并直接运行指定的命令源码文件.go test: 对Go程序进行测试.go tool: 运行指定的go工具, 例如go tool pprof用于性能分析.go version: 打印已安装的Go版本.go vet: 一个简单的工具, 用于检查Go源码中的静态错误.