网站首页 > 技术文章 正文
在面试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-12-17 C语言实现推箱子游戏!(超简单详细)代码思路+源码分享
- 2024-12-17 学习笔记之C#基础——数组和集合 c#中数组用法
- 2024-12-17 新函数REDUCE来了!Excel中的最强辅助,太强大了
- 2024-12-17 一篇文章学会golang语法,golang简明教程快速入门
- 2024-12-17 深入理解 Golang 中的值类型和引用类型
- 2024-12-17 SpringBoot系列之数据库初始化-datasource配置方式
- 2024-12-17 C++ 创建数组和使用数组学习笔记 c++如何建立数组
- 2024-12-17 Java Map 中那些巧妙的设计 javamap的用法
- 2024-12-17 大数据开发基础之一维数组的定义、初始化及与二维数组的区别
- 2024-12-17 go语言结构体与初始化 go 结构体初始化
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)