Fully understand the declaration method of Golang slice, shallow copy phenomenon, deep copy and append operation

Keywords: Go

What is slicing

A slice is a reference to a contiguous fragment of an array. Slice is a reference type. It doesn't actually store elements. It just identifies a continuous fragment on the array.

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

The data structure of slice is a structure body, which consists of three parameters.

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

slice diagram:

Declaration method

slice can be declared in three ways: [] T {}, new and make. The specific differences will be analyzed according to the following examples.

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

sl := make([]string, 4)

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

Shallow replication phenomenon

Shallow copy during assignment

Look at the example code

func example1a()  {
    sl := []string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    // Shallow copy 1: shallow copy that occurs during assignment
    sl1 := sl
    fmt.Printf("sl1:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl1, sl1, &sl1)
    sl1[0] = "a Modified"
    fmt.Println("================ sl1 After being modified ================")
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
    fmt.Printf("sl1:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl1, sl1, &sl1)
}
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl1:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc0000040c0
================ sl1 After being modified ================
sl:[a Modified b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl1:[a Modified b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc0000040c0

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

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

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

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

Slicing does not store array elements. It is just a porter, identifying the fragment interval on the array.

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

This phenomenon is also called shallow replication.

Shallow copy in function parameter

Shallow copying occurs not only in the process of variable assignment, but also quietly when calling function arguments to formal parameters.

func example1b()  {
    sl := []string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    // Shallow copy 2: a shallow copy that occurs in a function parameter
    func (slParam []string) {
        fmt.Printf("slParam:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", slParam, slParam, &slParam)
        slParam[0] = "a Modified"
        fmt.Println("================ slParam After being modified ================")
        fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
        fmt.Printf("slParam:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", slParam, slParam, &slParam)
    }(sl)
    // The external sl will also be changed
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
}
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
slParam:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc0000040c0
================ slParam After being modified ================
sl:[a Modified b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
slParam:[a Modified b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc0000040c0
sl:[a Modified b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078

Modifications to the parameter slice inside the function will affect the arguments outside the function. After reading example 1a, I believe you will not be too shocked by this result.

Slice arguments and formal parameters 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 replication of slices. How to solve the problem of shallow replication will be solved in this example.

func example3()  {
    sl := []string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    // Deep copy: use copy to solve the shallow copy during assignment
    sl2 := make([]string, 4)
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    copy(sl2, sl)
    fmt.Println("================ copy After copying ================")
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
    sl2[0] = "a It was modified"
    fmt.Println("================ sl2 After being modified ================")
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
}
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl2:[   ] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc0000200c0 Variable address: 0 xc0000040c0
================ copy After copying ================
sl2:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc0000200c0 Variable address: 0 xc0000040c0
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
================ sl2 After being modified ================
sl2:[a It was modified b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc0000200c0 Variable address: 0 xc0000040c0
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078

In this example, the deep copy operation of the copy method solves the shallow copy phenomenon 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, one party's modification will not affect the other party.

append operation

The append operation is shown in this example.

func example4()  {
sl := []string{"a", "b", "c", "d"}
    sl2 := sl
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))

    fmt.Println("================ The address corresponding to each element of the array ================")
    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] Slice sl2 Point to b element ================")
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)

    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))
    sl2 = append(sl2[:1], "e")
    fmt.Println("================ There is still free capacity for slicing append e ================")
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    sl2 = append(sl2, "f")
    fmt.Println("================ There is still free capacity for slicing append f  ================")
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))
    sl2 = append(sl2, "g")
    fmt.Println("================ The slice has no free capacity for processing append g  ================")
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    fmt.Println("================ After capacity expansion ================")
    fmt.Printf("sl2:cap:%d,len:%d\n", cap(sl2), len(sl2))
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    sl2 = sl2[:6]
    fmt.Printf("sl2:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl2, sl2, &sl2)
    fmt.Println("================ The address corresponding to each element of the new array ================")
    fmt.Printf("b:%p c:%p e:%p f:%p \n", &sl2[0], &sl2[1], &sl2[2], &sl2[3])
}
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl2:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004090
sl2:cap:4,len:4
================ The address corresponding to each element of the array ================
a:0xc000020080 b:0xc000020090 c:0xc0000200a0 d:0xc0000200b0 
================ sl2[1:2] Slice sl2 Point to b element ================
sl2:[b] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020090 Variable address: 0 xc000004090
sl2:cap:3,len:1
================ There is still free capacity for slicing append e ================
sl2:[b e] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020090 Variable address: 0 xc000004090
sl:[a b e d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
================ There is still free capacity for slicing append f  ================
sl2:[b e f] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020090 Variable address: 0 xc000004090
sl:[a b e f] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl2:cap:3,len:3
================ The slice has no free capacity for processing append g  ================
sl2:[b e f g] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc00004e060 Variable address: 0 xc000004090
sl:[a b e f] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
================ After capacity expansion ================
sl2:cap:6,len:4
sl2:[b e f g] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc00004e060 Variable address: 0 xc000004090
sl2:[b e f g  ] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc00004e060 Variable address: 0 xc000004090
================ The address corresponding to each element of the new array ================
b:0xc00004e060 c:0xc00004e070 e:0xc00004e080 f:0xc00004e090 

1. When the slice is first created, the internal pointers of 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). 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). The append operation directly modifies the array element d = > F.

5. When slicing append g to sl2, there is no free space in sl2 (Cap len = 0), and the append operation will lead to capacity expansion. Since the array space is fixed, the expansion will make sl2 point to a 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 capacity expansion has occurred and a new array has been generated.

In fact, sl2 only needs 4 spaces, but the corresponding new array provides 6 spaces. This should be related to the capacity expansion mechanism of slices, which may be further discussed in subsequent articles.

Other slice declarations and operation modes

&[]T

sl := &[]string{"a", "b", "c", "d"}
// Equivalent to
s := []string{"a", "b", "c", "d"}
sl := &s

sl will get the address to the slice, which is a pointer to the slice, and the pointer inside the slice points to the array.

func example2()  {
    sl := &[]string{"a", "b", "c", "d"}
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    sl1 := sl
    fmt.Printf("sl1:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl1, sl1, &sl1)
    *sl1 = append(*sl1, "e")
    fmt.Println("================ append after ================")
    fmt.Printf("sl1:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl1, sl1, &sl1)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    (*sl1)[0] = "a Modified"
    fmt.Println("================ sl1 After being modified ================")
    fmt.Printf("sl1:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl1, sl1, &sl1)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
}
sl:&[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028
sl1:&[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006038
================ append after ================
sl1:&[a b c d e] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006038
sl:&[a b c d e] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028
================ sl1 After being modified ================
sl1:&[a Modified b c d e] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006038
sl:&[a Modified b c d e] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028

make

Create slices in make mode. Make initializes the array space size, and the initial value of the element is zero by default.

func example5()  {
    sl := make([]string, 4)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
    sl[0] = "a"
    sl[1] = "b"
    sl[2] = "c"
    sl[3] = "d"
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
    sl = append(sl, "e", "f", "g", "h")
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
}
sl:[   ] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl:[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000020080 Variable address: 0 xc000004078
sl:[a b c d e f g h] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc00010a000 Variable address: 0 xc000004078

new

When new creates a slice, it will return the address. sl only gets the address at this time. The array corresponding to the slice is not even initialized. This slice cannot be used at this time.

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

func example6()  {
    sl := new([]string)
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
    // new only gets a pointer and cannot use this slice. It can only be used after making initialization
    *sl = make([]string, 4)
    fmt.Println("================ make after ================")
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    (*sl)[0] = "a"
    (*sl)[1] = "b"
    (*sl)[2] = "c"
    (*sl)[3] = "d"
    fmt.Println("================ After assignment ================")
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)

    *sl = append(*sl, "b")
    fmt.Println("================ append after ================")
    fmt.Printf("sl:%+v A variable (or a pointer to a variable structure) points to an address (variable value):%p Variable address:%p\n", sl, sl, &sl)
}
sl:&[] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028
================ make after ================
sl:&[   ] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028
================ After assignment ================
sl:&[a b c d] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028
================ append after ================
sl:&[a b c d b] A variable (or a pointer to a variable structure) points to an address (variable value): 0 xc000004078 Variable address: 0 xc000006028

summary

  1. slice can be declared in three ways: [] T {}, new and make.
  2. slice will make a shallow copy when the variable is assigned.
  3. copy() allows slice to make deep copies.
  4. When append operates the slice again, the slice will expand when the idle capacity is insufficient.

end!

Article from Fully understand the declaration method of Golang slice, shallow copy phenomenon, deep copy, append operation | monkey planet | Mr Houzi

Posted by Bleej on Tue, 30 Nov 2021 06:31:19 -0800