Correct and efficient use of slices in Go language skills (summarization of lecture notes -- easy to understand)

slicebasic

A slice is equivalent to a "dynamic array", but it is not an array or an array pointer. It uses internal pointers and related attributes to refer to array fragments to achieve variable-length functions.

Data structure in Slice source code:

type slice struct {
    
    
    array unsafe.Pointer // 指向底层数组
    len int // 长度
    cap int // 容量
}

Therefore, slice represents a variable-length sequence, and its underlying layer is an array.
A slice consists of 3 parts: pointer, length and capacity.
The pointer points to the underlying array, the length represents the current length of the slice, and the capacity represents the length of the underlying array.
In other words, the slice itself maintains a pointer property pointing to a collection of certain elements of its underlying array

Use slices correctly

This part of the content is explained through cases, and I feel that simple cases can be skipped.

case1

What does the following code output?

func main() {
    
    
	var s []int
	for i := 0; i < 3; i++ {
    
    
		s = append(s,i)
	}
	modifySlice(s)
	fmt.Println(s)
}

func modifySlice(s []int) {
    
    
	s[0] = 1024
}

Correct output: [1024 1 2]

Among them,
the append function: add the element i at the end of the slice s
fmt: is a standard library of go, and the fmt package implements formatted I/O similar to C language printf and scanf

case2

What does the following code output?

func main() {
    
    
	var s []int
	for i := 0; i < 3; i++ {
    
    
		s = append(s,i)
	}
	modifySlice(s)
	fmt.Println(s)
}
func modifySlice(s []int) {
    
    
	s = append(s, 2048)
	s[0] = 1024
}

Correct output: [1024 1 2]

insert image description here
As shown in the figure, the two S are different, but point to the same array address, and finally output according to the length of S in main, so it is [1024 1 2]

case3

What does the following code output?

func main() {
    
    
	var s []int
	for i := 0; i < 3; i++ {
    
    
		s = append(s, i)
	}
	modifySlice(s)
	fmt.Println(s)
}
func modifySlice(s []int) {
    
    
	s = append(s, 2048)
	s = append(s, 4096)
	s[0] = 1024
}

Correct output: [0 1 2]

insert image description here

When a slice is expanded through append, if the length of the slice is smaller than the current capacity, the slice will not be expanded. If the length of the slice after adding elements is greater than the current capacity, the slice will be expanded. The expansion mechanism is as follows:

  • When the length of the expanded element is less than 1024, it will be expanded with twice the capacity of the original slice;
  • When the expanded element length is greater than 1024, it will be expanded by 1.25 times the original slice capacity;

insert image description here

  • When expansion is not required, the append function returns the original slice of the original underlying array (the memory address remains unchanged);
  • When the slice needs to be expanded, the append function returns a new slice of the new underlying array (the memory address of the slice has changed);

We set s in main as S1, and s in modifySlice as S2

Therefore, after Append 4096, the capacity is expanded from 4 to 8, and a new address is opened, and S2 and S1 no longer point to the same address.
What is changed to 1024 is the new address slice. The output is S1 pointing to the old address in main.
2048 is actually written into the original slice S1, but the println function prints according to len, so it is not printed out.

case4

func main() {
    
    
	var s []int
	for i := 0; i < 3; i++ {
    
    
		s = append(s, i)
	}
	modifySlice(s)
	fmt.Println(s)
}
func modifySlice(s []int) {
    
    
	s[0] = 1024
	s = append(s, 2048)
	s = append(s, 4096)
}

Correct output: [1024 1 2]

In case4, 1024 is changed to slice S1 that has not been expanded to the old address, so 1024 is output.

Efficient use of slices

1. Simple and rude: directly define slice, and then perform append operation (the performance is relatively poor)
insert image description here

2. Pre-allocate capacity and then perform append operation (better than the first one)
insert image description here

3. After the capacity and length are all allocated, perform the append operation (the fastest)
insert image description here

  • Pre-allocating memory can improve performance (CPU is a more precious resource than memory)
  • Directly using index assignment instead of append can improve performance (append is actually an operation of calling a function)

Guess you like

Origin blog.csdn.net/qq_45884783/article/details/126186285