Skip to content

Go 语言切片(Slice)

切片是 Go 语言中最重要的数据类型之一,它提供了动态数组的功能。切片建立在数组之上,但比数组更强大、灵活且常用。

🎯 切片基础

切片声明和初始化

go
package main

import "fmt"

func main() {
    // 1. 声明切片(零值为 nil)
    var slice1 []int
    fmt.Printf("零值切片: %v, 长度: %d, 容量: %d, 是否为nil: %t\n", 
               slice1, len(slice1), cap(slice1), slice1 == nil)
    
    // 2. 使用字面量创建切片
    slice2 := []int{1, 2, 3, 4, 5}
    fmt.Printf("字面量切片: %v, 长度: %d, 容量: %d\n", 
               slice2, len(slice2), cap(slice2))
    
    // 3. 使用 make 创建切片
    slice3 := make([]int, 5)      // 长度和容量都是 5
    slice4 := make([]int, 3, 10)  // 长度 3,容量 10
    
    fmt.Printf("make切片(5): %v, 长度: %d, 容量: %d\n", 
               slice3, len(slice3), cap(slice3))
    fmt.Printf("make切片(3,10): %v, 长度: %d, 容量: %d\n", 
               slice4, len(slice4), cap(slice4))
    
    // 4. 从数组创建切片
    array := [6]int{10, 20, 30, 40, 50, 60}
    slice5 := array[1:4]  // 从索引1到3(不包括4)
    slice6 := array[:3]   // 从开始到索引2
    slice7 := array[2:]   // 从索引2到结束
    slice8 := array[:]    // 整个数组
    
    fmt.Printf("原数组: %v\n", array)
    fmt.Printf("slice5 [1:4]: %v\n", slice5)
    fmt.Printf("slice6 [:3]: %v\n", slice6)
    fmt.Printf("slice7 [2:]: %v\n", slice7)
    fmt.Printf("slice8 [:]: %v\n", slice8)
}

切片的内部结构

go
import "unsafe"

func main() {
    // 切片的内部结构:指针、长度、容量
    slice := []int{1, 2, 3, 4, 5}
    
    fmt.Printf("切片: %v\n", slice)
    fmt.Printf("长度: %d\n", len(slice))
    fmt.Printf("容量: %d\n", cap(slice))
    fmt.Printf("切片大小: %d 字节\n", unsafe.Sizeof(slice))
    
    // 切片头信息
    fmt.Printf("切片地址: %p\n", &slice)
    fmt.Printf("底层数组地址: %p\n", &slice[0])
    
    // 切片之间共享底层数组
    slice1 := slice[1:4]
    slice2 := slice[2:5]
    
    fmt.Printf("\n切片共享:\n")
    fmt.Printf("原切片: %v\n", slice)
    fmt.Printf("slice1[1:4]: %v\n", slice1)
    fmt.Printf("slice2[2:5]: %v\n", slice2)
    
    // 修改 slice1 会影响其他切片
    slice1[1] = 100
    fmt.Printf("\n修改 slice1[1] 后:\n")
    fmt.Printf("原切片: %v\n", slice)
    fmt.Printf("slice1: %v\n", slice1)
    fmt.Printf("slice2: %v\n", slice2)
}

➕ 切片操作

append 函数

go
func main() {
    // append 添加元素
    var slice []int
    fmt.Printf("初始切片: %v, 长度: %d, 容量: %d\n", slice, len(slice), cap(slice))
    
    // 逐个添加元素
    slice = append(slice, 1)
    fmt.Printf("添加1: %v, 长度: %d, 容量: %d\n", slice, len(slice), cap(slice))
    
    slice = append(slice, 2, 3, 4)
    fmt.Printf("添加2,3,4: %v, 长度: %d, 容量: %d\n", slice, len(slice), cap(slice))
    
    // 添加另一个切片
    slice2 := []int{5, 6, 7}
    slice = append(slice, slice2...)  // 使用 ... 展开切片
    fmt.Printf("添加另一个切片: %v, 长度: %d, 容量: %d\n", slice, len(slice), cap(slice))
    
    // 容量增长演示
    fmt.Println("\n容量增长演示:")
    slice3 := make([]int, 0, 2)
    for i := 0; i < 10; i++ {
        slice3 = append(slice3, i)
        fmt.Printf("添加%d: 长度=%d, 容量=%d\n", i, len(slice3), cap(slice3))
    }
}

copy 函数

go
func main() {
    // copy 函数复制切片
    source := []int{1, 2, 3, 4, 5}
    
    // 创建目标切片
    dest1 := make([]int, len(source))
    dest2 := make([]int, 3)  // 长度小于源切片
    dest3 := make([]int, 7)  // 长度大于源切片
    
    // 复制切片
    n1 := copy(dest1, source)
    n2 := copy(dest2, source)
    n3 := copy(dest3, source)
    
    fmt.Printf("源切片: %v\n", source)
    fmt.Printf("完整复制: %v (复制了%d个元素)\n", dest1, n1)
    fmt.Printf("部分复制: %v (复制了%d个元素)\n", dest2, n2)
    fmt.Printf("扩展复制: %v (复制了%d个元素)\n", dest3, n3)
    
    // 修改原切片不影响复制的切片
    source[0] = 100
    fmt.Printf("\n修改源切片后:\n")
    fmt.Printf("源切片: %v\n", source)
    fmt.Printf("复制的切片: %v\n", dest1)
    
    // 切片之间的复制
    slice1 := []int{1, 2, 3}
    slice2 := []int{4, 5, 6, 7, 8}
    
    fmt.Printf("\n切片间复制:\n")
    fmt.Printf("复制前: slice1=%v, slice2=%v\n", slice1, slice2)
    
    copy(slice2[1:4], slice1)  // 将 slice1 复制到 slice2 的中间位置
    fmt.Printf("复制后: slice1=%v, slice2=%v\n", slice1, slice2)
}

切片切分

go
func main() {
    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Printf("原切片: %v\n", slice)
    
    // 基本切分
    fmt.Printf("slice[2:7]: %v\n", slice[2:7])
    fmt.Printf("slice[:5]: %v\n", slice[:5])
    fmt.Printf("slice[3:]: %v\n", slice[3:])
    fmt.Printf("slice[:]: %v\n", slice[:])
    
    // 三参数切分:[start:end:cap]
    sub1 := slice[2:5:7]  // 从索引2到4,容量限制为7-2=5
    fmt.Printf("slice[2:5:7]: %v, 长度: %d, 容量: %d\n", 
               sub1, len(sub1), cap(sub1))
    
    // 切分的切片共享底层数组
    sub2 := slice[1:6]
    fmt.Printf("\n切分共享演示:\n")
    fmt.Printf("原切片: %v\n", slice)
    fmt.Printf("子切片: %v\n", sub2)
    
    sub2[2] = 100
    fmt.Printf("修改子切片后:\n")
    fmt.Printf("原切片: %v\n", slice)
    fmt.Printf("子切片: %v\n", sub2)
}

🔧 高级切片操作

切片扩容机制

go
func main() {
    // 观察切片扩容
    fmt.Println("切片扩容机制:")
    slice := make([]int, 0, 1)
    
    for i := 0; i < 20; i++ {
        oldCap := cap(slice)
        slice = append(slice, i)
        newCap := cap(slice)
        
        if newCap != oldCap {
            fmt.Printf("长度 %d: 容量从 %d 扩展到 %d (扩展比例: %.2f)\n", 
                       len(slice), oldCap, newCap, float64(newCap)/float64(oldCap))
        }
    }
    
    // 预分配容量的重要性
    fmt.Println("\n性能对比:")
    
    // 不预分配容量
    slice1 := []int{}
    for i := 0; i < 1000; i++ {
        slice1 = append(slice1, i)
    }
    
    // 预分配容量
    slice2 := make([]int, 0, 1000)
    for i := 0; i < 1000; i++ {
        slice2 = append(slice2, i)
    }
    
    fmt.Printf("不预分配: 最终容量 %d\n", cap(slice1))
    fmt.Printf("预分配: 最终容量 %d\n", cap(slice2))
}

切片删除操作

go
func main() {
    // 切片删除元素的各种方法
    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // 1. 删除第一个元素
    fmt.Printf("原切片: %v\n", slice)
    slice = slice[1:]
    fmt.Printf("删除第一个元素: %v\n", slice)
    
    // 重置切片
    slice = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // 2. 删除最后一个元素
    slice = slice[:len(slice)-1]
    fmt.Printf("删除最后一个元素: %v\n", slice)
    
    // 重置切片
    slice = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // 3. 删除中间元素(保持顺序)
    index := 4
    slice = append(slice[:index], slice[index+1:]...)
    fmt.Printf("删除索引4的元素: %v\n", slice)
    
    // 重置切片
    slice = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    // 4. 删除中间元素(不保持顺序,更高效)
    index = 4
    slice[index] = slice[len(slice)-1]  // 用最后一个元素覆盖要删除的元素
    slice = slice[:len(slice)-1]        // 缩短切片
    fmt.Printf("快速删除索引4的元素: %v\n", slice)
    
    // 5. 删除多个元素
    slice = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    start, end := 3, 7
    slice = append(slice[:start], slice[end:]...)
    fmt.Printf("删除索引3-6的元素: %v\n", slice)
}

// 删除切片元素的通用函数
func removeElement(slice []int, index int) []int {
    if index < 0 || index >= len(slice) {
        return slice
    }
    return append(slice[:index], slice[index+1:]...)
}

// 删除指定值的第一个出现
func removeValue(slice []int, value int) []int {
    for i, v := range slice {
        if v == value {
            return append(slice[:i], slice[i+1:]...)
        }
    }
    return slice
}

切片插入操作

go
func main() {
    slice := []int{1, 2, 4, 5}
    
    // 1. 在开头插入
    slice = append([]int{0}, slice...)
    fmt.Printf("在开头插入0: %v\n", slice)
    
    // 2. 在末尾插入
    slice = append(slice, 6)
    fmt.Printf("在末尾插入6: %v\n", slice)
    
    // 3. 在中间插入
    index := 3
    value := 3
    slice = append(slice[:index+1], slice[index:]...)
    slice[index] = value
    fmt.Printf("在索引3插入3: %v\n", slice)
    
    // 4. 批量插入
    slice2 := []int{1, 2, 6, 7}
    insertValues := []int{3, 4, 5}
    insertIndex := 2
    
    // 创建新切片用于插入
    result := make([]int, 0, len(slice2)+len(insertValues))
    result = append(result, slice2[:insertIndex]...)
    result = append(result, insertValues...)
    result = append(result, slice2[insertIndex:]...)
    
    fmt.Printf("批量插入: %v\n", result)
}

// 通用插入函数
func insertElement(slice []int, index, value int) []int {
    if index < 0 {
        index = 0
    }
    if index > len(slice) {
        index = len(slice)
    }
    
    slice = append(slice[:index+1], slice[index:]...)
    slice[index] = value
    return slice
}

// 批量插入函数
func insertElements(slice []int, index int, values []int) []int {
    if index < 0 {
        index = 0
    }
    if index > len(slice) {
        index = len(slice)
    }
    
    result := make([]int, 0, len(slice)+len(values))
    result = append(result, slice[:index]...)
    result = append(result, values...)
    result = append(result, slice[index:]...)
    return result
}

🎯 实际应用示例

动态数组实现

go
// 动态数组结构
type DynamicArray struct {
    data     []int
    size     int
    capacity int
}

func NewDynamicArray() *DynamicArray {
    return &DynamicArray{
        data:     make([]int, 0, 2),
        size:     0,
        capacity: 2,
    }
}

func (da *DynamicArray) Add(value int) {
    if da.size >= da.capacity {
        da.resize()
    }
    da.data = append(da.data, value)
    da.size++
}

func (da *DynamicArray) Get(index int) (int, bool) {
    if index < 0 || index >= da.size {
        return 0, false
    }
    return da.data[index], true
}

func (da *DynamicArray) Set(index, value int) bool {
    if index < 0 || index >= da.size {
        return false
    }
    da.data[index] = value
    return true
}

func (da *DynamicArray) Remove(index int) bool {
    if index < 0 || index >= da.size {
        return false
    }
    
    copy(da.data[index:], da.data[index+1:])
    da.data = da.data[:da.size-1]
    da.size--
    return true
}

func (da *DynamicArray) resize() {
    da.capacity *= 2
    newData := make([]int, da.size, da.capacity)
    copy(newData, da.data)
    da.data = newData
}

func (da *DynamicArray) Size() int {
    return da.size
}

func (da *DynamicArray) Capacity() int {
    return da.capacity
}

func (da *DynamicArray) ToSlice() []int {
    result := make([]int, da.size)
    copy(result, da.data[:da.size])
    return result
}

func main() {
    // 测试动态数组
    arr := NewDynamicArray()
    
    fmt.Println("=== 动态数组测试 ===")
    
    // 添加元素
    for i := 0; i < 10; i++ {
        arr.Add(i * 10)
        fmt.Printf("添加 %d: 大小=%d, 容量=%d\n", i*10, arr.Size(), arr.Capacity())
    }
    
    fmt.Printf("数组内容: %v\n", arr.ToSlice())
    
    // 获取元素
    if value, ok := arr.Get(5); ok {
        fmt.Printf("索引5的值: %d\n", value)
    }
    
    // 修改元素
    arr.Set(5, 999)
    fmt.Printf("修改后数组: %v\n", arr.ToSlice())
    
    // 删除元素
    arr.Remove(3)
    fmt.Printf("删除索引3后: %v\n", arr.ToSlice())
}

栈和队列实现

go
// 栈实现
type Stack struct {
    items []int
}

func NewStack() *Stack {
    return &Stack{items: make([]int, 0)}
}

func (s *Stack) Push(item int) {
    s.items = append(s.items, item)
}

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

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

func (s *Stack) IsEmpty() bool {
    return len(s.items) == 0
}

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

// 队列实现
type Queue struct {
    items []int
}

func NewQueue() *Queue {
    return &Queue{items: make([]int, 0)}
}

func (q *Queue) Enqueue(item int) {
    q.items = append(q.items, item)
}

func (q *Queue) Dequeue() (int, bool) {
    if len(q.items) == 0 {
        return 0, false
    }
    
    item := q.items[0]
    q.items = q.items[1:]
    return item, true
}

func (q *Queue) Front() (int, bool) {
    if len(q.items) == 0 {
        return 0, false
    }
    return q.items[0], true
}

func (q *Queue) IsEmpty() bool {
    return len(q.items) == 0
}

func (q *Queue) Size() int {
    return len(q.items)
}

func main() {
    // 测试栈
    fmt.Println("=== 栈测试 ===")
    stack := NewStack()
    
    // 入栈
    for i := 1; i <= 5; i++ {
        stack.Push(i * 10)
        fmt.Printf("入栈 %d, 栈大小: %d\n", i*10, stack.Size())
    }
    
    // 查看栈顶
    if top, ok := stack.Peek(); ok {
        fmt.Printf("栈顶元素: %d\n", top)
    }
    
    // 出栈
    for !stack.IsEmpty() {
        if item, ok := stack.Pop(); ok {
            fmt.Printf("出栈 %d, 剩余大小: %d\n", item, stack.Size())
        }
    }
    
    // 测试队列
    fmt.Println("\n=== 队列测试 ===")
    queue := NewQueue()
    
    // 入队
    for i := 1; i <= 5; i++ {
        queue.Enqueue(i * 10)
        fmt.Printf("入队 %d, 队列大小: %d\n", i*10, queue.Size())
    }
    
    // 查看队首
    if front, ok := queue.Front(); ok {
        fmt.Printf("队首元素: %d\n", front)
    }
    
    // 出队
    for !queue.IsEmpty() {
        if item, ok := queue.Dequeue(); ok {
            fmt.Printf("出队 %d, 剩余大小: %d\n", item, queue.Size())
        }
    }
}

🎓 小结

本章我们深入学习了 Go 语言的切片:

  • 切片基础:声明、初始化、内部结构
  • 切片操作:append、copy、切分
  • 高级操作:扩容机制、删除、插入
  • 实际应用:动态数组、栈、队列实现

切片是 Go 语言中最常用的数据结构,掌握切片的使用对于 Go 开发至关重要。


接下来,我们将学习 Go 语言范围(Range),掌握 Go 语言的遍历机制。

切片使用建议

  • 优先使用切片而不是数组
  • 预分配容量可以提高性能
  • 注意切片共享底层数组的特性
  • 删除元素时注意内存泄漏问题

本站内容仅供学习和研究使用。