Go 语言入门系列(三):复合数据类型——切片、map、结构体与方法
本篇目标:用 Go 的方式组织数据。这三种类型是日常代码的绝对主力,理解它们的底层行为能避开大量坑。
一、数组 vs 切片
数组长度固定,是值类型,赋值即复制:
var a [3]int // [0 0 0]
b := [3]int{1, 2, 3}
切片是日常主力,长度可变,底层是"指针 + 长度 + 容量"的结构:
s := []int{1, 2, 3}
s = append(s, 4) // 追加
fmt.Println(len(s), cap(s)) // 4 6(扩容后的容量)
append 超出容量时会分配新数组并复制,原数组不受影响。容量增长策略由运行时决定,不必记具体倍数。
截取与共享陷阱——s[1:3] 不复制,新切片与原切片共享底层数组:
s := []int{1, 2, 3, 4}
sub := s[1:3] // [2 3]
sub[0] = 99
fmt.Println(s) // [1 99 3 4],原切片被改
需要独立副本时用 slices.Clone 或 copy。
二、map
键值对集合,键必须可比较(不能用切片、map 作键):
scores := map[string]int{
"alice": 90,
"bob": 85,
}
scores["carol"] = 95
delete(scores, "bob")
// 判断键是否存在
if val, ok := scores["alice"]; ok {
fmt.Println(val)
}
注意:遍历顺序是随机的,不能依赖;未初始化的 map 是 nil,写入会 panic,读取返回零值。用前必须 make 或字面量初始化。
三、结构体
把相关字段聚在一起:
type User struct {
Name string
Age int
}
u := User{Name: "小明", Age: 20}
u2 := User{"小红", 18} // 按字段顺序,不推荐
匿名字段与嵌入——Go 用组合代替继承:
type Animal struct {
Name string
}
type Dog struct {
Animal // 嵌入,字段名即类型名
Breed string
}
d := Dog{Animal: Animal{Name: "旺财"}, Breed: "柴犬"}
fmt.Println(d.Name) // 直接访问嵌入字段,提升
四、方法
方法是带接收者的函数,接收者写在 func 和函数名之间:
func (u User) Greet() string {
return "你好," + u.Name
}
func (u *User) SetName(name string) {
u.Name = name
}
值接收者 vs 指针接收者:
- 值接收者:操作副本,改不到原值;适合小结构体、只读方法
- 指针接收者:操作原值,能修改;大结构体避免复制、需要修改时用
规则:同一类型的方法,接收者要么全用值、要么全用指针,保持一致。
五、零值可用性设计
Go 的一个好习惯:让结构体的零值就能用。比如 sync.Mutex、bytes.Buffer 零值即可使用。设计自己的结构体时也尽量做到——用 nil map 加惰性初始化,比强制要求构造函数更友好。
type Counter struct {
m map[string]int
}
func (c *Counter) Inc(key string) {
if c.m == nil {
c.m = make(map[string]int)
}
c.m[key]++
}
六、小结
- 切片共享底层数组,截取不复制,需要副本用
slices.Clone/copy - map 遍历无序,nil map 写入 panic,用前初始化
- 结构体嵌入实现组合,字段被提升可直接访问
- 方法接收者同类型保持一致;指针接收者可修改原值
- 尽量让结构体零值可用
练习:用 struct + map[string]*User 实现一个简易通讯录,提供 Add、Get、Delete 方法,并让零值即可使用。
下一篇讲接口与错误处理——Go 没有继承的世界里如何做抽象,错误如何流转。

浙公网安备 33010602011771号