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 没有继承的世界里如何做抽象,错误如何流转。

posted @ 2026-10-31 17:54  sunlon  阅读(3)  评论(0)    收藏  举报