切片:可以伸缩的数组

Go 里的数组长度是写死的,真正天天用的是切片(slice)。切片不用提前定死长度,用 make 创建,用 append 往里加元素。

package main

import "fmt"

func main() {
    // make(类型, 初始长度):创建一个装 int 的切片
    nums := make([]int, 3) // [0 0 0],未赋值的元素是零值
    nums[0] = 10

    // append 追加元素,必须用返回值接住
    nums = append(nums, 40, 50)
    fmt.Println(nums)   // 输出: [10 0 0 40 50]
    fmt.Println(len(nums)) // 输出: 5,当前元素个数
}

两个新手最容易踩的点:

  • append 的返回值必须重新赋给原变量,直接写 append(nums, 1) 是无效的;
  • len() 是长度,cap() 是容量,容量不够时 Go 会自动扩容,平时不用管。

切片还能从一个已有的切片上"切"出一块,语法是 [起始:结束),左闭右开:

lang := []string{"Go", "Java", "Python", "C++"}
fmt.Println(lang[1:3]) // 输出: [Java Python],取下标 1、2
fmt.Println(lang[:2])  // 输出: [Go Java],从头取到下标 2 之前

map:键值对容器

map 用来存"键 → 值"的映射,声明时写清楚键类型和值类型:

package main

import "fmt"

func main() {
    age := map[string]int{
        "小明": 18,
        "小红": 20,
    }

    age["小刚"] = 22        // 新增或覆盖
    fmt.Println(age["小红"]) // 输出: 20

    delete(age, "小明")      // 删除键值对
    fmt.Println(age)        // 输出: map[小刚:22 小红:20]
}

查一个可能不存在的键时,用"逗号 ok"写法判断键是否存在,避免拿到零值还蒙在鼓里:

v, ok := age["老王"]
if !ok {
    fmt.Println("老王不在表里") // 输出: 老王不在表里
} else {
    fmt.Println(v)
}

注意:map 遍历时的顺序是随机的,别指望每次输出顺序一样;需要有序就先把键排序。

range:统一的遍历写法

切片和 map 都用 for range 遍历。切片拿到的是"下标和值",map 拿到的是"键和值":

package main

import "fmt"

func main() {
    langs := []string{"Go", "Python", "Java"}
    for i, v := range langs {
        fmt.Println(i, v)
        // 输出: 0 Go
        // 输出: 1 Python
        // 输出: 2 Java
    }

    scores := map[string]int{"语文": 90, "数学": 95}
    for k, v := range scores {
        fmt.Println(k, "=", v)
    }
}

只要下标不要值时,写成 for _, v := range,用空占位符 _ 忽略下标,这是 Go 的惯用写法。

小结

  • 切片用 make([]T, n) 创建,append 追加元素且要用返回值接住;
  • 切片支持 [起:止) 的子切片写法,左闭右开;
  • map 是键值对容器,delete 删除键,"逗号 ok"判断键是否存在,遍历顺序随机;
  • for range 一套写法通吃切片和 map,_ 用来忽略不需要的返回值。

把这三样练熟,Go 里八成的数据操作就都有抓手了,下一步可以看看结构体和方法。