Xiaochat : This article is a summary of Xiaobai's
golang
first , 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 understandinggo
of andArray
, please read it!Slice
Table of contents
- 1. go array Array
- 2. go slice Slice
- 3. The difference between the use of arrays and slices
- 4. Expand
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. Array
For example, var arr [2]int
and var brr [3]int
are 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) go
Support using ==
and !=
to compare two arrays. We know that go
an 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 go
also directly Println
output 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]int
andvar 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
go
a 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 thatappend
can 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)
Slice
Allocate 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
Array
to . This is very important, about its own characteristics and connectionArray
with .
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 createdmake
in 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
,len
andcap
. For example:var s []int = make([]int, 2, 4)
.
If not
cap
set , the defaultlen
is the same as the value of . Regarding the relationship between the lengthlen
, capacitycap
and the lengthening strategy of the source code, in one sentence: whenappend
the length of the slice element exceedscap
, 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 currentcap
size .
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)
, wherearray
isslice
the 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:len
the 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 usinga := []int{}
ora := 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): 4,cap(s): 4
切片s:val: [1 2 0 0],len(s): 4,cap(s): 4
扩容后:切片s:val: [1 2 0 0 3 4],len(s): 6,cap(s): 8
扩容后:数组arr:val: [1 2 0 0],len(s): 4,cap(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 ;
Java
array 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.