6.3 Go 切片技巧

1. 删除指定索引的元素 — append 切片拼接

Go 没有内置的"删除元素"操作,用 append 把前半段和后半段拼接起来:

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]

要点

  • data[:index] — 索引之前的元素:[1, 2, 3]
  • data[index+1:] — 索引之后的元素:[5, 6, 7, 8, 9, 10]
  • append(data[:index], data[index+1:]...) — 把两段拼接,中间的元素 4 被删除
  • 这是 Go 删除切片元素的标准模式:append 前半段 + 后半段
  • 注意:这个操作修改了底层数组!删除后 data 的长度变短,但底层数组末尾仍有一个残留元素(原 data[9]=10 被复制到新位置,旧位置仍有值,只是不可访问)
  • 如果元素是指针类型或包含指针,需先置 nil 防止内存泄漏:
    data[index] = nil // 先置空,再 append
    data = append(data[:index], data[index+1:]...)
    

2. 在指定索引插入元素 — 双层 append

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

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)
}

执行结果:

After inserting 99 at index 2: [1 2 99 99 5 6 7 8 9 10]

要点

  • append(data[:insertIndex], insertValue) — 在前半段末尾追加新值,得到 [1, 2, 99]
  • append([1,2,99], data[insertIndex:]...) — 拼接后半段,得到 [1, 2, 99, 3, 5, …]
  • 双层 append 嵌套,内层 append 返回的切片作为外层 append 的第一个参数
  • 注意输出中有两个 99!这是因为内层 append(data[:2], 99) 在原底层数组上操作:
    • data[:2] = [1, 2],append 99 后底层位置 data[2] 被写为 99
    • 外层 append([1,2,99], data[2:]...)data[2:] 从原底层数组读取,data[2] 已经是 99
    • 所以出现了两个 99 — 这是 append 在共享底层数组上操作的副作用
  • 更安全的做法:先复制前半段到新切片,避免修改原底层数组:
    // 安全版本:使用临时切片
    temp := make([]int, insertIndex)
    copy(temp, data[:insertIndex])
    temp = append(temp, insertValue)
    data = append(temp, data[insertIndex:]...)
    

3. 原地反转切片 — 双指针交换

用两个指针从两端向中间交换元素,时间复杂度 O(n),空间复杂度 O(1):

package main

import "fmt"

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

    // 双指针:i 从头开始,j 从尾开始,交换后 i++ j--
    for i, j := 0, len(numbers)-1; i < j; i, j = i+1, j-1 {
        numbers[i], numbers[j] = numbers[j], numbers[i]
    }

    fmt.Println("Reversed numbers:", numbers)
}

执行结果:

Reversed numbers: [5 4 3 2 1]

要点

  • for i, j := 0, len(numbers)-1; i < j; i, j = i+1, j-1 — 初始化两个指针,i++ j-- 同时更新
  • numbers[i], numbers[j] = numbers[j], numbers[i] — Go 的多重赋值,同时交换两个元素
  • i < j — 当 i 和 j 相遇或越过时停止(奇数长度时中间元素不交换)
  • 原地操作:不需要额外空间,直接在原切片上交换
  • 交换过程:1↔5 → 2↔4 → 3不动(中间)→ 结果 [5, 4, 3, 2, 1]
  • Go 没有内置的反转函数,这是反转切片的标准写法(Go 官方 Wiki 推荐)

4. 过滤删除所有指定值 — append 收集保留值

遍历切片,把不等于目标值的元素收集到新切片:

package main

import "fmt"

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

    // 创建新切片,收集非目标值
    var filtered []int
    for _, v := range values {
        if v != target {
            filtered = append(filtered, v)
        }
    }

    fmt.Printf("After removing all %d: %v\n", target, filtered)
}

执行结果:

After removing all 2: [1 3 4 5]

要点

  • var filtered []int — nil 切片,append 自动分配底层数组
  • 遍历原切片,v != target 时 append 到新切片
  • 结果 [1, 3, 4, 5] — 所有 2 被移除
  • 这是"过滤"模式:遍历 + 条件判断 + append,Go 中极常用
  • 更高效的就地版本(不分配新底层数组):
    n := 0
    for _, v := range values {
        if v != target {
            values[n] = v
            n++
        }
    }
    values = values[:n] // 截断到新长度
    

5. 检查切片是否包含某值 — 手动遍历

Go 1.18 之前没有内置的 contains 函数,需要手动遍历(Go 1.21+ 有 slices.Contains):

package main

import "fmt"

func main() {
    haystack := []string{"apple", "banana", "cherry", "date"}
    needle := "cherry"

    var found bool
    for _, item := range haystack {
        if item == needle {
            found = true
            break // 找到后立即退出
        }
    }

    if found {
        fmt.Printf("Found '%s' in slice\n", needle)
    } else {
        fmt.Printf("'%s' not found in slice\n", needle)
    }
}

执行结果:

Found 'cherry' in slice

要点

  • 遍历切片逐个比较,break 找到后立即退出

  • var found bool — 默认值 false,找到后设为 true

  • Go 1.21+ 可以用 slices.Contains(haystack, needle) 更简洁:

    import "golang.org/x/exp/slices" // 或 Go 1.21: "slices"
    found := slices.Contains(haystack, needle)
    
  • 对于排序切片,可以用二分搜索提高效率:sort.SearchStrings(slice, target)


6. 去重 — map 记录已见元素

用 map 的键来跟踪哪些元素已经出现过,实现去重:

package main

import "fmt"

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

    // map 记录已见元素,只保留首次出现的
    seen := make(map[int]bool)
    var unique []int

    for _, v := range duplicates {
        if !seen[v] {        // v 还没出现过
            seen[v] = true   // 标记为已见
            unique = append(unique, v) // 加入结果
        }
    }

    fmt.Println("Unique elements:", unique)
}

执行结果:

Unique elements: [1 2 3 4 5]

要点

  • seen := make(map[int]bool) — map 的键是元素值,bool 标记是否已见
  • !seen[v] — v 不在 map 中 = 首次出现,加入结果并标记
  • seen[v] = true — 标记已见,下次遇到相同值时跳过
  • 结果 [1, 2, 3, 4, 5] — 保持了首次出现的顺序
  • 去重效率:O(n) 时间(map 查找是 O(1)),O(n) 空间(map 和结果切片)
  • 如果不关心顺序,直接用 map 的键即可:
    seen := make(map[int]struct{}) // struct{} 不占内存
    for _, v := range duplicates { seen[v] = struct{}{} }
    unique := make([]int, 0, len(seen))
    for k := range seen { unique = append(unique, k) }
    // 注意:map 遍历顺序不确定
    

知识点总结

知识点 关键概念
删除元素 append(data[:i], data[i+1:]...) — 前半段 + 后半段拼接
插入元素 append(append(data[:i], val), data[i:]...) — 双层 append,注意底层数组副作用
反转切片 双指针交换 for i,j := 0,len-1; i<j,Go 官方推荐写法
过滤删除 遍历 + 条件判断 + append 收集保留值
包含检查 手动遍历 + break,Go 1.21+ 用 slices.Contains
去重 map[元素]bool 记录已见,保持首次出现顺序
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容