网站首页 > 知识剖析 正文
在面试Golang工程师都会被面试官问到的一些关于slice原理的一些面试题:
- slice的底层实现原理?
- 数组和slice的区别是什么?
- slice的深拷贝和浅拷贝
- slice的扩容机制是怎么样的?
针对以上的面试题,我们需要深入理解slice的一些原理
slice的底层实现原理
slice是无固定长度的数组且使用之前要先分配内存,slice的底层结构是一个结构体有3个属性;
type slice struct {
array unsafe.Pointer // 8bytes
len int // 8bytes
cap int // 8bytes
}
- array:表示一个指向一个数组的指针,数据存储在这个指针指向的数组上;
- len:slice的长度;
- cap:slice的容量,同时也是底层数组的长度;
由此我们可以看出slice是一个引用类型,slice指向底层的数组,声明slice可以像声明数组一样,只是slice的长度是可变的。
slice跟数组的区别
数组
- 数组初始化
数组在初始化之前必须指定大小和初值,但是我们可以使用go的语法糖来灵活初始化数组,例如:使用...来自动获取长度;未指定值得时候用0赋予初始值。
var arr [5]int //声明了一个大小为5的数组,默认初始化值为[0,0,0,0,0]
arr := [5]int{1} //声明并初始化了一个大小为5的数组的第一个元素,初始化后值为[1,0,0,0,0]
arr := [...]int{1,2,3,4} //通过...自动获取数组长度,根据初始化的值的数量将大小初始化为4,初始化后值为[1,2,3,4]
arr := [...]int{2:1} // 指定序号为2的元素的值为1,通过...自动获取长度为3,初始化后值为[0 0 1]
- 数组作为函数参数
数组作为函数参数时,必须指定参数数组的大小,且传入的数组大小必须与指定的大小一致,数组为按值传递的,函数内对数组的值的改变不影响初始数组
package main
import "fmt"
func PrintArray(arr [4]int){
arr[1] = 5
fmt.Println(arr)
}
func main(){
mainArray := [...]int{1,2,3,4}
PrintArray(mainArray) // [1 5 3 4]
fmt.Println(mainArray) // [1 2 3 4]
}
切片slice
- 切片初始化
slice在初始化时需要初始化指针,长度和容量,容量未指定时将自动初始化为长度的大小。可以通过直接获取数组的引用、获取数组slice的切片构建或是make函数初始化数组
注意:如果通过slice初始化slice,因为两个slice都是指向同一个数组,所以改变某一个slice里面的值都会导致两个slice的值都改变。
var slice1 []int{1,2,3}
arr := [5]int{1,2,3,4,5}
slice2 := arr[0:3] // 通过数组来初始化切片,值为[1,2,3],长度为3,容量为5
slice3 := make([]int, 3,5) // 通过make函数来初始化切片,值为[0,0,0],长度为3,容量为5
- 切片作为函数参数
slice的参数传递是引用传递,在被调用函数修改元素的值,同时也是在修改调用方的slice的元素。
package main
import "fmt"
func modifySlice(s []int) {
s[0] = 10
fmt.Println("In modifySlice function, slice values is:", s)
}
func main() {
s := []int{1,2,3,4,5}
modifySlice(s)
fmt.Println("In main, slice values is:",s)
}
总结:
- 切片是指针类型,数组是值类型
- 数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)
- 数组只有长度一个属性,而切片比数组多了一个容量(cap)属性
- 切片的底层也是使用数组实现的
slice的深拷贝跟浅拷贝
深拷贝:拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值,既然内存地址不同,释放内存地址时,可分别释放。在Go中值类型的数据默认赋值操作都是深拷贝比如:数组,整型,字符串,结构体,浮点型,布尔型,如果引用类型的数据想通过深拷贝就需要copy函数来完成。
浅拷贝:拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。Go中引用类型的数据,默认全部都是浅拷贝,Slice、Map等。
- Go深拷贝
package main
improt "fmt"
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 5, 5)
// 深拷贝
copy(slice2, slice1)
fmt.Println(slice1, len(slice1), cap(slice1))
// [1 2 3 4 5] 5 5
fmt.Println(slice2, len(slice2), cap(slice2))
// [1 2 3 4 5] 5 5
slice1[1] = 100
fmt.Println(slice1, len(slice1), cap(slice1))
// [1 100 3 4 5] 5 5
fmt.Println(slice2, len(slice2), cap(slice2))
// [1 2 3 4 5] 5 5
}
- Go浅拷贝
注意:在复制 slice 切片的时候,slice切片 中数组的指针也会被复制了,在触发扩容逻辑之前,两个 slice 指向的是相同的数组,触发扩容逻辑之后指向的就是不同的数组了。
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3, 4 ,5 ,6 }
// 浅拷贝(注意赋值操作对于引用类型是浅拷贝,对于值类型是深拷贝)
slice2 := slice1
fmt.Printf("%p\n", slice1) // 0xc00001e1b0
fmt.Printf("%p\n", slice2) // 0xc00001e1b0
// 同时改变两个数组,这时就是浅拷贝,未扩容时,修改 slice1 的元素之后,slice2 的元素也会跟着修改
slice1[0] = 10
fmt.Println(slice1, len(slice1), cap(slice1))
// [10 2 3 4 5 6] 6 6
fmt.Println(slice2, len(slice2), cap(slice2))
// [10 2 3 4 5 6] 6 6
// 注意下:扩容后,slice1和slice2不再指向同一个数组,修改 slice1 的元素之后,slice2 的元素不会被修改了
slice1 = append(slice1, 5, 6, 7, 8)
slice1[0] = 11
// 这里可以发现,slice1[0] 被修改为了 11, slice1[0] 还是10
fmt.Println(slice1, len(slice1), cap(slice1))
// [11 2 3 4 5 6 5 6 7 8] 10 12
fmt.Println(slice2, len(slice2), cap(slice2))
[10 2 3 4 5 6] 6 6
}
slice切片的扩容机制是什么?
当slice的长度已经等于容量的时候,再使用append()给slice追加元素,会自动扩展底层数组的长度。这时候就会发生切片的扩容。
注意:
- 在原容量扩大两倍还要小于扩容后的容量时,预估容量就是扩容后的,当原 slice 容量小于 1024 的时候,新 slice 容量变成原来的 2 倍;原 slice 容量超过 1024,新 slice 容量变成原来的1.25倍
- append 函数调用 growslice 函数获取所需要的内存,这称为扩容,扩容会改变元素原来的位置。
package main
import "fmt"
func main() {
a := make([]int, 20)
b := make([]int, 42)
a = append(a, b...)
fmt.Println(len(a), cap(a)) // 62
}
猜你喜欢
- 2024-11-10 PHP数组学习笔记(1) php数组有哪几种类型
- 2024-11-10 Rust语言入门教程 数组和切片 rust语言例子
- 2024-11-10 javascript自学笔记:Array类型1 javascript自学笔记:array类型1怎么解决
- 2024-11-10 Array.from详解: 语法、功能与应用场景
- 2024-11-10 帮你精通JS:解析与盘点数组array的5类22种方法
- 2024-11-10 10 个实用的 JS 技巧 js常用方法大全
- 2024-11-10 WordPress 内置的数组处理相关函数大全
- 2024-11-10 3分钟短文 | PHP获取函数的代码片段,唯有反射最高效
- 2024-11-10 JS 中的类数组对象如何转换为数组?
- 2024-11-10 Go 中的循环是如何转为汇编的?看完你懂了吗?
- 最近发表
- 标签列表
-
- xml (46)
- css animation (57)
- array_slice (60)
- htmlspecialchars (54)
- position: absolute (54)
- datediff函数 (47)
- array_pop (49)
- jsmap (52)
- toggleclass (43)
- console.time (63)
- .sql (41)
- ahref (40)
- js json.parse (59)
- html复选框 (60)
- css 透明 (44)
- css 颜色 (47)
- php replace (41)
- css nth-child (48)
- min-height (40)
- xml schema (44)
- css 最后一个元素 (46)
- location.origin (44)
- table border (49)
- html tr (40)
- video controls (49)