Go Modules、Hello World、程序编译与运行
Go Modules——真正开始创建 Go 项目
1. 为什么需要 Go Modules?
先从熟悉的 Python 思考。
假设一个 Python 项目需要安装第三方库:
pip install requests
可能会通过 requirements.txt 或 pyproject.toml 来管理依赖。
而在 Go 中,官方的依赖管理体系叫 Go Modules。
Go Modules 用于组织相关的 Go Package,并记录模块所依赖的其他模块及版本。
它解决三个主要问题:
- 代码组织: 一个项目可以包含多个 Package。
- 依赖管理: 记录项目需要哪些第三方模块。
- 版本管理: 让团队成员尽量使用一致的依赖版本。
这里有三个非常重要的概念。
| 概念 | 作用 | 例子 |
|---|---|---|
| Module | 一组相关 Package 的模块单元 | example.com/hello |
| Package | 将相关 Go 代码组织在一起 | main、fmt |
go.mod |
声明 Module 及依赖要求 | 项目根目录的配置文件 |
Module 和 Package 有什么关系?
请看下面的结构。

记住:
Module 是模块组织与版本管理单位,Package 是代码组织与导入单位。
这两个概念并不相同。
2. 在Mac 上创建第一个 Go Module
现在请打开 VS Code,选择菜单中的:
Terminal → New Terminal
首先确认 Go 已经安装成功:
go version
Step 1:创建学习目录
在终端中执行:
mkdir -p ~/go-learning/module01/day01/hello
解释:
mkdir:创建文件夹。-p:自动创建尚不存在的父目录。~:当前用户的 Home 目录。
接着进入:
cd ~/go-learning/module01/day01/hello
查看当前位置:
pwd
正常情况下,输出类似:
/Users/你的用户名/go-learning/module01/day01/hello
Step 2:初始化 Go Module
执行:
go mod init example.com/hello
注意这里的 example.com/hello 只是本地学习时使用的示例模块路径,并不代表你已经创建了网站或远程仓库。
终端会显示类似:
go: creating new go.mod: module example.com/hello
这意味着 Go 创建了一个 go.mod 文件。
执行:
ls
你应该看到:
go.mod
Step 3:理解 go.mod
打开 go.mod,内容通常类似:
module example.com/hello
go 1.xx.x
其中 1.xx.x 是示意,实际版本由你的 Go 工具链决定。
逐行解释。
第一行:
module example.com/hello
这不是 Package 名称,而是当前 Module 的路径。
如果未来你将项目上传到 GitHub,Module 路径可以采用:
github.com/你的用户名/inventory-cli
这会成为当前模块内各个包的导入路径前缀。
第二行:
go 1.xx.x
它声明当前 Module 所要求的最低 Go 版本,同时影响语言特性及模块处理行为。
它不等于电脑实际安装的 Go 编译器版本。
例如,可能出现:
go.mod 要求的版本:1.23.0
本机安装的版本:1.25.0
这并不矛盾。
Step 4:go.sum 又是什么?
以后项目引入第三方模块时,你可能会看到:
go.sum
它记录依赖模块内容及其 go.mod 文件的校验哈希,用于验证依赖的一致性。
你可以简单记成:
go.mod:声明模块需求。go.sum:记录依赖的校验信息。
但不要把 go.sum 理解成一份完整的依赖锁定文件。
现在你的项目里没有 go.sum 是正常的。 因为我们还没有引入需要校验的外部模块依赖。
以上行为与 Go 官方的模块管理文档一致。
第三部分:编写第一个 Go 程序
现在才真正开始编写 Go 代码。
1. 创建 main.go
在 VS Code 中打开刚才创建的 hello 文件夹。
新建文件:
main.go
输入:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
fmt.Println("我是一个 Go 初学者")
fmt.Println("我的目标是成为 Go 后端开发者")
}
先不要复制完就直接运行。
我们要逐行分析。
2. 逐行理解代码
第一行:package main
package main
声明这个源文件所属的 Package。
现在它是一个可执行程序的入口包。
第二行:import "fmt"
import "fmt"
这行的意思是导入 Go 标准库中的 fmt 包。
fmt 是 Format 的缩写,主要提供格式化输入和输出能力。
你可以把它和 Python 中导入模块的方式对比:
Python:
import math
Go:
import "fmt"
但两门语言的模块和包系统并不完全相同。
这里有一个小细节:
为什么我们没有执行:
go get fmt
因为 fmt 属于 Go 标准库,随 Go 工具链一起提供,不需要作为第三方依赖安装。
第三行:func main()
func main() {
Go 使用 func 关键字定义函数。
Python:
def main(): print("Hello")
Go:
func main() {
fmt.Println("Hello")
}
最直观的区别是:
- Python 使用缩进表示代码块。
- Go 使用
{}表示代码块。 - Go 使用
func定义函数。
注意,Go 代码块内部也需要规范缩进,但其语法结构由大括号决定,而不是由缩进决定。
第四行:fmt.Println()
fmt.Println("Hello, Go!")
可以拆成三部分:
fmt:包名。Println:包中导出的函数。"Hello, Go!":传给函数的字符串参数。
Go 中,标识符首字母大写通常表示可以被其他包访问。
因此是:
fmt.Println()
而不是:
fmt.println()
这个大小写规则非常重要,后面学习 Struct、Method 和 Interface 时会反复遇到。
3. 运行你的第一个 Go 程序
在终端中确认位于项目根目录。
执行:
go run .
预期输出:
Hello, Go!
我是一个 Go 初学者
我的目标是成为 Go 后端开发者
然后再尝试:
go run main.go
你应该得到同样的输出。
为什么推荐 go run .?
这是一个重要的工程习惯。
go run main.go
表示使用指定的源文件运行程序。
而:
go run .
表示使用当前目录对应的整个 Go Package。
例如你的目录未来变成:
hello/
├── go.mod
├── main.go
└── helper.go
两个 Go 文件都属于 package main,而且 main.go 使用了 helper.go 中定义的函数。
此时:
go run main.go
可能会因为没有包含 helper.go 而编译失败。
但:
go run .
会按照当前 Package 构建,包括其中应当参与构建的 Go 源文件。
因此,从今天开始,我们优先养成使用 go run . 的习惯。
第四部分:真正编译一个可执行文件
接下来做一个实验,帮助你把编译和运行的关系理解清楚。
在项目根目录执行:
go build -o hello .
执行成功后,再输入:
ls -lh
你会看到类似:
go.mod
hello
main.go
这里的 hello 就是编译得到的可执行程序。
然后运行:
./hello
你应该看到之前的三行输出。
再执行一个 macOS 命令:
file hello
如果你的 Mac 是 Apple Silicon,输出通常包含:
Mach-O 64-bit executable arm64
如果你的 Mac 是 Intel 芯片,则通常包含:
Mach-O 64-bit executable x86_64
这证明你的 Go 源文件已经被构建成 macOS 可以加载的可执行文件。
加深理解:go run 和 go build 的关系

这个实验的目标,是让你真正理解 Go 的编译型语言特性。
第五部分:教授实验室——主动制造编译错误
现在我们做一个计算机课程里很有价值的练习。
不是只学习如何让代码正确运行,而是主动观察编译器如何识别错误。
你以后做后端开发,读懂编译错误的能力非常重要。
实验 A:删除 import
把原程序暂时修改成:
package main
func main() {
fmt.Println("Hello Go")
}
然后运行:
go run .
预期会出现类似错误:
undefined: fmt
为什么?
因为当前文件没有导入 fmt 包,编译器无法解析 fmt 这个标识符。
修复方法:重新添加 import "fmt"。
实验 B:导入但不使用
修改成:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("Hello Go")
}
然后执行:
go run .
预期出现类似:
"os" imported and not used
为什么?
Go 对未使用的导入有严格限制。
这是 Go 鼓励保持代码简洁的一种设计选择。
你不能随意导入一堆无用的包,而不进行处理。
修复方法:删除未使用的 os 导入。
实验 C:观察自动格式化
将代码修改成不规范的格式:
package main
import "fmt"
func main(){
fmt.Println("Hello Go")
}
然后执行:
go fmt ./...
Go 会根据 Go 的标准格式整理代码。
重新打开文件,观察发生了什么变化。
这就是 Go 工程生态的一项优势:统一格式规范可以减少团队协作中的无效争论。