• 本篇文章中涉及到汇编,不熟悉请忽略。

使用介绍

  1. append() 函数用于附加连接切片。
    • T是类型S的元素类型,比如S = []T
append(s S, x ...T) S
  1. append函数将0个或多个具有相同类型S的元素追加到切片s后面并且返回新的切片。
    • 追加的元素必须和原切片的元素同类型。
    • 它的可变参数必须是切片的类型,并返回结果切片,也就是S类型。
    • x传递给类型为 ... 的参数T,其中TS的元素类型。
  2. 如果s的容量不足以存储新增元素,append会分配新的切片来保证已有切片元素和新增元素的存储。
  3. 因此append()函数返回的切片可能已经指向一个不同的相关数组了,即使修改了数据也不会同步,具体需要根据S的容量进行判断。
  4. append()函数总是返回成功,除非系统内存耗尽了。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
s0 := []int{0, 0} // len 2  cap 2
// append 附加连接单个元素  s1 == []int{0, 0, 2}    len 3 cap 4
s1 := append(s0, 2)
// append 附加连接多个元素  s2 == []int{0, 0, 2, 3, 5, 7}   len 6 cap 8
s2 := append(s1, 3, 5, 7)
// append 附加连接切片s0   s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}  len 8 cap 8
s3 := append(s2, s0...)
// append 附加切片指定值   s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}  len 9 cap 10
s4 := append(s3[3:6], s3[2:]...) 

fmt.Println("s0", len(s0), cap(s0))
fmt.Println("s1", len(s1), cap(s1))
fmt.Println("s2", len(s2), cap(s2))
fmt.Println("s3", len(s3), cap(s3))
fmt.Println("s4", len(s4), cap(s4))

// s0 len:2 cap:2
// s1 len:3 cap:4   翻倍扩容
// s2 len:6 cap:8   翻倍扩容
// s3 len:8 cap:8
// s4 len:9 cap:10  翻倍扩容    这里翻倍扩容到10原因在于s3[3:6]的容量为5
  1. 切片的元素是空接口。
1
2
var t []interface{}
t = append(t, 42, 3.1415, "foo") // t == []interfase{}{42, 3.1415, "foo"}
  1. 切片的元素是字节。
1
2
var b []byte
b = append(b, "bar"...) // append附加连接字符串内容 b == []byte{'b', 'a', 'r'}
  1. append() 使用示例。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func ExampleAppend()  {
    // 切片容量不足 append() 返回的是新的切片 指向不同的关联数组
    s0 := []int{0,0}    // len 2 cap 2
    fmt.Println(s0, &s0[0], len(s0), cap(s0))// [0 0] 0xc00000a0b0 2 2

    // 由于s0的容量只有2,此时添加元素导致扩容,从新分配内存,并把之前数据复制过来
    s1 := append(s0, 2)
    fmt.Println(s1, &s1[0], len(s1), cap(s1))// [0 0 2] 0xc00000e340 3 4  这里显示扩容后地址变了
    s1[0] += 1
    fmt.Println(s0, s1) // [0 0] [1 0 2]

    // 此时添加元素 又会导致扩容 从新分配内存 并把之前数据复制过来
    s2 := append(s1, 3, 5, 7)
    fmt.Println(s2, &s2[0], len(s2), cap(s2))// [1 0 2 3 5 7] 0xc00000c300 6 8  这里显示扩容后地址又变了

    // 此时刚好达到最大容量,不会扩容 变量s3引用变量s2底层数组,修改其中元素会引起其他修改
    s3 := append(s2, s0...)
    fmt.Println(s3, len(s3), cap(s3))   // [1 0 2 3 5 7 0 0] 8 8
    s2[1] += 1
    fmt.Println(s2, s3) // [1 1 2 3 5 7] [1 1 2 3 5 7 0 0]

    // s3[3:6] len=3 cap=5
    // s3[2:] len=6 cap=6
    // 导致s4从新分配内存存储数据,s4 这里 len 9 cap 10
    s4 := append(s3[3:6], s3[2:]...)
    fmt.Println(s4)     // [3 5 7 2 3 5 7 0 0]
    
    // Output:
    // [0 0] 0xc00000a0b0 2 2
    // [0 0 2] 0xc00000e340 3 4
    // [0 0] [1 0 2]
    // [1 0 2 3 5 7] 0xc00000c300 6 8
    // [1 0 2 3 5 7 0 0] 8 8
    // [1 1 2 3 5 7] [1 1 2 3 5 7 0 0]
    // [3 5 7 2 3 5 7 0 0]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func ExampleAppend()  {
    // 如果切片容量足够 append()后不会生成新的切片
    s0 := make([]int, 10, 20)   // len.10   cap.20
    fmt.Println(s0, &s0[0], len(s0), cap(s0)) // [0 0 0 0 0 0 0 0 0 0] 0xc00007c000 10 20

    s1 := append(s0,2)          // len.11   cap.20
    fmt.Println(s1, &s1[0], len(s1), cap(s1)) // [0 0 0 0 0 0 0 0 0 0 2] 0xc00007c000 11 20

    s2 := append(s1, 3, 5, 7)   // len.14   cap.20
    fmt.Println(s2, &s2[0], len(s2), cap(s2)) // [0 0 0 0 0 0 0 0 0 0 2 3 5 7] 0xc00007c000 14 20

    // 此时扩容,从新开辟内存,复制原来数据
    s3 := append(s2, s0...)     // len.24   cap.40
    fmt.Println(s3, len(s3), cap(s3))   // [0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0] 24 40

    // 这里s4容量为37 是由于s3[3:6] 这里40-3=37容量
    // 此时容量足够,并没有扩容
    // s3[3:6]  ->  len = 6-3=3     cap = 40-3 = 37
    // s3[2:]   ->  len = 24-2=22   cap = 40-2 = 38
    s4 := append(s3[3:6], s3[2:]...)
    fmt.Println(s4, len(s4), cap(s4)) // [0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0] 25 37

    fmt.Println(s0) // [0 0 0 0 0 0 0 0 0 0]
    fmt.Println(s1) // [0 0 0 0 0 0 0 0 0 0 2]
    fmt.Println(s2) // [0 0 0 0 0 0 0 0 0 0 2 3 5 7]
    fmt.Println(s3) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0]
    fmt.Println(s4) // [0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0]
    s0[1] += 1
    fmt.Println(s0) // [0 1 0 0 0 0 0 0 0 0]
    fmt.Println(s1) // [0 1 0 0 0 0 0 0 0 0 2]
    fmt.Println(s2) // [0 1 0 0 0 0 0 0 0 0 2 3 5 7]
    fmt.Println(s3) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0]
    fmt.Println(s4) // [0 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0]
    s4[0] += 1
    fmt.Println(s3) // [0 0 0 1 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0]
    fmt.Println(s4) // [1 0 0 0 0 0 0 0 0 0 0 2 3 5 7 0 0 0 0 0 0 0 0 0 0]
}

Go中append的描述

  1. append 内置函数将元素附加到切片的末尾。如果它有足够的容量,目标将被重新切片以容纳新元素。如果没有,将分配一个新的底层数组。
  2. Append 返回更新后的切片。因此有必要将 append 的结果存储在保存切片本身的变量中:
    1. slice = append(slice, elem1, elem2): elem1,elem2 切片元素。
    2. slice = append(slice, anotherSlice...):anotherSlice 其他切片。
  3. 作为一种特殊情况,将字符串附加到字节切片是合法的,如下所示:
    1. slice = append([]byte("hello "), "world"...)
  4. append([]T, ...T) []T】 或 【append([]byte, string...) []byte
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//	slice = append(slice, elem1, elem2)			
//	slice = append(slice, anotherSlice...)		
// As a special case, it is legal to append a string to a byte slice, like this:
//	slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
  1. append() 不扩容时。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
func main() {
    s := make([]int, 0, 8)

    // 不存在扩容的情况
    s = append(s, 1, 2)

    _ = s
}

/*
TEXT main.main(SB) /mnt/hgfs/g/hello1/slice1.go
func main() {
  0x4551e0      4883ec60            SUBQ $0x60, SP
  0x4551e4      48896c2458          MOVQ BP, 0x58(SP)
  0x4551e9      488d6c2458          LEAQ 0x58(SP), BP
    s := make([]int, 0, 8)
  0x4551ee      440f113c24          MOVUPS X15, 0(SP)
  0x4551f3      440f117c2410        MOVUPS X15, 0x10(SP)
  0x4551f9      440f117c2420        MOVUPS X15, 0x20(SP)
  0x4551ff      440f117c2430        MOVUPS X15, 0x30(SP)
  0x455205      488d0424            LEAQ 0(SP), AX      # AX=0(SP)
  0x455209      8400                TESTB AL, 0(AX)
  0x45520b      eb00                JMP 0x45520d
  0x45520d      eb00                JMP 0x45520f
  0x45520f      4889442440          MOVQ AX, 0x40(SP)
  0x455214      48c744244800000000  MOVQ $0x0, 0x48(SP)
  0x45521d      48c744245008000000  MOVQ $0x8, 0x50(SP)
    s = append(s, 1, 2)
  0x455226      eb00                JMP 0x455228
  0x455228      48c7042401000000    MOVQ $0x1, 0(SP)
  0x455230      48c744240802000000  MOVQ $0x2, 0x8(SP)
  0x455239      4889442440          MOVQ AX, 0x40(SP)
  0x45523e      48c744244802000000  MOVQ $0x2, 0x48(SP)
  0x455247      48c744245008000000  MOVQ $0x8, 0x50(SP)
}
  0x455250      488b6c2458          MOVQ 0x58(SP), BP
  0x455255      4883c460            ADDQ $0x60, SP
  0x455259      c3                  RET
 */

//  +60 |   address of runtime.main
//      ----------------------------------------
//  +58 |   BP of runtime.main
//      ----------------------------------------    BP
//  +50 |   8                           s.cap
//      ----------------------------------------
//  +48 |   2                           s.len
//      ----------------------------------------
//  +40 |   0(SP)                       s.data
//      ----------------------------------------
//  +38 |   0                           *s.data.7
//      ----------------------------------------
//  +30 |   0                           *s.data.6
//      ----------------------------------------
//  +28 |   0                           *s.data.5
//      ----------------------------------------
//  +20 |   0                           *s.data.4
//      ----------------------------------------
//  +18 |   0                           *s.data.3
//      ----------------------------------------
//  +10 |   0                           *s.data.2
//      ----------------------------------------
//  +08 |   2                           *s.data.1
//      ----------------------------------------
//  +00 |   1                           *s.data.0
//      ----------------------------------------    SP
  1. append() 翻倍扩容时。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
func main() {
    s := make([]int, 0, 1)

    // 翻倍扩容情况
    s = append(s, 1, 2)

    _ = s
}

/*
TEXT main.main(SB) /mnt/hgfs/g/hello1/slice1.go
func main() {
  0x4551e0      493b6610            CMPQ 0x10(R14), SP
  0x4551e4      0f8681000000        JBE 0x45526b
  0x4551ea      4883ec68            SUBQ $0x68, SP
  0x4551ee      48896c2460          MOVQ BP, 0x60(SP)
  0x4551f3      488d6c2460          LEAQ 0x60(SP), BP
    s := make([]int, 0, 1)
  0x4551f8      48c744244000000000  MOVQ $0x0, 0x40(SP)
  0x455201      488d5c2440          LEAQ 0x40(SP), BX   # BX=0x40(SP)
  0x455206      8403                TESTB AL, 0(BX)
  0x455208      eb00                JMP 0x45520a
  0x45520a      eb00                JMP 0x45520c
  0x45520c      48895c2448          MOVQ BX, 0x48(SP)
  0x455211      48c744245000000000  MOVQ $0x0, 0x50(SP)
  0x45521a      48c744245801000000  MOVQ $0x1, 0x58(SP)
    s = append(s, 1, 2)
  0x455223      eb00                JMP 0x455225
  0x455225      488d05f44a0000      LEAQ 0x4af4(IP), AX     # AX=0x4af4(IP)   &type.int
  0x45522c      31c9                XORL CX, CX             # CX=0
  0x45522e      bf01000000          MOVL $0x1, DI           # DI=1
  0x455233      be02000000          MOVL $0x2, SI           # SI=2      翻倍扩容后 cap=2
  # runtime.growslice -> func growslice(et *_type, old slice, cap int) slice  扩容函数
  # 返回值 AX=r.data; BX=r.len; CX=r.cap
  0x455238      e8c3b2feff          CALL runtime.growslice(SB)	
  # DX=r.len+2  原因是runtime.growslice返回的r.len是old.len
  0x45523d      488d5302            LEAQ 0x2(BX), DX		
  0x455241      eb00                JMP 0x455243
  0x455243      48c70001000000      MOVQ $0x1, 0(AX)		
  0x45524a      48c7400802000000    MOVQ $0x2, 0x8(AX)
  0x455252      4889442448          MOVQ AX, 0x48(SP)
  0x455257      4889542450          MOVQ DX, 0x50(SP)
  0x45525c      48894c2458          MOVQ CX, 0x58(SP)
}
  0x455261      488b6c2460          MOVQ 0x60(SP), BP
  0x455266      4883c468            ADDQ $0x68, SP
  0x45526a      c3                  RET
func main() {
  0x45526b      e8f0ccffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x455270      e96bffffff          JMP main.main(SB)
 */

//  +68 |	address of runtime.main
//      ------------------------------------
//  +60 |	BP of runtime.main
//      ------------------------------------  BP
//  +58 |   1                       s.cap
//      ------------------------------------
//  +50 |   0                       s.len
//      ------------------------------------
//  +48 |   0x40(SP)                s.data
//      ------------------------------------
//  +40 |   0                   *s.data.0
//      ------------------------------------
//  +38 |
//      ------------------------------------
//  +30 |
//      ------------------------------------
//  +28 |
//      ------------------------------------
//  +20 |
//      ------------------------------------
//  +18 |
//      ------------------------------------
//  +10 |
//      ------------------------------------
//  +08 |
//      ------------------------------------
//  +00 |
//      ------------------------------------  SP

nil切片调用append函数

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
    var s []int // {nil, 0, 0}
    s = append(s, 1, 2)
    fmt.Println(s)
}
TEXT main.main(SB) /mnt/hgfs/g/hello1/slice1.go
func main() {
  0x4551e0      493b6610            CMPQ 0x10(R14), SP
  0x4551e4      7665                JBE 0x45524b
  0x4551e6      4883ec60            SUBQ $0x60, SP
  0x4551ea      48896c2458          MOVQ BP, 0x58(SP)
  0x4551ef      488d6c2458          LEAQ 0x58(SP), BP
    var s []int     // {nil, 0, 0}
  0x4551f4      48c744244000000000  MOVQ $0x0, 0x40(SP)
  0x4551fd      440f117c2448        MOVUPS X15, 0x48(SP)
    s = append(s, 1, 2)
  0x455203      eb00                JMP 0x455205
  0x455205      488d05144b0000      LEAQ 0x4b14(IP), AX     # AX=0x4b14(IP)  et=&type.int
  0x45520c      31db                XORL BX, BX             # BX=0      old.data
  0x45520e      31c9                XORL CX, CX             # CX=0      old.len
  0x455210      4889cf              MOVQ CX, DI             # DI=0      old.cap
  0x455213      be02000000          MOVL $0x2, SI           # SI=2      cap=2
  # runtime.growslice -> func growslice(et *_type, old slice, cap int) slice  扩容函数
  # 这里就是nil切片能使用append新增元素的原因
  0x455218      e8e3b2feff          CALL runtime.growslice(SB)
  0x45521d      488d5302            LEAQ 0x2(BX), DX
  0x455221      eb00                JMP 0x455223
  0x455223      48c70001000000      MOVQ $0x1, 0(AX)
  0x45522a      48c7400802000000    MOVQ $0x2, 0x8(AX)
  0x455232      4889442440          MOVQ AX, 0x40(SP)
  0x455237      4889542448          MOVQ DX, 0x48(SP)
  0x45523c      48894c2450          MOVQ CX, 0x50(SP)
}
  0x455241      488b6c2458          MOVQ 0x58(SP), BP
  0x455246      4883c460            ADDQ $0x60, SP
  0x45524a      c3                  RET
func main() {
  0x45524b      e810cdffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x455250      eb8e                JMP main.main(SB)
//  +60 |   address of runtime.main
//      -----------------------------------
//  +58 |   BP of runtime.main
//      ----------------------------------- BP
//  +50 |   0                       s.cap
//      -----------------------------------
//  +48 |   0                       s.len
//      -----------------------------------
//  +40 |   0                       s.data
//      -----------------------------------
//  +38 |
//      -----------------------------------
//  +30 |
//      -----------------------------------
//  +28 |
//      -----------------------------------
//  +20 |
//      -----------------------------------
//  +18 |
//      -----------------------------------
//  +10 |
//      -----------------------------------
//  +08 |
//      -----------------------------------
//  +00 |
//      ----------------------------------- SP

append 执行步骤

  1. 如果当前append()函数执行完后切片不会"翻倍扩容"那么,直接是把append后追加的数据拷贝到切片的后续空间即可。
  2. 如果当前append()函数执行完后需要"翻倍扩容",那么先调用runtime.growslice()扩容函数,然后在拷贝数据追加到新的内存空间。

append 函数总结

  1. append() 函数原型 func append(slice []Type, elems ...Type) []Type 支持两种形式。
1
2
3
4
// 形式一
slice = append(slice, elem1, elem2)			
// 形式二 anotherSlice... 这种形式只支持anotherSlice是切片
slice = append(slice, anotherSlice...)		
1
2
// 形式三 只有在这种形式下函数内才能使用 "world"... 形式解引用字符串
slice = append([]byte("hello "), "world"...)
  1. append() 函数是由编译器支持的没有函数原型只存在函数声明。
    • func append(slice []Type, elems ...Type) []Type
  2. 如果添加过程中需要扩容,编译器会调用 runtime.growslice 函数,该函数原型。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 函数参数
// et *_type:切片元素元类型
// old slice:未扩容前切片
// cap int:添加元素后的容量 old.len + n = cap

// 返回值 slice
//  slice.data:新分配的内存地址
//  slice.len:old.len 旧切片长度
//  slice.cap:翻倍扩容后的容量

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