SoFunction
Updated on 2025-03-05

Slice learning summary in Go language

concept

Slice slice is an encapsulation of the underlying array Array. The essence of storage in memory is an array, which is reflected in continuous memory blocks. After the array is defined in Go, the length is fixed and its length cannot be changed during use. Slice can be regarded as an array of variable length for use. The most important thing is that the array is passed in value during use. When an array is assigned to a new variable or passed as a method parameter, it is A copy of the source array is completely copied in memory, instead of referring to the address of the source array in memory. In order to meet the application requirements of multiplexing of memory space and consistency of the values ​​of array elements, Slice appears. Each Slice is a reference to the address of the source array in memory. The source array can derive multiple slices, and Slice can also continue to derive the slice. In memory, there are always only source arrays. Of course, there are exceptions, let's talk about it later.

usage

Definition

Slice can be defined in two ways, one is derived from the source array, and the other is defined through the make function. Essentially, they are the same. They are all opened up a piece of memory through the initialization of the array, and divide it into several small pieces to store array elements. Then Slice refers to the entire or local array elements.
Initialize a slice directly:

Copy the codeThe code is as follows:

 s := []int{1, 2, 3}

Note that this is a little different from initializing an array. Some students think that this writing method is to define and initialize an array. In fact, this writing method is to build an array with 3 elements in memory, and then assign the application of this array to the Slice, which is differentiated by the following array definition:

Copy the codeThe code is as follows:

 a := [3]int{1, 2, 3}
 b := [...]int{1, 2, 3}
 c := []int{1, 2, 3}
 (cap(a), cap(b), cap(c))
 a = append(a, 4)//Error:first argument to append must be slice; have [3]int
 b = append(b, 4)//Errot:first argument to append must be slice; have [3]int
c = append(c, 4)//Normal, indicating that the variable c is Slice type

It can be seen that the rules for array definition are emphasized: length and type must be specified. If the array length is automatically calculated based on the actual number of elements, the definition of [...] is required, rather than just [].

Build a slice from an array:

Copy the codeThe code is as follows:

 a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 s := a[2:8]
(s) //Output: [3 4 5 6 7 8]

Define an array a, cut off the subscripts between 2 and 8 (including 2 and not 8), and build a slice.

Definition through the make function:

Copy the codeThe code is as follows:

 s := make([]int, 10, 20)
(s) //Output: [0 0 0 0 0 0 0 0 0 0 0]

The first parameter of the make function represents the type of the built array, the second parameter is the length of the array, and the third parameter is optional, which is the capacity of the slice, which defaults to the second parameter value.

Length and capacity

Slice has two more confusing concepts, namely length and capacity. What is length? This length is the same as the length of the array, that is, the number of actual existing elements has been initialized in memory. What is capacity? If the capacity parameters are specified when creating a Slice through the make function, the memory manager will first divide a piece of memory space according to the specified capacity value, and then store an array element in it. The excess is in an idle state. When adding elements to the Slice, it will first be placed in this free memory. If the number of added parameters exceeds the capacity value, the memory manager will re-divider a piece of memory space with a capacity value of the original capacity value * 2, and so on. The advantage of this mechanism is that it can improve computing performance, because memory re-division will reduce performance.

Copy the codeThe code is as follows:

 a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 s := a[0:]
 s = append(s, 11, 22, 33)
 sa := a[2:7]
 sb := sa[3:5]
(a, len(a), cap(a))    //Output: [1 2 3 4 5 6 7 8 9 0] 10 10
(s, len(s), cap(s))    //Output: [1 2 3 4 5 6 7 8 9 0 11 22 33] 13 20
(sa, len(sa), cap(sa)) //Output: [3 4 5 6 7] 5 8
(sb, len(sb), cap(sb)) //Output: [6 7] 2 5

It can be seen that the len and cap of the array are always equal, and are specified at the time of definition and cannot be changed. Slice s refers to all elements of this array, with the initial length and capacity of 10. After adding 3 elements, its length becomes 13 and the capacity is 20. The slice sa cuts off the array segments with subscripts 2 to 7, with a length of 5 and a capacity of 8. The change rule of this capacity is to subtract the starting subscript from the original capacity value. If an element is added at this time, the value existing in the original memory address will be overwritten. Slice sb intercepts the array segment of slice sa subscripts 3 to 5. Note that the subscript here refers to the subscript of sa, not the subscript of the source array, with a length of 2 and a capacity of 8-3=5.

It's a reference type

As mentioned above, Slice is a reference to the source array. Changing the value of the element in Slice is essentially changing the value of the element of the source array.

Copy the codeThe code is as follows:

 a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 sa := a[2:7]
 sa = append(sa, 100)
 sb := sa[3:8]
 sb[0] = 99
(a) //Output: [1 2 3 4 5 99 7 100 9 0]
(sa) //Output: [3 4 5 99 7 100]
(sb) //Output: [99 7 100 9 0]

It can be seen that whether it is an append operation or an assignment operation, it affects the source array or other elements that reference the Slice of the same array. When Slice refers to an array, it actually points the pointer to the address of the specific element in memory, such as the memory address of the array. In fact, it is the memory address of the first element in the array. The same is true for Slice.

Copy the codeThe code is as follows:

 a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
 sa := a[2:7]
 sb := sa[3:8]
("%p\n", sa)   //Output: 0xc084004290
(&a[2], &sa[0])      //Output: 0xc084004290 0xc084004290
("%p\n", sb)   //Output: 0xc0840042a8
(&a[5], &sb[0])      //Output: 0xc0840042a8 0xc0840042a8

"Accident" occurred in reference pass

As we have been saying above, Slice is a reference type, pointing to the same piece of memory in memory. However, in actual applications, sometimes "accidents" occur. This situation only occurs when slicing elements like append. Slice's processing mechanism is like this. When the capacity of Slice is still free, the elements entering the append will directly use the free capacity space. However, once the number of elements entering the append exceeds the original specified capacity value, the memory manager reopens up a larger memory space to store the extra elements, and will copy the original element and put it in this newly opened memory space.

Copy the codeThe code is as follows:

 a := []int{1, 2, 3, 4}
 sa := a[1:3]
("%p\n", sa) //Output: 0xc0840046e0
 sa = append(sa, 11, 22, 33)
("%p\n", sa) //Output: 0xc084003200

You can see that after performing the append operation, the memory address has changed, which means that it is no longer a reference pass.