TL;DR:本文介绍了Go程序是如何在计算机上运行的
在此之前我们装好了环境,跑通了 Hello。但你有没有想过,你写的那几行魔法,计算机怎么跑起来的
在真正开始写代码之前,先花一篇的时间搞清楚这件事,不需要太深入底层,但理解这个过程,会让你少点疑问
计算机只认识 0 和 1
计算机本质上是一堆电路,电路只有两种状态:通电和断电。人类用 1 表示通电,0 表示断电,这就是二进制
所有的程序,最终都要变成一串 0 和 1,CPU 才能执行。但没有人会直接写 0 和 1,太痛苦了。所以人类发明了编程语言,用接近自然语言的方式写代码,再让工具把它翻译成机器能懂的东西
这个翻译的过程,有两种主流方式
编译型 vs 解释型
编译型语言
先把整个源代码翻译成机器码,生成一个可执行文件,然后再运行。翻译这个动作叫编译,做这件事的工具叫编译器
Go 是编译型语言。你写好代码,go build 一下,生成一个二进制文件,直接运行,不需要任何额外环境
源代码 → 编译器 → 可执行文件 → 运行优点是运行快,因为机器码直接在 CPU 上跑。缺点是每次改完代码都要重新编译
解释型语言
不提前翻译,运行的时候边读代码边执行。做这件事的工具叫解释器
Python 是解释型语言。你写好代码,python hello.py,解释器一行一行读,一行一行执行
源代码 → 解释器 → 运行(边翻译边执行)优点是改完代码立刻能跑,开发方便。缺点是运行速度比编译型慢,因为翻译是实时发生的
Go 是编译型,但提供了 go run 让你感觉像解释型:它在后台帮你编译,编译完立刻运行,临时文件放在系统缓存目录里,你不用关心中间过程
学习阶段用 go run,部署的时候用 go build,这个习惯记住就行
Go 程序的基本结构
回头看上一篇那个:
package main
import "fmt"
func main() { fmt.Println("Hello, Go!")}package
每个文件都属于一个包
package mainGo 的代码是用包(package) 来组织的。每个 .go 文件的第一行,必须声明自己属于哪个包
main 是一个特殊的包名,意思是”这是程序的入口”。Go 运行的时候,会找 package main 里的 main 函数,从那里开始执行
如果你把 package main 改成 package hello,这个文件就变成了一个库,不能直接运行。包的组织方式后面会专门讲,现在只需要记住:能直接运行的程序,包名一定是 main
import
引入别人写好的代码
import "fmt"import 的意思是”引入这个包,我要用”。fmt 是 Go 标准库里的一个包,提供格式化输入输出的功能
引入多个包时用括号括起来:
import ( "fmt" "time")Go 有一个强规定:引入了包必须用,不然报错
import ( "fmt" "time")
func main() { fmt.Println("hello") // time 没用}// 报错:time imported and not used这个设计和变量必须被使用一样,目的是保持代码干净,不留垃圾
如果有时候你确实需要引入一个包但不直接调用它(比如只需要它的初始化副作用),这时候用 _ 匿名引入:
import _ "some/package"还可以给包起别名,在包名太长或者有冲突的时候用:
import ( f "fmt")
func main() { f.Println("hello") // 用别名 f 代替 fmt}func main()
程序从这里开始
func main() { // 代码写在这里}func 是定义函数的关键字,main 是函数名,() 是参数列表,现在是空的,{} 里面是函数体
Go 程序启动时,自动从 func main() 的第一行开始,从上到下依次执行,执行完程序就结束了
遇到函数调用时,会跳进那个函数执行完,再回到原来的位置继续往下走:
func main() { fmt.Println("第一行") // 1. 先执行这里 fmt.Println("第二行") // 2. 再执行这里 fmt.Println("第三行") // 3. 最后这里}输出永远是按顺序的:
第一行第二行第三行这个顺序执行的概念听起来理所当然,但你要真正记住它,因为后面学并发的时候,这个顺序会被打破
fmt.Println
fmt.Println("Hello, Go!")fmt 是包名,Println 是这个包里的一个函数,. 是”属于”的意思
注意 Println 的首字母是大写的。在 Go 里,大小写决定了一个东西能不能被外部访问
- 大写字母开头:公开的,其他包可以用
- 小写字母开头:私有的,只有同一个包内部能用
fmt.Println 之所以能被我们调用,就是因为它首字母大写,是公开的。如果 Go 官方把它写成 fmt.println,你就调用不了
这个规则贯穿整个 Go,函数、变量、结构体,都遵守这个规定,后面会反复遇到
为什么花括号不能换行
你可能注意到,Go 的开括号 { 必须和函数声明在同一行:
// 正确func main() { fmt.Println("hello")}
// 报错func main(){ fmt.Println("hello")}Go 在编译之前,会自动在某些行末尾插入分号。规则很简单:如果一行最后一个词是标识符、数字、字符串,或者 )、]、} 这类符号,就自动加分号
所以 func main() 这一行,因为最后是 ),编译器会自动加分号变成 func main();,然后下一行的 { 就成了孤立的符号,语法错误
把 { 放在同一行就不会触发这个规则
注释
注释是写给人看的,编译器会完全忽略它
单行注释:
// 这是注释fmt.Println("hello") // 行尾注释多行注释:
/*这是多行注释*/实际项目里单行注释用得更多。多行注释一般用来临时屏蔽一段代码,调试的时候很方便
多个文件组成一个程序
现在你写的程序只有一个文件,但真实项目不可能把所有代码塞进一个文件里
Go 允许同一个包的代码分散在多个文件里:
golearn/ main.go helper.gomain.go:
package main
import "fmt"
func main() { fmt.Println(greet("Chongxi"))}helper.go:
package main
func greet(name string) string { return "你好," + name}两个文件都声明了 package main,Go 会把它们当成同一个包处理。运行的时候:
go run main.go helper.go# 或者go run . # 运行当前目录所有 .go 文件函数和变量的概念后面会讲,这里只是让你知道多文件是怎么回事,不用深究
工具链
Go 自带一套工具,开发过程中会经常用到
go run
编译并立刻运行,适合开发阶段:
go run hello.gogo build
编译生成可执行文件,适合生产部署:
go build hello.go./hellogo fmt
自动格式化代码,解决所有缩进和换行问题:
go fmt hello.go # 格式化单个文件go fmt ./... # 格式化当前目录所有文件养成习惯,写完代码跑一下 go fmt,不要手动纠结格式
go vet
静态检查,帮你找代码里的潜在问题,比如格式化字符串用错了类型:
go vet hello.gogo vet 不报错不代表代码一定对,但它报错了就一定有问题,要认真看
go doc
查看任何包或函数的文档,不用开浏览器:
go doc fmtgo doc fmt.Println标准库文档
Go 的标准库文档在 pkg.go.dev/std,所有官方包都在这里,每个函数都有说明和示例
比如你想知道 fmt 里还有哪些函数,打开 pkg.go.dev/fmt 就能看到
遇到不认识的函数或者不确定怎么用,先查这里。标准库文档是 Go 最权威的参考资料
小结
本篇偏概念,现在来过一遍:
- Go 属于编译型语言,先翻译再运行
- 每个文件必须声明
package,能直接运行的程序包名是main import引入包,引入了必须用或者_匿名,可以起别名- 程序从
func main()开始,从上到下顺序执行 - 大写字母开头是公开的,小写是私有的
- 花括号不能换行, Go 有自动分号插入机制
go fmt格式化,go vet静态检查,go doc查文档
下一篇会讲真正有意思的部分:边亮变量
Auth_Verified: 2026.05.01