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:
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:
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:
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:
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.
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.
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.
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.
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.