基于本文回答
0
评论

数组(Array)和切片(Slice)的区别?

知识点图片

在 Go 语言中,数组(Array)切片(Slice)是两个紧密相关但行为截然不同的概念。

简单总结:数组是固定长度的值类型,切片是动态长度的引用类型(底层是对数组的抽象)。

以下是详细的区别对比:

1. 长度与类型定义

  • 数组(Array):长度是类型的一部分。
    • [5]int[10]int 是完全不同的两种数据类型,不能互相赋值。
    • 数组一旦定义,长度固定,不可修改。
  • 切片(Slice):长度不是类型的一部分。
    • []int 可以表示任意长度的整数序列。
    • 切片的长度是动态的,可以随着元素的添加(append)而自动扩容。

2. 内存传递(值类型 vs 引用类型)

这是两者最核心的区别,决定了它们在函数传参和赋值时的行为。

  • 数组是值类型(Value Type):
    • 当你将一个数组赋值给另一个变量,或者作为参数传递给函数时,会发生完全拷贝(Deep Copy)。
    • 修改副本不会影响原数组。
    • 缺点:如果数组很大,传参会消耗大量内存和性能。
  • 切片是引用类型(Reference Type):
    • 切片本质上是一个轻量级的结构体(Header),包含三个字段:指向底层数组的指针、长度(len)、容量(cap)。
    • 赋值或传参时,只拷贝这个轻量级的 Header(浅拷贝)。
    • 修改切片中的元素,会影响到底层数组,因此其他共享该底层数组的切片也会受到影响。

3. 初始化与声明

  • 数组: 需要指定长度(或使用 ... 让编译器推断)。
    go
    var a [5]int            // 声明一个长度为5的数组,全为0
    b := [3]int{1, 2, 3}    // 初始化
    c := [...]int{1, 2, 3}  // 编译器自动推断长度为3
  • 切片: 不需要指定长度,或者使用 make
    go
    var s []int             // 声明一个切片,默认为 nil
    s1 := []int{1, 2, 3}    // 字面量初始化
    s2 := make([]int, 5, 10) // 使用 make 创建:长度5,容量10
    s3 := arr[1:3]          // 从数组 arr 切分出来

4. 扩容与增删

  • 数组: 不支持扩容,不支持动态添加或删除元素。
  • 切片: 支持使用内置函数 append() 添加元素。
    • 当切片容量不足时,Go 运行时会自动分配一个新的、更大的底层数组,并将原数据拷贝过去(扩容机制)。

5. 比较(Comparison)

  • 数组: 如果数组的元素类型是可比较的(如 int, string),那么数组也是可比较的。可以使用 ==!= 比较两个数组的内容。
  • 切片: 不可比较。切片只能和 nil 进行比较。不能使用 == 判断两个切片是否相等(如果需要比较,需使用 reflect.DeepEqual 或循环遍历)。

总结对比表

特性 数组 (Array) 切片 (Slice)
长度 固定,长度是类型的一部分 动态,长度不影响类型
类型定义 [n]T (如 [5]int) []T (如 []int)
赋值/传参 值拷贝 (复制整个数组数据) 引用传递 (复制指针、长度、容量)
扩容 不支持 支持 append 自动扩容
比较 支持 == (若元素可比较) 不支持 (只能与 nil 比较)
底层结构 连续的内存块 结构体:{ptr, len, cap}

代码示例

1. 数组的值拷贝行为:

go
func main() {
    a := [3]int{1, 2, 3}
    b := a // 发生完全拷贝
    b[0] = 100
    
    fmt.Println(a) // 输出: [1 2 3] (原数组未变)
    fmt.Println(b) // 输出: [100 2 3]
}

2. 切片的引用行为:

go
func main() {
    a := []int{1, 2, 3}
    b := a // 仅拷贝 Header,指向同一个底层数组
    b[0] = 100
    
    fmt.Println(a) // 输出: [100 2 3] (原切片也被修改了)
    fmt.Println(b) // 输出: [100 2 3]
}

什么时候用哪个?

  • 99% 的情况使用切片。切片灵活、高效、功能强大,是 Go 语言处理序列数据的标准方式。
  • 使用数组的情况
    • 当你确切知道数据的长度,且长度很小(例如 MD5 的返回值是 [16]byte)。
    • 为了避免内存分配开销(数组在栈上分配,切片底层数组通常在堆上)。
    • 需要对数据布局进行精细控制时。
右滑查看面试常问