数组、切片和Map

1. 数组

1.1 数组声明与初始化

Go 数组是固定长度、同类型元素的序列。长度是数组类型的一部分, [3]int 和 [5]int 是不同类型,不能互相赋值,赋值后会编译报错。

go 复制代码
package main

import "fmt"

func main() {
    // 声明数组:var 名称 [长度]类型 --- 元素自动为零值
    var numbers [5]int // 5个int,全部为0

    // 数组字面量初始化:[长度]类型{值1, 值2, ...}
    colors := [3]string{"red", "green", "blue"}

    // ... 让 Go 自动计算长度
    days := [...]string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"}

    // 通过索引访问和修改元素
    numbers[2] = 42 // 修改第3个元素(索引2)

    // len() 获取数组长度
    fmt.Println("Length of colors array:", len(colors))

    // 打印整个数组
    fmt.Println("Numbers:", numbers)
    fmt.Println("Colors:", colors)
    fmt.Println("Days:", days)
}

1.2 数组遍历

  • 使用for循环遍历
go 复制代码
package main

import "fmt"

func main() {
    scores := [5]int{85, 92, 78, 96, 88}

    // 传统 for 循环:通过索引访问每个元素, Go 的 len() 是编译时确定的(数组长度固定),不需要运行时计算
    for i := 0; i < len(scores); i++ {
        fmt.Printf("Score %d: %d\n", i+1, scores[i])
    
}
  • 使用range遍历
go 复制代码
package main

import "fmt"

func main() {
    names := [...]string{"Alice", "Bob", "Charlie", "Diana", "Eve"}

    // range 同时返回索引和值
    fmt.Println("Names with indices using range:")
    for index, value := range names {
        fmt.Printf("Index %d: %s\n", index, value)
        // value 是元素的拷贝,修改 value 不影响原数组
    }
}

1.3 多维数组声明与初始化

多维数组是"数组的数组",Go 通过 行列 语法定义:

go 复制代码
// 初始化 2x4 数组(2行4列)
grades := [2][4]int{{85, 92, 78, 96}, {88, 91, 84, 87}}

1.4 多维数组遍历

  • 使用for循环遍历
less 复制代码
func main() {
    // 声明 3x3 矩阵,元素全部为零值 0
    var matrix [3][3]int

    // 初始化 2x4 数组(2行4列)
    grades := [2][4]int{{85, 92, 78, 96}, {88, 91, 84, 87}}

    // 修改元素:matrix[行][列]
    matrix[1][1] = 5 // 第2行第2列设为5

    // 打印矩阵:嵌套 for 循环
    fmt.Println("Matrix:")
    for i := 0; i < len(matrix); i++ {
        for j := 0; j < len(matrix[i]); j++ {
            fmt.Printf("%3d ", matrix[i][j]) // %3d 右对齐,宽度3
        }
        fmt.Println() // 每行末尾换行
    }
}
  • 使用range遍历
go 复制代码
package main

import "fmt"

func main() {
    grades := [2][4]int{{85, 92, 78, 96}, {88, 91, 84, 87}}

    // range 遍历:外层遍历行,内层遍历每行的元素
    for i, row := range grades {
        for _, grade := range row {
            fmt.Printf("%d ", grade)
        }
        fmt.Println()
    }

    // 求所有元素总和:嵌套 for 循环
    var totalSum int
    for i := 0; i < len(grades); i++ {
        for j := 0; j < len(grades[i]); j++ {
            totalSum += grades[i][j]
        }
    }
    fmt.Println("Total sum of grades:", totalSum)

    // 获取维度:行数 × 列数
    rows := len(grades)
    var cols int
    if rows > 0 {
        cols = len(grades[0])
    }
    fmt.Printf("Grades array dimensions: %dx%d\n", rows, cols)
}

1.5 数组排序 --- sort 包

Go 的 sort 包提供排序函数,但只能对切片 排序,需要用 [:] 把数组转为切片。

1.5.1 升序排列

  • sort.Ints --- int 升序排序
  • sort.Strings --- string 按字典序
  • sort.Float64s --- float64 升序
go 复制代码
package main

import (
    "fmt"
    "sort"
)

func main() {
    numbers := [5]int{64, 34, 25, 12, 22}
    names := [4]string{"Charlie", "Alice", "Bob", "David"}
    prices := [6]float64{19.99, 9.99, 29.99, 4.99, 39.99, 14.99}

    fmt.Println("Before sorting:")
    fmt.Println("Numbers:", numbers)
    fmt.Println("Names:", names)
    fmt.Println("Prices:", prices)

    // sort.Ints() 排序 int 切片(升序)
    sort.Ints(numbers[:])  // numbers[:] 把数组转为切片

    // sort.Strings() 排序 string 切片(升序)
    sort.Strings(names[:])

    // sort.Float64s() 排序 float64 切片(升序)
    sort.Float64s(prices[:])

    fmt.Println("\nAfter sorting:")
    fmt.Println("Numbers:", numbers)
    fmt.Println("Names:", names)
    fmt.Println("Prices:", prices)

    // 检查是否已排序
    isSorted := sort.IntsAreSorted(numbers[:])
    fmt.Println("Numbers array is sorted:", isSorted)

运行结果:

yaml 复制代码
Before sorting:
Numbers: [64 34 25 12 22]
Names: [Charlie Alice Bob David]
Prices: [19.99 9.99 29.99 4.99 39.99 14.99]

After sorting:
Numbers: [12 22 25 34 64]
Names: [Alice Bob Charlie David]
Prices: [4.99 9.99 14.99 19.99 29.99 39.99]
Numbers array is sorted: true
  • sort.Ints(numbers[:]) --- numbers[:] 把 [5]int 数组转为 []int 切片(切片引用数组的底层内存)
  • 排序后数组本身也被修改了!因为切片和数组共享同一块内存
  • sort.IntsAreSorted(numbers[:]) --- 检查切片是否已排序,返回 bool
  • 数组不能直接排序:sort.Ints(numbers) 编译报错,必须转为切片

1.5.2 降序排列 sort.Reverse(...)

go 复制代码
package main

import (
    "fmt"
    "sort"
)

func main() {
    descNumbers := [5]int{1, 2, 3, 4, 5}
    fmt.Println("Before reverse sort:", descNumbers)

    // 降序排序:sort.Sort + sort.Reverse + sort.IntSlice
    sort.Sort(sort.Reverse(sort.IntSlice(descNumbers[:])))

    fmt.Println("After reverse sort:", descNumbers)
}
  • sort.Sort 接收一个 sort.Interface,对其执行执行排序算法,原地修改底层数组
  • sort.Reverse(sort.IntSlice(slice)) 返回一个接口值,内部包装了 sort.IntSlice。
    它的 Less(i, j) 实际调用的是 IntSlice.Less(j, i),即 slice[j] < slice[i],比较方向反转,变成 降序。
  • sort.IntSlice 是一个定义类型。sort.IntSlice(slice) 是把 []int 类型转换 成 sort.IntSlice 。不是复制,底层数组仍然是原来的 slice。此时它的 Less 是升序比较。

1.5.3 数组查找 sort.SearchInts(sortedNumbers:, xxx)

go 复制代码
package main

import (
    "fmt"
    "sort"
)

func main() {
    sortedNumbers := [8]int{5, 12, 23, 34, 45, 67, 78, 89}
    binaryTarget := 45

    // sort.SearchInts 在已排序切片中查找目标
    binaryIndex := sort.SearchInts(sortedNumbers[:], binaryTarget)

    // 必须验证:SearchInts 返回的可能是"应插入的位置",而非找到的位置
    if binaryIndex < len(sortedNumbers) && sortedNumbers[binaryIndex] == binaryTarget {
        fmt.Printf("Binary search: Found %d at index %d\n", binaryTarget, binaryIndex)
    } else {
        fmt.Printf("Binary search: %d not found\n", binaryTarget)
    }
}

2. 切片

2.1 切片创建

切片是 Go 中最核心的数据结构,它是动态数组的"视图",由指针(指向底层数组)、长度len、容量cap三部分组成。

go 复制代码
package main

import "fmt"

func main() {
    // 从数组创建切片:arr[low:high],包含 low 到 high-1 的元素
    arr := [5]int{1, 2, 3, 4, 5}
    slice1 := arr[1:4] // [2, 3, 4]

    // make() 创建切片:make([]type, length, capacity)
    slice2 := make([]string, 3, 5) // 长度3,容量5,元素为string零值""
    // 长度3:已有3个""元素可访问;容量5:底层数组可容纳5个元素

    // 切片字面量:直接声明(与数组字面量不同,没有长度)
    slice3 := []int{10, 20, 30} // 切片,不是数组!没有 [N]

    // nil 切片:var 声明未初始化
    var slice4 []int // slice4 == nil,len=0, cap=0

    if slice4 == nil {
        fmt.Println("slice4 is nil")
    }

    // len() 和 cap() 获取长度和容量
    length := len(slice2)
    capacity := cap(slice2)

    // 修改切片元素会影响原数组(共享底层内存)
    slice1[1] = 99 // arr[2] 也变成 99

    fmt.Println("Original array:", arr)
    fmt.Println("slice1:", slice1)
    fmt.Println("slice2:", slice2)
    fmt.Println("slice3:", slice3)
    fmt.Printf("slice2 - length: %d, capacity: %d\n", length, capacity)
}

2.2 切片操作

  • 子切片会共享其"父切片"的底层数组;
  • 切片为 nil 时,获取子切片的结果仍然是 nil

2.2.1 切片表达式

  • ‌基础形式 ‌:s[low:high],左闭右开,取索引 [low, high) 区间的元素。省略 low 默认 0,省略 high 默认原切片长度。
  • 区间范围:0 <= low <= high <= len(s)
go 复制代码
s := []int{2, 3, 5, 7, 11} // len: 5 cap: 5
s1 := s[1:3]
fmt.Println(s1) // [3 5]

fmt.Println(s[2:]) // [5 7 11]     --- 等同于 s[2:len(s)]
fmt.Println(s[:3]) // [2 3 5]      --- 等同于 s[0:3]
fmt.Println(s[:])  // [2 3 5 7 11] --- 等同于 s[0:len(s)]

// 索引 low 和 high 必须满足范围 否则会报错
star := "Polaris"
fmt.Println(star[-1])  // 无效参数:索引 -1
fmt.Println(star[0:8]) // 运行时错误:slice bounds out of range [:8] with length 7

// 当从切片获取子切片时,结果切片的容量 cap 等于基础切片的容量减去 low(即 cap(s) - low),而不是基础切片的长度
teams := []string{"Bayern Munich", "Real Madrid", "Manchester City"}
t := teams[1:2]
fmt.Println(t, "| len:", len(t), "cap:", cap(t)) // [Real Madrid] | len: 1 cap: 2
fmt.Println(t[1:3]) // 运行时错误:slice bounds out of range [:3] with capacity 2

// 简单切片表达式支持数组、切片、字符串,以及指向数组/切片的指针.
consts := new([3]float64)
*consts = [3]float64{3.1416, 2.718, 1.618} // 指向数组
fmt.Println(consts[1:3])                   // [2.718 1.618]

animals := new([]string)
*animals = []string{"cat", "dog", "fish"} // 指向切片
fmt.Println((*animals)[1:3])              // ["dog" "fish"]
  • ‌完整形式 ‌:s[low:high:max],额外指定容量上限,cap = max - low,可以限制子切片容量,避免意外修改底层数组或引发内存问题。
  • 区间范围:0 <= low <= high <= max <= cap(s)
  • 与简单切片表达式类似,len = high - low,但容量 cap = max - low -> 4 - 1 = 3。
  • 在完整切片表达式中,只有 low 可以省略,缺省为 0;但 high 和 max 不允许省略
  • 完整切片表达式仅支持数组、切片或指向数组的指针,不支持字符串,否则会报错
go 复制代码
s := []int{2, 3, 5, 7, 11}
s4 := s[1:3:4]
fmt.Println(s4, "| len:", len(s4), "cap:", cap(s4)) // [3 5] | len: 2 cap: 3

2.2.2 追加和扩容

append() 向切片追加元素,超过容量时自动扩容

go 复制代码
package main

import "fmt"

func main() {
    // 从 nil 切片开始 append(完全合法)
    var numbers []int
    numbers = append(numbers, 1)       // 追加单个元素
    numbers = append(numbers, 2, 3, 4) // 追加多个元素

    // 用 ... 展开切片追加
    moreNumbers := []int{5, 6, 7}
    numbers = append(numbers, moreNumbers...) // 展开追加

    // 观察容量增长:make([]string, 0, 2)
    fruits := make([]string, 0, 2) // 长度0,容量2
    fmt.Printf("Initial - len: %d, cap: %d\n", len(fruits), cap(fruits)) // Initial - len: 0, cap: 2

    fruits = append(fruits, "apple")
    fmt.Printf("After 1 append - len: %d, cap: %d\n", len(fruits), cap(fruits)) // After 1 append - len: 1, cap: 2

    fruits = append(fruits, "banana")
    fmt.Printf("After 2 appends - len: %d, cap: %d\n", len(fruits), cap(fruits)) // After 2 appends - len: 2, cap: 2

    // 第3次 append 超出容量,触发扩容:2 → 4
    fruits = append(fruits, "cherry")
    fmt.Printf("After 3 appends - len: %d, cap: %d\n", len(fruits), cap(fruits))  After 3 appends - len: 3, cap: 4

    // nil 切片可以直接 append
    var colors []string // nil 切片
    colors = append(colors, "red") // append 自动分配底层数组

    fmt.Println("Numbers:", numbers) // Numbers: [1 2 3 4 5 6 7]
    fmt.Println("Fruits:", fruits) // Fruits: [apple banana cherry]
    fmt.Println("Colors:", colors) // Colors: [red]
}
  • append(slice, element) --- 返回新切片(长度+1),必须用 = 接住返回值
  • 扩容策略:通常翻倍(2→4→8→16...),但 Go 运行时可能根据情况调整

2.2.1 删除指定索引的元素 --- append 切片拼接

Go 没有内置的"删除元素"操作,用 append 把前半段和后半段拼接起来。Go 删除切片元素的标准模式:append 前半段 + 后半段。

go 复制代码
package main
 
import "fmt"
 
func main() {
    data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
 
    // 删除索引3的元素(值=4)
    // append(data[:3], data[4:]...) → [1,2,3] + [5,6,7,8,9,10]
    index := 3
    data = append(data[:index], data[index+1:]...)
 
    fmt.Println("After removing index 3:", data) // After removing index 3: [1 2 3 5 6 7 8 9 10]
}

2.2.2 在指定索引插入元素 --- 双层 append

插入元素也用 append,分三步:取前半段 → append 新值 → append 后半段

go 复制代码
package main
 
import "fmt"
 
func main() {
    data := []int{1, 2, 3, 5, 6, 7, 8, 9, 10} // 删除4之后的切片
 
    // 在索引2处插入值99
    insertIndex := 2
    insertValue := 99
 
    // 步骤拆解:
    // 1. append(data[:2], 99) → [1, 2, 99]
    // 2. append([1, 2, 99], data[2:]...) → [1, 2, 99, 3, 5, 6, 7, 8, 9, 10]
    data = append(append(data[:insertIndex], insertValue), data[insertIndex:]...)
 
    fmt.Println("After inserting 99 at index 2:", data)
}

2.3 切片拷贝

切片的"拷贝"有浅拷贝(共享底层数组)和深拷贝(独立底层数组)两种。

  • 浅拷贝 original[:] --- 新切片头 + 共享底层数组,修改互相影响
  • 深拷贝 copy(dest, src) --- 创建独立底层数组,修改互不影响。返回实际复制的元素数 = min(len(dest), len(src))
  • 目标长度3,源长度5:只复制3个 → [a b c] ,所以copy 不会扩容目标切片!目标切片必须预先 make 并有足够长度
  • 源长度2,目标长度5:复制2个,剩余保持零值 → [100 200 0 0 0]
go 复制代码
package main

import "fmt"

func main() {
    original := []int{1, 2, 3, 4, 5}

    // 浅拷贝:original[:] 创建新切片头,但共享底层数组
    shallow := original[:]
    shallow[0] = 999 // 修改 shallow 也修改 original!
    fmt.Println("After modifying shallow copy:")
    fmt.Println("Original:", original) // [999 2 3 4 5] --- 被修改了
    fmt.Println("Shallow:", shallow)    // [999 2 3 4 5]

    // 重置 original
    original[0] = 1

    // 深拷贝:copy() 创建独立底层数组
    deep := make([]int, len(original)) // 先创建目标切片
    copy(deep, original)               // copy(dest, src) --- 复制元素
    deep[0] = 888                      // 修改 deep 不影响 original
    fmt.Println("\nAfter modifying deep copy:")
    fmt.Println("Original:", original) // [1 2 3 4 5] --- 未被修改
    fmt.Println("Deep:", deep)         // [888 2 3 4 5]

    // copy 返回复制的元素数量
    source := []string{"a", "b", "c", "d", "e"}
    destination := make([]string, 3)   // 目标长度3
    copied := copy(destination, source) // 只能复制3个(目标容量限制)
    fmt.Printf("\nCopied %d elements: %v\n", copied, destination)

    // 小切片复制到大切片
    small := []int{100, 200}
    large := make([]int, 5)
    n := copy(large, small) // 只复制2个,剩余3个保持零值0
    fmt.Printf("Copied %d elements to large slice: %v\n", n, large)
}

2.4 切片容量与底层共享

切片容量是从切片起始位置到底层数组末尾的元素数量,理解容量是理解切片的关键

go 复制代码
package main

import "fmt"

func main() {
    // make 指定长度和容量
    slice1 := make([]int, 5, 10) // 5个零值元素,底层数组容量10
    fmt.Printf("slice1 - len: %d, cap: %d, slice: %v\n", len(slice1), cap(slice1), slice1) // slice1 - len: 5, cap: 10, slice: [0 0 0 0 0]

    for i := range slice1 {
        slice1[i] = i * 2
    }
    fmt.Printf("After filling: %v\n", slice1) // After filling: [0 2 4 6 8]

    // 从数组切片:容量 = 底层数组从起始位置到末尾的长度
    array := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    slice2 := array[2:6] // 索引2到5,长度4
    // 容量 = 10 - 2 = 8(从索引2到数组末尾)
    fmt.Printf("slice2 - len: %d, cap: %d, slice: %v\n", len(slice2), cap(slice2), slice2) // slice2 - len: 4, cap: 8, slice: [3 4 5 6]

    // 修改切片会影响原数组
    slice2[0] = 999 // array[2] 也变成 999
    fmt.Printf("Array after modifying slice2: %v\n", array) // Array after modifying slice2: [1 2 999 4 5 6 7 8 9 10]
    fmt.Printf("slice2 after modification: %v\n", slice2) // slice2 after modification: [999 4 5 6]

    // append 超出容量时触发扩容
    smallSlice := make([]int, 2, 3) // 长度2,容量3
    fmt.Printf("Initial smallSlice - len: %d, cap: %d\n", len(smallSlice), cap(smallSlice)) // Initial smallSlice - len: 2, cap: 3

    smallSlice[0] = 10
    smallSlice[1] = 20

    fmt.Println("Appending elements and observing capacity:")
    // 第1次 append:还在容量内
    smallSlice = append(smallSlice, 30)
    fmt.Printf("After append(30) - len: %d, cap: %d, slice: %v\n", len(smallSlice), cap(smallSlice), smallSlice) // After append(30) - len: 3, cap: 3, slice: [10 20 30]

    // 第2次 append:超出容量,扩容 3→6
    smallSlice = append(smallSlice, 40)
    fmt.Printf("After append(40) - len: %d, cap: %d, slice: %v\n", len(smallSlice), cap(smallSlice), smallSlice) // After append(40) - len: 4, cap: 6, slice: [10 20 30 40]

    // 第3次 append 多个元素:7个 > 容量6,扩容 6→12
    smallSlice = append(smallSlice, 50, 60, 70)
    fmt.Printf("After append(50,60,70) - len: %d, cap: %d, slice: %v\n", len(smallSlice), cap(smallSlice), smallSlice) // After append(50,60,70) - len: 7, cap: 12, slice: [10 20 30 40 50 60 70]
}

2.5 切片自定义排序

2.5.1 自定义排序函数

sort.Slice(slice, lessFunc) 用自定义的比较函数对切片排序,lessFunc 返回 true 表示 i 应排在 j 前面。

go 复制代码
package main
 
import (
    "fmt"
    "sort"
)
 
type Person struct {
    Name   string
    Age    int
    Salary int
}

func main() {
    people := []Person{
        {"Alice", 28, 75000},
        {"Bob", 32, 85000},
        {"Charlie", 25, 65000},
        {"Diana", 29, 90000},
        {"Eve", 25, 70000},
    }
 
    fmt.Println("Original people:")
    printPeople(people)
 
    // 按年龄升序:people[i].Age < people[j].Age → i 排前面
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Println("\nSorted by age (ascending):")
    printPeople(people)
}

2.5.2 稳定排序(保持相等元素的原始顺序)

sort.SliceStable 在排序时保持相等元素的原始相对顺序。

go 复制代码
package main
 
import (
    "fmt"
    "sort"
)
 
type Person struct {
    Name   string
    Age    int
    Salary int
}
 
func printPeople(people []Person) {
    for _, p := range people {
        fmt.Printf("  %s (Age: %d, Salary: $%d)\n", p.Name, p.Age, p.Salary)
    }
}
 
func main() {
    // 先按名字排序,建立初始顺序
    people := []Person{
        {"Alice", 28, 75000},
        {"Bob", 32, 85000},
        {"Charlie", 25, 65000},
        {"Diana", 29, 90000},
        {"Eve", 25, 70000},
    }
 
    // 多级排序:先按年龄,年龄相同则按薪资降序
    sort.SliceStable(people, func(i, j int) bool {
        if people[i].Age == people[j].Age {
            return people[i].Salary > people[j].Salary // 同年龄,薪资高排前面
        }
        return people[i].Age < people[j].Age // 不同年龄,年龄小排前面
    })
 
    fmt.Println("Sorted by age, then salary (stable sort):")
    printPeople(people)
}

2.5.3 检查是否已按自定义规则排序

sort.SliceIsSorted(slice, lessFunc) 是检查切片是否已按自定义规则排序。比较函数与sort.Slice使用相同的逻辑:ages[i] <= ages[j]表示升序。(注意用<= 而不是<:<= 允许相等元素,< 会认为相等元素是"未排序")。

返回 true 表示切片已按该规则排序,false 表示未排序

go 复制代码
package main
 
import (
    "fmt"
    "sort"
)
 
func main() {
    ages := []int{25, 30, 35, 40, 45}
 
    isSorted := sort.SliceIsSorted(ages, func(i, j int) bool {
        return ages[i] <= ages[j] // 升序规则(注意用 <= 而不是 <)
    })
    fmt.Printf("Ages %v is sorted: %t\n", ages, isSorted)
}

2.6 数组和切片的区别

对比项 数组 切片
类型 [N]T []T
长度 固定,编译期确定,是类型的一部分 动态,运行时由 len 决定
示例 [3]int、[5]string []int、[]string
是否可增长 不可以 可以用 append 扩容
内存情况 一段连续内存,直接存放元素 切片本身不直接存数据,它只是一个"切片头",通常包含:- 指向底层数组的指针- 长度 len- 容量 cap
赋值情况 - 值类型,赋值或传参时会复制整个数组 。- [3]int 和 [4]int 是不同类型 ,不能互相赋值 - 值类型,但复制的是切片头,底层数组仍然共享 。- []int 不关心长度,任意长度的 []int 都是同一类型,可以相互赋值
比较 如果元素类型可比较,数组可以用 == 比较、 只能和 nil 比较,不能直接用 == 比较两个切片。Go 1.21 后可以用 slices.Equal(s1, s2) 比较切片内容。

3. Map

  • Map 是一种无序的键值对集合,零值是 nil,可按「键」快速查找「值」。
  • Map 是引用类型,赋值或传参时传递的是引用(底层指向同一个哈希表)。
  • Map 的键必须是可比较的类型 (如布尔、数字、字符串、指针、通道、接口、结构体、数组等),不能是slice切片、map、函数(因为它们不可比较);值可以是任意类型。
  • Map 只能与 nil 比较,不能互相比较。

3.1 声明和初始化

  • 声明一个 nil map

只声明不初始化的 map 是 nil,可以读、可以 len 、可以 range 、可以 delete ,但****不能写入元素。

go 复制代码
// 不可以直接对 nil map 赋值
var m map[string]int // m 是 nil map
m["a"] = 1             // panic: assignment to entry in nil map
  • 先声明再赋值

声明后必须让 map 变成非 nil,才能往里面写键值对。

go 复制代码
// 方式一:声明后用 make 初始化
var m map[string]int
m = make(map[string]int) // 初始化
m["a"] = 1               // OK

// 方式二:声明后直接赋一个字面量 map
var m map[string]int
m = map[string]int{"a": 1} // OK
m["b"] = 2                 // OK

// 方式三:声明时直接初始化
m := make(map[string]int)
m["a"] = 1
// 或
m := map[string]int{"a": 1}

nil map 与空 map

  • var m map[string]int 是 nil map,不能写 ,但可读、可 len、可 range。只声明,没有分配底层哈希表
  • make(map[string]int) 是空 map,可读写 。调用 make,分配了底层哈希表。
  • 字面量
go 复制代码
m := map[string]int{
    "apple":  1,
    "banana": 2,
}
  • make创建
go 复制代码
m := make(map[string]int)      // 空 map,可读写
m2 := make(map[string]int, 10) // 预分配容量约 10

m["a"] = 1

3.2 基本操作

go 复制代码
// 1.增/改
m["orange"] = 3
m["apple"] = 100 // 已存在则覆盖

// 2.查
// 方式一:不存在时返回零值
v := m["apple"] 

// 方式二:判断键是否存在,用"逗号 ok"语法
v, ok := m["apple"]
if ok {
    fmt.Println("存在,值为", v)
} else {
    fmt.Println("不存在")
}

// 3.删: 
delete(m, "apple")

// 4.遍历
for k, v := range m {
    fmt.Println(k, v)
}
// 只遍历键
for k := range m { ... }
// 只遍历值
for _, v := range m { ... }

// 5.获取长度
length :=len(m)
  • 删除不存在的键不会报错。
  • 遍历顺序是随机的,每次运行可能不同,不能依赖顺序。
  • map 不是并发安全的 。多个 goroutine 同时读写会触发 panic。可以使用 sync.RWMutex 加锁;使用 sync.Map(适合读多写少或键值对固定的场景);使用分片锁等自定义方案解决。
相关推荐
专注仿真1 小时前
ThinkPHP6 API接口开发实例:从零搭建RESTful风格接口
后端·php·restful
开心猴爷1 小时前
应对iOS审核4.3:可以使用 IpaGuard 来给属性名混淆的技术细节与平衡之道
后端·ios
1m555555551 小时前
RAG 工程实战(五):正确知识已经进入 Prompt,为什么大模型还是会答错?
后端·面试
易小尊1 小时前
我的模型在 65% 以上时准得可怕,在 50% 时像在掷硬币
后端
用户2788607285671 小时前
fallback 会盖掉平台提示词:system、空数组和 systemAppend 的差别
前端·后端
iOS开发上架哦1 小时前
iOS应用耗电测试:使用Energy Log、Keymob和Instruments进行性能监控与能源消耗分析
后端·ios
掘金挖土1 小时前
前端手摸手跑路之 AI 应用开发(十一)
前端·后端
用户9888321871742 小时前
从“限流数字”到可维护策略:ASP.NET Core 固定窗口限流的落地边界
后端
IT_陈寒2 小时前
Vue动态组件缓存的坑你踩过了吗?
前端·人工智能·后端