Go-数组与切片

在 Golang 中,数组和切片是存储和处理集合数据的基本结构。为了编写高效、清晰的 Go 程序,必须深入理解它们的特性及内存管理方式。本文将全面讲解数组与切片的声明、初始化、使用、内存布局及常见操作。

1. 数组概述

数组是固定长度、元素类型相同的集合。定义数组时,其长度必须明确指定,且一旦定义就不能改变。

数组声明与初始化

声明数组

1
var arr [5]int  // 长度为5的整型数组,元素默认值为0

初始化数组

  1. 使用大括号进行初始化:
1
arr := [5]int{1, 2, 3, 4, 5}  // 长度为5的数组
  1. 使用 ... 自动推断长度:
1
arr := [...]int{10, 20, 30}  // 长度为3
  1. 部分初始化:
1
arr := [5]int{1: 10, 3: 30}  // [0, 10, 0, 30, 0]

访问和修改数组元素

通过索引访问和修改元素,索引从0开始:

1
2
arr[0] = 100  // 修改第一个元素
fmt.Println(arr[0]) // 输出100

数组遍历

使用 for 循环遍历数组:

1
2
3
for i, value := range arr {
fmt.Printf("Index: %d, Value: %d\n", i, value)
}

2. 切片概述

切片是对数组的抽象,提供了更灵活和强大的功能。切片可以动态调整长度,但它仍然依赖底层数组。

切片的声明与初始化

从数组创建切片

1
2
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // 包含arr的第2到第4个元素 [2, 3, 4]

使用 make 创建切片

1
slice := make([]int, 3, 5)  // 创建长度为3、容量为5的切片
  • 3 是切片的长度。
  • 5 是切片的容量。

使用字面量创建切片

1
slice := []int{10, 20, 30}  // 长度和容量均为3

切片的基本操作

追加元素

使用 append 函数向切片添加元素:

1
2
slice := []int{1, 2, 3}
slice = append(slice, 4, 5) // 追加4和5

当切片容量不足时,append 会创建新的底层数组并扩容。

切片的切片

可以对切片再进行切片操作:

1
subSlice := slice[1:3]  // 从slice中获取子切片

切片拷贝

使用 copy 函数将一个切片复制到另一个切片:

1
2
3
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // 将src拷贝到dst

3. 切片的内存结构

切片包含三部分:指针长度容量

  • 指针:指向底层数组中切片起始位置。
  • 长度:切片中当前可用的元素数目。
  • 容量:从切片起始位置到底层数组末尾的元素数目。

内存共享

切片和底层数组共享内存,因此修改切片中的元素会影响底层数组。

1
2
3
4
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]
slice[0] = 100 // 修改slice,也会影响arr
fmt.Println(arr) // 输出 [1, 100, 3, 4, 5]

切片扩容机制

当切片容量不足时,append 会分配新的底层数组。扩容策略通常是将容量倍增,以提高效率。

1
2
slice := []int{1, 2, 3}
slice = append(slice, 4, 5, 6) // 容量自动扩增

4. 数组与切片的区别

  • 长度:数组长度固定,切片长度可变。
  • 性能:数组适用于需要确定大小的数据集;切片更灵活,适合处理动态数据集。
  • 内存使用:切片使用底层数组的内存,可以节省内存分配开销。

5. 常见操作与注意事项

切片长度与容量

使用 len()cap() 获取切片长度和容量:

1
2
3
slice := make([]int, 3, 5)
fmt.Println("Length:", len(slice)) // 输出3
fmt.Println("Capacity:", cap(slice)) // 输出5

零值切片

切片的零值是 nil,可以用 nil 检查切片是否为空:

1
2
3
4
var slice []int
if slice == nil {
fmt.Println("Slice is nil")
}

深拷贝与浅拷贝

  • 浅拷贝:切片复制或切片派生操作会创建新的切片结构,但底层数组共享内存。
  • 深拷贝:使用 copy() 函数创建数据独立的拷贝。

6. 示例:数组与切片在实际场景中的应用

使用数组实现固定大小的缓冲区

1
2
3
4
5
6
7
func main() {
var buffer [10]int
for i := 0; i < len(buffer); i++ {
buffer[i] = i * i
}
fmt.Println("Buffer:", buffer)
}

使用切片处理动态数据

1
2
3
4
5
func main() {
data := []string{"Go", "Rust", "Python"}
data = append(data, "JavaScript")
fmt.Println("Languages:", data)
}