Slice底层
数组
几乎所有计算机语言,数组的实现都是相似的:一段连续的内存。GO语言也一样,GO语言的数组底层实现就是一段连续的内存空间。
由于内存连续,CPU很容易计算索引,所以可以快速迭代数组里所有的元素。
C语言里面的Array是指向数组第一个元素的指针,GO语言的Array不像C语言里面的Array,在GO语言里,Array是一个Struct,一个数组变量就是整个数组。所以GO语言里Array是值类型,所以数组在传递的时候,传递的是原数组的拷贝。
GO语言slice底层数据结构
GO语言的Slice底层其实就是一个Struct:
type slice struct{
array unsafe.Pointer
len int
cap int
}
它的struct包含三个字段:1.指向数组的指针 2.它的长度 3.它的容量
所以说,一个slice其实是截取了某个数组的一部分而已。
由于是指针,所以它的改变会直接影响到它所指向的数组。
当然我说的是影响它所指向的数组,如果它所指向的数组被更换了,就影响更换之后的数组了。
请看这段代码:
package main
import (
"fmt"
)
func main() {
var s = [5]string{"1", "2", "3", "4", "5"}
fmt.Printf("原数组s:\n %v\n", s)
sli := s[0:5]
newsli := append(sli, "t")
sli[1] = "x"
fmt.Printf("截取数组s后的sli:\n %v\n", sli)
fmt.Printf("原数组s:\n %v\n", s)
fmt.Printf("append sli后的newsli:\n %v\n", newsli)
newsli[0] = "w"
fmt.Printf("改变了newsli的第一个元素之后的newsli:\n %v\n", newsli)
fmt.Printf("原数组:\n %v\n", s)
fmt.Printf("sli:\n %v\n", sli)
}
输出结果:
原数组s:
[1 2 3 4 5]
截取数组s后的sli:
[1 x 3 4 5]
原数组s:
[1 x 3 4 5]
append sli后的newsli:
[1 2 3 4 5 t]
改变了newsli的第一个元素之后的newsli:
[w 2 3 4 5 t]
原数组:
[1 x 3 4 5]
sli:
[1 x 3 4 5]
由此可见,我们得出以下几个结论:
以数组为基础建立的slice,这个slice的改变(改变slice里某个索引的值 或者 append的方式在其末尾添加元素),会直接影响到底层数组(它底层指针所指向的数组)
append的返回值是一个新的slice,它也是截取了跟它第一个参数(就是那个需要append的slice)一样的底层数组,所以这个append的返回值的改变,一样也直接影响到底层数组,它和那个append的第一个参数一样,它们的改变都能够直接影响到底层数组。
如果append之后超过了原数组的容量,那么得到的新slice的变化将不再影响到该底层数组,因为超过了原有底层数组的容量之后,它的底层数组就会发生改变。GO会开辟一块新的内存把原来的值拷贝过来。
扩容的原则是:
- 如果切片的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2;一旦元素个数超过1024个元素,增长因子就变成1.25,即每次增加原来容量的四分之一。