golang中的slice

x33g5p2x  于2021-11-12 转载在 Go  
字(1.5k)|赞(0)|评价(0)|浏览(481)

golang中的slice表示一个可变长度的数组,声明为 []T ,其中T是类型。 关于数组请参见:golang中的数组

slice跟数组紧密相连,slice有三个属性,分别是:指针、长度(len)和容量(cap),其中指针指向底层数组,所以它天然依赖底层数组。从某种意义上来讲,slice更像一个结构体,这个结构体里面必然包含一个指针。

slice的指针指向底层数组

arr := [...]int{1, 2, 3, 4, 5}
slice1 := arr[:3]
for i := range slice1 {
    slice1[i]++
}
fmt.Println(slice1)		//	[2 3 4]
fmt.Println(arr)			//	[2 3 4 4 5]

对slice中的元素的值加一,影响到了数组arr中的值,因为 arr就是slice1的底层数组,slice1中的指针指向arr。

如果对字符串(等同于[]byte)使用 x[m:m],返回的还是一个字符串(其实还是一个[]byte),但如果对数组使用 x[m:m],返回的就不是数组了,而是slice。

注意一下初始化数组和初始化slice的方式

// 初始化数组,编译器会自动解析数组长度
arr := [...]int{1, 2, 3, 4, 5}
// 初始化slice,没有指定长度。golang会创建一个固定长度的底层数组,并用slice指向它
slice := []int{1, 2, 3, 4, 5}

slice的可比较性

slice之间无法直接比较,因为slice的本质是一个固定范围的指针,并不是直接的元素,并且底层数组还有可能会改变。

不过,slice可以和nil作对比。

if slice == nil {}

slice的零值

nil是slice的零值,它的没有底层数组,len为0,cap为0。

var s []int 	// len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s== nil
s = []int{}		// len(s) == 0, s != nil

所以检查一个slice是否为空,应该使用 len(s) == 0, 而不是与nil作比较。

当s为nil时,len(s)也是安全的,返回0。

使用make创建slice

make([]T, len)
make([]T, len, cap)

如果我们一开始就知道要创建多长的slice,例如长度为x,那么这样写会更高效,因为可以避免在后续的append中重新创建底层数组。

make([]T, 0, x)

append 函数

append([]T x, T y)

append的实现原理是,先检查当前slice是否有足够的容量,如果有,则定义一个新的slice,和之前的slice指向相同的底层数组,然后将新元素复制到新slice的新位置,并返回新slice。如果容量不足,则创建一个容量更大的slice(按照某种算法扩大容量),同时也会创建一个新的底层数组,将原slice的值复制过来,再将新元素复制到新slice的新位置。

所以,append每次都会返回一个新的slice,但我们无法确定旧slice和新slice是否指向同一个底层数组。

因此,使用append函数的正确姿势是

slice = append(slice, item)

实际上,我们对任何可能会生成新slice的函数,都要再把返回值赋给原变量。

slice的本质

slice的本质是一段固定范围的指针,append函数会改变slice的长度(len),所以需要返回一个新的slice。这么看来,slice并不像是严格意义上引用类型,更像是一种聚合类型

type IntSlice struct {
	ptr		*int
	len, cap 	int
}

相关文章