Go language learning notes-006 slice

Keywords: Go

Slice of Go language foundation

Because the length of the array is fixed and the length of the array is part of the type, the array has many limitations, such as summing the parts of the array.

package main

import "fmt"

func arraySum(x [3]int) int {
    var sum int
    for _, v := range x {
        sum += v
    }
    return sum
}

func main() {
    a := [...]int{1,2,3}
    arraySum(a)
}

This summation function can only accept [3]int type, and others are not supported.

section

A Slice is a variable length sequence of elements of the same type. It is a layer of encapsulation based on array types. It is very flexible and supports automatic capacity expansion.

Slice is a reference type. Its internal structure includes address, length and capacity. Slicing is generally used to quickly manipulate a data set.

Definition of slice

The basic syntax for declaring slice types is as follows:

var name []T

Of which:

  • Name: indicates variable name
  • T: Represents the element type in the slice
package main

import "fmt"

func main() {
    var a []string
    var b = []int{}
    var c = []bool{false, true}
    var d = []bool{false, true}
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(a == nil)
    fmt.Println(b == nil)
    fmt.Println(c == nil)
	// fmt.Println(c == d) / / slice is a reference type. Direct comparison is not supported. It can only be compared with nil
}

Output:

[]
[]
[false true]
[false true]
true
false
false

Length and capacity of slices

Slices have their own length and capacity. We can calculate the length by using the built-in len() function and the capacity by using the built-in cap() function.

Slice expression

Slice expressions construct substrings or slices from strings, arrays, and pointers to arrays or slices. It has two variants: one is a simple form of specifying the low and high index limits, and the other is a complete form of specifying the capacity in addition to the low and high index limits.

Simple slice expression

The real estate of slice is a sum array index. We can get slice through slice expression based on array. low and high in the slice expression represent an index range (included on the left and not included on the right). As follows:

package main

import "fmt"

func main() {
    a := []int{9,8,7,6,5,4,3,2,1,0}
    b := a[0:3]
    fmt.Printf("b:%v len(b):%v cap(b):%v", b, len(b), cap(b))
}

Output:

b:[9 8 7] len(b):3 cap(b):10

For convenience, any index in the slice expression can be omitted. If low is omitted, it defaults to 0; If high is omitted, it defaults to the length of the slice operand:

a[4:]  // a[4:len(a)]
a[:4]  // a[0:4]
a[:]  // a[0:len(a)]

be careful:

For arrays or strings, if 0 < = low < = high < = len (a), the index is legal, otherwise the index will be out of range.

When a slice expression is executed on a slice (slice and then slice), the upper bound of high is the capacity cap(a) of the slice, not the length. The constant index must be non negative and can be represented by a value of type int; For arrays or constant strings, the constant index must also be within the valid range. If both low and high indicators are constants, they must meet low < = high. If the index is in the run-time range, a run-time panic will occur.

package main

import "fmt"

func main() {
    a := []int{1,2,3,4,5,6,7,8,9,0}
    b := a[5:7]
    fmt.Printf("b:%v len(b):%v cap(b):%v \n", b, len(b), cap(b))
    c := b[2:4]
    fmt.Printf("c:%v len(c):%v cap(c):%v \n", c, len(c), cap(c))
}

Output:

b:[6 7] len(b):2 cap(b):5 
c:[8 9] len(c):2 cap(c):3 

Full slice expression

For arrays, pointers to arrays or slice a (note that it cannot be a string) support full slice expressions:

a[low: high: max]

The above code constructs slices of the same type, length, and element as the simple slice expression a[low:high]. In addition, it sets the capacity of the resulting slice to Max low. In the full slice expression, only the first index value (low) can be omitted; It defaults to 0.

package main

import "fmt"

func main() {
    a := []int{1,2,3,4,5,6,7,8,9,0}
    b := a[1:4:5]
    fmt.Printf("b:%v len(b):%v cap(b):%v \n", b, len(b), cap(b))
}

Output:

b:[2 3 4] len(b):3 cap(b):4 

Use the make() function to construct slices

To dynamically create a slice, you need to use the built-in make() function. The format is as follows:

make([]T, size, cap)

Of which:

  • T: Element type of slice
  • size: number of elements in the slice
  • cap: capacity of slice

For example:

package main

import "fmt"

func main() {
    a := make([]int, 2, 10)
    fmt.Println(a)
    fmt.Println(len(a))
    fmt.Println(cap(a))
}

Output:

[0 0]
2
10

In the above code, 10 internal storage spaces of a have been allocated, but actually only 2 have been allocated. The capacity does not affect the number of current elements, so len(a) returns 2 and cap(a) returns the capacity of the slice.

The essence of slicing

The essence of slicing is the encapsulation of the underlying array, which contains three information: the pointer of the underlying array, the length of the slice (len) and the capacity of the slice (cap).

For example: A: = [] int {1,2,3,4,5,6,7,8,9,0}, slice B: = a [: 5].

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-qpr2yjgr-163133302946) (C: \ users \ 10217 \ documents \ notes \ slice diagram \ slice diagram 1.png)]

Slice C: = a [3:5], the corresponding schematic diagram is as follows:

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-zedyonoi-163133302949) (C: \ users \ 10217 \ documents \ notes \ slice diagram \ slice diagram 2.png)]

Determine whether the slice is empty

To check whether the slice is empty, always use len(s) == 0 instead of s == nil.

Slices cannot be compared directly

Slices cannot be compared. We cannot use the = = operator to judge whether two slices contain all equal elements. The only valid comparison of slices is with nil. A slice with nil value has no underlying array. The length and capacity of a slice with nil value are 0. However, we cannot say that a slice with length and capacity of 0 must be nil, such as:

var s1 []int  // len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{}  // len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0)  // len(s3)=0;cap(s3)=0;s3!=nil

Therefore, to judge whether a slice is empty, use len(s)==0 instead of s==nil.

Assigned copy of slice

The following code demonstrates that the two variables before and after copying share the underlying array and copy the reference. The modification of one slice will affect the content of the other slice, which needs special attention.

package main

import "fmt"

func main() {
    s1 := make([]int, 3)
    s2 := s1
    s2[0] = 100
    fmt.Println(s1)
    fmt.Println(s2)
}

Output:

[100 0 0]
[100 0 0]

Slice traversal

The traversal mode of slices is consistent with that of arrays, and supports index traversal and for range traversal.

package main

import "fmt"

func main() {
    s := []int{1,3,5}
    
    for i := 0; i < len(s); i++ {
        fmt.Println(i, s[i])
    }
    
    for index, value := range s {
        fmt.Println(index, value)
    }
}

The append() method adds elements to the slice

The Go language's built-in function append() can dynamically add elements to slices. You can add one element at a time, multiple elements, or elements in another slice (followed by...).

package main

import "fmt"

func main() {
    var s []int
    s = append(s, 1)  // [1]
    s = append(s, 2, 3, 4)  // [1 2 3 4]
    s2 := []int{5, 6, 7}
    s = append(s, s2...)  // [1 2 3 4 5 6 7]
}

be careful:

The zero value slice declared by var can be used directly in the append() function without initialization.

var s []int
s = append(s, 1, 2, 3)

There is no need to initialize a slice like the following code and pass it into the append() function.

s := []int{}  // There is no need to instantiate
s = append(s, 1, 2, 3)

var s = make([]int)  // There is no need to instantiate
s = append(s, 1, 2, 3)

Each slice will point to an underlying array. If the capacity of this array is enough, new elements will be added. When the underlying array cannot accommodate new elements, the slice will automatically "expand" according to certain strategies, and the underlying array pointed to by the slice will be replaced. The "capacity expansion" operation often occurs when the append() function is called, so you need to use the original variable to receive the return value of the append function.

package main

import "fmt"

func main() {
    //append() add element and slice expansion
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}

Output:

[0]  len:1  cap:1  ptr:0xc000084020
[0 1]  len:2  cap:2  ptr:0xc000084060
[0 1 2]  len:3  cap:4  ptr:0xc000090020
[0 1 2 3]  len:4  cap:4  ptr:0xc000090020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc000092040
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc000092040
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc000092040
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc000092040
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc000082080
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc000082080

According to the output, it can be seen that:

  1. The append() function appends the element to the end of the slice and returns the slice.
  2. The capacity of slice numSlice is automatically expanded according to the law of 1, 2, 4, 8 and 16. After each expansion, it is twice that before expansion.

The append() function also supports appending multiple elements at once. For example:

package main

import "fmt"

func main() {
	var citySlice []string
	// Append an element
	citySlice = append(citySlice, "Nan'an District")
	// Append multiple elements
	citySlice = append(citySlice, "Yubei District", "Yuzhong District","Jiangbei District")
	// Append slice
	a := []string{"Banan District", "jiulongpo district "}
    citySlice = append(citySlice, a...)
    fmt.Println(citySlice)  // [Nan'an District, Yubei District, Yuzhong District, Jiangbei District, Banan District, Jiulongpo District]
}

Expansion strategy of slice

You can view the 181 lines of the source code of $GOROOT/src/runtime/slice.go. The codes related to capacity expansion are as follows:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
    newcap = cap
} else {
    if old.cap < 1024 {
        newcap = doublecap
    } else {
        // Check 0 < newcap to detect overflow
        // and prevent an infinite loop.
        for 0 < newcap && newcap < cap {
            newcap += newcap / 4
        }
        // Set newcap to the requested cap when
        // the newcap calculation overflowed.
        if newcap <= 0 {
            newcap = cap
        }
    }
}

The logic from the above code is as follows:

  • First, judge that if the newly applied capacity (cap) is greater than twice the old capacity (old.cap), the final capacity is the newly applied capacity (cap)
  • Otherwise, if the length of the old slice is less than 1024, the final capacity (newcap) is twice the old capacity (old.cap), that is (newcap=doublecap)
  • Otherwise, it is judged that if the old slice length is greater than or equal to 1024, the final capacity (newcap) starts from the old capacity (old.cap) and circularly increases the original 1 / 4, that is (newcap=oldcap, for {newcap+=newcap/4}) until the final capacity (newcap) is greater than or equal to the newly applied capacity (CAP), that is (newcap > = cap)
  • If the calculated value of the final capacity (cap) overflows, the final capacity (cap) is the newly requested capacity (cap).

It should be noted that slice expansion will be handled differently according to the types of elements in the slice. For example, int and string types are handled differently.

Use the copy() function to copy slices

The built-in copy() function can quickly copy the data of one slice to another slice space. The format of copy() function is as follows:

copy(destSlice, srcSlice []T)

Of which:

  • srcSlice: data source slice
  • destSlice: target slice

For example:

package main

import "fmt"

func main() {
    // copy() copy slice
    a := []int{1, 2, 3, 4, 5}
    b := make([]int, 5, 5)
    copy(b, a)
    fmt.Println(a)
    fmt.Println(b)
    b[0] = 1000
    fmt.Println(a)
    fmt.Println(b)
}

Output:

[1 2 3 4 5]
[1 2 3 4 5]
[1 2 3 4 5]
[1000 2 3 4 5]

Delete element from slice

There is no special method to delete slice elements in Go language. We can use the characteristics of slice itself to delete elements.

package main

import "fmt"

func main() {
    // Delete element from slice
    a := []int{20,21,22,23,24,25,26,27,28,29,30}
    fmt.Println(a)
    // To delete an element with index 5
    a = append(a[:5], a[6:]...)
    fmt.Println(a)
}

Output is:

[20 21 22 23 24 25 26 27 28 29 30]
[20 21 22 23 24 26 27 28 29 30]

(a)
fmt.Println(b)
}

Output: 

[1 2 3 4 5]
[1 2 3 4 5]
[1 2 3 4 5]
[1000 2 3 4 5]

### Delete element from slice

Go There is no special method to delete slice elements in the language. We can use the characteristics of slice itself to delete elements.

```go
package main

import "fmt"

func main() {
    // Delete element from slice
    a := []int{20,21,22,23,24,25,26,27,28,29,30}
    fmt.Println(a)
    // To delete an element with index 5
    a = append(a[:5], a[6:]...)
    fmt.Println(a)
}

Output is:

[20 21 22 23 24 25 26 27 28 29 30]
[20 21 22 23 24 26 27 28 29 30]

Posted by jack_indigo on Mon, 13 Sep 2021 12:49:15 -0700