Go (six) slice

Table of contents

Primer

slice

slice definition

slice length and capacity

slice expression

Use the make() function to construct slices

The nature of slices

Check if slice is empty

Slices are not directly comparable

assignment copy of slice

slice traversal

The append() method adds elements to the slice

Slice expansion strategy

Copy slices using the copy() function

remove elements from slice


Primer

Because the length of the array is fixed and the length of the array is part of the type, arrays have many limitations. For example:

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

This sum function can only accept [3]inttypes, others are not supported. Another example,

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

	fmt.Println(arraySum(a)) //6
}

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

There are already three elements in the array a, we can no longer add new elements to the array a.

slice

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

        A slice is a reference type whose internal structure contains 地址, 长度and 容量. Slices are generally used to quickly manipulate a collection of data.

slice definition

        The basic syntax for declaring a slice type is as follows:

var name []T //和数组是一样的  只是不需要加数字

in,

  • name: Indicates the variable name
  • T: Indicates the element type in the slice

for example:

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)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //false
	fmt.Println(c == nil)       //false
	// fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
}

slice length and capacity

        Slices have their own length and capacity. We can use built-in len()functions to find the length and use built-in cap()functions to find the capacity of slices .

slice expression

        A slice expression constructs a substring or slice from a string, array, pointer to array, or slice. It has two variants: a simple form that specifies both low and high index bounds, and a full form that specifies capacity in addition to the low and high index bounds.

simple slice expression

        The bottom layer of a slice is an array, so we can get a slice through a slice expression based on the array. The sum in the slice expression lowrepresents highan index range ( left is included, right is not included ) , that is, the elements selected from the array a in the following code form a 1<=索引值<4slice s, and the capacity of the obtained slice 长度=high-lowis equal to the capacity of the underlying array of the obtained slice .

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}

output:

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

        For convenience, any indexing in slice expressions can be omitted. If omitted low, it defaults to 0; if omitted high, it defaults to the length of the slice operand:

a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]

Notice:

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

        When performing a slice expression on a slice (slice-reslice), highthe upper bound is the slice's capacitycap(a) , not its length. A constant index must be non-negative and representable by a value of type int; for arrays or constant strings, the constant index must also be within the valid range. If lowand highboth indices are constant, they must be satisfied low <= high. Runtime happens if the index goes out of bounds at runtime panic.

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
	s2 := s[3:4]  // 索引的上限是cap(s)而不是len(s)
	fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}

output:

s:[2 3] len(s):2 cap(s):4
s2:[5] len(s2):1 cap(s2):1

full slice expression

For arrays, pointers to arrays, or slice a ( not strings ) support full slice expressions:

a[low : high : max]

The code above constructs a[low: high]a slice of the same type, length, and elements as the simple slice expression. Additionally, it sets the resulting slice's capacity to max-low. Only the first index value (low) can be omitted in a full slice expression; it defaults to 0.

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

Output result:

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

A full slice expression needs to satisfy the 0 <= low <= high <= max <= cap(a)same conditions as a simple slice expression.

Use the make() function to construct slices

We are all slices created based on arrays. If we need to create a slice dynamically, we need to use the built-in make()function. The format is as follows:

make([]T, size, cap)

in:

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

for example:

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

        The internal storage space in the above code ahas allocated 10, but only 2 are actually used. The capacity does not affect the number of current elements, so if len(a)2 is returned, cap(a)the capacity of the slice is returned.

The nature of slices

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

        For example, now there is an array a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}, slice s1 := a[:5], and the corresponding diagram is as follows.

Slice s2 := a[3:6], the corresponding diagram is as follows:

Check if slice is empty

To check if a slice is empty, always use len(s) == 0to determine, you should never use s == nilto determine.

Slices are not directly comparable

Slices are not comparable, and we cannot use ==operators to determine whether two slices contain all equal elements. The only legal comparison operation for slices is and nilcomparison. A nilslice of values ​​has no underlying array, and a nilslice of values ​​has length and capacity of zero. But we can't say that a slice with length and capacity of 0 must be nil, such as the following example:

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

So to judge whether a slice is empty, if it len(s) == 0is used for judgment, it should not be used s == nilfor judgment.

assignment copy of slice

The following code demonstrates that the two variables before and after copying share the underlying array, and modifications to one slice will affect the content of the other slice, which requires special attention.

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}

slice traversal

The slice traversal method is consistent with the array, and supports index traversal and for rangetraversal.

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 built-in function of the Go language append()can dynamically add elements to the slice. You can add one element at a time, you can add multiple elements, or you can add elements from another slice (following ...).

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]
}

Note: Zero-valued slices declared via var can append()be used directly in functions without initialization.

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

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

s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)

var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)

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

for example:

func main() {
	//append()添加元素和切片扩容
	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:0xc0000a8000
[0 1]  len:2  cap:2  ptr:0xc0000a8040
[0 1 2]  len:3  cap:4  ptr:0xc0000b2020
[0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

From the above results it can be seen that:

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

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

var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

Slice expansion strategy

You can view $GOROOT/src/runtime/slice.gothe source code, where the expansion-related code is as follows:

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
	newcap = cap
} else {
	if old.len < 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 following can be seen from the code above:

  • First judge, if the new application capacity (cap) is greater than twice the old capacity (old.cap), the final capacity (newcap) 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), ie (newcap=doublecap),
  • Otherwise, if the length of the old slice is greater than or equal to 1024, the final capacity (newcap) will increase by 1/4 from the old capacity (old.cap), ie (newcap=old.cap, for {newcap += newcap/4 }) until the final capacity (newcap) is greater than or equal to the newly applied capacity (cap), ie (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 also be processed differently depending on the type of elements in the slice, for example, the processing method is different intfrom stringthe type.

Copy slices using the copy() function

First let's look at a problem:

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

Since slices are reference types, both a and b actually point to the same memory address. When b is modified, the value of a will also change.

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

copy(destSlice, srcSlice []T)

in:

  • srcSlice: data source slice
  • destSlice: destination slice

for example:

func main() {
	// copy()复制切片
	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]
}

remove elements from slice

There is no dedicated method to delete slice elements in Go language, we can use the characteristics of slice itself to delete elements. code show as below:

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

To sum up: to delete indexthe element with index from slice a, the method of operation isa = append(a[:index], a[index+1:]...)

Guess you like

Origin blog.csdn.net/qq_54729417/article/details/127828980