Go 入门 04
集合 · 结构体 · 接口 · 测试
从数据建模到行为验证
slice序列
map索引
test验证
Chapter 04
用集合保存数据
Go 入门阶段最常用的集合是 array、slice 和 map。array 是固定长度数组;slice 是更常用的动态序列视图;map 是键值映射。官方 Create a module 教程会用 slice 保存多个问候对象,用 map 保存名字到消息的关系。
定义:slice
slice 是对底层数组某一段的描述,包含长度、容量和指向底层数组的引用。日常 Go 编程里,动态列表通常优先用 slice。
names := []string{"Ada", "Ken", "Rob"}
names = append(names, "Gopher")
for index, name := range names {
fmt.Println(index, name)
}
定义:map
map 是键值表,通过 key 快速找到 value。读取 map 时可以用第二个返回值判断 key 是否存在。
scores := map[string]int{
"Ada": 98,
"Ken": 95,
}
score, ok := scores["Rob"]
if !ok {
fmt.Println("Rob has no score")
} else {
fmt.Println(score)
}
Struct
结构体把字段组织成概念
结构体用于把多个字段组合成一个具名概念。它不是“带方法的 map”,而是一个明确的数据模型。字段名、字段类型和方法共同描述这个概念能表达什么。
type User struct {
ID int
Name string
Email string
}
func (user User) DisplayName() string {
if user.Name == "" {
return "anonymous"
}
return user.Name
}
DisplayName 前面的 (user User) 叫接收者,表示这个函数是 User 类型的方法。值接收者适合不修改对象的小方法;指针接收者适合需要修改对象或避免复制较大结构体的场景。
Interface
接口描述行为,而不是继承层级
Go 的 interface 是方法集合。一个类型只要实现了接口要求的所有方法,就自动满足该接口,不需要显式声明 implements。
type Notifier interface {
Notify(message string) error
}
type EmailNotifier struct {
Address string
}
func (notifier EmailNotifier) Notify(message string) error {
fmt.Printf("send %q to %s\n", message, notifier.Address)
return nil
}
func SendWelcome(notifier Notifier) error {
return notifier.Notify("welcome")
}
这种设计鼓励你在调用侧定义小接口。调用者只声明自己真正需要的方法,具体类型可以来自当前包、其他包,甚至测试里的假实现。
接口经验:初学者不要一上来就设计大接口。先写具体类型,等出现真实替换需求时,再抽出最小接口。
Testing
用 go test 保护行为
Go 的 testing 包和 go test 命令是标准测试入口。测试文件以 _test.go 结尾,测试函数形如 func TestXxx(*testing.T)。官方 testing 文档说明,go test 会自动执行这些测试函数。
package greeting
import "testing"
func Hello(name string) string {
if name == "" {
return "Hello, anonymous"
}
return "Hello, " + name
}
func TestHello(t *testing.T) {
got := Hello("Ada")
want := "Hello, Ada"
if got != want {
t.Errorf("Hello(\"Ada\") = %q, want %q", got, want)
}
}
执行测试:
go test ./...
测试不是等项目写完才补的文档,而是开发过程中持续校验行为的工具。Go 官方教程也把“添加测试”放在模块入门路径中,说明它属于基础能力,而不是高级技巧。
Generics
泛型先理解用途,不急着滥用
Go 1.18 引入泛型。泛型允许函数或类型在一组类型上复用逻辑,常见场景是容器、算法和类型安全的通用工具。官方泛型教程从两个 map 求和函数开始,展示如何合并成一个泛型函数。
type Number interface {
~int64 | ~float64
}
func SumValues[K comparable, V Number](values map[K]V) V {
var sum V
for _, value := range values {
sum += value
}
return sum
}
初学阶段先把普通函数、接口和具体类型写熟。只有当你确实在重复同一套类型无关逻辑时,再考虑泛型。
复习速查
- slice:动态序列,常配合
append和range。 - map:键值表,读取时用
value, ok判断是否存在。 - struct:把字段组织成领域概念。
- interface:描述一组方法,类型隐式满足接口。
- testing:测试文件
_test.go,测试函数TestXxx(*testing.T)。
参考来源
- The Go Authors. Tutorial: Create a Go module. go.dev/doc/tutorial/create-module
- The Go Authors. Add a test. go.dev/doc/tutorial/add-a-test
- The Go Authors. Tutorial: Getting started with generics. go.dev/doc/tutorial/generics
- The Go Authors. Package testing. pkg.go.dev/testing