golang基础:一.切片(slice)
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
slice基础
切片的结构
slice是一个结构体
参照源码: src/runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
array:数组指针,len表示长度,cap表示容量
切片的声明
var a []int
采用数组创建切片
var a = [10]int{1,2,3,4,5,6,7,8,9}
sl := a[5:] //切片
fmt.Println(sl)
采用make创建切片
// make: 第一个参数:类型,第二个参数:初始长度,第三个参数:容量(如果容量不够的情况自动扩容)
var sl = make([]int, 3, 10)
fmt.Println(len(sl),cap(sl))
append函数
- 可以在Slice尾部追加元素
- 可以将一个Slice追加在另一个Slice尾部
a := []int{}
a = append(a,1)
b := []int{3,4,5}
a = append(a,b...)
// ... 是go的一种语法糖,用于打散切片,或者传不定参数。
copy函数
a := []int{1,2,3,4,5}
b := []int{7,8,9}
copy(a,b) // 将第二个切片拷贝到第一个切片上。且第一个切片的长度不会变
fmt.Println(a) //[7 8 9 4 5]
slice 扩容机制
- 长度小于容量时(不扩容)
a := make([]int,2,3)
a = append(a,2)
fmt.Println("长度:",len(a))
fmt.Println("容量:",cap(a))
// 结果:----------------------------
// 长度: 3
// 容量: 3
- 长度超过容量,且容量小于1024(容量翻倍)
a := make([]int,1023,1023)
a = append(a,2)
fmt.Println("长度:",len(a))
fmt.Println("容量:",cap(a))
// 结果:----------------------------
// 长度: 1024
// 容量: 2048
- 长度超过容量,且容量大于等于1024,容量增大1/4,且内存对齐
参照源码: src/runtime/slice.go
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
当切片的长度超过容量时,容量会进行扩容,如果容量(cap)小于1024,则容量翻倍,否则newcap += newcap / 4,然后再进行向上的内存对齐
slice的地址
- slice不扩容不改变地址
b := make([]int,0,10)
fmt.Printf("b的地址: %p\n",b)
b = append(b,1)
fmt.Printf("b的地址: %p\n",b)
// 结果:----------------------------
// b的地址: 0xc000016230
// b的地址: 0xc000016230
- slice扩容即改变地址
b := make([]int,1,1)
fmt.Printf("b的地址: %p\n",b)
b = append(b,1)
fmt.Printf("b的地址: %p\n",b)
// 结果:----------------------------
// b的地址: 0xc00000a128
// b的地址: 0xc00000a140
slice作为函数的参数
- 函数内改变slice的值,函数外的slice也会改变
func main() {
a := []int{2}
updateSlice(a)
fmt.Println(a)
}
func updateSlice (s []int) {
if len(s) > 0 {
s[0] = 10
}
}
// 结果:----------------------------
// [10]
- 函数内append,函数外为函数append前的值
func main() {
a := []int{2}
appendSlice(a,3)
fmt.Println(a)
}
func appendSlice(s []int, v int) {
if len(s) > 0 {
s[0] = 5
}
s = append(s,v)
}
// 结果:----------------------------
// [5]
总结
- 切片是对底层数组的一个抽象,描述了它的一个片段。
- 切片实际上是一个结构体,它有三个字段:长度,容量,底层数据的地址。
- 多个切片可能共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片。
- append 函数会在切片容量不够的情况下扩容,扩容会改变元素原来的位置。
- 扩容策略并不是简单的扩为原切片容量的 2 倍或 1.25 倍,还有内存对齐的操作。扩容后的容量 >= 原容量的 2 倍或 1.25 倍。
- 当直接用切片作为函数参数时,可以改变切片的元素,不能改变切片本身;想要改变切片本身,可以将改变后的切片返回,函数调用者接收改变后的切片或者将切片指针作为函数参数。