What is slice

A slice is a reference to a continuous slice of an array. Slice is a reference type, it does not actually store elements, it just identifies a continuous segment on the array.

An array is a series of memory spaces in memory, and each element occupies a block of memory.

The data structure of the slice is a structure with three parameters.

  • Pointer points to the starting element of the fragment it wants to represent in the array;
  • len length
  • cap maximum capacity
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

Schematic diagram of slice:

slice1.png

Way of declaration

There are three declaration methods for []T{} , new , and make The specific differences will be analyzed based on the following examples.

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

sl := make([]string, 4)

sl := new([]string)
*sl = make([]string, 4)

Shallow copy phenomenon

Shallow copy during assignment

Look at the example code

func example1a()  {
    sl := []string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    // 浅复制1:赋值过程中发生的浅复制
    sl1 := sl
    fmt.Printf("sl1:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl1, sl1, &sl1)
    sl1[0] = "a被修改"
    fmt.Println("================ sl1 被修改后 ================")
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
    fmt.Printf("sl1:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl1, sl1, &sl1)
}
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl1:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc0000040c0
================ sl1 被修改后 ================
sl:[a被修改 b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl1:[a被修改 b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc0000040c0

The sl statement gets a slice and creates an array. The internal pointer of the sl slice points to this array.

sl1 is assigned by sl, and sl1 gets the same slice as sl, and its internal pointer also points to the originally created array.

When the index 0 of sl1 is modified, the value of the element corresponding to sl will also change.

slice2.png

Usually, developers who do not understand the slice structure will mistakenly think that sl1 and sl are completely independent, and their modification will not affect each other. In fact, they are indeed two completely independent memories, but their internal structures all point to the same array.

slice does not store array elements, it is just the porter , which identifies the segment interval on the array.

Therefore, the modification of sl1[0] is actually the modification of the element value on the array corresponding to index 0 of sl1. When sl is accessed, it will also be affected when it reads its own fragments on the array.

This phenomenon is also called shallow copy.

Shallow copy in function parameters

shallow copy not only occurs in the variable assignment process, but also quietly occurs when the actual parameter of the calling function is passed to the formal parameter.

func example1b()  {
    sl := []string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    // 浅复制2:函数形参中发生的浅复制
    func (slParam []string) {
        fmt.Printf("slParam:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", slParam, slParam, &slParam)
        slParam[0] = "a被修改"
        fmt.Println("================ slParam 被修改后 ================")
        fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
        fmt.Printf("slParam:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", slParam, slParam, &slParam)
    }(sl)
    // 外部的 sl 也将受到变化
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
}
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
slParam:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc0000040c0
================ slParam 被修改后 ================
sl:[a被修改 b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
slParam:[a被修改 b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc0000040c0
sl:[a被修改 b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078

The modification of the parameter slice inside the function will affect the actual parameters outside the function. After reading Example 1a, I believe you will not be too shocked by this result.

The slice argument and the formal parameter are two different variables, but they have the same internal structure, and the pointers in the internal structure still point to the array respectively.

Deep copy operation

Examples 1a and 1b show the phenomenon of shallow copying of slices. How to solve the problem of shallow copying will be answered in this example.

func example3()  {
    sl := []string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    // 深复制:通过 copy 解决赋值过程中发生的浅复制
    sl2 := make([]string, 4)
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    copy(sl2, sl)
    fmt.Println("================ copy 复制后 ================")
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
    sl2[0] = "a被修改了"
    fmt.Println("================ sl2 被修改后 ================")
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
}
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl2:[   ] 变量(或变量结构某个指针)指向地址(变量值):0xc0000200c0 变量地址:0xc0000040c0
================ copy 复制后 ================
sl2:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc0000200c0 变量地址:0xc0000040c0
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
================ sl2 被修改后 ================
sl2:[a被修改了 b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc0000200c0 变量地址:0xc0000040c0
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078

this example, the deep copy shallow copy phenomenon of 161a62e722f2e3 in the assignment process sl2 and sl will be two completely different slices, and their internal pointers will also point to two different arrays. In this way, the modification of one party will not affect the other party.

slice3.png

append operation

In this example, the append operation is shown.

func example4()  {
sl := []string{"a", "b", "c", "d"}
    sl2 := sl
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))

    fmt.Println("================ 数组每个元素对应的地址 ================")
    fmt.Printf("a:%p b:%p c:%p d:%p \n", &sl[0], &sl[1], &sl[2], &sl[3])
    sl2 = sl2[1:2]
    fmt.Println("================ sl2[1:2] 使切片 sl2 指向了 b 元素 ================")
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)

    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))
    sl2 = append(sl2[:1], "e")
    fmt.Println("================ 切片还有空闲容量进行 append e ================")
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    sl2 = append(sl2, "f")
    fmt.Println("================ 切片还有空闲容量进行 append f  ================")
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))
    sl2 = append(sl2, "g")
    fmt.Println("================ 切片没有空闲容量进行 append g  ================")
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    fmt.Println("================ 发生扩容后 ================")
    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    sl2 = sl2[:6]
    fmt.Printf("sl2:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl2, sl2, &sl2)
    fmt.Println("================ 新数组每个元素对应的地址 ================")
    fmt.Printf("b:%p c:%p e:%p f:%p \n", &sl2[0], &sl2[1], &sl2[2], &sl2[3])
}
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl2:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004090
sl2:cap:4,len:4
================ 数组每个元素对应的地址 ================
a:0xc000020080 b:0xc000020090 c:0xc0000200a0 d:0xc0000200b0 
================ sl2[1:2] 使切片 sl2 指向了 b 元素 ================
sl2:[b] 变量(或变量结构某个指针)指向地址(变量值):0xc000020090 变量地址:0xc000004090
sl2:cap:3,len:1
================ 切片还有空闲容量进行 append e ================
sl2:[b e] 变量(或变量结构某个指针)指向地址(变量值):0xc000020090 变量地址:0xc000004090
sl:[a b e d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
================ 切片还有空闲容量进行 append f  ================
sl2:[b e f] 变量(或变量结构某个指针)指向地址(变量值):0xc000020090 变量地址:0xc000004090
sl:[a b e f] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl2:cap:3,len:3
================ 切片没有空闲容量进行 append g  ================
sl2:[b e f g] 变量(或变量结构某个指针)指向地址(变量值):0xc00004e060 变量地址:0xc000004090
sl:[a b e f] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
================ 发生扩容后 ================
sl2:cap:6,len:4
sl2:[b e f g] 变量(或变量结构某个指针)指向地址(变量值):0xc00004e060 变量地址:0xc000004090
sl2:[b e f g  ] 变量(或变量结构某个指针)指向地址(变量值):0xc00004e060 变量地址:0xc000004090
================ 新数组每个元素对应的地址 ================
b:0xc00004e060 c:0xc00004e070 e:0xc00004e080 f:0xc00004e090 

1. When the first slice is just created, the internal pointers of the sl and sl2 slices point to the first element a of the array.

2. After sl2 = sl2[1:2] , sl2 points to the second b element in the array.

3. When slicing append e to sl2, there is still free space in sl2 (cap-len>0), and the append operation directly modifies the array element c => e.

4. When slicing append f to sl2, there is still free space in sl2 (cap-len>0), and the append operation directly modifies the array element d => f.

slice4.png

5. When slicing append g to sl2, sl2 has no free space at this time (cap-len=0), and the append operation will cause capacity expansion. Since the array space is fixed, the expansion will make sl2 point to the new array. The first element of sl2 is still b, but the address it points to is no longer the address of element b in the original array. This can prove that the expansion has occurred and a new array has been created.

In fact, sl2 only needs 4 spaces, but the corresponding new array provides 6 spaces. As for this point, it should be related to the expansion mechanism of slices, and subsequent articles may continue to discuss in depth.

The other slice declarations and operation methods

&[]T

sl := &[]string{"a", "b", "c", "d"}
// 等价于
s := []string{"a", "b", "c", "d"}
sl := &s

What sl will get is the address that points to the slice, which is a pointer to the slice, and the internal pointer of the slice points to the array.

slice5.png

func example2()  {
    sl := &[]string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    sl1 := sl
    fmt.Printf("sl1:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl1, sl1, &sl1)
    *sl1 = append(*sl1, "e")
    fmt.Println("================ append 后 ================")
    fmt.Printf("sl1:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl1, sl1, &sl1)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    (*sl1)[0] = "a被修改"
    fmt.Println("================ sl1 被修改后 ================")
    fmt.Printf("sl1:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl1, sl1, &sl1)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
}
sl:&[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028
sl1:&[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006038
================ append 后 ================
sl1:&[a b c d e] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006038
sl:&[a b c d e] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028
================ sl1 被修改后 ================
sl1:&[a被修改 b c d e] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006038
sl:&[a被修改 b c d e] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028

make

The slice is created in the make method. Make initializes the size of the array, and the initial value of the element is zero by default.

func example5()  {
    sl := make([]string, 4)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
    sl[0] = "a"
    sl[1] = "b"
    sl[2] = "c"
    sl[3] = "d"
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
    sl = append(sl, "e", "f", "g", "h")
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
}
sl:[   ] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl:[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000020080 变量地址:0xc000004078
sl:[a b c d e f g h] 变量(或变量结构某个指针)指向地址(变量值):0xc00010a000 变量地址:0xc000004078

new

New creates a slice to return the address. At this time, sl only gets the address, and the array corresponding to the slice is not even initialized. At this time, this slice cannot be used.

Until after *sl = make([]string, 4) , the slice can be operated normally through the pointer.

func example6()  {
    sl := new([]string)
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
    // new 只拿到了一个指针,并没法使用这个slice,必须经过 make 初始化后,才能使用
    *sl = make([]string, 4)
    fmt.Println("================ make 后 ================")
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    (*sl)[0] = "a"
    (*sl)[1] = "b"
    (*sl)[2] = "c"
    (*sl)[3] = "d"
    fmt.Println("================ 赋值后 ================")
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)

    *sl = append(*sl, "b")
    fmt.Println("================ append 后 ================")
    fmt.Printf("sl:%+v 变量(或变量结构某个指针)指向地址(变量值):%p 变量地址:%p\n", sl, sl, &sl)
}
sl:&[] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028
================ make 后 ================
sl:&[   ] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028
================ 赋值后 ================
sl:&[a b c d] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028
================ append 后 ================
sl:&[a b c d b] 变量(或变量结构某个指针)指向地址(变量值):0xc000004078 变量地址:0xc000006028

Summarize

  1. There are three declaration methods for []T{} , new , and make
  2. Slice will be shallowly copied during variable assignment.
  3. copy() allows slices to perform deep copying.
  4. When append operates slices again, expansion will occur when the free capacity of the slice is insufficient.

end!

Article from understand Golang slice statement method, shallow copy phenomenon, deep copy, append operation | Monkey Planet | Mr-houzi

Mr_houzi
964 声望22 粉丝