初识 Go 与开发环境搭建
1. 初识 Go 与开发环境搭建
Go 是 Google 在 2007 年立项、2009 年 11 月开源、2012 年 3 月发布 1.0 版本的编程语言。它由 Robert Griesemer、Rob Pike、Ken Thompson 三人设计,目标是解决大规模工程中"编译慢、语言特性多、并发难写"这三类问题。
这篇笔记先建立对 Go 的整体认识,然后把开发环境搭好,最后跑通第一个程序,并说清楚源文件的一些基本约定。
1.1 Go 的设计目标
Go 的核心设计目标可以归纳为下面几条,后面的所有语言特性几乎都是围绕它们展开的:
- 编译型语言:源码编译为机器码后直接执行,没有虚拟机或解释器参与运行。
- 静态类型:所有变量的类型在编译期确定,类型不匹配在编译期就报错。
- 原生并发:
goroutine(由运行时管理的轻量执行单元)和channel(用于在 goroutine 之间传递数据的管道)是语言级特性,不是库。 - 语法简洁:关键字只有 25 个,没有类继承、没有异常、没有隐式类型转换。
- 编译速度快:大型项目也能在几秒内完成构建,这是明确的设计指标。
- 内置工具链:格式化、静态检查、测试、文档、依赖管理都集成在
go命令里,不需要额外选型。 - 部署简单:编译产物是单个可执行文件,不依赖外部运行库(使用 cgo 时除外)。
Go 在云原生基础设施中的使用非常广泛,Docker、Kubernetes、etcd、Prometheus、Terraform、TiDB 等项目都是用它写的。这也是目前 Go 岗位最集中的领域。
1.2 版本演进与特性分界
Go 承诺 1.x 系列的向后兼容:升级小版本不会破坏已有代码。但这不代表版本之间没有变化,下面这些是影响较大的分界点:
| 版本 | 发布时间 | 关键变化 |
|---|---|---|
| 1.0 | 2012-03 | 首个正式版本,语言特性稳定 |
| 1.11 | 2018-08 | 引入 Go Modules(模块化依赖管理,实验性) |
| 1.16 | 2021-02 | 模块模式成为默认,不再需要 GO111MODULE 开关 |
| 1.18 | 2022-03 | 泛型(类型参数)、模糊测试、多模块工作区 go work、any 别名 |
| 1.19 | 2022-08 | GOMEMLIMIT 软内存上限、atomic.Int64 等类型 |
| 1.20 | 2023-02 | errors.Join、unsafe.String/unsafe.SliceData |
| 1.21 | 2023-08 | 内置 min/max/clear,标准库 slices/maps/cmp,log/slog,PGO |
| 1.22 | 2024-02 | for 循环变量每轮迭代独立、ServeMux 路由增强、range 支持整数 |
| 1.23 | 2024-08 | 迭代器(range 支持函数)、time.Timer 语义调整 |
| 1.24 | 2025-02 | 泛型类型别名、os.Root、weak 包、工具依赖管理 |
| 1.25 | 2025-08 | testing/synctest 稳定、sync.WaitGroup.Go、容器感知的 GOMAXPROCS |
本文写作时最新的稳定版本是 Go 1.27,本系列以 Go 1.22 及以上为基准编写。凡是用到高版本才有的特性,都会在正文中标注最低版本,例如"泛型需要 1.18+"、“atomic.Int64 需要 1.19+”、“循环变量语义变化在 1.22+”。
注:Go 的版本号形如
1.22.3,其中1.22是语言版本,.3是补丁版本。补丁版本只修 bug 与安全问题,不引入新特性。
1.3 安装 Go
各平台的安装方式:
| 平台 | 方式 |
|---|---|
| Windows | 从官网 https://go.dev/dl/ 下载 .msi 安装包,双击安装 |
| macOS | 下载 .pkg 安装包,或 brew install go |
| Linux | 下载 .tar.gz 解压到 /usr/local,或使用发行版包管理器安装 golang 包 |
| 容器 | 直接使用 golang 官方镜像作为构建环境 |
安装完成后打开终端验证:
go version
输出形如:
go version go1.27.1 windows/amd64
后半部分 windows/amd64 是目标平台与架构,也就是 GOOS/GOARCH 的默认取值。
1.4 环境变量
Go 的行为由一组环境变量控制,最常用的三个如下:
| 变量名 | 含义 | 默认值 |
|---|---|---|
GOROOT |
Go 安装目录(标准库源码所在位置) | 安装时自动设置,如 D:\Go |
GOPATH |
工作区目录,现在主要用于存放下载的模块缓存与 go install 的默认目标 |
用户主目录下的 go 文件夹,如 C:\Users\you\go |
GOBIN |
go install 安装可执行文件的位置 |
$GOPATH/bin(默认) |
用 go env 查看当前取值:
go env GOROOT GOPATH GOBIN
输出示例(路径随机器不同):
D:\Go
C:\Users\you\go
C:\Users\you\go\bin
GOBIN 默认不在 PATH 里,如果希望 go install 装出来的命令行工具能直接调用,需要把它加进系统的 PATH。修改环境变量推荐用 go env -w,它写入的是 Go 自己的配置文件,不会污染系统环境:
go env -w GOPROXY=https://goproxy.cn,direct
GOPATH 时代与模块时代
早期 Go 要求所有代码必须放在 $GOPATH/src 目录下,导入路径就是相对 src 的路径:
$GOPATH/
src/
github.com/user/project/
main.go
这种方式无法表达依赖的版本,同一个依赖只能存在一份,两个项目需要不同版本时就会冲突。后来出现了 vendor 目录(把依赖复制到项目内的方案),但仍然不够灵活。
Go 1.11 引入了 Go Modules(模块化依赖管理),1.16 起成为默认模式。模块模式下:
- 项目可以放在任意目录,不再受
GOPATH/src约束; - 依赖版本记录在
go.mod中,go.sum记录校验和; - 下载的依赖存放在
$GOPATH/pkg/mod里,可以同时存在多个版本。
也就是说,现在 GOPATH 的主要用途只剩"存放模块缓存"和"提供 GOBIN 默认值",代码放在哪里已经不重要了。
注:
GOPATH目录下通常能看到pkg/mod(依赖缓存)和bin(go install的默认输出目录)。清理依赖缓存可以用go clean -modcache。
1.5 第一个程序
新建一个目录,例如 hello,在其中初始化模块:
mkdir hello
cd hello
go mod init example.com/hello
go mod init 会生成 go.mod 文件,内容形如:
module example.com/hello
go 1.27.1
module example.com/hello:模块路径,也是本模块内所有包导入路径的前缀。用域名风格命名是为了避免不同项目的路径冲突,本地练习用什么名字都可以。go 1.27.1:该模块使用的语言版本,决定了编译器启用哪些语法特性。
接着创建 main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
逐行说明:
package main:声明当前文件属于main包。**包(package)**是 Go 中代码组织的基本单位,一个目录就是一个包。名为main的包有特殊地位:它可以被编译成可执行文件,且必须包含main函数。import "fmt":导入标准库的fmt包,它提供格式化输入输出函数。导入后通过fmt.Println这种"包名.标识符"的形式使用。func main() { ... }:程序入口。main函数没有参数、没有返回值,程序从它的第一行开始执行。fmt.Println("Hello, Go!"):向标准输出打印一行文本并换行。
运行程序:
go run .
输出:
Hello, Go!
go run 会先编译再执行,编译产物放在临时目录里,不会留在当前目录。
run、build、install 的区别
| 命令 | 作用 | 产物位置 |
|---|---|---|
go run |
编译后立即执行,适合开发阶段快速验证 | 临时目录,执行完即丢弃 |
go build |
只编译不执行,用于产出可交付的程序 | 当前目录(可用 -o 指定路径) |
go install |
编译并把可执行文件放到 GOBIN |
$GOBIN(默认 $GOPATH/bin) |
go build -o hello.exe .
go install .
go build 后面跟 . 表示编译当前目录对应的包。不加 -o 时,可执行文件的文件名取自包所在目录名(这里就是 hello.exe)。go install 适合安装自己写的命令行工具,装好后可以直接在终端调用。
1.6 常用命令速览
| 命令 | 作用 |
|---|---|
go version |
查看版本 |
go env |
查看环境变量取值 |
go mod |
模块管理:init、tidy、edit 等 |
go get |
添加或升级依赖(详见第 11 篇) |
go fmt |
按官方风格格式化代码 |
go vet |
静态检查可疑写法(不是编译器错误) |
go doc |
查看包或标识符的文档 |
go test |
运行测试 |
go list |
列出包及其依赖信息 |
go clean |
清理构建产物 |
go fmt 实际调用的是 gofmt 工具。它会报告哪些文件不符合规范,并可以直接改写:
gofmt -l . # 列出不符合格式的文件
gofmt -w . # 就地格式化
比如下面这段没有对齐的代码:
package main
import "fmt"
func main() {
x:=1
y := 2
fmt.Println(x+y)
}
gofmt -d . 会给出差异:
diff main.go.orig main.go
--- main.go.orig
+++ main.go
@@ -1,7 +1,9 @@
-package main
+package main
+
import "fmt"
+
func main() {
- x:=1
- y := 2
- fmt.Println(x+y)
+ x := 1
+ y := 2
+ fmt.Println(x + y)
+}
可以看出 gofmt 做的不只是缩进:它统一用制表符缩进、在运算符两侧加空格、在包声明与导入、导入与函数之间插入空行。Go 社区的所有代码都使用同一套格式,因此几乎没有"代码风格争论"。
go doc 用来查文档,例如查看 fmt.Println 的签名与说明:
go doc fmt.Println
输出:
package fmt // import "fmt"
func Println(a ...any) (n int, err error)
Println formats using the default formats for its operands and writes to
standard output. Spaces are always added between operands and a newline
is appended. It returns the number of bytes written and any write error
encountered.
1.7 源文件的基本约定
- 源码文件必须以
.go为后缀。 - 源文件统一使用 UTF-8 编码,字符串字面量里可以直接写中文。
- 语句末尾不需要写分号。
第三点是初学者最容易踩坑的地方,因为"不需要写分号"并不等于"随便换行都没问题"。
自动插入分号的规则
Go 的语法规则里仍然有分号,只是编译器会在词法分析阶段自动插入。插入条件是:某行的最后一个记号(token)属于下面几类时,该行末尾自动补一个分号。
- 标识符(变量名、函数名、包名等)
- 字面量:整数、浮点数、虚数、字符、字符串
- 关键字:
break、continue、fallthrough、return - 运算符与标点:
++、--、)、]、}
这条规则带来两个必须遵守的书写约定。
左花括号不能另起一行
package main
import "fmt"
func main()
{
fmt.Println("Hello, Go!")
}
编译报错:
.\main.go:6:1: syntax error: unexpected semicolon or newline before {
原因是 func main() 这一行以 ) 结尾,编译器自动补上了分号,于是函数声明提前结束,下一行的 { 就成了孤立的记号。正确的写法是把 { 放在行尾:
func main() {
fmt.Println("Hello, Go!")
}
多行表达式必须保留逗号
调用函数时如果要换行,最后一个参数后面的逗号不能省略,否则同样会在该行末尾插入分号:
fmt.Println(
"第一行",
"第二行", // 这个逗号不能省略
)
gofmt 会自动帮你对齐这类写法,但不会替你补逗号——漏掉逗号是语法错误,不是格式问题。
1.8 练习
- 用
go mod init初始化一个模块,写出打印"你好,Go"的程序,分别用go run和go build执行它,观察当前目录的文件变化。 - 用
go doc fmt.Sprintf查看Sprintf的签名,说清楚它和Println在返回值上的差别。 - 故意把
{换到下一行,记录编译器给出的报错信息,再用一句话解释这个报错是怎么产生的。



