Go的切片原理详解 - Go语言中文社区

Go的切片原理详解


前言

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些类似于其他语言中的数组,但是有一些不同寻常的特性。 本文将深入切片的本质,并讲解它的用法。

切片

数组虽然有适用它们的地方,但是数组不够灵活,因此在Go代码中数组使用的并不多。 但是,切片则使用得相当广泛。切片基于数组构建,但是提供更强的功能和便利。

切片类型的写法是 []T ,  T 是切片元素的类型。和数组不同的是,切片类型并没有给定固定的长度。

 

letters := []string{"a", "b", "c", "d"}

 

我们可以创建一个var s []byte

然后往切片里边放入值,

s[0] = 0

s[1] = 1

s[2] = 2

s[3] = 3

s[4] = 4

或者也可以这样:

s = append(s,0)

s = append(s,1)

s = append(s,2)

s = append(s,3)

s = append(s,4)

或者索性这样:

s = append(s,0,1,2,3,4)

总之切片是这样的

var s[]int = []int{0,1,2,3,4}

切片也可以使用内置函数 make 创建,函数签名为:

s := make([]byte, 5)

可以使用内置函数 len 和  cap 获取切片的长度和容量信息。

len(s) == 5

cap(s) == 5

切片的底层原理

一个切片是一个数组片段的描述(实际上是个整形数组)。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。如下图

 

前面使用 make([]byte, 5) 创建的切片变量  s 的结构如下:

 

长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。我们可以看到 长度len = 5 , 容量cap = 5。 因为我们使用make([]byte, 5)并没有指定cap的大小,系统默认cap = 5,其使用效果与make([]byte, 5,5)一样。

为了更一步了解长度和容量的区域,我们继续对 s 进行切片,观察切片的数据结构和它引用的底层数组:

 

S1 = s[2:4]

 

 

 

通过 s1=s[2:4] 操作后,我们发现s1的切片的len=2,cap=3。 s1的指针指向了s数组索引为2的数组位置,s1切片所指向的数据为{2,3} 这也是左闭右开原则,所以长度len=2, 但因为指针指向s数组索引位置2,其当前切片的可用的容量是从当前指针位置往后数,即当前指针到最后有3个位置空间。即cap=3。 这一切的一切只说明了s1,但s数组的len和cap并没有因此改变。

 

接着

s1 = append(s,8)

 

 

S1追加了一个数据,那么追加的这个数据’8’放到了s1指针切片的末端也就是s数组的索引为4的位置,此时追加改变了 s1的len(len=3) 同时也改变了s数组索引4位置的数据。故修改了原先数组!

 

好了我们已经对切片有了简单的认识了,接下来我们深入以下切片特性,请明白:

1: 占用资源很小

2: slice[a:b] 包含a索引值,不包含b索引值,默认容量上界索引为被操作对象容量上界索引

3: slice[a:b:c] 包含a索引值,不包含b索引值,容量上界索引为c(cap中不包含索引C位置)

4: slice 只能向后扩展,不能向前扩展

5: slice append

(1)某次操作未超过该slice容量上界索引,此次改变会更新原数组;

(2)某次操作超过该slice容量上界索引则新的被操作对象数组会被新建,此次改变不会更新原数组

 

 

 

前面创建的切片 s1 追加后长度依旧小于s1的容量。如果追加的长度超过它本身容量的话该如何扩容呢?

 

这次我们把s数组重新初始化,10个数组元素分别是0,1,2,3,4,5,6,7,8,9

 

var s []int = [...]int{0,1,2,4,5,6,7,8,9}

var s0 [] int

s0 = s[3:9]

 

s0的len=6 , cap = 7 (ps:为什么cap = 7,因为其可用的容量是从当前指针位置往后数到最大上限,这里容量的上限没有给定,故索引因此默认为数组的容量上限索引为10)

 

接着我们定义s1切片并为s1扩容

 

s1 := s[3:6:8]

 

图中已经标出属于s1所指的区域和它的容量区域 , 接着进行追加数据如下

 

s1 = append(s1, 100)

 

我们发现跟预期一样,没有超出容量范围追加数据直接在原先len后追加,s1所指数据由3,4,5,100。同时这个过程中改变了原先s数组的索引为6的数据,改为100。

 

我们再连续继续追加两个数据:

s1 = append(s1, 100, 100)

 

其实s1的容量已经存放不下了,此次 超过该s1容量上界索引”8”, 不更新原数组, 新建数组。新建尺寸的大小是cap的大小。(划重点!切片扩容都是以cap为尺寸大小扩容,内存大小是cap的整数倍)

 

如图所示,此时,s1的len=6, cap=10 。s1={3,4,5,100,100,100}

s={0,1,2,3,4,5,100,7,8,9}

 

代码

// 切片
	// 1: 占用资源很小
	// 2: slice[a:b] 包含a索引值,不包含b索引值,默认容量上界索引为被操作对象容量上界索引
	// 3: slice[a:b:c] 包含a索引值,不包含b索引值,容量上界索引为c
	// 4: slice 只能向后扩展,不能向前扩展
	// 5: slice append
	// (1)某次操作未超过该slice容量上界索引,此次改变会更新原数组;
	// (2)某次操作超过该slice容量上界索引则新的被操作对象数组会被新建,此次改变不会更新原数组
	numArr := [10]uint{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	slice0 := numArr[3:9]             // 容量上界索引为10
	slice1 := numArr[3:6:8]           // 容量上界索引为8
	slice1 = append(slice1, 100)      // 此次 未超过该slice容量上界索引 更新原数组, 不新建数组
	slice1 = append(slice1, 100, 100) // 此次 超过该slice容量上界索引 不更新原数组, 新建数组
	var s []int = []int{1, 2, 3, 4}
	fmt.Print(s)
	for i := 0; i < len(slice0); i++ {
		fmt.Println(i, slice0[i])
	}
	fmt.Println("slice0 ==>", len(slice0), cap(slice0))

	for i := 0; i < len(slice1); i++ {
		fmt.Println(i, slice1[i])
	}
	fmt.Println("slice1 ==>", len(slice1), cap(slice1))

	for i := 0; i < len(numArr); i++ {
		fmt.Println(i, numArr[i])
	}

	fmt.Println("slice ==>", len(numArr), cap(numArr))

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/shaoduo/article/details/93221220
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-09-05 14:29:04
  • 阅读 ( 985 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢