← 学习

学习学习专题:go

Go语言基础与编程思维-Go 语言入门与程序执行02

Go Modules、Hello World、程序编译与运行 Go Modules——真正开始创建 Go 项目 1. 为什么需要 Go Modules? 先从熟悉的 Python 思考。 假设一个 Python 项目需要安装第三方库: 可能会通过 requirements.txt…

目录 · 19 节

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,并记录模块所依赖的其他模块及版本。

它解决三个主要问题:

  1. 代码组织: 一个项目可以包含多个 Package。
  2. 依赖管理: 记录项目需要哪些第三方模块。
  3. 版本管理: 让团队成员尽量使用一致的依赖版本。

这里有三个非常重要的概念。

概念 作用 例子
Module 一组相关 Package 的模块单元 example.com/hello
Package 将相关 Go 代码组织在一起 main、fmt
go.mod 声明 Module 及依赖要求 项目根目录的配置文件

Module 和 Package 有什么关系?

请看下面的结构。

image

记住:

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 的关系

image

这个实验的目标,是让你真正理解 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 工程生态的一项优势:统一格式规范可以减少团队协作中的无效争论。