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 语言的遍历机制。
切片使用建议
- 优先使用切片而不是数组
- 预分配容量可以提高性能
- 注意切片共享底层数组的特性
- 删除元素时注意内存泄漏问题