preface
I always want to find out what kind of technical article is a good article? Because it is not easy to write an article that you are willing to read in the future, you can only strive for this goal for the time being.
Recently, I started to brush some questions with Go and encountered some detailed problems related to slicing. Here is a summary. The design idea of slicing is derived from the concept of dynamic array, so that developers can make it more convenient for a data structure to be automatically added and reduced. But the slice itself is not dynamic data or array pointers.
Slice structure
type slice struct { array unsafe.Pointer // A pointer to the underlying array (the element in the slice exists in the array to which it points) len int // Length of slice: the number of elements contained cap int // The capacity of the slice, len < = cap. If len == cap, adding elements will trigger the expansion of the slice }
The illustration of int slice with length of 3 and capacity of 5 is as follows. At this time, only subscripts 0, 1 and 2 can be accessed in the slice array, and the excess part cannot be accessed.
Declaration and initialization
nil slice
Declare the nil slice, and it will be initialized after declaration (it will be initialized with nil by default). At this time, slice == nil is established, which is used to describe a non-existent slice.
var slice []int // slice == nil is valid
Empty slice
Declare and initialize an empty slice to represent an empty collection. The empty slice points to an address other than nil.
slice := make([]int, 0) // slice == nil does not hold at this time slice := []int{}
Whether nil slice or empty slice, the effect is the same when calling the built-in functions append, len and cap.
Slice containing elements
The slice is not empty at this time.
slice := []int{1, 2, 3, 4, 5} // Declare and initialize a slice. len and cap are both 5 slice := make([]int, 4) // Declare and initialize a slice. The second parameter indicates that the length len and capacity cap of the slice are both 4 slice := make([]int, 4, 6) // Declare and initialize a slice. The second parameter represents len and the third represents cap ----------------------------------------------------------- array := [4]int{1, 2, 3, 4} slice := array[1:2] // For array[i:j], len=j-i of the new slice and cap=k-i (where k is the size of the original array)
Test the fourth method of initializing slice above:
func main() { array := [...]int{1, 2, 3, 4} slice := array[1:2] fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) } /* Output: 0xc00000c030 1 3 [2] */
Copy
Use: = copy
Note: in the following code, the newSlice slice is declared and initialized through slice slice slice. Although the printed addresses of the two slices are different, the array pointed to by the address pointer of the slice is the same. After modifying slice[0] = 100, newSlice[0] also becomes 100. This rule applies to passing slices as parameters to the function. The value copy of the incoming slice is used inside the function (create a new memory to store the slice, but the address pointer of the slice points to the same array)
func main() { array := [...]int{1, 2, 3, 4} slice := array[1:2] newSlice := slice // Copy. The slice information is stored in a new memory /* The above method of initializing newSlice is equivalent to: var newSlice []int newSlice = slice */ fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) slice[0] = 100 fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) } /* Output: 0xc00000c030 1 3 [2] 0xc00000c048 1 3 [2] 0xc00000c030 1 3 [100] 0xc00000c048 1 3 [100] */
Copy using the copy function
The two parameters of the copy function are two slices (overwriting the value of the second slice to the first slice), and their address pointers point to two different arrays.
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1) fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2) //copy(slice2, slice1) // Only the first three elements of slice1 will be copied to slice2 copy(slice1, slice2) // Only the three elements of slice2 will be copied to the first three positions of slice1 fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1) //fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2) slice2[0] = 200 slice1[0] = 100 fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1) fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2) } /* Output: 5 5 0xc00000c018 [1 2 3 4 5] 3 3 0xc00000c030 [5 4 3] 5 5 0xc00000c018 [5 4 3 4 5] 5 5 0xc00000c018 [100 4 3 4 5] copy The address pointers of the two slices of the function point to two different arrays respectively (the modified values do not affect each other) 3 3 0xc00000c030 [200 4 3] */
Capacity expansion
The append method is used for capacity expansion.
Add element when len == cap
func main() { slice := []int{1, 2, 3} // At this time, len == cap == 3 of slice, and the append element will trigger capacity expansion. After capacity expansion, the slice address points to the new array (the specific capacity expansion strategy will not be discussed here for the time being) fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) newSlice := append(slice, 1) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) slice[0] = 100 fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) } /* Output: 0xc00000c030 3 3 [1 2 3] 0xc00000c060 4 6 [1 2 3 1] // append After an element, the pointer in the slice points to a new array after capacity expansion (the address pointer changes. What is printed here is the slice address. Whether or not the capacity is expanded, the memory addresses of newSlice and slice must be different. Don't confuse them with the address pointers owned by them) 0xc00000c030 3 3 [100 2 3] // Modify the element of slice[0] to 100 0xc00000c060 4 6 [1 2 3 1] // But the elements in newSlice[0] have not changed (here we can prove that the address pointers of the two slices point to two different arrays) */
Add element when len < cap
At this time, calling the append method to add an element 1 will not create a new array, but 1 will overwrite the array[2].
func main() { array := [4]int{1, 2, 3, 4} slice := array[0:2] fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) newSlice := append(slice, 1) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) slice[0] = 100 fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice) fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice) fmt.Println("array = ", array) } /* Output: 0xc00000c030 2 4 [1 2] // Here you can see that len==2 and cap==4 of slice initialized from the array 0xc00000c060 3 4 [1 2 1] // append After that, add an element, and len becomes 3 (and the append element will overwrite the underlying array, where 1 overwrites the previous 3) 0xc00000c030 2 4 [100 2] // Note that the len printed by slice here is still 2. The len and cap of slice are isolated from those of newSlice. Although their address pointers point to the same array, slice[0]=100 is modified here 0xc00000c060 3 4 [100 2 1] // newSlice[0]Also modified to 100 array = [100 2 1 4] // The array pointed to by the proof has also been modified */
end
It's almost the new year. I wish you a happy new year and keep getting offer s.
We have built a spring and autumn recruitment preparation / internal push / chat group. Welcome to join us.
Pay attention to the official account [programmer white] and bring you to a programmer / student party with a bit of trouble.