面试题答案
一键面试Slice的内存布局
- 结构组成:在Go语言中,
Slice
本质上是一个包含三个字段的结构体。这三个字段分别是:- 指向底层数组的指针:该指针指向Slice所引用的底层数组的第一个元素。通过这个指针,Slice能够访问到底层数组的数据。
- 长度(length):表示Slice当前包含的元素个数。长度是Slice可以访问的元素数量的上限,并且不能超过底层数组的容量。
- 容量(capacity):表示从Slice的起始元素开始,到其底层数组末尾的元素个数。容量反映了Slice在不重新分配内存的情况下,最多能容纳多少个元素。
- 示例:假设有如下代码:
s := make([]int, 5, 10)
这里创建了一个Slice
,其底层数组是一个可以容纳10个int
类型元素的数组。Slice
当前长度为5,容量为10。其内存布局中,指向底层数组的指针指向数组第一个元素的内存地址,长度字段值为5,容量字段值为10。
Slice与数组在内存布局上的主要区别
- 固定与动态:
- 数组:数组的长度在声明时就固定下来,其内存布局是一段连续的内存空间,大小为数组元素类型大小乘以数组长度。例如
var a [5]int
,数组a
在内存中占据5 * sizeof(int)
大小的连续空间,且这个空间大小在数组生命周期内不会改变。 - Slice:Slice的长度是动态可变的。虽然它基于底层数组,但自身的长度可以在一定范围内变化(只要不超过容量)。其内存布局除了指向底层数组的指针,还有长度和容量字段,这使得Slice在使用上比数组更灵活。
- 数组:数组的长度在声明时就固定下来,其内存布局是一段连续的内存空间,大小为数组元素类型大小乘以数组长度。例如
- 内存管理方式:
- 数组:数组是值类型,当数组作为函数参数传递时,会完整地复制一份。这意味着函数内部对数组的修改不会影响原始数组,除非传递的是数组指针。例如:
func modifyArray(arr [5]int) {
arr[0] = 100
}
var a [5]int
modifyArray(a)
// a[0] 仍然是初始值0,因为函数内修改的是副本
- **Slice**:Slice作为函数参数传递时,传递的是其结构体(包含指针、长度和容量),这意味着函数内部对Slice的修改可能会影响原始Slice,因为它们共享底层数组。例如:
func modifySlice(s []int) {
s[0] = 100
}
s := make([]int, 5)
modifySlice(s)
// s[0] 变为100,因为函数内修改的是同一个底层数组
- 容量特性:
- 数组:数组没有容量的概念,其大小就是声明时指定的长度。
- Slice:Slice有容量的概念,容量决定了在不重新分配内存的情况下,Slice能扩展到多大。当Slice的长度增长到等于容量时,如果再追加元素,就需要重新分配内存,通常会分配一个更大的底层数组,并将原数组的数据复制过去。