Go 文档
简洁、以示例为主的 Go 参考文档。先读概念,再看代码,然后在 Coddy 学习旅程中动手练习。
开始课程入门
- Go 是什么?Go(常被称为 Golang)是一门静态类型的编译型语言,由 Google 设计,用来构建快速、可靠的服务器和工具。本页介绍 Go 是什么、用来做什么,以及它的不足之处。
- 安装 Go如何在 Windows、macOS 和 Linux 上下载并安装 Go,用 go version 检查安装结果,理解 GOPATH,以及解决“go: command not found”错误。
- Hello World逐行讲解 Go 语言的 hello world 程序:package main、import 块、func main 和 fmt.Println,以及如何运行它、新手最先遇到的编译错误。
- go run 与 go buildgo run、go build 和 go install 各自做什么,Go 如何给二进制文件命名,如何用 GOOS 和 GOARCH 交叉编译,以及提交前要运行的 go fmt 和 go vet 检查。
- Go 模块Go 模块的工作原理:用 go mod init 创建模块、选择模块路径、用 go get 添加依赖、用 go mod tidy 清理、go.sum 的作用,以及本地开发时使用 replace。
- 包与导入Go 的包是如何工作的:package 声明、导入标准库和模块中的包、导出与未导出的名字、把一个包拆分到多个文件、internal 包、导入别名、空白导入,以及 go run main.go 报出的 "undefined" 错误。
基础
- 变量如何在 Go 中用 var 和简短形式 := 声明变量,什么是零值,多重赋值,以及常让 Go 新手栽跟头的变量遮蔽和未使用变量错误。
- 常量Go 中 const 的工作方式:声明常量、有类型常量与无类型常量的区别、任意精度的常量表达式,以及为什么 Go 没有常量切片、map 或结构体。
- 枚举与 iotaGo 没有 enum 关键字。本页介绍地道的替代写法:命名类型加上使用 iota 的 const 块,以及如何添加 String()、校验、解析、位标志和 JSON 支持。
- 数据类型Go 的内置类型:各种整数的大小和范围、float32 与 float64、bool、string、byte 与 rune、复数、零值,以及如何用 %T 打印值的类型。
- 类型转换如何在 Go 中进行类型转换:用 strconv 实现 int 转 string 和 string 转 int,浮点数转整数,用 T(v) 做数值转换,字节与 rune,string(65) 的陷阱,以及处理转换错误。
- 字符串在 Go 中处理字符串:strings 包(Split、Join、Contains、Replace、Fields、TrimSpace、ToUpper),用 strings.Builder 高效拼接字符串,多行原始字符串,不可变性,以及为什么 len 统计的是字节数。
- rune 与 byteGo 中 rune 和 byte 是什么意思,字符串如何以 UTF-8 存储,为什么 len 计算的是字节数,range 如何解码出 rune,string、[]byte 和 []rune 之间如何转换,以及如何使用 bytes 包。
- fmt.Printf 与 SprintfGo 的 fmt 包如何打印和格式化值:Println、Printf、Sprintf 与 Errorf 的区别,完整的格式化动词表(%v、%+v、%d、%s、%q、%f、%T、%w 等),以及宽度、精度和填充。
流程控制
- if / elseGo 中 if、else if 和 else 的用法:条件不加圆括号,花括号必不可少,带初始化语句的 if 及其作用域,if err != nil 惯用法,以及用提前返回代替深层嵌套。
- for 循环Go 只有一个循环关键字 for,但它能胜任一切:三段式计数循环、只有条件的 while 式循环、无限循环,以及对切片、map、字符串、channel、整数和函数的 range 遍历。另外还有带标签的 break 和 continue,以及 Go 1.22 对循环变量的改动。
- while 循环Go 没有 while 关键字。只带条件的 for 循环就是 Go 的 while 循环,不带条件的 for 是无限循环,而 do-while 就是在末尾检查条件的无限循环。
- range 循环for range 在遍历 slice、数组、字符串、map、channel、整数和迭代器函数时分别返回什么,为什么值变量是一个副本,在遍历时修改 slice 或 map 会发生什么,以及 Go 1.23 中的 range over func。
- switch 语句Go 中的 switch 如何工作:case 默认不会贯穿,一个 case 可以列出多个值,没有条件的 switch 可以替代 if/else 链,带初始化语句的 switch,fallthrough 关键字,以及类型 switch 简介。
- 三元运算符Go 没有三元运算符(cond ? a : b)。本页解释原因,展示惯用的 if/else 替代写法、用 cmp.Or 设置默认值,以及泛型辅助函数和它与真正三元运算符不同的陷阱。
函数
- 函数如何在 Go 中声明和调用函数:参数、共享类型的参数、返回值、值传递,以及可以存储和传递的函数值。
- 多返回值Go 函数可以返回多个值。本页讲解语法、(value, error) 约定、comma-ok 惯用法、命名返回值、裸返回以及空白标识符。
- 可变参数函数Go 的可变参数函数可以在末尾接收任意数量、同一类型的参数。学习 ...T 语法、如何用 s... 传入切片、共享切片的陷阱,以及 fmt.Println 如何使用 ...any。
- 闭包Go 中的匿名函数可以捕获外层作用域中的变量,由此形成闭包:计数器、生成器、中间件,以及保留自身状态的回调函数。
- defer 语句defer 安排一个调用在外层函数返回时执行。了解后进先出的顺序、参数何时求值、关闭文件和释放互斥锁、循环中的 defer,以及修改命名返回值。
- 方法方法就是带接收者的函数。学习如何声明方法、什么时候使用值接收者或指针接收者、方法集与接口,以及在非结构体类型上定义方法。
集合
- 数组Go 数组的长度是固定的,并且是类型的一部分,赋值时整个数组会被复制。学习如何声明、遍历、比较和传递数组,以及为什么大多数 Go 代码改用切片。
- 切片切片是 Go 中最常用的列表类型。学习 make、append 与扩容、len 与 cap 的区别、切片操作如何共享底层数组(以及由此引发的 bug)、copy、slices 包、二维切片,以及 nil 切片与空切片。
- MapGo 的 map 存储键值对,查找速度很快。学习如何创建 map、用 comma-ok 判断键是否存在、删除、遍历(顺序是随机的)、对键排序、存储结构体,以及避免 nil map 和并发写入导致的 panic。
- 集合Go 没有内置的集合类型。标准的惯用写法是值为空结构体的 map。学习添加、查找和删除元素,并集、交集和差集,以及如何编写一个小巧的泛型 Set。
- 排序在 Go 中用 slices.Sort 和 slices.SortFunc 给切片排序,用 cmp.Compare 按一个或多个字段给结构体排序,用稳定排序保持相等元素的原有顺序,并看懂旧代码里的 sort.Slice。
结构体、接口与泛型
- 结构体结构体把多个具名字段组合成一个类型。学习如何定义和初始化结构体、零值、结构体指针、匿名结构体、结构体比较、结构体标签,以及 NewX 构造函数的惯例。
- 指针指针保存的是一个值的地址。学习 & 和 *、new、结构体指针、为什么可以安全地返回局部变量的指针、什么时候该用指针,以及空指针解引用引发的 panic。
- 接口Go 的接口是隐式实现的:只要一个类型拥有对应的方法,它就实现了这个接口。了解 io.Reader 和 fmt.Stringer 这样的小接口、空接口 any、nil 接口陷阱,以及“接收接口,返回结构体”原则。
- 结构体嵌入嵌入是把一个类型放进另一个类型而不写字段名,这样它的字段和方法会被提升到外层类型。了解提升的规则、嵌入接口、名称冲突,以及为什么嵌入不是继承。
- 类型断言类型断言用来从接口中取出具体的值。学习 x.(T)、永远不会 panic 的 comma-ok 形式、断言为另一个接口、类型 switch,以及处理被包装错误的 errors.As。
- 泛型泛型(Go 1.18+)让一个函数或类型在保持类型安全的同时适用于多种类型。学习类型参数,any、comparable 和 cmp.Ordered 约束,用 ~ 自定义约束,泛型类型,以及什么时候不该用泛型。
错误
- 错误处理Go 把错误当作函数返回的普通值来处理。学习 error 接口、if err != nil、errors.New 和 fmt.Errorf、带上下文返回错误、用 errors.Is 和 errors.As 检查错误,以及每个错误只处理一次。
- 自定义错误定义哨兵错误和自定义错误类型,用 %w 包装错误,用 errors.Is 和 errors.As 检查错误,用 errors.Join 合并多个错误,并在需要时编写 Unwrap 和 Is 方法。
- panic 与 recoverpanic 会中止正常执行并展开调用栈,同时执行被 defer 的调用。了解哪些情况会引发 panic,在 defer 函数中调用 recover 如何拦住它,你会遇到的运行时错误信息,以及什么时候主动 panic 才是正确的选择。
并发
- Goroutine如何用 go 关键字并发运行函数、等待它们完成、取回结果,以及如何避免 goroutine 很容易引发的数据竞争、泄漏和崩溃。
- 通道Go 通道如何在 goroutine 之间传递值:无缓冲和带缓冲的通道、关闭与 range 遍历、方向类型、死锁错误,以及用通道搭建的流水线。
- select 语句select 如何同时等待多个 channel 操作:在就绪的 case 中选择、用 default 实现非阻塞的发送和接收、用 time.After 实现超时,以及用退出 channel 或 context 停止循环。
- WaitGroupsync.WaitGroup 如何等待一组 goroutine 结束:Add、Done 和 Wait 的规则,为什么必须传指针,如何收集结果和错误,以及基于它构建的工作池。
- Mutex 互斥锁如何用 sync.Mutex 和 sync.RWMutex 保护多个 goroutine 之间的共享状态,什么时候用 sync/atomic 就够了,sync.Once 如何保证初始化只执行一次,以及会导致死锁的常见加锁错误。
- context 包context.Context 如何在 Go 程序中传递取消信号、截止时间和请求范围的值:Background、WithCancel、WithTimeout、WithValue、在 select 中使用 ctx.Done,以及 HTTP 服务器和客户端中的 context。
标准库
- time 包如何在 Go 中处理日期和时间:time.Now 与 time.Sleep、Duration 运算、Format 和 Parse 使用的 2006-01-02 15:04:05 参考布局、时区、Unix 时间戳,以及定时器和 Ticker。
- JSON如何用 encoding/json 在 Go 中编码和解码 JSON:Marshal 和 Unmarshal,omitempty、omitzero 等结构体标签,格式化输出,解码到 map[string]any,拒绝未知字段,以及用 Decoder 处理数据流。
- 读写文件如何在 Go 中读写文件:os.ReadFile 和 os.WriteFile、用 bufio.Scanner 逐行读取、用 os.OpenFile 追加内容、检查文件是否存在,以及操作目录。
- HTTP 服务器如何用 Go 标准库 net/http 包搭建 Web 服务器:处理器、支持方法和路径通配符的 ServeMux 路由(Go 1.22)、JSON 响应、状态码、中间件、超时和优雅关闭。
- HTTP 客户端如何用 net/http 在 Go 中发送 HTTP 请求:http.Get、读取并关闭响应体、检查状态码、http.Client 超时、带 context 和请求头的请求、查询参数,以及用 POST 发送 JSON。
- 命令行参数Go 程序如何读取命令行:os.Args、用于类型化选项的 flag 包、用 FlagSet 实现子命令、用 os.Getenv 和 os.LookupEnv 读取环境变量,以及用 os.Exit 设置退出码。
- 正则表达式如何用 regexp 包在 Go 中使用正则表达式:MustCompile、MatchString、FindString 和 FindAllString、捕获组与命名组、ReplaceAllString,以及 RE2 语法的限制,例如不支持后行断言。
- 日志如何在 Go 中记录日志:经典的 log 包及其标志和 log.Fatal,以及用于结构化日志的 log/slog(Go 1.21),包括日志级别、键值属性、文本和 JSON 处理器,以及用 With 携带上下文的 logger。