我可能并不会使用golang slice

2,392 阅读12分钟

使用了很多的slice,最近再准备面试的时候,才发现,自己对他是一知半解的,倘若问几个比较戏剧性的问题,发现自己还是第一时间无法给出正确答案的,所以今天,系统性的整理一下有关slice的一些知识点。

package main

import (
	"fmt"
)

func main() {


	a := make([]int, 10)
	a = append(a, 1, 2)
	FuncA(a)
	fmt.Println(a)
	a[0] = 44
	FuncA(a)
	fmt.Println(a)
}

func FuncA(b []int) {
	b[0] =19
}

上面的示例输出是:

[19 0 0 0 0 0 0 0 0 0 1 2]
[19 0 0 0 0 0 0 0 0 0 1 2]

从输出看,一般情况下,会有两个疑问:

  • 我实例化一个切片之后,append两个元素,为什么不是切片的第一、二个元素
  • 我一个值传递,调用FuncA,为啥可以在它函数体内,修改我切片的内容

有问题再细细看,就明白了切片的原理, 首先看第一个问题。实例化切片后,进行append操作

当我们在执行make([]int, 10)这样的语句时,底层调用的是makeslice函数。

    0x002f 00047 (main2.go:10)	LEAQ	type.int(SB), AX
	0x0036 00054 (main2.go:10)	PCDATA	$2, $0
	0x0036 00054 (main2.go:10)	MOVQ	AX, (SP)
	0x003a 00058 (main2.go:10)	MOVQ	$10, 8(SP)
	0x0043 00067 (main2.go:10)	MOVQ	$10, 16(SP)
	0x004c 00076 (main2.go:10)	CALL	runtime.makeslice(SB)

makeslice函数的三个参数为,type.int, 10, 10 后面两个参数是slice的长度和底层数组的长度,即slice容量。

func makeslice(et *_type, len, cap int) unsafe.Pointer {
	mem, overflow := math.MulUintptr(et.size, uintptr(cap))
	if overflow || mem > maxAlloc || len < 0 || len > cap {
		// NOTE: Produce a 'len out of range' error instead of a
		// 'cap out of range' error when someone does make([]T, bignumber).
		// 'cap out of range' is true too, but since the cap is only being
		// supplied implicitly, saying len is clearer.
		// See golang.org/issue/4085.
		mem, overflow := math.MulUintptr(et.size, uintptr(len))
		if overflow || mem > maxAlloc || len < 0 {
			panicmakeslicelen()
		}
		panicmakeslicecap()
	}

	return mallocgc(mem, et, true)
}

makeslice首先做的是检查栈溢出,如果溢出,直接panic,否则,调用mallocgc进行资源分配,看一下该函数的原型

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer 

分配一个大小为size的对象。小对象是从per-P缓存的空闲列表中分配的。大对象(> 32 kB)是从堆中直接分配的。 mallocgc的参数为,要分配的内存大小:mem= type.int.size * cap。第二个参数为元素类型。第三个参数是个bool值,如果needzero为true,则返回范围的内存将归零。该方法的返回值是分配的资源的地址。 上面的动作完成之后,就是变量赋值了

	0x004c 00076 (main2.go:10)	CALL	runtime.makeslice(SB)
	0x0051 00081 (main2.go:10)	PCDATA	$2, $1
	0x0051 00081 (main2.go:10)	MOVQ	24(SP), AX
	0x0056 00086 (main2.go:10)	MOVQ	AX, "".a+112(SP)
	0x005b 00091 (main2.go:10)	MOVQ	$10, "".a+120(SP)
	0x0064 00100 (main2.go:10)	MOVQ	$10, "".a+128(SP)

该赋值动作涵盖三部分,第一部分就是分配的内存地址,第二部分就是slice长度,第三部分就是底层数组的长度,这也印证了,slice的数据类型由三部分组成。

在调用函数append进行增加元素的时候,我们看看slice又做了哪些操作

	0x0070 00112 (main2.go:11)	JMP	114
	0x0072 00114 (main2.go:11)	PCDATA	$2, $2
	0x0072 00114 (main2.go:11)	LEAQ	type.int(SB), CX
	0x0079 00121 (main2.go:11)	PCDATA	$2, $1
	0x0079 00121 (main2.go:11)	MOVQ	CX, (SP)
	0x007d 00125 (main2.go:11)	PCDATA	$2, $0
	0x007d 00125 (main2.go:11)	MOVQ	AX, 8(SP)
	0x0082 00130 (main2.go:11)	MOVQ	$10, 16(SP)
	0x008b 00139 (main2.go:11)	MOVQ	$10, 24(SP)
	0x0094 00148 (main2.go:11)	MOVQ	$12, 32(SP)
	0x009d 00157 (main2.go:11)	CALL	runtime.growslice(SB)
	0x00a2 00162 (main2.go:11)	PCDATA	$2, $1
	0x00a2 00162 (main2.go:11)	MOVQ	40(SP), AX
	0x00a7 00167 (main2.go:11)	MOVQ	48(SP), CX
	0x00ac 00172 (main2.go:11)	MOVQ	56(SP), DX
	0x00b1 00177 (main2.go:11)	ADDQ	$2, CX
	0x00b5 00181 (main2.go:11)	JMP	183
	0x00b7 00183 (main2.go:11)	MOVQ	$1, 80(AX)
	0x00bf 00191 (main2.go:11)	MOVQ	$2, 88(AX)
	0x00c7 00199 (main2.go:11)	PCDATA	$0, $1
	0x00c7 00199 (main2.go:11)	MOVQ	AX, "".a+112(SP)
	0x00cc 00204 (main2.go:11)	MOVQ	CX, "".a+120(SP)
	0x00d1 00209 (main2.go:11)	MOVQ	DX, "".a+128(SP)

我们发现,在增加元素之前,先对slice进行了一次扩容,调用了runtime.growslice函数,该函数原型是

func growslice(et *_type, old slice, cap int) slice

该函数提供了三个参数,第一个参数是元素类型type.int,第二个参数是扩容前原始slice,第三个参数是新slice的容量,该例子中是12.该函数调用完成后,返回新的slice. 新的slice进行了一个长度增加2的动作后,把新的元素放到了slice中数组中是放在偏移量为80和88的地址里的。

	0x00b7 00183 (main2.go:11)	MOVQ	$1, 80(AX)
	0x00bf 00191 (main2.go:11)	MOVQ	$2, 88(AX)

这也就是第一个问题,其将数据是追加到数组的最后的。

所有动作完成之后,又重新进行了一次赋值操作

    0x00c7 00199 (main2.go:11)	MOVQ	AX, "".a+112(SP)
	0x00cc 00204 (main2.go:11)	MOVQ	CX, "".a+120(SP)
	0x00d1 00209 (main2.go:11)	MOVQ	DX, "".a+128(SP)

之后就开始了FuncA函数的调用

    0x00d9 00217 (main2.go:12)	MOVQ	AX, (SP)
	0x00dd 00221 (main2.go:12)	MOVQ	CX, 8(SP)
	0x00e2 00226 (main2.go:12)	MOVQ	DX, 16(SP)
	0x00e7 00231 (main2.go:12)	CALL	"".FuncA(SB)

此次调用非常有意思的是,参数上,将a slice的底层切片结构提供给了FuncA,这也就印证了第二个问题,为啥我在FuncA中对slice的元素修改,可以影响到我的原始slice. 再看一下FuncA的函数调用情况

"".FuncA STEXT nosplit size=50 args=0x18 locals=0x8
	0x0000 00000 (main2.go:19)	TEXT	"".FuncA(SB), NOSPLIT|ABIInternal, $8-24
	0x0000 00000 (main2.go:19)	SUBQ	$8, SP
	0x0004 00004 (main2.go:19)	MOVQ	BP, (SP)
	0x0008 00008 (main2.go:19)	LEAQ	(SP), BP
	0x000c 00012 (main2.go:19)	FUNCDATA	$0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
	0x000c 00012 (main2.go:19)	FUNCDATA	$1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
	0x000c 00012 (main2.go:19)	FUNCDATA	$3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
	0x000c 00012 (main2.go:20)	PCDATA	$2, $1
	0x000c 00012 (main2.go:20)	PCDATA	$0, $0
	0x000c 00012 (main2.go:20)	MOVQ	"".b+16(SP), AX
	0x0011 00017 (main2.go:20)	PCDATA	$0, $1
	0x0011 00017 (main2.go:20)	CMPQ	"".b+24(SP), $0
	0x0017 00023 (main2.go:20)	JHI	27
	0x0019 00025 (main2.go:20)	JMP	43
	0x001b 00027 (main2.go:20)	PCDATA	$2, $0
	0x001b 00027 (main2.go:20)	MOVQ	$19, (AX)
	0x0022 00034 (main2.go:21)	MOVQ	(SP), BP
	0x0026 00038 (main2.go:21)	ADDQ	$8, SP
	0x002a 00042 (main2.go:21)	RET
	0x002b 00043 (main2.go:20)	CALL	runtime.panicindex(SB)
	0x0030 00048 (main2.go:20)	UNDEF
	0x0000 48 83 ec 08 48 89 2c 24 48 8d 2c 24 48 8b 44 24  H...H.,$H.,$H.D$
	0x0010 10 48 83 7c 24 18 00 77 02 eb 10 48 c7 00 13 00  .H.|$..w...H....
	0x0020 00 00 48 8b 2c 24 48 83 c4 08 c3 e8 00 00 00 00  ..H.,$H.........
	0x0030 0f 0b                                            ..
	rel 44+4 t=8 runtime.panicindex+0

首先进行一个索引的比较,如果是0,则执行赋值。然后收回资源返回。

再来看看几个有意思的示例:

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 10)
	FuncA(a)
	fmt.Println(a)
}

func FuncA(b []int) {
	b = append(b, 1, 2)
}

还是相同的例子,只不过,我们在FuncA中执行的是append操作,而不是索引的赋值操作。首先看一下该程序的输出:

[0 0 0 0 0 0 0 0 0 0]

发现么,原始slice的值并没有修改,这特么的为啥,不是可以修改么?

	0x0021 00033 (main2.go:11)	MOVQ	"".b+96(SP), AX
	0x0026 00038 (main2.go:11)	LEAQ	2(AX), CX
	0x002a 00042 (main2.go:11)	PCDATA	$2, $1
	0x002a 00042 (main2.go:11)	MOVQ	"".b+88(SP), DX
	0x002f 00047 (main2.go:11)	PCDATA	$0, $1
	0x002f 00047 (main2.go:11)	MOVQ	"".b+104(SP), BX
	0x0034 00052 (main2.go:11)	CMPQ	CX, BX
	0x0037 00055 (main2.go:11)	JLS	59
	0x0039 00057 (main2.go:11)	JMP	103
	0x003b 00059 (main2.go:11)	PCDATA	$2, $-2
	0x003b 00059 (main2.go:11)	PCDATA	$0, $-2
	0x003b 00059 (main2.go:11)	JMP	61
	0x003d 00061 (main2.go:11)	PCDATA	$2, $1
	0x003d 00061 (main2.go:11)	PCDATA	$0, $1
	0x003d 00061 (main2.go:11)	MOVQ	$1, (DX)(AX*8)
	0x0045 00069 (main2.go:11)	MOVQ	$2, 8(DX)(AX*8)
	0x004e 00078 (main2.go:11)	PCDATA	$2, $0
	0x004e 00078 (main2.go:11)	MOVQ	DX, "".b+88(SP)
	0x0053 00083 (main2.go:11)	MOVQ	CX, "".b+96(SP)
	0x0058 00088 (main2.go:11)	MOVQ	BX, "".b+104(SP)
	0x005d 00093 (main2.go:12)	MOVQ	72(SP), BP
	0x0062 00098 (main2.go:12)	ADDQ	$80, SP
	0x0066 00102 (main2.go:12)	RET
	0x0067 00103 (main2.go:11)	PCDATA	$2, $1
	0x0067 00103 (main2.go:11)	MOVQ	AX, ""..autotmp_1+64(SP)
	0x006c 00108 (main2.go:11)	PCDATA	$2, $2
	0x006c 00108 (main2.go:11)	LEAQ	type.int(SB), SI
	0x0073 00115 (main2.go:11)	PCDATA	$2, $1
	0x0073 00115 (main2.go:11)	MOVQ	SI, (SP)
	0x0077 00119 (main2.go:11)	PCDATA	$2, $0
	0x0077 00119 (main2.go:11)	MOVQ	DX, 8(SP)
	0x007c 00124 (main2.go:11)	MOVQ	AX, 16(SP)
	0x0081 00129 (main2.go:11)	MOVQ	BX, 24(SP)
	0x0086 00134 (main2.go:11)	MOVQ	CX, 32(SP)
	0x008b 00139 (main2.go:11)	CALL	runtime.growslice(SB)
	0x0090 00144 (main2.go:11)	PCDATA	$2, $1
	0x0090 00144 (main2.go:11)	MOVQ	40(SP), DX
	0x0095 00149 (main2.go:11)	MOVQ	48(SP), AX
	0x009a 00154 (main2.go:11)	MOVQ	56(SP), BX
	0x009f 00159 (main2.go:11)	LEAQ	2(AX), CX
	0x00a3 00163 (main2.go:11)	MOVQ	""..autotmp_1+64(SP), AX
	0x00a8 00168 (main2.go:11)	JMP	61

首先判断是否需要growslice,如果需要,先进行growslice操作,之后再进行元素的追加,元素的追加也是很简单的,就是寄存器的地址运算,然后将值移动到指定的。

0x003d 00061 (main2.go:11)	MOVQ	$1, (DX)(AX*8)
	0x0045 00069 (main2.go:11)	MOVQ	$2, 8(DX)(AX*8)

完成后进行赋值操作

	0x004e 00078 (main2.go:11)	MOVQ	DX, "".b+88(SP)
	0x0053 00083 (main2.go:11)	MOVQ	CX, "".b+96(SP)
	0x0058 00088 (main2.go:11)	MOVQ	BX, "".b+104(SP)

上面的这个函数的操作,是在扩容的情况下,新分配的底层数组DX寄存器,+AX寄存器的偏移后,进行的赋值,此时b作为独立的slice,值是放到该slice中的底层数组中的。至于FuncA函数返回后,对于原始的slice就并没有产生什么影响。 下面的示例更能说明情况

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 10, 10)
	b := a
	b = append(b ,1,2)
	b[0] =19
	fmt.Println(a)
}

结果是[0 0 0 0 0 0 0 0 0 0].append在操作时,会判断,是否需要扩容,就是其cap(底层数组的长度)是否够用.如果不够用,进行了扩容的操作,那么会分配一个新的基础数组,那么b和a就没了联系,并不会再共用同一个底层数组,但是如果并没有进行扩容操作,底层数组够用,那么其实他们还是会共用同一个底层数组,只不过其所看到的内容不同罢了。

上面的例子还是比较极端的情况,再看一个不用进行growslice操作的情况,看看会不会有什么影响;

package main



func main() {
	a := make([]int, 10, 20)
	FuncA(a)
}

func FuncA(b []int) {
	b = append(b, 1, 2)
}

首先看一下其输出情况:

[0 0 0 0 0 0 0 0 0 0]

奇怪,我分配了那么大cap的slice,在FuncA中,并不需要进行growslice操作,为什么还是没有影响到原始的slice呢 .我们有理由怀疑,这一切的原因都是append函数,导致的,那么append到底进行了什么操作呢?

append函数是golang内建的函数,具体声明为:

func append(slice []Type, elems ...Type) []Type

就是说,内建的append函数将元素追加到slice尾部,如果它有足够的容量,目标可以重新切片来容纳新的元素。 如果没有足够的容量,将分配一个新的底层的数组。 append返回更新后的切片。 因此,有必要将存储append的结果,通常在保存切片本身的变量中,例如:

slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)

作为特殊的情况,将字符串附加到字节切片中也是合法的,就像这样:

slice = append([]byte("hello "), "world"...)

具体到,更简单的操作,来看一下:

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 10, 20)
	b := a
	b = append(b ,1,2)
	fmt.Println(a)
}

其结果一样的,输出是[0 0 0 0 0 0 0 0 0 0]

但是通过b可以实现对a slice底层数组的索引修改,比如

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 10, 20)
	b := a
	b = append(b ,1,2)
	b[0] =19
	fmt.Println(a)
}

输出是:[19 0 0 0 0 0 0 0 0 0].其底层数组中,指定是同一个数组,只是append的操作,并不影响a slice罢了。 a和b仅仅共用其相同的部分,其追加的东西并不会共享。

还有一种比较有意思的情况

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 10, 10)
	FuncA(a)
	fmt.Println(a)
}

func FuncA(b []int) {
	for i, v :=range b {
		v += 1
	}
}

其结果输出是:[0 0 0 0 0 0 0 0 0 0]

"".FuncA STEXT nosplit size=139 args=0x18 locals=0x40
	0x0000 00000 (main2.go:9)	TEXT	"".FuncA(SB), NOSPLIT|ABIInternal, $64-24
	0x0000 00000 (main2.go:9)	SUBQ	$64, SP
	0x0004 00004 (main2.go:9)	MOVQ	BP, 56(SP)
	0x0009 00009 (main2.go:9)	LEAQ	56(SP), BP
	0x000e 00014 (main2.go:9)	FUNCDATA	$0, gclocals·2d7c1615616d4cf40d01b3385155ed6e(SB)
	0x000e 00014 (main2.go:9)	FUNCDATA	$1, gclocals·6d81f9fc90b2254ac2f1067a7bf2c67c(SB)
	0x000e 00014 (main2.go:9)	FUNCDATA	$3, gclocals·db688afbc90e26183a53c9ad23b80c29(SB)
	0x000e 00014 (main2.go:10)	PCDATA	$2, $0
	0x000e 00014 (main2.go:10)	PCDATA	$0, $0
	0x000e 00014 (main2.go:10)	MOVQ	"".b+88(SP), AX
	0x0013 00019 (main2.go:10)	MOVQ	"".b+80(SP), CX
	0x0018 00024 (main2.go:10)	PCDATA	$2, $1
	0x0018 00024 (main2.go:10)	PCDATA	$0, $1
	0x0018 00024 (main2.go:10)	MOVQ	"".b+72(SP), DX
	0x001d 00029 (main2.go:10)	PCDATA	$2, $0
	0x001d 00029 (main2.go:10)	PCDATA	$0, $2
	0x001d 00029 (main2.go:10)	MOVQ	DX, ""..autotmp_2+32(SP)
	0x0022 00034 (main2.go:10)	MOVQ	CX, ""..autotmp_2+40(SP)
	0x0027 00039 (main2.go:10)	MOVQ	AX, ""..autotmp_2+48(SP)
	0x002c 00044 (main2.go:10)	MOVQ	$0, ""..autotmp_3+24(SP)
	0x0035 00053 (main2.go:10)	MOVQ	""..autotmp_2+40(SP), AX
	0x003a 00058 (main2.go:10)	MOVQ	AX, ""..autotmp_4+16(SP)
	0x003f 00063 (main2.go:10)	JMP	65
	0x0041 00065 (main2.go:10)	MOVQ	""..autotmp_4+16(SP), AX
	0x0046 00070 (main2.go:10)	CMPQ	""..autotmp_3+24(SP), AX
	0x004b 00075 (main2.go:10)	JLT	79
	0x004d 00077 (main2.go:10)	JMP	129
	0x004f 00079 (main2.go:10)	MOVQ	""..autotmp_3+24(SP), AX
	0x0054 00084 (main2.go:10)	SHLQ	$3, AX
	0x0058 00088 (main2.go:10)	PCDATA	$2, $2
	0x0058 00088 (main2.go:10)	ADDQ	""..autotmp_2+32(SP), AX
	0x005d 00093 (main2.go:10)	PCDATA	$2, $0
	0x005d 00093 (main2.go:10)	MOVQ	(AX), AX
	0x0060 00096 (main2.go:10)	MOVQ	AX, ""..autotmp_5+8(SP)
	0x0065 00101 (main2.go:10)	MOVQ	AX, "".v(SP)
	0x0069 00105 (main2.go:11)	INCQ	AX
	0x006c 00108 (main2.go:11)	MOVQ	AX, "".v(SP)
	0x0070 00112 (main2.go:11)	JMP	114
	0x0072 00114 (main2.go:10)	MOVQ	""..autotmp_3+24(SP), AX
	0x0077 00119 (main2.go:10)	INCQ	AX
	0x007a 00122 (main2.go:10)	MOVQ	AX, ""..autotmp_3+24(SP)
	0x007f 00127 (main2.go:10)	JMP	65
	0x0081 00129 (<unknown line number>)	PCDATA	$0, $1
	0x0081 00129 (<unknown line number>)	MOVQ	56(SP), BP
	0x0086 00134 (<unknown line number>)	ADDQ	$64, SP
	0x008a 00138 (<unknown line number>)	RET
	0x0000 48 83 ec 40 48 89 6c 24 38 48 8d 6c 24 38 48 8b  H..@H.l$8H.l$8H.
	0x0010 44 24 58 48 8b 4c 24 50 48 8b 54 24 48 48 89 54  D$XH.L$PH.T$HH.T
	0x0020 24 20 48 89 4c 24 28 48 89 44 24 30 48 c7 44 24  $ H.L$(H.D$0H.D$
	0x0030 18 00 00 00 00 48 8b 44 24 28 48 89 44 24 10 eb  .....H.D$(H.D$..
	0x0040 00 48 8b 44 24 10 48 39 44 24 18 7c 02 eb 32 48  .H.D$.H9D$.|..2H
	0x0050 8b 44 24 18 48 c1 e0 03 48 03 44 24 20 48 8b 00  .D$.H...H.D$ H..
	0x0060 48 89 44 24 08 48 89 04 24 48 ff c0 48 89 04 24  H.D$.H..$H..H..$
	0x0070 eb 00 48 8b 44 24 18 48 ff c0 48 89 44 24 18 eb  ..H.D$.H..H.D$..
	0x0080 c0 48 8b 6c 24 38 48 83 c4 40 c3                 .H.l$8H..@.

如果改成如下形式,结果就不一样了:

package main

import (
	"fmt"
)

func main() {
	a := make([]int, 10, 10)
	FuncA(a)
	fmt.Println(a)
}

func FuncA(b []int) {
	for i, _ :=range b {
		b[i] += 1
	}
}

其结果输出是:[1 1 1 1 1 1 1 1 1 1]

"".FuncA STEXT nosplit size=135 args=0x18 locals=0x20
	0x0000 00000 (main2.go:9)	TEXT	"".FuncA(SB), NOSPLIT|ABIInternal, $32-24
	0x0000 00000 (main2.go:9)	SUBQ	$32, SP
	0x0004 00004 (main2.go:9)	MOVQ	BP, 24(SP)
	0x0009 00009 (main2.go:9)	LEAQ	24(SP), BP
	0x000e 00014 (main2.go:9)	FUNCDATA	$0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
	0x000e 00014 (main2.go:9)	FUNCDATA	$1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
	0x000e 00014 (main2.go:9)	FUNCDATA	$3, gclocals·7cae486b9f11463edb9d0e91d30ff0f7(SB)
	0x000e 00014 (main2.go:10)	PCDATA	$2, $0
	0x000e 00014 (main2.go:10)	PCDATA	$0, $0
	0x000e 00014 (main2.go:10)	MOVQ	$0, ""..autotmp_2+16(SP)
	0x0017 00023 (main2.go:10)	MOVQ	"".b+48(SP), AX
	0x001c 00028 (main2.go:10)	MOVQ	AX, ""..autotmp_3+8(SP)
	0x0021 00033 (main2.go:10)	JMP	35
	0x0023 00035 (main2.go:10)	MOVQ	""..autotmp_3+8(SP), AX
	0x0028 00040 (main2.go:10)	CMPQ	""..autotmp_2+16(SP), AX
	0x002d 00045 (main2.go:10)	JLT	49
	0x002f 00047 (main2.go:10)	JMP	118
	0x0031 00049 (main2.go:10)	MOVQ	""..autotmp_2+16(SP), AX
	0x0036 00054 (main2.go:10)	MOVQ	AX, "".i(SP)
	0x003a 00058 (main2.go:11)	PCDATA	$2, $1
	0x003a 00058 (main2.go:11)	MOVQ	"".b+40(SP), CX
	0x003f 00063 (main2.go:11)	CMPQ	"".b+48(SP), AX
	0x0044 00068 (main2.go:11)	JHI	72
	0x0046 00070 (main2.go:11)	JMP	116
	0x0048 00072 (main2.go:11)	PCDATA	$2, $0
	0x0048 00072 (main2.go:11)	MOVQ	(CX)(AX*8), CX
	0x004c 00076 (main2.go:11)	PCDATA	$2, $2
	0x004c 00076 (main2.go:11)	MOVQ	"".b+40(SP), DX
	0x0051 00081 (main2.go:11)	INCQ	CX
	0x0054 00084 (main2.go:11)	CMPQ	"".b+48(SP), AX
	0x0059 00089 (main2.go:11)	JHI	93
	0x005b 00091 (main2.go:11)	JMP	114
	0x005d 00093 (main2.go:11)	PCDATA	$2, $0
	0x005d 00093 (main2.go:11)	MOVQ	CX, (DX)(AX*8)
	0x0061 00097 (main2.go:11)	JMP	99
	0x0063 00099 (main2.go:10)	MOVQ	""..autotmp_2+16(SP), AX
	0x0068 00104 (main2.go:10)	INCQ	AX
	0x006b 00107 (main2.go:10)	MOVQ	AX, ""..autotmp_2+16(SP)
	0x0070 00112 (main2.go:10)	JMP	35
	0x0072 00114 (main2.go:11)	PCDATA	$2, $-2
	0x0072 00114 (main2.go:11)	PCDATA	$0, $-2
	0x0072 00114 (main2.go:11)	JMP	128
	0x0074 00116 (main2.go:11)	JMP	128
	0x0076 00118 (<unknown line number>)	MOVQ	24(SP), BP
	0x007b 00123 (<unknown line number>)	ADDQ	$32, SP
	0x007f 00127 (<unknown line number>)	RET
	0x0080 00128 (main2.go:11)	PCDATA	$2, $0
	0x0080 00128 (main2.go:11)	PCDATA	$0, $1
	0x0080 00128 (main2.go:11)	CALL	runtime.panicindex(SB)
	0x0085 00133 (main2.go:11)	UNDEF
	0x0000 48 83 ec 20 48 89 6c 24 18 48 8d 6c 24 18 48 c7  H.. H.l$.H.l$.H.
	0x0010 44 24 10 00 00 00 00 48 8b 44 24 30 48 89 44 24  D$.....H.D$0H.D$
	0x0020 08 eb 00 48 8b 44 24 08 48 39 44 24 10 7c 02 eb  ...H.D$.H9D$.|..
	0x0030 45 48 8b 44 24 10 48 89 04 24 48 8b 4c 24 28 48  EH.D$.H..$H.L$(H
	0x0040 39 44 24 30 77 02 eb 2c 48 8b 0c c1 48 8b 54 24  9D$0w..,H...H.T$
	0x0050 28 48 ff c1 48 39 44 24 30 77 02 eb 15 48 89 0c  (H..H9D$0w...H..
	0x0060 c2 eb 00 48 8b 44 24 10 48 ff c0 48 89 44 24 10  ...H.D$.H..H.D$.
	0x0070 eb b1 eb 0c eb 0a 48 8b 6c 24 18 48 83 c4 20 c3  ......H.l$.H.. .
	0x0080 e8 00 00 00 00 0f 0b                             .......
	rel 129+4 t=8 runtime.panicindex+0

总结:

1.append操作在无需扩容的情况下,新的slice和老slice共用底层的数组,通过索引对新的slice的修改,会影响到老的slice

2.所谓共用底层数组,仅仅共用其公共部分,至于特有的部分,各方也是无法修改和查看的

3.在for循环遍历slice时,value是一次值copy,对其的修改,并不会影响到slice底层的数据

本系列文章:

有任何问题,欢迎留言