golang中的slice

上文讲解了数组这篇文章主要讲解Slice(切片)。Slice代表变长的序列,其里面的每个元素都有相同的类型。Slice字面量为[]T其中T表示slice的类型,slice和数组的语法很像,只是没有固定长度。

1 创建slice

1.1 var slice []int

这种创建出来的 slice 是一个 nil slice。它的长度和容量都为0。和nil比较的结果为true。

1.2 make创建

如果cap可以省略那len就等于cap。其中len可以为0表示这个slice的长度为0,容量为0。

s1 := make([]int, len, cap)
1.3 new创建
s2 := new([]int)
1.4 字面量形式创建切片
s3 := []int{1, 2, 3, 4, 5, 6}
s4 := []int{}  //创建空切片
s5 := []string{99: 100}   //初始化第100个元素
1.5 基于数组创建数组切片
var array = [10]int{1, 2, 3, 4, 5, 6}
var s6  = array[1:4] //[2,3,4] 左闭右开
var s7  = array[4:] //[5,6,0,0,0,0] 
var s8 = array[2:4:6] //[3,4] len=2,cap=4 data[low, high, max] low表示索引开始处闭区间,high表示len开区间,max表示容量开区间。

1.6 基于切片创建切片
slice := []int{1, 2, 3, 4, 5, 6}
s10 := slice[:4]  //beginIndex如果为空则表示从0开始
s11 := slice[4:]  //endIndex如果为空则表示到数组最后一个元素
var 12 = slice[2:4:6] //同1.5

2 底层数据结构

一个slice是一个轻量级的数据结构(结构体),提供了访问数组的元素的功能。一个slice由3部分组成pointer,len,cap,其中pointer指向底层数组的地址(注意不一定是首地址)。以下是slice的定义:

#runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
#反射中的SliceHeader
#reflect/value.go
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

图片示意图表示slice中指针指向了数组首地址,len是4,cap是6。


图1

3 共享底层数据

  • 多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠
func main()  {
    var array  = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var s1 = array[0:4]
    var s2 = array[0:3]
    fmt.Println(s1,s2)
}
//输出结果:[1 2 3 4] [1 2 3] 表示1,2,3是引用相同的位置的数据。

图2是示意图:


图2

图3是gdb显示的变量,内存的值:


图3

由此可见如果s1修改了共享的数据,那s2的值也会改变。

4 append追加元素

函数原型:
func append(slice []Type, elems ...Type) []Type
参数elems参数可变,因此可以追加多个值到slice中,还可以用...传入一个切片。

slice := append(slice, elem1, elem2)
slice := append(slice, slice_other...)

append会返回新的slice,append返回值必须使用否则编译器会报错。
追加元素是向底层数组中追加元素,但是底层数组长度是固定的,如果数组已经满了就没法添加了。这就会涉及到扩容问题了。

var slice = make([]int, 6)
fmt.Printf("%p\n",slice)

sliceNew := append(slice, 7)
fmt.Printf("%p\n",sliceNew)
fmt.Println(cap(sliceNew))


//0xc420018150
//0xc420074060
//12

可以看到slice与sliceNew的地址不同说明sliceNew已经迁移到别的地方了。在查看新slice的容量为12证明新的容量扩大了,也就是说新slice预留了一些缓存,防止每次append都去迁移造成资源消耗。具体扩容多少可以查看大佬的文章深度解密Go语言之Slice--饶全成 [码农桃花源]

如果将var slice = make([]int, 6,)改成var slice = make([]int, 6, 7),就是还有1个容量,那在运行此代码发现slice与sliceNew是一样的,没有用迁移。

5 for range

for循环会对slice元素值一次拷贝到item。更改item中的值不会改变原slice的元素值。

slice := []int{1,2,3}
for _, item := range slice {
    item++
}
    fmt.Println(slice)
//output: [1,2,3]

6 函数传参

函数传slice是引用传参,修改被调函数的值,调用函数的slice也会改变。

func main()  {
    slice := []int{1,2,3}
    test(slice)
    fmt.Println(slice)
}
func test(a []int) {
    a[1] = 100
}
//output [1,100,3]

7 两个slice不能用==比较

因为slice底层数据有可能变化。

8 make slice 汇编执行过程

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

go tool compile -S run.go >> run.s
生成的汇编代码如下:

1 "".main STEXT size=206 args=0x0 locals=0x58
2   0x0000 00000 (run.go:5) TEXT    "".main(SB), ABIInternal, $88-0   //为main函数分配栈帧大小为88B
3   0x0000 00000 (run.go:5) MOVQ    (TLS), CX
4   0x0009 00009 (run.go:5) CMPQ    SP, 16(CX)//是否需要扩容
5   0x000d 00013 (run.go:5) JLS 196 //跳转到196处去扩容
6   0x0013 00019 (run.go:5) SUBQ    $88, SP  //将sp向低地址移动88B
7   0x0017 00023 (run.go:5) MOVQ    BP, 80(SP) //parent BP 缓存到80(sp)处
8   0x001c 00028 (run.go:5) LEAQ    80(SP), BP //将80(SP)处的地址存入BP寄存器中,这样main函数的栈底设置完毕。
9   0x0021 00033 (run.go:5) FUNCDATA    $0, gclocals·69c1753bd5f81501d95132d08af04464(SB) //gc相关忽略
10  0x0021 00033 (run.go:5) FUNCDATA    $1, gclocals·568470801006e5c0dc3947ea998fe279(SB) //gc相关忽略
11  0x0021 00033 (run.go:5) FUNCDATA    $3, gclocals·bfec7e55b3f043d1941c093912808913(SB) //gc相关忽略
12  0x0021 00033 (run.go:5) FUNCDATA    $4, "".main.stkobj(SB) //gc相关忽略
13  0x0021 00033 (run.go:7) PCDATA  $2, $1 //gc相关忽略
14  0x0021 00033 (run.go:7) PCDATA  $0, $0 //gc相关忽略

//s := make([]int, 3, 10) make开始处
//将type.int(SB) =>[]int地址赋值给AX
15  0x0021 00033 (run.go:7) LEAQ    type.int(SB), AX
16  0x0028 00040 (run.go:7) PCDATA  $2, $0
将AX中的[]int地址移动到栈顶处(SP)
17  0x0028 00040 (run.go:7) MOVQ    AX, (SP)
//将参数3移动到离栈顶8个字节处
18  0x002c 00044 (run.go:7) MOVQ    $3, 8(SP)
//将参数10移动到离栈顶16个字节处
19  0x0035 00053 (run.go:7) MOVQ    $10, 16(SP)

//调用函数makeslice(SB)
//makeslice的原型如下:
//func makeslice(et *_type, len, cap int) slice 
//其中*_type 为slice类型,len为长度,cap为容量, slice为返回值
20  0x003e 00062 (run.go:7) CALL    runtime.makeslice(SB)

21  0x0043 00067 (run.go:7) PCDATA  $2, $1 //gc相关忽略
函数调用完之后的返回值slice
22  0x0043 00067 (run.go:7) MOVQ    24(SP), AX
23  0x0048 00072 (run.go:8) PCDATA  $2, $0
24  0x0048 00072 (run.go:8) MOVQ    AX, (SP)
25  0x004c 00076 (run.go:8) MOVQ    $3, 8(SP)
26  0x0055 00085 (run.go:8) MOVQ    $10, 16(SP)

  
//func convTslice(val []byte) (x unsafe.Pointer) 
//调用convTslice
27  0x005e 00094 (run.go:8) CALL    runtime.convTslice(SB)
28  0x0063 00099 (run.go:8) PCDATA  $2, $1
29  0x0063 00099 (run.go:8) MOVQ    24(SP), AX
30  0x0068 00104 (run.go:8) PCDATA  $0, $1
31  0x0068 00104 (run.go:8) XORPS   X0, X0
32  0x006b 00107 (run.go:8) MOVUPS  X0, ""..autotmp_11+64(SP)
33  0x0070 00112 (run.go:8) PCDATA  $2, $2
34  0x0070 00112 (run.go:8) LEAQ    type.[]int(SB), CX
35  0x0077 00119 (run.go:8) PCDATA  $2, $1
36  0x0077 00119 (run.go:8) MOVQ    CX, ""..autotmp_11+64(SP)
37  0x007c 00124 (run.go:8) PCDATA  $2, $0
38  0x007c 00124 (run.go:8) MOVQ    AX, ""..autotmp_11+72(SP)
39  0x0081 00129 (run.go:8) XCHGL   AX, AX

//以下是调用Println()详见通过Println分析如何系统调用的

40  0x0082 00130 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $1
41  0x0082 00130 ($GOROOT/src/fmt/print.go:275) MOVQ    os.Stdout(SB), AX
42  0x0089 00137 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $2
43  0x0089 00137 ($GOROOT/src/fmt/print.go:275) LEAQ    go.itab.*os.File,io.Writer(SB), CX
44  0x0090 00144 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $1
45  0x0090 00144 ($GOROOT/src/fmt/print.go:275) MOVQ    CX, (SP)
46  0x0094 00148 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $0
47  0x0094 00148 ($GOROOT/src/fmt/print.go:275) MOVQ    AX, 8(SP)
48  0x0099 00153 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $1
49  0x0099 00153 ($GOROOT/src/fmt/print.go:275) PCDATA  $0, $0
50  0x0099 00153 ($GOROOT/src/fmt/print.go:275) LEAQ    ""..autotmp_11+64(SP), AX
51  0x009e 00158 ($GOROOT/src/fmt/print.go:275) PCDATA  $2, $0
52  0x009e 00158 ($GOROOT/src/fmt/print.go:275) MOVQ    AX, 16(SP)
53  0x00a3 00163 ($GOROOT/src/fmt/print.go:275) MOVQ    $1, 24(SP)
54  0x00ac 00172 ($GOROOT/src/fmt/print.go:275) MOVQ    $1, 32(SP)
55  0x00b5 00181 ($GOROOT/src/fmt/print.go:275) CALL    fmt.Fprintln(SB)


//恢复parent func stack 将缓存在80(SP)的parent BP恢复到BP
56  0x00ba 00186 (<unknown line number>)    MOVQ    80(SP), BP
//将栈顶向高地址偏移88个字节从而恢复parent func stack
57  0x00bf 00191 (<unknown line number>)    ADDQ    $88, SP

58  0x00c3 00195 (<unknown line number>)    RET

//扩容栈
59  0x00c4 00196 (<unknown line number>)    NOP
60  0x00c4 00196 (run.go:5) PCDATA  $0, $-1
61  0x00c4 00196 (run.go:5) PCDATA  $2, $-1
62  0x00c4 00196 (run.go:5) CALL    runtime.morestack_noctxt(SB)

关键函数:
CALL runtime.makeslice(SB) //创建slice对象
CALL runtime.convTslice(SB) //将interface 转换成slice类型
CALL fmt.Fprintln(SB) //打印
CALL runtime.morestack_noctxt(SB) //扩容栈

9 总结

slice在实际开发中会经常遇到,熟悉它的原理,对于理解和运用slice有很大的帮助。

参考:
深度解密Go语言之Slice--饶全成 [码农桃花源]
深入解析 Go 中 Slice 底层实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343