【Golang】--- Slice

发布于:2025-09-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

切片解析

  • Go语言切片(Slice)
    • 一、切片的本质:为什么需要切片?
    • 二、切片的声明与初始化:3种核心方式
      • 2.1 方式1:零值切片(nil切片)——未初始化的切片
        • 代码示例
        • 关键结论:
      • 2.2 方式2:基于数组创建切片——切片是数组的视图
        • 代码示例
        • 切片表达式规则:
        • 注意事项:
      • 2.3 方式3:make函数创建切片——直接指定长度和容量
        • 代码示例
        • 关键说明:
    • 三、切片的核心操作:赋值、遍历、拷贝、删除
      • 3.1 切片的赋值:引用传递,共享底层数组
        • 代码示例
        • 原理分析:
        • 注意事项:
      • 3.2 切片的遍历:2种常用方式
        • 代码示例
        • 遍历技巧:
      • 3.3 切片的拷贝:copy函数实现深拷贝
        • 代码示例
        • copy函数的关键特性:
        • 常见用法:
      • 3.4 切片的删除:append函数实现元素删除
        • 代码示例
        • 通用删除公式:
        • 注意事项:
    • 四、切片的扩容机制:append函数与容量增长规则
      • 4.1 append函数的基本用法
        • 代码示例
        • 关键特性:
      • 4.2 切片的扩容规则:Go 1.18前后的差异
        • 4.2.1 Go 1.18 之前的扩容规则
        • 4.2.2 Go 1.18 之后的扩容规则
      • 4.3 扩容的性能影响与优化建议
        • 扩容的性能开销:
        • 优化建议
    • 五、切片的实战技巧:排序、nil切片处理、嵌套切片
      • 5.1 切片排序:使用sort包
        • 代码示例
      • 5.2 nil切片的安全处理
        • 代码示例
        • 关键结论:
      • 5.3 嵌套切片:切片的元素是切片
        • 代码示例:
        • 注意事项:
    • 六、切片常见问题与避坑指南
      • 6.1 坑点1:切片赋值后共享底层数组,修改元素导致意外修改
      • 6.2 坑点2:切片扩容后,原切片与新切片指向不同底层数组
      • 6.3 坑点4:使用 `for range` 遍历切片时修改元素无效
    • 七、总结

Go语言切片(Slice)

在Go语言中,切片(Slice)是最常用的数据结构之一,它作为动态数组的实现,解决了数组长度固定的局限性,同时保留了数组的高效性。

一、切片的本质:为什么需要切片?

在学习切片之前,我们首先要理解数组(Array) 的局限性——Go语言中的数组是长度固定的连续内存空间,一旦声明,长度便无法修改。例如:

// 数组a的长度为5,无法动态添加或删除元素
var a [5]int = [5]int{1,2,3,4,5}

当业务场景需要动态调整数据长度时,数组就显得力不从心。而切片(Slice) 作为数组的"视图",本质是一个包含三个字段的结构体:

  • 指针(Pointer):指向底层数组的起始位置
  • 长度(Length):切片当前包含的元素个数(len() 函数获取)
  • 容量(Capacity):切片底层数组从起始位置到末尾的元素个数(cap() 函数获取)

这种结构既保留了数组的内存连续性(高效访问),又支持动态长度调整(动态扩容),成为Go语言中处理序列数据的首选。

二、切片的声明与初始化:3种核心方式

切片的声明和初始化是使用切片的基础,不同场景下需要选择合适的方式。以下结合代码示例详细讲解。

2.1 方式1:零值切片(nil切片)——未初始化的切片

通过 var 关键字声明切片但不初始化时,切片会处于nil状态(指针为nil,长度和容量均为0)。

代码示例
package main

import "fmt"

func main() {
    // 1. 仅声明,未初始化:nil切片
    var a []int          
    // 2. 声明并初始化空切片(非nil)
    var b []int = []int{} 
    // 3. make创建空切片(非nil)
    c := make([]int, 0)

    // 判断是否为nil
    if a == nil {
        fmt.Println("a==nil") // 输出:a==nil
    }
    fmt.Println("a:", a, "len(a):", len(a), "cap(a):", cap(a)) // 输出:a: [] len(a): 0 cap(a): 0

    if b == nil {
        fmt.Println("b==nil") // 无输出(b非nil)
    }
    fmt.Println("b:", b, "len(b):", len(b), "cap(b):", cap(b)) // 输出:b: [] len(b): 0 cap(b): 0

    if c == nil {
        fmt.Println("c==nil ") // 无输出(c非nil)
    }
    fmt.Println("c:", c, "len(c):", len(c), "cap(c):", cap(c)) // 输出:c: [] len(c): 0 cap(c): 0
}
关键结论:
  • nil切片:仅通过 var s []T 声明,未初始化(如示例中的 a),指针为nil,常用于表示"未赋值的空状态"(如函数返回错误时的空结果)。
  • 空切片:通过 []T{}make([]T, 0) 创建(如示例中的 bc),指针非nil,仅长度为0,常用于表示"明确的空集合"(如查询结果为空但无错误)。
  • 判断切片是否为空必须用 len(s) == 0,而非 s == nil!因为空切片的 len 也是0,但 s != nil,若用 s == nil 判断会误判。

2.2 方式2:基于数组创建切片——切片是数组的视图

切片可以通过数组切片表达式array[low:high])从数组中截取一部分,形成新的切片。切片的底层数组就是原数组,因此修改切片会影响原数组。

代码示例
package main

import "fmt"

func main() {
    // 1. 定义一个长度为5的数组
    a := [5]int{55, 56, 57, 58, 59}
    // 2. 基于数组创建切片:左闭右开区间 [low, high)
    b := a[1:4] // 截取数组索引1、2、3的元素(56,57,58)

    fmt.Println("切片b:", b)          // 输出:切片b: [56 57 58]
    fmt.Println("b的长度len(b):", len(b)) // 输出:b的长度len(b): 3(元素个数)
    fmt.Println("b的容量cap(b):", cap(b)) // 输出:b的容量cap(b): 4(从索引1到数组末尾共4个元素:56,57,58,59)
    fmt.Printf("b的类型: %T\n", b)     // 输出:b的类型: []int(切片类型)

    // 3. 切片再次切片(基于切片b创建新切片c)
    c := b[:len(b)] // 截取b的所有元素(等同于b[0:3])
    fmt.Println("切片c:", c)          // 输出:切片c: [56 57 58]
    fmt.Printf("c的类型: %T\n", c)     // 输出:c的类型: []int
}
切片表达式规则:
  • 完整格式array[low:high:max](可选max参数,限制切片的最大容量为 max - low
  • 省略规则
    • low 省略时默认0(如 a[:4] 等同于 a[0:4]
    • high 省略时默认数组长度(如 a[1:] 等同于 a[1:5]
    • lowhigh 都省略时表示整个数组(如 a[:] 等同于 a[0:5]
  • 容量计算
    • 无max时:cap(切片) = len(数组) - low
    • 有max时:cap(切片) = max - low(避免切片越界访问原数组后续元素)
注意事项:
  • 切片是数组的视图,修改切片元素会同步修改原数组。例如:
    b[0] = 100 // 修改切片b的第一个元素
    fmt.Println(a) // 输出:[55 100 57 58 59](原数组a的索引1元素被修改)
    

2.3 方式3:make函数创建切片——直接指定长度和容量

make 函数是Go语言用于创建引用类型(切片、映射、通道)的内置函数,创建切片时可以直接指定长度(len)容量(cap),底层会自动分配一个匿名数组。

代码示例
package main

import "fmt"

func main() {
    // make(类型, 长度, 容量):容量可选,默认等于长度
    d := make([]int, 5, 10) // 类型int,长度5(初始5个0),容量10

    fmt.Println("切片d:", d)          // 输出:切片d: [0 0 0 0 0](长度5,元素默认初始化)
    fmt.Println("d的长度len(d):", len(d)) // 输出:d的长度len(d): 5
    fmt.Println("d的容量cap(d):", cap(d)) // 输出:d的容量cap(d): 10
    fmt.Printf("d的类型: %T\n", d)     // 输出:d的类型: []int
}
// 扩展示例:make创建切片后追加元素
func main6() {
    // 创建长度5、容量10的字符串切片(初始5个空字符串)
    var a = make([]string, 5, 10)
    // 向切片追加10个元素(从索引5开始填充)
    for i := 0; i < 10; i++ {
        a = append(a, fmt.Sprintf("%v", i))
    }
    fmt.Println("最终切片a:", a) 
    // 输出:最终切片a: [    0 1 2 3 4 5 6 7 8 9](前5个为空字符串,后10个为数字)
}
关键说明:
  • make([]T, len):容量默认等于长度(如 make([]int, 3) 等同于 make([]int, 3, 3))。
  • 切片元素的默认值:与数组一致,根据类型自动初始化(int为0,string为空串,bool为false)。
  • 适用场景:明确知道切片的初始长度和预期容量时(如提前预估数据量,避免后续扩容)。

三、切片的核心操作:赋值、遍历、拷贝、删除

掌握切片的核心操作是实际开发的基础,以下结合代码示例逐一讲解。

3.1 切片的赋值:引用传递,共享底层数组

切片的赋值是引用传递,即新切片与原切片指向同一个底层数组,修改其中一个会影响另一个。

代码示例
package main

import "fmt"

func main() {
    // 1. 创建切片a(长度3,容量3,底层数组[0,0,0])
    a := make([]int, 3) // [0 0 0]
    // 2. 切片赋值:b与a共享底层数组
    b := a              

    // 3. 修改b的元素
    b[0] = 100

    // 4. 打印a和b:两者都被修改
    fmt.Println("切片a:", a) // 输出:切片a: [100 0 0]
    fmt.Println("切片b:", b) // 输出:切片b: [100 0 0]
}
原理分析:
  • 赋值后,ab指针、长度、容量完全相同,指向同一个底层数组。
  • 若通过切片修改元素(如 b[0] = 100),底层数组的对应位置会被修改,因此所有引用该数组的切片都会看到变化。
注意事项:
  • 若需避免共享底层数组,需使用 copy 函数(见3.3节)或重新创建切片(如 b := append([]int{}, a...))。

3.2 切片的遍历:2种常用方式

切片的遍历与数组类似,支持索引遍历for range遍历,后者更简洁,是Go语言的推荐写法。

代码示例
package main

import "fmt"

func main() {
    // 定义一个切片c
    c := []int{1, 2, 3, 4, 5}

    // 方式1:基于索引遍历(适合需要索引和元素的场景)
    fmt.Println("=== 索引遍历 ===")
    for i := 0; i < len(c); i++ {
        fmt.Printf("索引%d: %d\n", i, c[i])
    }
    // 输出:
    // 索引0: 1
    // 索引1: 2
    // 索引2: 3
    // 索引3: 4
    // 索引4: 5

    // 方式2:for range遍历(适合仅需元素或同时需要索引的场景)
    fmt.Println("\n=== for range遍历 ===")
    for index, value := range c {
        fmt.Printf("索引%d: %d\n", index, value)
    }
    // 输出与索引遍历一致
}
遍历技巧:
  • 忽略索引:若不需要索引,可使用空白标识符 _ 忽略(如 for _, v := range c { ... })。
  • 遍历部分元素:可通过切片表达式截取部分元素后遍历(如 for _, v := range c[1:3] { ... } 遍历索引1和2的元素)。
  • 性能考量:for range遍历会复制元素,若切片元素是大型结构体,建议使用索引遍历或遍历指针切片(避免拷贝开销)。

3.3 切片的拷贝:copy函数实现深拷贝

由于切片赋值是引用传递,若需完全独立的切片(不共享底层数组),需使用内置的 copy 函数实现深拷贝copy 函数的签名为:

func copy(dst, src []T) int // 返回值为实际拷贝的元素个数
代码示例
package main

import "fmt"

func main5() {
    // 1. 原切片a(底层数组[1,2,3,4,5])
    a := []int{1, 2, 3, 4, 5}
    // 2. 创建目标切片b(长度5,容量5,底层数组[0,0,0,0,0])
    b := make([]int, 5, 5)
    // 3. 切片赋值:c与b共享底层数组(用于验证拷贝效果)
    var c []int = b

    // 4. 拷贝src(a)到dst(b):拷贝5个元素
    copy(b, a)

    // 5. 修改b的元素(验证是否影响a和c)
    b[0] = 100

    // 6. 打印结果:a未变,b和c被修改(b和c共享底层数组)
    fmt.Println("原切片a:", a)   // 输出:原切片a: [1 2 3 4 5](未受影响)
    fmt.Println("目标切片b:", b) // 输出:目标切片b: [100 2 3 4 5](被修改)
    fmt.Println("切片c:", c)    // 输出:切片c: [100 2 3 4 5](与b共享底层数组)
}
copy函数的关键特性:
  1. 拷贝数量:取 len(dst)len(src) 中的较小值(如 dst 长度3,src 长度5,仅拷贝3个元素)。
  2. 底层数组独立:拷贝后,dstsrc 指向不同的底层数组,修改其中一个不会影响另一个。
  3. 浅拷贝元素copy 是"元素级拷贝",若切片元素是引用类型(如切片、映射),则仅拷贝指针(仍共享底层数据),需注意嵌套场景的深拷贝问题。
常见用法:
  • 完全拷贝:确保 dst 的长度等于 src(如 dst := make([]T, len(src)),再 copy(dst, src))。
  • 部分拷贝:通过切片表达式指定拷贝范围(如 copy(dst, src[1:3]) 拷贝 src 的索引1和2的元素)。

3.4 切片的删除:append函数实现元素删除

Go语言没有专门的 delete 函数用于切片,而是通过切片表达式+append函数实现元素删除,核心思路是:将删除位置前后的元素拼接成新切片。

代码示例
package main

import "fmt"

func main() {
    // 1. 定义一个字符串切片(包含4个元素)
    e := []string{"北京", "上海", "广州", "深圳"}
    fmt.Println("删除前:", e) // 输出:删除前: [北京 上海 广州 深圳]

    // 2. 删除索引为2的元素("广州")
    // 原理:将e[:2](["北京","上海"])和e[3:](["深圳"])拼接
    e = append(e[:2], e[3:]...)

    fmt.Println("删除后:", e) // 输出:删除后: [北京 上海 深圳]
}
通用删除公式:

删除切片 s 中索引为 index 的元素:

s = append(s[:index], s[index+1:]...)
  • s[:index]:切片 s 中索引 0index-1 的元素(左半部分)。
  • s[index+1:]...:切片 s 中索引 index+1 到末尾的元素,... 表示将切片展开为可变参数。
  • append 函数:将左半部分和右半部分拼接,返回新的切片(可能指向新的底层数组)。
注意事项:
  1. 索引合法性:删除前需确保 index[0, len(s)-1] 范围内,否则会导致切片越界(运行时panic)。
  2. 底层数组残留:若原切片的底层数组有其他引用,删除元素后,原位置的元素不会被立即回收(仍存在于底层数组中),可能导致内存泄漏。若需彻底清除,可手动将原位置元素置为零值(如 s[index] = nils[index] = 0)。
  3. 删除多个元素:可通过多次删除或切片表达式批量删除(如 s = s[:index] 删除索引 index 及之后的所有元素)。

四、切片的扩容机制:append函数与容量增长规则

append 函数是切片动态扩容的核心,当切片的长度(len)等于容量(cap)时,继续追加元素会触发扩容(分配新的底层数组,拷贝原数据,更新切片的指针、长度和容量)。理解扩容规则对优化切片性能至关重要。

4.1 append函数的基本用法

append 函数的签名为:

func append(s []T, vs ...T) []T // 返回新的切片
  • s:原切片。
  • vs...:可变参数,可传入多个元素或另一个切片(需用 ... 展开)。
代码示例
package main

import "fmt"

func main() {
    var a []int // nil切片

    // 1. 追加单个元素
    a = append(a, 10)
    fmt.Println("追加单个元素后:", a) // 输出:追加单个元素后: [10]

    // 2. 追加多个元素
    a = append(a, 11, 12, 13)
    fmt.Println("追加多个元素后:", a) // 输出:追加多个元素后: [10 11 12 13]

    // 3. 追加另一个切片(需用...展开)
    b := []int{14, 15}
    a = append(a, b...)
    fmt.Println("追加切片后:", a) // 输出:追加切片后: [10 11 12 13 14 15]
}
关键特性:
  • append 函数不修改原切片,而是返回新的切片(原切片的指针、长度、容量可能不变)。
  • 若原切片容量足够(len(s) < cap(s)),append 会直接在底层数组的剩余空间添加元素,新切片与原切片共享底层数组。
  • 若原切片容量不足(len(s) == cap(s)),append 会触发扩容,新切片指向新的底层数组,与原切片完全独立。

4.2 切片的扩容规则:Go 1.18前后的差异

切片的扩容规则由Go语言 runtime 源码(src/runtime/slice.go)定义,不同版本有细微差异,以下分别讲解。

4.2.1 Go 1.18 之前的扩容规则
  1. 新申请容量(cap)> 2倍旧容量(old.cap):新容量 = 新申请容量。
  2. 旧切片长度(old.len)< 1024:新容量 = 2倍旧容量(翻倍扩容)。
  3. 旧切片长度(old.len)≥ 1024:新容量 = 旧容量 + 旧容量/4(每次增加25%),直到新容量 ≥ 新申请容量。
  4. 若计算过程中出现容量溢出(新容量 ≤ 0):新容量 = 新申请容量。
package main

import "fmt"

func main() {
    var a []int // nil切片(len=0, cap=0)

    // 循环追加10个元素,观察容量变化
    for i := 0; i < 10; i++ {
        a = append(a, i)
        fmt.Printf("a=%v, len=%d, cap=%d, ptr=%p\n", a, len(a), cap(a), a)
    }
}

输出结果:

a=[0], len=1, cap=1, ptr=0x140000a6008
a=[0 1], len=2, cap=2, ptr=0x140000a6010
a=[0 1 2], len=3, cap=4, ptr=0x140000a8020  // len=3 <1024,cap翻倍(2→4)
a=[0 1 2 3], len=4, cap=4, ptr=0x140000a8020
a=[0 1 2 3 4], len=5, cap=8, ptr=0x140000aa040  // len=5 <1024,cap翻倍(4→8)
a=[0 1 2 3 4 5], len=6, cap=8, ptr=0x140000aa040
a=[0 1 2 3 4 5 6], len=7, cap=8, ptr=0x140000aa040
a=[0 1 2 3 4 5 6 7], len=8, cap=8, ptr=0x140000aa040
a=[0 1 2 3 4 5 6 7 8], len=9, cap=16, ptr=0x140000ac080 // len=9 <1024,cap翻倍(8→16)
a=[0 1 2 3 4 5 6 7 8 9], len=10, cap=16, ptr=0x140000ac080
4.2.2 Go 1.18 之后的扩容规则

Go 1.18 优化了大容量切片的扩容效率,调整后的规则:

  1. 新申请容量(cap)> 2倍旧容量(old.cap):新容量 = 新申请容量。
  2. 旧容量(old.cap)< 256:新容量 = 2倍旧容量(翻倍扩容)。
  3. 旧容量(old.cap)≥ 256:新容量 = 旧容量 + (旧容量/4) + 192(每次增加25% + 192),直到新容量 ≥ 新申请容量。
  4. 实际分配时会根据内存对齐规则微调(可能增加少量容量,确保内存块是2的幂次倍数,提高分配效率)。

规则解读:

  • 小容量切片(<256)仍保持翻倍扩容,确保高效。
  • 大容量切片(≥256)改为"25% + 192"的增长方式,避免容量过大导致的内存浪费(如1024容量的切片,1.18前扩容到1280,1.18后扩容到1024+256+192=1472,更贴合实际需求)。

4.3 扩容的性能影响与优化建议

扩容的性能开销:
  • 内存分配:每次扩容都需要向操作系统申请新的内存块,开销较大。
  • 数据拷贝:扩容时需要将原切片的所有元素拷贝到新的底层数组,时间复杂度为O(n),元素越多,拷贝开销越大。
优化建议
  1. 提前预估容量:创建切片时通过 make 函数指定足够的容量(如 make([]int, 0, 1000)),避免频繁扩容。
    • 示例:若已知需要存储1000个int元素,直接创建 s := make([]int, 0, 1000),后续append无需扩容,性能提升显著。
  2. 避免不必要的切片创建:尽量复用切片,减少临时切片的创建(如通过 s = s[:0] 清空切片,复用底层数组)。
  3. 警惕切片泄漏:若切片指向大型底层数组,但仅使用少量元素,可通过 copy 函数创建小切片(如 small := make([]T, len(large[:10]))copy(small, large[:10])),释放原底层数组的内存。

五、切片的实战技巧:排序、nil切片处理、嵌套切片

除了基础操作,切片在实战中还有一些高频用法,以下结合代码示例讲解。

5.1 切片排序:使用sort包

Go语言的 sort 包提供了对切片的排序功能,支持int、string、float64等基本类型,核心函数包括 sort.Ints()sort.Strings()sort.Float64s()

代码示例
package main

import (
    "fmt"
    "sort"
)

func main() {
    // 1. 定义一个int数组(需转换为切片后排序)
    var a = [...]int{3, 7, 8, 9, 1}
    fmt.Println("排序前数组a:", a) // 输出:排序前数组a: [3 7 8 9 1]

    // 2. 将数组转换为切片(a[:]),调用sort.Ints排序
    sort.Ints(a[:]) 

    fmt.Println("排序后数组a:", a) // 输出:排序后数组a: [1 3 7 8 9]
}

5.2 nil切片的安全处理

nil切片虽然长度和容量为0,但可以直接用于 append 函数(无需初始化),这是Go语言的设计特性,需合理利用。

代码示例
package main

import "fmt"

func main() {
    var a []int // nil切片(未初始化)

    // 直接append,无需初始化(安全)
    a = append(a, 1, 2, 3)
    fmt.Println(a) // 输出:[1 2 3]

    // 错误用法:直接对nil切片的索引赋值(会触发panic)
    // a[0] = 100 // runtime error: index out of range [0] with length 0
}
关键结论:
  • nil切片可以直接 append,但不能直接通过索引赋值(需先初始化或append元素后再赋值)。
  • 函数返回切片时,若结果为空且无错误,建议返回空切片([]T{}make([]T, 0));若结果为空且有错误,建议返回nil切片(明确表示"未赋值"状态)。

5.3 嵌套切片:切片的元素是切片

Go语言支持嵌套切片(切片的元素类型是另一个切片),常用于表示二维数据(如矩阵、表格)。

代码示例:
package main

import "fmt"

func main() {
    // 1. 定义一个嵌套切片(二维int切片)
    var matrix [][]int

    // 2. 向嵌套切片中添加行(每行是一个切片)
    matrix = append(matrix, []int{1, 2, 3})
    matrix = append(matrix, []int{4, 5, 6})
    matrix = append(matrix, []int{7, 8, 9})

    fmt.Println("二维切片matrix:", matrix) // 输出:二维切片matrix: [[1 2 3] [4 5 6] [7 8 9]]

    // 3. 遍历嵌套切片
    fmt.Println("\n遍历二维切片:")
    for i, row := range matrix {
        for j, val := range row {
            fmt.Printf("matrix[%d][%d] = %d ", i, j, val)
        }
        fmt.Println()
    }
}
注意事项:
  • 嵌套切片的每行长度可以不同(如 matrix = append(matrix, []int{10}),形成不规则二维数据)。
  • 嵌套切片的扩容仅针对外层切片,内层切片的扩容需单独处理(如 matrix[0] = append(matrix[0], 10) 为第一行追加元素)。

六、切片常见问题与避坑指南

在使用切片的过程中,容易因对底层原理理解不深而出现问题,以下总结常见坑点及解决方案。

6.1 坑点1:切片赋值后共享底层数组,修改元素导致意外修改

问题示例

func main() {
    s1 := []int{1, 2, 3}
    s2 := s1 // s2与s1共享底层数组
    s2[0] = 100
    fmt.Println(s1) // 输出:[100 2 3](意外修改)
}

解决方案

  • 使用 copy 函数创建独立切片:s2 := make([]int, len(s1)); copy(s2, s1)
  • 使用 append 函数创建新切片:s2 := append([]int{}, s1...)

6.2 坑点2:切片扩容后,原切片与新切片指向不同底层数组

问题示例

func main() {
    s1 := make([]int, 2, 2) // len=2, cap=2
    s2 := s1
    s1 = append(s1, 3) // s1扩容(cap变为4),指向新数组
    s1[0] = 100
    fmt.Println(s2) // 输出:[0 0](s2仍指向原数组,未被修改)
}

解决方案

  • 若需保持关联,提前预估容量,避免扩容(如 make([]int, 2, 3))。
  • 若扩容后需同步修改,使用指针切片([]*int),确保修改的是同一元素。

6.3 坑点4:使用 for range 遍历切片时修改元素无效

问题示例

func main() {
    s := []int{1, 2, 3}
    // 错误:range遍历的是元素的副本,修改副本不影响原切片
    for _, v := range s {
        v *= 2
    }
    fmt.Println(s) // 输出:[1 2 3](未修改)
}

解决方案

  • 使用索引遍历,直接修改原切片元素:
    for i := range s {
        s[i] *= 2
    }
    fmt.Println(s) // 输出:[2 4 6](修改成功)
    

七、总结

切片作为Go语言的核心数据结构,它的设计兼顾了效率和灵活性。掌握切片的关键在于理解其底层数组+指针+长度+容量的结构,以及引用传递、扩容等特性。
最后如果哪些地方的不足,欢迎大家在评论区中指正!


网站公告

今日签到

点亮在社区的每一天
去签到