90DaysOfDevOps 第 9 天:逐行拆解 Go 的 Hello World——编译、包与 main 函数入门
90DaysOfDevOps 第 9 天:逐行拆解 Go 的 Hello World——编译、包与 main 函数入门
本篇指南聚焦 90DaysOfDevOps 学习路线中 Go 语言的第一份真实代码:Hello #90DaysOfDevOps。在第 8 天完成 Go 安装与首个程序后,本文带你理解"编译"到底做了什么、Go 的包(package)机制如何组织代码,并逐行剖析 main.go 中每一行的职责与背后规则。读完你不仅能完全看懂这段 Hello World,还能掌握 go run、go build 的区别,以及为后续变量、数据类型等进阶内容打好地基。
从第 8 天说起:先有一个能跑的 Hello World
在 Día 8 中,我们完成了 Go 的安装、创建了工作目录,并在 src/Hello 下写出了第一份 main.go,最终用 go run main.go 与 go build main.go 让程序跑了起来。仓库中这份程序的本体位于 hello.go,全部代码只有 6 行:
package main
import "fmt"
func main() {
fmt.Println("Until tomorrow #90DaysOfDevOps")
}
今天的工作,就是把这几行代码从"能运行"提升到"能看懂":它们分别解决了程序归属(package)、外部依赖(import)和执行入口(func main)三个核心问题。
什么是编译:从人类可读到机器可执行
Python、Java、Go、C++ 这类我们日常使用的语言都属于高级语言(high-level language),即对人友好的、可读的文本。但机器执行程序时,需要的是它能直接理解的形态——机器码(machine code)。把我们的人类可读代码翻译成机器码的这一动作,就叫做编译(compiling)。
编译流程可以概括为下图所示的过程:
对应到我们第 8 天的操作:我们创建了简单的 Hello World main.go,然后执行 go build main.go 编译出可执行文件。在 Windows 上编译后运行 ./main.exe,得到输出 Hello #90DaysOfDevOps。
如果对第 8 天的步骤感到陌生,可以回到 Día 8 重温安装与首个程序的创建过程。
编译、运行与交叉编译的三种姿势
在 Día 10 中,Go 工作区与编译运行方式被进一步展开,这里提前梳理三个核心命令:
go run:编译并运行命令行指定的.go文件所组成的 main 包,编译产物落在临时目录,运行完即弃,适合开发调试;go build:编译当前目录下的包与依赖。若项目包含main包,会在当前目录生成可执行文件;否则产物放入pkg目录供其他 Go 程序导入使用。go build还能为任何 Go 支持的平台生成可执行文件(交叉编译);go install:行为与go build相同,但可执行文件会安装到bin目录。
仓库中的 makefile 正是交叉编译的实战样例——同一个 main.go 通过设置 GOARCH 与 GOOS 环境变量,一条命令产出五种平台的二进制:
build:
GOARCH=amd64 GOOS=darwin go build -o ${BINARY_NAME}_0.2_darwin main.go
GOARCH=amd64 GOOS=linux go build -o ${BINARY_NAME}_0.2_linux main.go
GOARCH=amd64 GOOS=windows go build -o ${BINARY_NAME}_0.2_windows main.go
GOARCH=arm64 GOOS=linux go build -o ${BINARY_NAME}_0.2_linux_arm64 main.go
GOARCH=arm64 GOOS=darwin go build -o ${BINARY_NAME}_0.2_darwin_arm64 main.go
这说明"编译"不只是把代码变成可执行文件,更是 Go 语言跨平台分发能力的基石——这也是 DevOps 场景下用 Go 构建 CLI 工具的一大优势。
什么是包:组织与复用代码的基本单元
包(package)是同一目录下、一起编译的源文件集合。更直白地说,一个包就是同一目录下的一组 .go 文件。还记得第 8 天的 Hello 文件夹吗?当你编写更复杂的 Go 程序时,可能会有 folder1、folder2、folder3…… 各自存放不同的 .go 文件,分别构成多个包,共同组装成完整程序。
我们使用包的最大动机是复用他人的代码:不必从零编写一切。假如你的程序需要一个计算器,很可能已经存在包含数学函数的现成 Go 包,把它导入自己的代码即可,省时省力——这正是代码共享与开源精神的体现。
Go 语言也鼓励你用包组织代码,让源码易于复用与维护。所有 .go 文件都必须属于某个包,且第一行必须书写 package <包名>。
Go 工作区的三大目录
结合 Día 10 的说明,Go 工作区(GOPATH)下三个目录各司其职:
src:存放所有 Go 程序与项目源码,负责 Go 仓库的命名空间与包管理,我们的Hello项目就在其中;pkg:存放程序已安装(或曾安装)的包的归档文件,有助于加速编译过程;bin:存放所有编译后的可执行二进制文件。
理解这个布局,才能明白 go build(产物在当前目录或 pkg)与 go install(产物进 bin)的差异从何而来。
Hello #90DaysOfDevOps 逐行拆解
现在回到我们的 main.go,逐行过一遍。完整代码文件见 hello.go,其整体结构如下:
第 1 行:package main——程序的归属与起点
第一行的 package main 表示此文件属于名为 main 的包。所有 .go 文件都必须归属某个包,并在首行写上 package 某名字。
包名可以任意起,但这里的包必须叫 main,因为该包包含程序的起始点——这是一条规则:main 包是 Go 程序的入口包,只有它才能被编译成可执行文件,而其他包则作为库被导入。
第 5 行:func main()——真正的执行入口
每当我们要编译并运行代码,必须告诉机器执行从哪里开始。我们通过编写名为 main 的函数做到这一点:机器会寻找名为 main 的函数作为程序入口。
函数(function)是一段能完成特定任务、可在整个程序中复用的代码块。 你可以用 func 关键字给函数起任何名字,但这里必须叫 main,因为它是代码开始执行的地方。第 5 行的 func main() { 就是入口函数本身(main 函数必须没有参数、没有返回值,且只能存在于 main 包中)。
第 3 行:import "fmt"——引入标准库
接着看第 3 行的导入语句:它表示你想把另一个包带入你的主程序。fmt 是 Go 提供的标准包(standard package),内含 Println() 函数;正因导入了它,我们才能在第 6 行使用 Println()。
Go 标准库提供了大量可直接引入、复用、免去从零编写的包,例如字符串处理、数学运算、文件操作、网络编程等。Go 官方标准库目录位于 Go Standard Library(https://pkg.go.dev/std)。注意:导入的包必须在代码中被使用,否则编译会报错——这与变量未使用报错同理,都是为了杜绝死代码。
第 6 行:fmt.Println("Until tomorrow #90DaysOfDevOps")——输出到标准输出
Println() 是一种把标准输出写入终端的方式——可执行文件在哪个终端被成功运行,输出就会显示在哪个终端。括号内的消息可以随意修改,比如改回教程中经典的 "Hello #90DaysOfDevOps"。第 6 行在终端打印这段文字,程序执行完毕。
TLDR:六行代码的速查表
- 第 1 行(package main):此文件属于名为
main的包;必须叫main,因为它包含程序的入口点。 - 第 3 行(import "fmt"):为了使用
Println(),必须导入fmt包,以便在第 6 行调用。 - 第 5 行(func main()):真正的起点——
main函数。 - 第 6 行(fmt.Println(...)):在系统终端打印 "Hello #90DaysOfDevOps" 等消息。
继续深入:变量、常量与数据类型
理解 Hello World 只是 Go 之旅的起点。在 Día 11 中,这段代码会被扩展成真正的"挑战天数统计"程序——引入 var challenge = "#90DaysOfDevOps" 变量、const daystotal = 90 常量与 dayscomplete 数据,并开始讲解 bool、数值、string 三类基本数据类型与 %T 格式化输出。仓库中的 day11_example1.go 等示例文件展示了从 Hello World 向实际应用的演化过程。
至此,你已经能够完整解读 Go 的第一个程序:理解编译的本质、包的组织方式、main 包的入口规则、标准库导入与输出函数的使用。下一步(Día 10)我们将深入 Go 工作区与编译运行工具链,请继续在 Día 10 相见。

