A comprehensive and brief understanding of go array Array and slice Slice

Xiaochat : This article is a summary of Xiaobai's golangfirst , feature comparison and discussion based on basic learning and understanding, what is the difference between go's arrays? What's the benefit of slicing? How to distinguish their use? Adding some knowledge expansion will help to deepen the understanding goof andArray , please read it!Slice

Table of contents



1. go array Array

1.1. Definition of go Array

  • basic definition
var arr1 [3]int = [3]int{
    
    1, 2, 3}
var arr2 = [3]int{
    
    1, 2, 3}	 // 可省去前面[3]int,var会自动识别类型
arr3 := [3]int{
    
    1, 2, 3}      //局部定义(函数内)
arr4 := [...]int{
    
    1, 2, 3}    //初始化后会自动计算数组长度并给它,所以无区别
arr5 := [...]int{
    
    1: 1, 5: 5} //指定索引序号初始化赋值,其它索引处是int的默认值0
arr6 := [...]struct {
    
    
    name string
}{
    
    {
    
    name: "Alice"}} //结构体数组,也必须在初始化时赋予初始,可省略属性名name(注意,这是一个匿名结构体)
var arr7 *[3]int = &[3]int{
    
    } // 数组指针
var arr8 [3]*int = [3]*int{
    
    } // 指针数组
  • Multidimensional Arrays
arr := [2][2]int{
    
    {
    
    1, 2}, {
    
    3, 4}}
brr := [...][2]int{
    
    {
    
    1, 2}, {
    
    3, 4}, {
    
    5, 6}}

1.2. Interesting things about go Array

(1) The arraygo is generally used in the same way as other languages, but there is a big difference in the type definition: the length of the array initialization is also a part of the array type. ArrayFor example, var arr [2]intand var brr [3]intare different types.

package main

import "fmt"

func main() {
    
    
	var arr [2]int = [2]int{
    
    }
	var brr [3]int = [3]int{
    
    }

	// 输出他们的类型
	fmt.Printf("arr: %T\n", arr) // arr: [2]int
	fmt.Printf("brr: %T\n", brr) // brr: [3]int
}

(2) goSupport using ==and !=to compare two arrays. We know that goan array is a value type, and it is already a fixed-length data sequence when it is initialized, and it is stored in memory. So you can goalso directly Printlnoutput the value of the entire array without traversing. At this point, it is easy to understand if it can be directly compared. Of course, different types cannot be compared, and an error will be reported when compiling, such as var a [2]intandvar d [3]int

package main

import "fmt"

func main() {
    
    
	a := [2]int{
    
    1, 2}
	b := [2]int{
    
    1, 2}
	c := [2]int{
    
    3, 4}
	// d := [3]int{1, 2}

	if a == b {
    
    
		fmt.Println("a == b") // 输出:a == b
	} else {
    
    
		fmt.Println("a != b")
	}

	if a == c {
    
    
		fmt.Println("a == c")
	} else {
    
    
		fmt.Println("a != c") // 输出:a != c
	}
	// 编译报错
	// if a == d {
    
    
	// 	fmt.Println("a == d")
	// } else {
    
    
	// 	fmt.Println("a != d")
	// }
}

2. go slice Slice

2.1. Definition of go Array

  • basic definition
package main
import "fmt"

func main() {
    
    
	// 创建切片方式
	s1 := []int{
    
    }
	fmt.Printf("s1: %v, 类型: %T\n", s1, &s1)                       // s1: [], 类型: *[]int
	var s2 []int = make([]int, 2)
	fmt.Printf("s2: %v, len: %v, cap: %v\n", s2, len(s2), cap(s2)) // s2: [0 0], len: 2, cap: 2
	var s3 []int = make([]int, 2, 4)
	fmt.Printf("s3: %v, len: %v, cap: %v\n", s3, len(s3), cap(s3)) // s3: [0 0], len: 2, cap: 4

	// 利用数组进行切片初始化
	arr := [...]int{
    
    0, 1, 2, 3, 4}
	var s4 []int = arr[0:2]
	fmt.Printf("s4: %v\n", s4) //s4: [0 1]
	var s5 []int = arr[:2]
	fmt.Printf("s5: %v\n", s5) //s5: [0 1]
	var s6 []int = arr[2:]
	fmt.Printf("s6: %v\n", s6) //s6: [2 3 4]
	var s7 []int = arr[:]
	fmt.Printf("s7: %v\n", s7) //s7: [0 1 2 3 4]
}

Operations that initialize slices on an array basis:

operate meaning
var s int[] := arr[n] item at index n in slice s
var s int[] := arr[:] The slice obtained from index positions 0 to len(s)-1 of slice s
var s int[] := arr[low:] The slice obtained from index position low of slice s to len(s)-1
var s int[] := arr[:high] Slice obtained from index position О to high of slice s, len=high
var s int[] := arr[low:high] The slice obtained from the index position low to high of the slice s, len=high-low
var s int[] := arr[low:high:max] Slice obtained from index position low to high of slice s, len=high-low, cap=max-low
  • slice array
package main

import "fmt"

func main() {
    
    
	s8 := make([][]int, 2, 4)
	fmt.Printf("s8: %v, len: %v, cap: %v\n", s8, len(s8), cap(s8)) // s8: [[] []], len: 2, cap: 4
	s9 := [][]int{
    
    
		[]int{
    
    1, 2},
		[]int{
    
    3, 4},
	}
	fmt.Printf("s9: %v, len: %v, cap: %v\n", s9, len(s9), cap(s9)) // s9: [[1 2] [3 4]], len: 2, cap: 2
}

2.2. Interesting aspects of go Slice

Because slice is goa unique definition, the content will be more detailed. Let’s introduce it first, and then talk about how to use it. By the way, we will talk about the principle and its relationship with arrays.

Everyone should have a brief understanding of slicing, here is a summary.
A slice is similar to an array and can be used much like an array, with the most obvious difference: it is a dynamic array that appendcan grow automatically when elements are appended. Hey, some students will say that this is similar to Java's collection and Python's list. Indeed, language has commonality, but it still has its characteristics.

  • (1) SliceAllocate the length and capacity according to the internal strategy, and then implement the variable length scheme, so the slice can be used as a variable array.
s := []int{
    
    1, 2}
fmt.Printf("s: %v, len: %v, cap: %v\n", s, len(s), cap(s)) // s: [1 2], len: 2, cap: 2
s = append(s, 3)
fmt.Printf("s: %v, len: %v, cap: %v\n", s, len(s), cap(s)) // s: [1 2 3], len: 3, cap: 4
  • (2) A slice is a reference type, not a value type, it is a reference Arrayto . This is very important, about its own characteristics and connection Arraywith .

What is a reference type, which is similar to a pointer but not a pointer. It c++is easy , that is, it can operate arrays in a way similar to pointers. Why is it an array? Because the bottom layer is an array when the slice is initialized. So when we define a slice, we can define the slice type on the basis of an array, and the actual operation of this slice is that array, because a slice is a reference type, which refers to this array. If we want to modify the elements in this slice at this time, it will affect the contents of the array, because their memory physical addresses are the same. Of course, the slice we created makein the way is a new array. However, there are still restrictions on initializing slices on fixed-length arrays, which will be introduced later.[Click here: What is a reference type]

package main
import "fmt"

func main() {
    
    
	// 我们举个数组初始化的例子
	arr := [3]int{
    
    1, 1, 1}
	fmt.Printf("初始数组arr : %v\n", arr) // 初始数组arr : [1 1 1]
	s := arr[:]
	fmt.Printf("初始化后的切片s2 : %v\n", s) // 初始化后的切片s2 : [1 1 1]
	s[0] = 2
	fmt.Printf("修改后的切片s2 : %v\n", arr)  // 修改后的切片s2 : [2 1 1]
	fmt.Printf("修改后的数组arr : %v\n", arr) // 修改后的数组arr : [2 1 1]
	// 然而我们再来打印索引为0的地址确认一下:
	fmt.Printf("数组arr索引为0的地址: %p\n", &arr[0]) // 数组arr索引为0的地址: 0xc000016150
	fmt.Printf("切片s2索引为0的地址: %p\n", &s[0])    // 切片s2索引为0的地址: 0xc000016150
}
  • The parameters of the slice definition are []type, lenand cap. For example: var s []int = make([]int, 2, 4).

If not capset , the default lenis the same as the value of . Regarding the relationship between the length len, capacity capand the lengthening strategy of the source code, in one sentence: when appendthe length of the slice element exceeds cap, the expansion will be triggered. The source code of the expansion mechanism explains the normal situation: when the current total capacity is less than 1024, the one-time expansion is triggered. Increase the current cap size by 1. When it exceeds 1024, the trigger expansion is to increase the current capsize .

package main
import "fmt"

func main() {
    
    
	s := make([]int, 2)
	fmt.Println("初始切片情况:")
	fmt.Printf("val: %v\nlen(s): %v,cap(s): %v\n", s, len(s), cap(s)) // len(s): 2,cap(s): 2
	fmt.Println("第一次扩容:")
	s = append(s, 100, 200)
	fmt.Printf("val: %v\nlen(s): %v,cap(s): %v\n", s, len(s), cap(s)) // len(s): 4,cap(s): 4
	fmt.Println("第二次扩容:")
	s = append(s, 300, 400)
	fmt.Printf("val: %v\nlen(s): %v,cap(s): %v\n", s, len(s), cap(s)) // len(s): 6,cap(s): 8
	fmt.Println("第三次扩容:")
	s = append(s, 500, 600, 700)
	fmt.Printf("val: %v\nlen(s): %v,cap(s): %v\n", s, len(s), cap(s)) // len(s): 9,cap(s): 16
}
  • When initializing a slice based on an array, the fixed length limit of the original array cannot be exceeded. That is 0 <= len(slice) <= len(array), where arrayis slicethe array referenced by .

In the above knowledge, we already know that to initialize the slice on the basis of the array, the slice is the reference of the original array, and they share the operation data.
Then there will be such a question: My slicing theory can expand and grow infinitely, but the array is not variable, so what is the range of length when initializing the array? Will slices be limited by the fixed length of the array when adding data?
The answer is: lenthe maximum is the length of the array; the expansion of the slice will not be limited, and the expanded area is a newly opened space, which is exclusively used by the slice. The way of using a := []int{}or a := make([]int, 0)is normal, because they create a new underlying array without initializing a fixed length limit.

package main

import "fmt"

func main() {
    
    
	arr := [4]int{
    
    1, 2}
	fmt.Printf("数组arr:val: %v,len(s): %v,cap(s): %v\n", arr, len(arr), cap(arr))
	var s []int = arr[:]
	fmt.Printf("切片s:val: %v,len(s): %v,cap(s): %v\n", s, len(s), cap(s)) 
	s = append(s, 3, 4)
	fmt.Printf("扩容后:切片s:val: %v,len(s): %v,cap(s): %v\n", s, len(s), cap(s))         
	fmt.Printf("扩容后:数组arr:val: %v,len(s): %v,cap(s): %v\n", arr, len(arr), cap(arr)) 
}

// 输出
数组arr:val: [1 2 0 0]len(s): 4cap(s): 4
切片s:val: [1 2 0 0]len(s): 4cap(s): 4
扩容后:切片s:val: [1 2 0 0 3 4]len(s): 6cap(s): 8
扩容后:数组arr:val: [1 2 0 0]len(s): 4cap(s): 4

3. The difference between the use of arrays and slices

Through the above knowledge combing, when it comes to the difference, you can almost think of and understand the following questions and examples.

  • The assignment between arrays is to copy the entire array data, slices and arrays or slices and slices are copy references, not copying the entire data ; Javaarray assignments are copy references
package main
import "fmt"

func main() {
    
    
	arr := [3]int{
    
    1, 2, 3}
	brr := arr
	fmt.Printf("arr[0]的地址: %p\n", &arr[0]) // arr[0]的地址: 0xc000016150
	fmt.Printf("brr[0]的地址: %p\n", &brr[0]) // brr[0]的地址: 0xc000016168
	slice1 := make([]int, 5)
	slice2 := slice1
	fmt.Printf("slice1[0]的地址: %p\n", &slice1[0]) // slice1[0]的地址: 0xc000010480
	fmt.Printf("slice2[0]的地址: %p\n", &slice2[0]) // slice2[0]的地址: 0xc000010480
}
  • Similarly, when a function passes parameters, the array is passed by value, and the slice is passed by reference, so in most cases, using slices (of course, pointers are also fine) will save a lot of memory consumption
package main
import "fmt"

func change1(arr [5]int) [5]int {
    
    
	for i := 0; i < 5; i++ {
    
    
		arr[i] = i
	}
	return arr
}

func change2(s []int) []int {
    
    
	for i := 0; i < 5; i++ {
    
    
		s[i] = i
	}
	return s
}

func main() {
    
    
	arr := [5]int{
    
    }
	slice := make([]int, 5)
	fmt.Printf("初始值:arr: %v\n", arr)     // 初始值:arr: [0 0 0 0 0]
	fmt.Printf("初始值:slice: %v\n", slice) // 初始值:slice: [0 0 0 0 0]
	change1(arr)
	change2(slice)
	fmt.Printf("修改后值:arr: %v\n", arr)     // 修改后值:arr: [0 0 0 0 0]
	fmt.Printf("修改后值:slice: %v\n", slice) // 修改后值:slice: [0 1 2 3 4]
}


expand

4.1. Simple understanding of references

Definition: A reference type is a data type represented by an actual value reference (similar to a pointer) of the type. If you assign a variable a reference type), the variable will refer to (or "point to") the original value. No copies are created. Reference types include classes, interfaces, delegates, and boxed value types.

Because the pointer itself is also a kind of reference, the original pointer and reference can be combined for discussion. However, because the reference shields the implementation details, the programmer does not necessarily know the operation of the reference, which part of the function is specific, and there are more unexpected situations that need to be pointed out than the transparent pointer.

Let me give a simple example: "In a class, when the teacher wants a student to stand up and answer a question, how can everyone know which classmate it is? One: call his name; two: point at him with a pointer."
Then , all students are memory data, the name here is the guide, and the pointer is the pointer. The difference is that when I want to operate the memory, the reference is the name given to the target memory during initialization. It is fixed, and the pointer points to the physical location of the memory data. Wherever it points, it means the data to be accessed, but the pointer can Move, point to different memory addresses to get different data, but references cannot, just like the name, it is fixed when it is initialized when it is born. But their functions are similar, that is, to find the target data. Click here to jump back


4.2. Go value passing, reference passing and pointer passing

Take the parameter passing of the calling method as an example:

Value transfer : the value is copied, and the value of the formal parameter is modified without affecting the original value.

Transfer by reference : The referenced information is copied, but the underlying memory data is not copied, and the same data is operated.

Pointer passing : the physical address pointed to by the pointer is copied, and the value of the same physical address is manipulated.

Note: Since Go does not allow arithmetic on pointers, there is no chance of accidentally changing a pointer. And if a new value is assigned to the pointer, subsequent modifications will of course no longer affect the value pointed to by the old value. Since the pointer mechanism is transparent, this is easy to understand.



essay

insert image description here

Guess you like

Origin blog.csdn.net/m0_48489737/article/details/127178400