【从零开始捡起GO】7. 数组和切片

数组

Go 语言提供了数组类型的数据结构。

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。

数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

作为强类型语言,和弱类型语言php相比的区别在于,golang的数组是固定类型定长的数据集合(当然你可以在初始化的时候不将其填满),数组在声明后不可改变其长度大小。 数组索引是一个从0开始的递增的整数,并不能和php一样使用[k=>v]的形式指定索引的值。

声明数组

声明数组使用:var 变量名 [数组大小(int)] 数组内元素的类型(type)

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var arr1 [8]int
	var arr2 [4]bool
	fmt.Println(arr1, len(arr1))
	fmt.Println(arr2, len(arr2))
}

输出

[0 0 0 0 0 0 0 0] 8
[false false false false] 4

可以看到在声明完数组后,会自动使用对应元素类型的默认值填充这个数组。

数组初始化/修改数组内元素

初始化数组可以用{}写入元素的值,用,隔开:var 变量名 = [数组大小]数组类型{元素1,元素2....}

在初始化的时候如果需要对指定索引的元素初始化可以在{}内通过索引:值的方式初始化指定索引上的元素:var 变量名 = [数组大小]数组类型{元素1,元素2...., 索引:值}

func main() {
	var arr1 = [8]int{0,1,2,3,4,7:1}
	fmt.Println(arr1, len(arr1))
}

输出

[0 1 2 3 4 0 0 1] 8
初始化时可能遇到的错误
  1. 初始化时{}内的元素不能大于数组的长度,当超过数组长度时编译会报错:array index 8 out of bounds [0:8] 索引超过了边界。当然,你也不能指定一个超过数组长度的索引初始化这个数组,会报同样的错误。

  2. 你可以使用索引:值的方式来对指定索引进行初始化,但是你不能重复初始化同一个索引,当重复初始化同一个索引时编译会报错:duplicate index in array literal: 0 数组内存在重复的索引

  3. 当你使用索引:值的方式对指定索引进行初始化后,指针将指向你指定的索引,后面的初始化默认会将指针后移一位,在初始化时可能会产生重复初始化索引的问题,以这段代码为例子:var arr1 = [8]int{1:1,0:0,1}

  4. 首先毋庸置疑的这里是声明了一个长度为8的int数组集合

  5. 通过指定指针1设置了值,当前数组指针在1

  6. 通过指定指针0设置了值,当前数组指针在0

  7. 直接为下一个指针初始化,此时指针自动移动到下一个,指针变为1,因为重复对索引为1的元素初始化,编译时会出错:duplicate index in array literal: 1

初始化未知大小的数组

虽然是强类型语言,但是go还是可以初始化一个未知大小的数组的。在初始化数组时数组长度设置为[...]即可根据元素个数推导数组长度:var 变量名 = [...]类型{元素1,元素2.....}

访问/修改数组内的元素

访问数组内的元素使用变量[索引]的方法来访问,对索引对应的元素赋值可以修改元素:

func main() {
	arr1 := [...]int{0,1,2,3,4,5}
	arr1[3] *= 10
	fmt.Println(arr1, len(arr1),arr1[3])
}

输出

[0 1 2 30 4 5] 6 30

当访问不存在的指针的时候编译会报索引错误:invalid array index 99 (out of bounds for 6-element array)索引超过数组大小

索引是从0开始递增的整数,当访问一个负数索引时编译也会报索引错误:invalid array index -1 (index must be non-negative)索引必须是非负数

遍历数组

for循环
func main() {
	arr := [...]string{"foo", "bar", "foobar"}
	for i := 0; i < len(arr); i ++ {
		fmt.Printf("index:%v, value:%v\r\n",i,arr[i])
	}
}

输出

index:0, value:foo
index:1, value:bar
index:2, value:foobar
使用for+range来遍历
func main() {
	arr := [...]string{"foo", "bar", "foobar"}
	for k, v := range arr {
		fmt.Printf("index:%v, value:%v\r\n", k, v)
	}
}

输出

index:0, value:foo
index:1, value:bar
index:2, value:foobar

还记得怎么做到不接收k或者v吗?

多维数组

多维数组其实就是数组中的每一个元素都是数组,定义方式如下: var 变量名 = [第一层的元素个数][第二层的元素个数][第三层的元素个数]...[第N层的元素个数]类型{{{},{}},{}}

那么这里定义一个三维数组作为demo:

func main() {
	arr := [2][3][4]string{
		{
			{"1a","1b","1c","1d"},
			{"2a","2b","2c","2d"},
			{"3a","3b","3c","3d"},
		},{
			{"4a","4b","4c","4d"},
			{"5a","5b","5c","5d"},
			{"6a","6b","6c","6d"},
		},
	}
	fmt.Print(arr)
}

输出

[[[1a 1b 1c 1d] [2a 2b 2c 2d] [3a 3b 3c 3d]] [[4a 4b 4c 4d] [5a 5b 5c 5d] [6a 6b 6c 6d]]]

访问方式为:变量名[第一层索引][第二层索引]...[第N层索引]

这里访问一下上面的数组

fmt.Println(arr[0][2][2])	//	输出3c

切片

如果你和我一样,习惯了编写php代码,那么一定会对上面的数据感到反人类(然而强类型语言都这样)。那么有没有一个东西可以定义一个随意改变长度的“数组”呢?有的有的,就是这里要说的切片。

定义一个切片

切片的定义方式:var 变量名 = []元素类型

细心的你一定发现了,这个切片的定义就是定义数组的时候不设置数组长度。

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

Go 语言切片是对数组的抽象。,切片在本质上就是数组的某一个段落。

切片是一个很小的对象,它对底层的数组(内部是通过数组保存数据的)进行了抽象,并提供相关的操作方法。切片是一个有三个字段的数据结构,这些数据结构包含 Golang 需要操作底层数组的元数据。这 3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。

可以看到,切片存储的并不是底层数组里的每一个元素实体,而是数组的指针,也就是说切片内存储的是内存地址(引用传递)。首先,还是来演示下如何初始化一个切片

初始化切片

初始化切片有3种方法

  1. 通过make方法初始化:var 变量名 = make([]类型,初始长度,容量)

  2. 显性初始化:var 变量名 = []类型{元素1,元素2...}

  3. 通过数组初始化:var 变量名 = 一个数组[开始的索引:结束的索引(并不包含)]

这里分别以上面3种方式举例。

通过make初始化
func main() {
	s1 := make([]int,3,4)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v", s1, len(s1), cap(s1))
}

输出

s1 = [0 0 0], len(s1) = 3, cap(s1) = 4

这里的切片是这样的

显性初始化
func main() {
	s1 := []int{0, 1, 2, 3}
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v", s1, len(s1), cap(s1))
}

输出

s1 = [0 1 2 3], len(s1) = 4, cap(s1) = 4

这里的切片是这样的

通过数组初始化
func main() {
	arr := [8]int{0,1,2,3,4,5,6,7}
	s1 := arr[1:6]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	// 输出 s1 = [1 2 3 4 5], len(s1) = 5, cap(s1) = 7
	
	s1 = arr[:4]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	// 输出 s1 = [0 1 2 3], len(s1) = 4, cap(s1) = 8
	
	s1 = arr[4:]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	// 输出 s1 = [4 5 6 7], len(s1) = 4, cap(s1) = 4
	
	s1 = arr[:]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	// 输出 s1 = [0 1 2 3 4 5 6 7], len(s1) = 8, cap(s1) = 8
}

首先说说 开始索引(i):结束位置(n) 。通过数组的开始索引,结束位置来创建切片会从开始索引开始(也就是以开始索引为这个切片的初始指针)然后截取到第n个元素结束

也就是说相当于索引从i开始,到(n-1)索引位置

当开始索引为空时默认为从头开始(从索引为0开始),当结束索引为空时默认为到数组结束为止(个数为len())

当开始结束都为空时可以理解为:s1 = arr[0:len(s1)]

这里着重看一下长度len和容量cap,观察一下上面4个切片的长度和容量:

  1. s1 := arr[1:6] // len(s1) = 5, cap(s1) = 7
  2. s1 = arr[:4] // len(s1) = 4, cap(s1) = 8
  3. s1 = arr[4:] // len(s1) = 4, cap(s1) = 4
  4. s1 = arr[:] // len(s1) = 8, cap(s1) = 8

通过数组创建切片,初始容量即为底层数组的对应开始位置到结束的大小,下面继续拿出祖传的excel辅助理解长度和容量:

PS:图中ARR[4:0]错了,应该是ARR[4:],凑合看吧各位,画个图不容易太耗时了

对于s1 = arr[1:6]

对于s2 = arr[:4]

对于s3 = arr[4:]

对于s4 = arr[:]

切片到底是引用还是赋值

经过上面4个图辅助,相信你对切片会有更深的认识。那么进入下一个话题:切片存储的并不是底层数组里的每一个元素实体,而是数组的指针,也就是说切片内存储的是内存地址(引用传递)。

想知道是不是引用很简单,通过修改数组内容观察切片变化就可以知道了。下面做一个简单的实验

func main() {
	arr := [8]int{0,1,2,3,4,5,6,7}
	s1 := arr[1:6]
	s2 := arr[:4]
	s3 := arr[4:]
	s4 := arr[:]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [1 2 3 4 5], len(s1) = 5, cap(s1) = 7
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [0 1 2 3], len(s2) = 4, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [4 5 6 7], len(s3) = 4, cap(s3) = 4
	fmt.Printf("s4 = %v, len(s4) = %v, cap(s4) = %v\r\n", s4, len(s4), cap(s4))
	//	s4 = [0 1 2 3 4 5 6 7], len(s4) = 8, cap(s4) = 8

	arr[0] = -100
	arr[7] = 100

	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [1 2 3 4 5], len(s1) = 5, cap(s1) = 7
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [-100 1 2 3], len(s2) = 4, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [4 5 6 100], len(s3) = 4, cap(s3) = 4
	fmt.Printf("s4 = %v, len(s4) = %v, cap(s4) = %v\r\n", s4, len(s4), cap(s4))
	//	s4 = [-100 1 2 3 4 5 6 100], len(s4) = 8, cap(s4) = 8
}

可以看到,修改数组后,切片同样发生了变化。修改切片同理:

func main() {
	arr := [8]int{0,1,2,3,4,5,6,7}
	s1 := arr[1:6]
	s2 := arr[:4]
	s3 := arr[4:]
	s4 := arr[:]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [1 2 3 4 5], len(s1) = 5, cap(s1) = 7
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [0 1 2 3], len(s2) = 4, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [4 5 6 7], len(s3) = 4, cap(s3) = 4
	fmt.Printf("s4 = %v, len(s4) = %v, cap(s4) = %v\r\n", s4, len(s4), cap(s4))
	//	s4 = [0 1 2 3 4 5 6 7], len(s4) = 8, cap(s4) = 8

	s1[0] = -100
	s3[2] = 100

	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [-100 2 3 4 5], len(s1) = 5, cap(s1) = 7
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [0 -100 2 3], len(s2) = 4, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [4 5 100 7], len(s3) = 4, cap(s3) = 4
	fmt.Printf("s4 = %v, len(s4) = %v, cap(s4) = %v\r\n", s4, len(s4), cap(s4))
	//	s4 = [0 -100 2 3 4 5 100 7], len(s4) = 8, cap(s4) = 8
}

那么再画一张图来说明这点

切片追加长度

增加切片使用func append(slice []Type, elems ...Type) []Typeappen()方法,append()方法会在切片中追加切片并返回操作后的切片。

func main() {
	arr := [8]int{0,1,2,3,4,5,6,7}
	s1 := arr[:1]
	s2 := append(s1,10,20,30,40,50)
	fmt.Println(arr)
	//	[0 10 20 30 40 50 6 7]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [0], len(s1) = 1, cap(s1) = 8
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [0 10 20 30 40 50], len(s2) = 6, cap(s2) = 8

	arr[0] = 100
	fmt.Println(arr)
	//	[100 10 20 30 40 50 6 7]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [100], len(s1) = 1, cap(s1) = 8
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [100 10 20 30 40 50], len(s2) = 6, cap(s2) = 8
}

那么问题来了,如果追加后的切片长度超过了原始容量会发生什么?

func main() {
	arr := [8]int{0,1,2,3,4,5,6,7}
	s1 := arr[6:7]
	s2 := arr[6:7]
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [6], len(s1) = 1, cap(s1) = 2
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [6], len(s2) = 1, cap(s2) = 2

	s1 = append(s1,11)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [6 11], len(s1) = 2, cap(s1) = 2
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [6], len(s2) = 1, cap(s2) = 2

	s2 = append(s2,12)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [6 12], len(s1) = 2, cap(s1) = 2
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [6 12], len(s2) = 2, cap(s2) = 2

	s1 = append(s1,21)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [6 12 21], len(s1) = 3, cap(s1) = 4
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [6 12], len(s2) = 2, cap(s2) = 2

	s2 = append(s2,22)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [6 12 21], len(s1) = 3, cap(s1) = 4
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [6 12 22], len(s2) = 3, cap(s2) = 4
}

捋一捋上面的流程:

  1. arr := [8]int{0,1,2,3,4,5,6,7}中创建了2个切片[6:7]s1和s2
  2. 为s1追加了一个元素11,此时该元素的指针在原数组的索引7位置,原数组索引7对应的数据变为了11
  3. 为s2追加了一个元素12,此时该元素的指针在原数组的索引7位置,原数组索引7对应的数据变为了12,同时,该s1的第二个索引也是在此位置的,同样的s1[1]也变为了12
  4. 为s1追加了一个元素21,此时s1切片指针右移,已经超出了原有的底层数组,此时会重新申请一个随机地址为起始索引的数组作为新的底层数组追加,s1[2]变为了21
  5. 为s2追加了一个元素22,此时s2切片指针右移,已经超出了原有的底层数组,此时会重新申请一个随机地址为起始索引的数组作为新的底层数组追加,而此时s1新申请的底层数组和s2新申请的底层数组并不是共享的同一个底层数组,所以s2[2]变为了22,而s1[1]依旧是21

那么新追加新的数组,它的长度是怎么样的呢?或者说切片的容量是怎么增加的呢?源码在%GOROOT/src/runtime/slice.go

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
func growslice(et *_type, old slice, cap int) slice {
	if raceenabled {
		callerpc := getcallerpc()
		racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
	}
	if msanenabled {
		msanread(old.array, uintptr(old.len*int(et.size)))
	}

	if cap < old.cap {
		panic(errorString("growslice: cap out of range"))
	}

	if et.size == 0 {
		// append should not create a slice with nil pointer but non-zero len.
		// We assume that append doesn't need to preserve old.array in this case.
		return slice{unsafe.Pointer(&zerobase), old.len, cap}
	}

	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
			}
		}
	}

	var overflow bool
	var lenmem, newlenmem, capmem uintptr
	// Specialize for common values of et.size.
	// For 1 we don't need any division/multiplication.
	// For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
	// For powers of 2, use a variable shift.
	switch {
	case et.size == 1:
		lenmem = uintptr(old.len)
		newlenmem = uintptr(cap)
		capmem = roundupsize(uintptr(newcap))
		overflow = uintptr(newcap) > maxAlloc
		newcap = int(capmem)
	case et.size == sys.PtrSize:
		lenmem = uintptr(old.len) * sys.PtrSize
		newlenmem = uintptr(cap) * sys.PtrSize
		capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
		overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
		newcap = int(capmem / sys.PtrSize)
	case isPowerOfTwo(et.size):
		var shift uintptr
		if sys.PtrSize == 8 {
			// Mask shift for better code generation.
			shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
		} else {
			shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
		}
		lenmem = uintptr(old.len) << shift
		newlenmem = uintptr(cap) << shift
		capmem = roundupsize(uintptr(newcap) << shift)
		overflow = uintptr(newcap) > (maxAlloc >> shift)
		newcap = int(capmem >> shift)
	default:
		lenmem = uintptr(old.len) * et.size
		newlenmem = uintptr(cap) * et.size
		capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
		capmem = roundupsize(capmem)
		newcap = int(capmem / et.size)
	}

	// The check of overflow in addition to capmem > maxAlloc is needed
	// to prevent an overflow which can be used to trigger a segfault
	// on 32bit architectures with this example program:
	//
	// type T [1<<27 + 1]int64
	//
	// var d T
	// var s []T
	//
	// func main() {
	//   s = append(s, d, d, d, d)
	//   print(len(s), "\n")
	// }
	if overflow || capmem > maxAlloc {
		panic(errorString("growslice: cap out of range"))
	}

	var p unsafe.Pointer
	if et.ptrdata == 0 {
		p = mallocgc(capmem, nil, false)
		// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
		// Only clear the part that will not be overwritten.
		memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
	} else {
		// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
		p = mallocgc(capmem, et, true)
		if lenmem > 0 && writeBarrier.enabled {
			// Only shade the pointers in old.array since we know the destination slice p
			// only contains nil pointers because it has been cleared during alloc.
			bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem)
		}
	}
	memmove(p, old.array, lenmem)

	return slice{p, old.len, newcap}
}

向 slice 追加元素的时候,若容量不够,会调用 growslice 函数,创建一个新的 newcap,此时newcap会根据原切片的大小增加一倍或1/4倍容量,并对新切片作了内存对齐,这个和内存分配策略相关。之后,向 Go 内存管理器申请内存,将老切片中的数据复制过去,并且将append的元素添加到新的底层数组中。最后,向 growslice 函数调用者返回一个新的切片,这个切片的长度并没有变化,而容量却增大了。

这里需要记住的最重要的一点就是:

	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
			}
		}
	}

当旧切片的长度小于1024时,会追加一倍长度的容量,当长度大于1024时,会增加1/4的容量,当容量溢出了就无法增加容量了。

切片的深层拷贝

刚才说到,切片在初始化时是浅层拷贝(引用),如果想要将原数组的值提出出来变为一个新的数组切片,可以使用copy()方法:

func copy(dst, src []Type) int

func main() {
	arr := [8]int{0,1,2,3,4,5,6,7}
	s1 := arr[0:3]
	s2 := make([]int,5,8)
	s3 := make([]int,0,8)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [0 1 2], len(s1) = 3, cap(s1) = 8
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [0 0 0 0 0], len(s2) = 5, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [], len(s3) = 0, cap(s3) = 8

	copy(s2,s1)
	copy(s3,s1)
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [0 1 2], len(s1) = 3, cap(s1) = 8
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [0 1 2 0 0], len(s2) = 5, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [], len(s3) = 0, cap(s3) = 8

	s1[0] = 100
	s2[0] = 200
	fmt.Printf("s1 = %v, len(s1) = %v, cap(s1) = %v\r\n", s1, len(s1), cap(s1))
	//	s1 = [100 1 2], len(s1) = 3, cap(s1) = 8
	fmt.Printf("s2 = %v, len(s2) = %v, cap(s2) = %v\r\n", s2, len(s2), cap(s2))
	//	s2 = [200 1 2 0 0], len(s2) = 5, cap(s2) = 8
	fmt.Printf("s3 = %v, len(s3) = %v, cap(s3) = %v\r\n", s3, len(s3), cap(s3))
	//	s3 = [], len(s3) = 0, cap(s3) = 8
}

使用copy()方法会使用后面的切片作为原型数据,赋值一分数据到前面切片的数组中,另外使用copy时长度不会自动增加。并且最多copy到原数据长度的位置。

总结

入门思维导图下载(使用xmind打开):golang.xmind

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论