go's slice will be encountered when you get started, but most people stay in simple use. Some old programmers who have worked for several years can't understand what's going on inside. There are a lot of holes in it. It happens that I'm free today. Please tidy up and never step on the pit
Why slice
Most other languages use arrays. In go, the length of arrays cannot be changed and is fixed after declaration. Therefore, go has slices, and the length can be changed. We usually use slices most.
What is slicing
Basic concepts
- slice
- Slice is the reference type of array, so it is reference passing (in fact, it is value passing, which will be analyzed in detail below)
- The use of slices is similar to that of arrays
- The length of slices can be changed (dynamically changing array)
- Slice definition syntax:
var slice name [] data type (no length)
For example, var laozhao []int
Layout of slices in memory
- The slice is a reference to the array, so the order of occurrence of the array must precede the slice (it can be understood that the slice is a sliding window on the array)
- To declare an array intArr, first open up an array space in memory to store the number we declared (the value here is the address of 22 in the array)
- Then we declare a slice, which is the part of the array subscript from the first to the third (the header does not include the tail). At this time, another space will be opened up in the memory. The essence of the slice is a structure, which consists of three parts: the address, length and capacity of the first element in the slice in the array
- Changing the data in the slice will change the value of the array
Slice data structure is essentially a structure
type slice struct{
ptr unsafe.Pointer
len int
cap int
}
Value passing and reference passing
Before understanding the transfer mode of slice, first understand what is value transfer and reference transfer
Value Passing: when a method is called, the actual parameter passes its value to the corresponding formal parameter. The change of formal parameter value during method execution does not affect the value of the actual parameter.
Reference passing: also known as address passing. When a method is called, the reference of the actual parameter (address, not the value of the parameter) is passed to the corresponding formal parameter in the method. During method execution, the operation on the formal parameter is actually the operation on the actual parameter. The change of the formal parameter value during method execution will affect the value of the actual parameter.
Is the slice passed by value or by reference
First, let's look at the running result of this code
func main() { var a = [3]int{6, 6, 6} ages := a[:] //fmt.Printf("%T\n", ages) //fmt.Printf("%p\n",&ages) //fmt.Printf("address of the first element of the array,% v \ n", &a [0]) //fmt.Printf("address of the first element of the slice,% v \ n", & ages [0]) fmt.Printf("original slice Your memory address is%p\n", ages) modify(ages) fmt.Println(ages) } func modify(ages []int) { fmt.Printf("Received in function slice Your memory address is%p\n", ages) ages[0] = 1 }
Operation results
As you can see here, the address of the initial slice is 0xc0000a0120, and the address after passing in the modify function is 0xc0000a0120, and the operation of the slice in modify can affect the initial slice,
So many people will say that slicing is reference passing. In fact, it is not. Let's look at the following code
func main() { var a = [3]int{6, 6, 6} ages := a[:] //fmt.Printf("%T\n", ages) //fmt.Printf("%p\n",&ages) fmt.Printf("The address of the first element of the array%v\n", &a[0]) fmt.Printf("Address of the first element of the slice%v\n", &ages[0]) fmt.Printf("original slice Your memory address is%p\n", ages) modify(ages) fmt.Println(ages) } func modify(ages []int) { fmt.Printf("Received in function slice Your memory address is%p\n", ages) ages[0] = 1 }
Operation results
The array header address, slice header address and slice address are the same
Suddenly realized that the first address of the array stored in the original%p printed slice is also a copy of the address when the function passes parameters
Why%p prints the address pointing to the array in the slice, because fmt has special processing for the slice
Then the slice itself also has an address, which is the part circled by the red box in the figure below
Look at the address of the slice itself
func main() { var a = [3]int{6, 6, 6} ages := a[:] //fmt.Printf("%T\n", ages) fmt.Printf("Address of the slice itself %p\n",&ages) //fmt.Printf("address of the first element of the array,% v \ n", &a [0]) //fmt.Printf("address of the first element of the slice,% v \ n", & ages [0]) fmt.Printf("original slice Your memory address is%p\n", ages) modify(ages) fmt.Println(ages) } func modify(ages []int) { fmt.Printf("Received in function slice Your memory address is%p\n", ages) fmt.Printf("Address of the slice itself %p\n",&ages) ages[0] = 1 }
Operation results
You can see that the address of the slice itself changes after being passed in as a parameter, which is value transfer. The reason why the operation in the function can change the value in the original slice is that the first address of the array stored in the slice is the same
Of course, after being passed in as a parameter, the len and cap of the original slice cannot be modified. If the len and cap change, the array pointed to will change. See the following code
func main() { var a = [3]int{6, 6, 6} ages := a[:] //fmt.Printf("%T\n", ages) //fmt.Printf("address of slice itself,% P \ n", & ages) //fmt.Printf("address of the first element of the array,% v \ n", &a [0]) //fmt.Printf("address of the first element of the slice%v\n", &ages[0]) fmt.Printf("original slice Your memory address is%p\n", ages) modify(ages) fmt.Println(ages) } func modify(ages []int) { fmt.Printf("Received in function slice Your memory address is%p\n", ages) //fmt.Printf("address of slice itself,% P \ n", & ages) fmt.Println(len(ages)," ", cap(ages)) ages = append(ages, 6) fmt.Println(len(ages)," ", cap(ages)) fmt.Printf("Received in function slice Your memory address is%p\n", ages) //fmt.Printf("address of slice itself,% P \ n", & ages) }
Operation results
You can see that the first address of the array in the slice has changed, and the original slice data has not changed.
If you want to modify the cap of the original slice (such as append operation), you need to pass in the pointer. See the following demonstration
func main() { var a = [3]int{6, 6, 6} ages := a[:] //fmt.Printf("%T\n", ages) //fmt.Printf("address of slice itself,% P \ n", & ages) //fmt.Printf("address of the first element of the array,% v \ n", &a [0]) //fmt.Printf("address of the first element of the slice,% v \ n", & ages [0]) fmt.Printf("original slice Your memory address is%p\n", ages) modify(&ages) // Pointer passed in fmt.Println(ages) } func modify(ages *[]int) { fmt.Printf("Received in function slice Your memory address is%p\n", ages) //fmt.Printf("address of slice itself,% P \ n", & ages) fmt.Println(len(*ages)," ", cap(*ages)) *ages = append(*ages, 6) fmt.Println(len(*ages)," ", cap(*ages)) fmt.Printf("Received in function slice Your memory address is%p\n", ages) //fmt.Printf("address of slice itself,% P \ n", & ages) }
You can see that the cap of the original slice can be changed by passing in the slice pointer. The original slice points to the new array
Pit related to slicing appent operation
During append, you should pay attention to whether the cap is changed. If the cap is updated, the bottom layer will point to the new array (the new array after capacity expansion)
func test(){ var array =[]int{1,2,3,4,5}// len:5,capacity:5 var newArray=array[1:3]// len:2,capacity:4 (two positions have been used, so two empty positions can be attached) fmt.Printf("%p\n",array) //0xc420098000 fmt.Printf("%p\n",newArray) //0xc420098008 you can see that the address of newArray refers to the address of array[1], that is, they still use an array at the bottom fmt.Printf("%v\n",array) //[1 2 3 4 5] fmt.Printf("%v\n",newArray) //[2 3] newArray[1]=9 //After the change, the array and newArray are changed fmt.Printf("%v\n",array) // [1 2 9 4 5] fmt.Printf("%v\n",newArray) // [2 9] newArray=append(newArray,11,12)//After the append operation, the len and capacity of the array remain unchanged, the len of the newArray becomes 4, and the capacity is 4. Because this is an operation on newArray fmt.Printf("%v\n",array) //[1 2 9 11 12] / / note that after the append operation on newArray, the values of array [3] and array [4] also change fmt.Printf("%v\n",newArray) //[2 9 11 12] newArray=append(newArray,13,14) // Because len of newArray is equal to capacity, append will exceed capacity again, // At this time, the append function will create a new underlying array (an expanded array), copy the underlying array pointed to by the array, and then append a new value. fmt.Printf("%p\n",array) //0xc420098000 fmt.Printf("%p\n",newArray) //0xc4200a0000 fmt.Printf("%v\n",array) //[1 2 9 11 12] fmt.Printf("%v\n",newArray) //[2 9 11 12 13 14] they no longer point to the same underlying array y }
Slice copy
Slice copy can use copy or equal sign
Copy refers to the reference copy. When copying, the pointer to the array and related information in the slice
The equal sign is a complete copy of the value, which will open up a new space in memory and create a new array
func main() { sl_from := []int{1, 2, 3} b := make([]int, len(sl_from)) copy(b, sl_from) a := sl_from fmt.Printf("Original slice:%p copy After:%p Equal to:%p\n", sl_from, b, a) fmt.Printf("Original slice:%p copy After:%p Equal to:%p\n", &sl_from, &b, &a) }
According to the results, we can see that the addresses of the two slices copied are completely different
But the slice after is equal to is still pointing to the same array