Go 入门到精通-08-复合类型之数组与切片

目录

🟢 Go 入门到精通 - 复合类型之数组与切片

📅 更新于 2026年7月 | ✍️ 原创文章,转载请注明出处 | 🧑‍💻 作者:布朗克168



一、引言:Go复合类型的基石

如果说基本类型是Go语言的"原子",那么复合类型就是"分子"。在Go的复合类型体系中有四大天王:

复制代码
┌──────────────────────────────────────────────────────────────┐
│                  Go 复合类型四大天王                            │
├──────────────┬──────────────┬──────────────┬─────────────────┤
│    数组       │    切片       │    Map       │    Struct       │
│   [N]T       │    []T       │  map[K]V     │   struct{}      │
├──────────────┼──────────────┼──────────────┼─────────────────┤
│  固定长度     │  动态长度     │  键值对       │  字段集合        │
│  值类型       │  引用语义     │  引用类型     │  值类型          │
│  类型的一部分  │  最常用!     │  无序         │  自定义类型      │
└──────────────┴──────────────┴──────────────┴─────────────────┘

数组和切片是这四大天王中关系最紧密的两个------切片建立在数组之上,理解了它们的关系,才算真正入门了Go。

🎯 核心认知:数组是切片的"地基",切片是数组的"智能窗口"。90%的Go代码使用切片而非数组,但理解数组才能理解切片的底层行为。


二、数组:固定长度的序列

2.1 数组的声明与初始化

在Go中,数组是固定长度、同类型元素的序列。长度一旦确定,不可改变。

go 复制代码
package main

import "fmt"

func main() {
    // === 声明方式 ===

    // 1. 声明但不初始化(元素为零值)
    var arr1 [5]int
    fmt.Println(arr1) // [0 0 0 0 0]

    // 2. 声明并初始化
    var arr2 [5]int = [5]int{1, 2, 3, 4, 5}
    fmt.Println(arr2) // [1 2 3 4 5]

    // 3. 短声明 + 初始化
    arr3 := [5]int{10, 20, 30, 40, 50}
    fmt.Println(arr3) // [10 20 30 40 50]

    // 4. 让编译器推断长度
    arr4 := [...]int{1, 2, 3, 4, 5, 6} // 长度自动为6
    fmt.Printf("arr4 长度: %d, 值: %v\n", len(arr4), arr4)
    // 输出:arr4 长度: 6, 值: [1 2 3 4 5 6]

    // 5. 指定索引初始化
    arr5 := [10]int{0: 1, 4: 5, 9: 10}
    fmt.Println(arr5)
    // 输出:[1 0 0 0 5 0 0 0 0 10]

    // 6. 多维数组
    matrix := [3][4]int{
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
    }
    fmt.Println(matrix[1][2]) // 7
}

2.2 长度是类型的一部分

这是Go数组最核心也最容易被忽视的特性:

go 复制代码
var a [3]int
var b [5]int

// a 的类型是 [3]int
// b 的类型是 [5]int
// [3]int 和 [5]int 是两种不同的类型!

// a = b // ❌ 编译错误:cannot use b (type [5]int) as type [3]int

这意味着:

特性 影响
函数参数 func f(arr [5]int) 只能接收长度为5的数组
类型断言 不能将[3]int断言为[5]int
接口匹配 [3]int[5]int满足不同的接口约束
实用后果 这导致数组作为函数参数几乎不可用------因为长度写死了

这就是为什么Go中切片远比数组常用

2.3 数组的遍历

go 复制代码
arr := [5]string{"🍎", "🍌", "🍊", "🍇", "🍓"}

// 方式1:传统for
for i := 0; i < len(arr); i++ {
    fmt.Printf("arr[%d] = %s\n", i, arr[i])
}

// 方式2:range迭代
for i, fruit := range arr {
    fmt.Printf("第%d个水果: %s\n", i+1, fruit)
}

// 方式3:只关心值
for _, fruit := range arr {
    fmt.Print(fruit, " ")
}

2.4 数组作为函数参数

go 复制代码
package main

import "fmt"

// 数组是值类型------传递时会发生完整拷贝!
func modifyArray(arr [5]int) {
    arr[0] = 999 // 只修改了副本
    fmt.Println("函数内:", arr) // [999 2 3 4 5]
}

func main() {
    original := [5]int{1, 2, 3, 4, 5}
    modifyArray(original)
    fmt.Println("函数外:", original) // [1 2 3 4 5] 不受影响!

    // 如果数组很大(如[1000000]int),复制开销巨大
    // 解决方法:传递数组指针
}

// ✅ 传递指针可以避免拷贝
func modifyArrayPtr(arr *[5]int) {
    arr[0] = 999 // 通过指针修改原数组
}

⚠️ 值传递的开销 :一个[1000000]int数组大小约8MB,每次传参都要完整复制。这就是为什么Go中几乎不直接使用大数组。


三、切片:动态数组的完美实现

3.1 切片是什么

切片(slice)是Go中最常用的数据结构。它是一个对底层数组的引用,提供动态大小、灵活的子序列视图。

go 复制代码
// 切片声明:没有指定长度
var s []int  // 这就是切片!nil切片,len=0, cap=0

// 对比:
var arr [5]int // 数组,指定了长度

3.2 切片的底层结构

每个切片在运行时由三个字段组成:

复制代码
┌──────────────────────────────────────────────────┐
│                  切片结构体 (reflect.SliceHeader)   │
├──────────┬──────────┬─────────────────────────────┤
│   ptr    │   len    │          cap                │
│  *T      │   int    │          int                │
├──────────┼──────────┼─────────────────────────────┤
│ 指向底层  │ 当前长度  │  容量(从ptr开始到底层数组末尾) │
│ 数组元素  │          │                             │
└──────────┴──────────┴─────────────────────────────┘

图示理解

复制代码
底层数组: [A] [B] [C] [D] [E] [F] [G] [H]
           ↑                   ↑
           ptr                底层数组末尾
           |-- len=3 --|
           |----------- cap=7 -----------|
           切片 s := arr[0:3]
           s[0]=A, s[1]=B, s[2]=C
           剩余容量 4 个位置可用于append
go 复制代码
package main

import "fmt"

func main() {
    arr := [8]string{"A", "B", "C", "D", "E", "F", "G", "H"}

    // 创建切片:arr[0:3]
    s := arr[0:3]

    fmt.Printf("len=%d, cap=%d, %v\n", len(s), cap(s), s)
    // 输出:len=3, cap=8, [A B C]

    // 访问切片元素
    fmt.Println(s[0]) // A
    fmt.Println(s[2]) // C

    // 切片共享底层数组
    s[0] = "X"
    fmt.Println("arr:", arr) // [X B C D E F G H]
    fmt.Println("s:  ", s)   // [X B C]
    // arr也被修改了!
}

3.3 创建切片的多种方式

go 复制代码
package main

import "fmt"

func main() {
    // === 方式1:字面量(底层自动创建数组) ===
    s1 := []int{1, 2, 3, 4, 5}
    fmt.Printf("s1: len=%d cap=%d %v\n", len(s1), cap(s1), s1)
    // s1: len=5 cap=5 [1 2 3 4 5]

    // === 方式2:从数组切片 ===
    arr := [8]int{10, 20, 30, 40, 50, 60, 70, 80}
    s2 := arr[1:5] // 索引1到4(左闭右开)
    fmt.Printf("s2: len=%d cap=%d %v\n", len(s2), cap(s2), s2)
    // s2: len=4 cap=7 [20 30 40 50]

    // === 方式3:从切片切片 ===
    s3 := s2[1:3] // s2是切片,s3也是切片
    fmt.Printf("s3: len=%d cap=%d %v\n", len(s3), cap(s3), s3)
    // s3: len=2 cap=6 [30 40]

    // === 方式4:make函数 ===
    s4 := make([]int, 5)    // len=5, cap=5
    s5 := make([]int, 5, 10) // len=5, cap=10
    fmt.Printf("s4: len=%d cap=%d\n", len(s4), cap(s4))
    fmt.Printf("s5: len=%d cap=%d %v\n", len(s5), cap(s5), s5)
    // s5: len=5 cap=10 [0 0 0 0 0]

    // === 方式5:nil切片 vs 空切片 ===
    var nilSlice []int          // nil切片,len=0, cap=0, ptr=nil
    emptySlice := []int{}       // 空切片,len=0, cap=0, ptr有值
    makeEmpty := make([]int, 0) // 空切片,len=0, cap=0

    fmt.Println(nilSlice == nil)  // true
    fmt.Println(emptySlice == nil) // false
}

3.4 make函数详解

make是Go中专门用于创建切片、map、channel的内置函数:

go 复制代码
make([]T, len)       // 创建len=cap的切片
make([]T, len, cap)  // 创建指定len和cap的切片
go 复制代码
package main

import "fmt"

func main() {
    // 场景1:知道大概需要多少元素------预分配容量
    users := make([]string, 0, 1000) // 预分配1000容量,避免多次扩容
    for i := 0; i < 1000; i++ {
        users = append(users, fmt.Sprintf("user_%d", i))
    }
    fmt.Printf("最终: len=%d cap=%d\n", len(users), cap(users))
    // 输出:最终: len=1000 cap=1000(可能更大,取决于扩容策略)

    // 场景2:需要指定初始值------带长度的make
    scores := make([]int, 10) // 前10个位置都是零值0
    for i := 0; i < 10; i++ {
        scores[i] = i * 10 // 直接赋值,不需要append
    }
    fmt.Println(scores) // [0 10 20 30 40 50 60 70 80 90]
}

四、切片的核心操作

4.1 切片截取

切片的截取语法[low:high:max]是最灵活的特性之一:

go 复制代码
package main

import "fmt"

func main() {
    arr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    // 基本截取 [low:high] ------ 左闭右开
    s1 := arr[2:5]  // 索引 2,3,4
    fmt.Printf("arr[2:5]: len=%d cap=%d %v\n", len(s1), cap(s1), s1)
    // arr[2:5]: len=3 cap=8 [2 3 4]

    // 省略low:从0开始
    s2 := arr[:5]   // arr[0:5]
    fmt.Println("arr[:5]:", s2) // [0 1 2 3 4]

    // 省略high:到末尾
    s3 := arr[5:]   // arr[5:10]
    fmt.Println("arr[5:]:", s3) // [5 6 7 8 9]

    // 省略两者:复制整个数组为切片
    s4 := arr[:]
    fmt.Println("arr[:]:", s4) // [0 1 2 3 4 5 6 7 8 9]

    // 三索引切片 [low:high:max] ------ 控制容量
    // 限制cap,防止子切片访问超出预期的底层数组
    s5 := arr[2:5:6] // len=3, cap=4 (max-low=6-2)
    fmt.Printf("arr[2:5:6]: len=%d cap=%d %v\n", len(s5), cap(s5), s5)
    // arr[2:5:6]: len=3 cap=4 [2 3 4]

    // ❌ max不能超过底层数组长度,high不能超过max
    // s6 := arr[2:5:4] // 编译错误:invalid slice index: 5 > 4
}

截取规则速查表

表达式 len cap 含义
s[:] len(s) cap(s) 完整切片
s[:n] n cap(s) 前n个
s[n:] len(s)-n cap(s)-n 从第n个到末尾
s[m:n] n-m cap(s)-m 从m到n-1
s[m:n:k] n-m k-m 限制容量为k-m

4.2 append操作

append是切片最常用的操作,用于向切片末尾追加元素:

go 复制代码
package main

import "fmt"

func main() {
    // 基本append
    s := []int{1, 2, 3}
    s = append(s, 4)
    fmt.Println(s) // [1 2 3 4]

    // 追加多个元素
    s = append(s, 5, 6, 7)
    fmt.Println(s) // [1 2 3 4 5 6 7]

    // 追加另一个切片(需要...展开)
    s2 := []int{8, 9, 10}
    s = append(s, s2...)
    fmt.Println(s) // [1 2 3 4 5 6 7 8 9 10]

    // ⚠️ 注意:append的返回值必须接收!
    // append可能触发扩容,返回新的切片头
    // 如果不接收返回值,追加可能丢失

    // ❌ 错误做法
    _ = append(s, 100)
    fmt.Println(s) // [1 2 3 4 5 6 7 8 9 10] ------ 没有100!

    // ✅ 正确做法
    s = append(s, 100)
    fmt.Println(s) // [1 2 3 4 5 6 7 8 9 10 100]
}

append的核心规则

复制代码
如果 len + 新元素数 <= cap:
    → 直接在底层数组上追加,返回原切片头(但len增加)
如果 len + 新元素数 > cap:
    → 分配新的底层数组,复制原数据,追加新元素,返回新切片头

4.3 copy操作

copy用于将一个切片的数据复制到另一个切片:

go 复制代码
package main

import "fmt"

func main() {
    src := []int{1, 2, 3, 4, 5}
    dst := make([]int, 3) // len=3

    // copy返回实际复制的元素个数(取min(len(src), len(dst)))
    n := copy(dst, src)
    fmt.Printf("复制了%d个元素: %v\n", n, dst)
    // 复制了3个元素: [1 2 3]

    // 如果dst更大
    dst2 := make([]int, 10)
    n2 := copy(dst2, src)
    fmt.Printf("复制了%d个元素: %v\n", n2, dst2)
    // 复制了5个元素: [1 2 3 4 5 0 0 0 0 0]

    // 实战:深拷贝切片
    original := []int{10, 20, 30}
    copied := make([]int, len(original))
    copy(copied, original)
    copied[0] = 999
    fmt.Println("original:", original) // [10 20 30] 没变
    fmt.Println("copied:  ", copied)   // [999 20 30]

    // vs 直接赋值(共享底层数组)
    shared := original
    shared[0] = 999
    fmt.Println("original:", original) // [999 20 30] 被改变了!
}

五、切片扩容机制深度解析

5.1 Go 1.18前后的扩容策略

切片的扩容机制是Go面试中最常见的问题之一。Go 1.18是一个重要分水岭:

版本 策略
Go 1.17及以前 oldCap < 1024 时容量翻倍;oldCap >= 1024 时增加25%
Go 1.18开始 更平滑的过渡:<256时翻倍;>=256时使用公式 newcap += (newcap + 3*threshold) / 4

Go 1.18+的扩容算法(简化版)

go 复制代码
// 扩容的核心逻辑(伪代码)
func growslice(oldCap, newLen int) int {
    newcap := oldCap
    doublecap := oldCap * 2
    
    if newLen > doublecap {
        newcap = newLen
    } else {
        const threshold = 256
        if oldCap < threshold {
            newcap = doublecap
        } else {
            // 平滑过渡公式
            for newcap < newLen {
                newcap += (newcap + 3*threshold) / 4
            }
            // 内存对齐修正(略)
        }
    }
    return newcap
}

5.2 扩容示例演示

go 复制代码
package main

import "fmt"

func main() {
    s := make([]int, 0)

    // 追踪扩容过程
    prevCap := cap(s)
    for i := 1; i <= 2000; i++ {
        s = append(s, i)
        if cap(s) != prevCap {
            fmt.Printf("len=%-5d cap=%-5d 扩容倍数=%.2f\n",
                len(s), cap(s), float64(cap(s))/float64(prevCap))
            prevCap = cap(s)
        }
    }
}

// Go 1.18+ 可能的输出:
// len=1     cap=1     扩容倍数=+Inf
// len=2     cap=2     扩容倍数=2.00   ← 翻倍
// len=3     cap=4     扩容倍数=2.00
// len=5     cap=8     扩容倍数=2.00
// ...
// len=256   cap=512   扩容倍数=2.00   ← 256以内翻倍
// len=513   cap=848   扩容倍数=1.66   ← 之后平滑增长
// len=849   cap=1280  扩容倍数=1.51
// len=1281  cap=1792  扩容倍数=1.40
// len=1793  cap=2304  扩容倍数=1.29

💡 实践启示 :如果能预估切片最终大小,用make([]T, 0, expectedSize)预分配容量,可以避免多次扩容带来的内存分配和数据复制开销。这在性能敏感的场景中非常重要。


六、切片作为函数参数

切片的参数传递语义经常引起混淆------切片本身是值传递,但它包含指向底层数组的指针

go 复制代码
package main

import "fmt"

// 修改切片元素------会影响原切片(共享底层数组)
func modifyElement(s []int) {
    s[0] = 999 // 通过ptr修改底层数组
}

// append操作------可能不影响原切片(取决于是否扩容)
func appendElement(s []int) {
    s = append(s, 100) // 如果扩容,s指向新数组,原切片不受影响
    fmt.Println("函数内append后:", s)
}

// 要修改切片本身(长度/容量),需要传指针
func appendAndReturn(s *[]int) {
    *s = append(*s, 200)
}

func main() {
    // 测试1:修改元素
    s1 := []int{1, 2, 3}
    modifyElement(s1)
    fmt.Println("modifyElement后:", s1) // [999 2 3] ------ 受影响

    // 测试2:append(可能触发扩容)
    s2 := make([]int, 3, 3) // len=3, cap=3,一append就扩容
    s2[0], s2[1], s2[2] = 1, 2, 3
    appendElement(s2)
    fmt.Println("appendElement后:", s2) // [1 2 3] ------ 不变!因为扩容了

    // 测试3:append(不扩容的情况)
    s3 := make([]int, 3, 10) // len=3, cap=10,append不扩容
    s3[0], s3[1], s3[2] = 1, 2, 3
    appendElement(s3)
    fmt.Println("appendElement后(有容量):", s3) // [1 2 3] ------ 但函数内外len不同!

    // 测试4:通过指针修改切片本身
    s4 := []int{1, 2, 3}
    appendAndReturn(&s4)
    fmt.Println("appendAndReturn后:", s4) // [1 2 3 200]
}

⚠️ 关键记忆点append是否影响调用者的切片,取决于是否触发扩容。这是Go中最容易出错的细节之一。安全做法:永远把append的返回值赋回原变量


七、数组 vs 切片完整对比

维度 数组 [N]T 切片 []T
长度 固定,编译时确定 动态,运行时可变
类型 [3]int[5]int 所有[]int是同类型
值/引用 值类型(赋值=完整拷贝) 引用语义(赋值=共享底层数组)
内存布局 连续内存块 24字节头 + 底层数组(堆上)
零值 所有元素为零值 nil(len=0, cap=0, ptr=nil)
比较 == 逐元素比较(元素类型可比) 只能与nil比较
作为参数 值拷贝(大数组昂贵) 只拷贝24字节头(高效)
创建方式 var arr [5]int make([]int, 5), []int{...}
使用频率 极少直接使用 ⭐ 日常主力
适用场景 长度绝对不变的固定集合 几乎所有场景
go 复制代码
// 数组可以直接比较
a1 := [3]int{1, 2, 3}
a2 := [3]int{1, 2, 3}
fmt.Println(a1 == a2) // true

// ❌ 切片不能比较(编译错误)
// s1 := []int{1,2,3}
// s2 := []int{1,2,3}
// fmt.Println(s1 == s2) // invalid operation: s1 == s2

// ✅ 切片只能与nil比较
var s []int
fmt.Println(s == nil) // true

// 如需比较两个切片,使用 reflect.DeepEqual 或手动遍历

八、常见陷阱与避坑指南

陷阱1:子切片共享底层数组(高频!)

这是Go切片中最经典的坑,无数Go开发者都踩过:

go 复制代码
package main

import "fmt"

func main() {
    // 场景:创建一个大切片,然后取一个"小窗口"
    original := make([]int, 0, 1000000) // cap=100万
    for i := 0; i < 100; i++ {
        original = append(original, i)
    }

    // 取出前10个
    sub := original[:10]

    // 💣 问题:sub的底层数组仍然是那个100万元素的巨大数组
    // 即使original本身不再被引用,GC也无法回收这100万元素的内存
    fmt.Printf("sub: len=%d cap=%d\n", len(sub), cap(sub))
    // sub: len=10 cap=1000000 ← 注意cap!

    // ✅ 解决方案:使用copy创建独立切片
    safe := make([]int, 10)
    copy(safe, original[:10])
    fmt.Printf("safe: len=%d cap=%d\n", len(safe), cap(safe))
    // safe: len=10 cap=10 ← 干净的独立数据

    // original可以安全回收了
}

陷阱2:append后原切片数据"消失"

go 复制代码
package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := a       // b和a共享底层数组
    b = append(b, 4) // b可能扩容到新数组

    // 如果扩容了,a和b指向不同的底层数组
    b[0] = 999
    fmt.Println("a:", a) // [1 2 3] ------ a不受影响
    fmt.Println("b:", b) // [999 2 3 4]
}

陷阱3:range遍历中修改切片

go 复制代码
// ❌ 在range中append可能导致混乱
s := []int{1, 2, 3}
for i, v := range s {
    fmt.Println(i, v)
    s = append(s, v*2)
}
// 输出:0 1 \n 1 2 \n 2 3
// range在开始时就确定了迭代次数,后续append不影响本次遍历

// ✅ 如果需要动态增长,用传统for
for i := 0; i < len(s); i++ {
    s = append(s, s[i]*2)
    if len(s) > 100 { break }
}

陷阱4:nil切片和空切片的行为差异

go 复制代码
var nilSlice []int       // nil
emptySlice := []int{}     // 非nil,但len=0

// 大多数操作对两者无差别
fmt.Println(len(nilSlice))   // 0
fmt.Println(len(emptySlice)) // 0
nilSlice = append(nilSlice, 1) // ✅ 可以对nil切片append

// 但在JSON序列化中有差异
import "encoding/json"
j1, _ := json.Marshal(nilSlice)   // null
j2, _ := json.Marshal(emptySlice) // []

避坑清单

陷阱 症状 解决方案
子切片cap过大 内存泄漏 copy创建独立切片
append不接收返回值 数据丢失 始终s = append(s, ...)
切片共享底层 意外修改 需要独立数据时用copy
range中append 行为不确定 用传统for循环
nil切片JSON 输出null而非\[\] make([]T, 0)初始化

九、实战案例

案例1:去重函数

go 复制代码
package main

import "fmt"

// 保持顺序的去重
func unique(s []int) []int {
    seen := make(map[int]bool)
    result := make([]int, 0, len(s))

    for _, v := range s {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}

func main() {
    nums := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}
    fmt.Println(unique(nums)) // [3 1 4 5 9 2 6]
}

案例2:高效拼接字符串切片

go 复制代码
package main

import (
    "fmt"
    "strings"
)

func main() {
    words := []string{"Go", "是", "最好的", "语言", "!"}

    // ❌ 低效:循环中使用 + 拼接
    // result := ""
    // for _, w := range words { result += w }

    // ✅ 高效:使用 strings.Join
    result := strings.Join(words, " ")
    fmt.Println(result) // Go 是 最好的 语言 !

    // 或者使用 strings.Builder
    var builder strings.Builder
    builder.Grow(100) // 预分配
    for _, w := range words {
        builder.WriteString(w)
    }
    fmt.Println(builder.String())
}

案例3:实现一个简单的Stack

go 复制代码
package main

import "fmt"

type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    return s.items[len(s.items)-1], true
}

func (s *Stack[T]) Size() int {
    return len(s.items)
}

func main() {
    stack := Stack[string]{}
    stack.Push("🍎")
    stack.Push("🍌")
    stack.Push("🍊")

    fmt.Println("大小:", stack.Size()) // 3

    if top, ok := stack.Peek(); ok {
        fmt.Println("栈顶:", top) // 🍊
    }

    for item, ok := stack.Pop(); ok; item, ok = stack.Pop() {
        fmt.Println("弹出:", item)
    }
    // 弹出: 🍊
    // 弹出: 🍌
    // 弹出: 🍎
}

十、小结与预告

📝 核心知识点回顾

知识点 核心内容
数组 固定长度,长度是类型一部分,值类型,完整拷贝
切片结构 ptr + len + cap,24字节头,引用底层数组
创建方式 字面量、make、从数组/切片截取
截取规则 [low:high:max],左闭右开,cap=cap(原)-low
append 超过cap触发扩容,必须接收返回值
copy 取min(len(src),len(dst)),深拷贝
扩容策略 1.18前:<1024翻倍,>=1024增25%;1.18后更平滑
共享底层 子切片与原切片共享底层数组,注意内存泄漏
参数传递 切片本身值传递,但指向共享底层数组

🤔 互动问题

  1. 为什么Go设计者要让[3]int[5]int是不同类型?这带来了哪些好处和困扰?
  2. 在实际项目中,你是否遇到过因子切片共享底层数组导致的内存泄漏?是如何发现的?
  3. 如果你来设计扩容策略,你会选择固定的倍增因子还是Go 1.18后的平滑方案?为什么?

📖 下篇预告

下一篇我们将学习另一个核心复合类型------Map(映射/字典)。Map的增删改查、comma ok模式、无序遍历、并发安全问题,以及如何用map实现Set------这些知识点将让你的Go数据处理能力再上一个台阶!


📚 参考资料


💡 学习建议 :切片是Go中最核心的数据结构,理解其底层结构(ptr/len/cap)是写出正确、高效Go代码的前提。强烈建议读者自己写代码实验append的扩容行为,并用fmt.Printf("%p", s)观察底层数组地址变化。动手实践远比死记硬背有效!

相关推荐
fliter1 小时前
从手写 HTTP/1.1 到拆开 HTTP/2
后端
CaffeinePro1 小时前
FastAPI自动接口文档定制与美化、权限管控
后端·fastapi
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第151题】【06_Spring篇】第11题:说一下 Spring Bean 的生命周期?
java·开发语言·后端·spring·面试
广州浮点FLOATLIC2 小时前
Creo 许可证利用率怎么优化:制造企业该先看共享规则,还是先看模块占用结构
java·开发语言
wuyk5552 小时前
21. 嵌入式面试避坑指南:sizeof 是关键字,不是函数!
c语言·开发语言·stm32·单片机·嵌入式硬件
2601_962440842 小时前
计算机毕业设计之jsp教室管理系统
java·开发语言·笔记·分布式·算法·课程设计·推荐算法
赫媒派3 小时前
Gin 12年零破坏API,架构哲学如何练成?
后端·go·gin
fliter4 小时前
Arborium:把 tree-sitter 语法高亮打包成 Rust 文档生态的基础设施
后端
张三丰24 小时前
不会写代码的高管用Claude Code两天上线新程序,工程师接手后发现:一个Bug,让AI一天烧掉一个月服务器费!
后端