Follow the old cat to get the GO container

Keywords: Go

Early review

The previous chapter mainly shared with you the definition of functions in GO language and the simple usage of pointers in GO language. In this chapter, old cat will learn about containers in GO language with you.

array

Definition of array

When it comes to containers, people with programming experience must first think of arrays. Of course, small partners with programming experience will think that arrays are not containers. But in any case, when it comes to arrays, in fact, they are just a way to store and organize data. Don't get too tangled.

Let's go directly to the example of array definition, as follows:

var arr1 [5]int //Define a default type with a length of 5
arr2:=[3]int{1,2,3} //Define an array and specify a length of 3
arr3:=[...]int{1,2,3,4,5,6} //Define an array, and give the specific length to the compiler to calculate
var grid [4][5] bool //Define a two-dimensional array of four rows and five columns
fmt.Println(arr1,arr2,arr3,grid)

The output of the above example is as follows

[0 0 0 0 0] [1 2 3] [1 2 3 4 5 6] [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

You can summarize that arrays have several characteristics

  • In terms of writing, it is actually opposite to other programming languages. The length of the defined array is written in front of the variable type
  • The contents stored in the array must be of the same type

Traversal of array

So how do we traverse to get the data in the array? In fact, you should know that you can use the for loop to traverse and obtain. One way you can easily think of is as follows (let's take traversing the above arr3 as an example)

for i:=0;i<len(arr3);i++ {
    fmt.Println(arr3[i])
}

This way, of course, we can get it. Next, the old cat actually wants to share another way with you, using the range keyword

//i represents the position subscript of the data in the array, and v represents the actual value
for i,v :=range arr3 {
    fmt.Println(i,v)
}
//Well, if we only want the value, we can look back on what the old cat said before. We can use it_ i is omitted
for _,v :=range arr3 {
    fmt.Println(v)
}
//If we just need the position subscript, we can write as follows
for i:=range arr3 {
    fmt.Println(i)
}

Which of the above two methods do you think will be more elegant? Obviously, it is the latter, with clear meaning and beautiful appearance.

Array is passed by value in go language

In addition, to synchronize with you, the array is also passed as a parameter. Let's redefine a new function as follows:

func printArray(arr [5]int){
    for i,v:=range arr {
        println(i,v)
    }
}

Then let's make relevant calls in the main function (to demonstrate compilation errors, old cat uses pictures here)

According to the above figure, you can clearly see that a compilation error is reported when calling printarray (arrar2). In fact, this shows that in go language, even if arrays of the same type have different lengths, the compiler still thinks they are of different types.

Let's change the value of the passed in array at this time, as shown in the following code

func main() {
    arr3:=[...]int{1,2,3,4,5} //Define an array with variable length
    printArray(arr3)

    for i,v:=range arr3 {
        println(i,v)
    }
}

func printArray(arr [5]int){
    arr[0] = 300
    for i,v:=range arr {
        println(i,v)
    }
}

You can see that the old cat has printed twice here. The first printing is directly printed in the function. At this time, the first value has been changed, and the result printed inside the function is

0 300
1 2
2 3
3 4
4 5

Obviously, the internal value has changed. However, let's look at the printed value of the external function, as follows

0 1
1 2
2 3
3 4
4 5

In fact, there is no change. What does this actually mean? It actually means that when calling printArray, the array is directly copied into the function, and the external array is not updated. It also directly shows that GO language is a parameter transfer method of value transfer.

When you use this array, you must pay attention to it. If you're not sure, you'll be trapped. You may think this array is really difficult to use. In fact, you can tell you a good news. In GO language, generally, we don't use arrays directly. We still use "slicing" more

section

When it comes to slicing, we'd better understand Slicing Based on the above array. Let's start with an example

func main() {
    arr := [...]int{1,2,3,4,5,6,7}
    fmt.Println("arr[2:6]",arr[2:6])
    fmt.Println("arr[:6]",arr[:6])
    fmt.Println("arr[2:]",arr[2:])
    fmt.Println("arr[:]",arr[:])
}

In fact, for definitions like '[]', we call it slice, which means a variable length sequence with the same type of elements. Let's look at the results:

arr[2:6] [3 4 5 6]
arr[:6] [1 2 3 4 5 6]
arr[2:] [3 4 5 6 7]
arr[:] [1 2 3 4 5 6 7]

In fact, it's easier to understand slice. We can take it as a view. Take arr[2:6], we actually extract the elements from the second position to the sixth position as values on the basis of the original array. Of course, our values are left closed and right open intervals.

slice is actually a view concept

As we said above, slice is equivalent to the view of array. In the following examples, let's confirm the above statement. See the following examples in detail

func main() {
    arr := [...]int{1,2,3,4,5,6,7}
    fmt.Println("arr[2:6]",arr[2:6])
    updateSlice(arr[2:6])
    fmt.Println("arr[2:6]",arr[2:6])
    fmt.Println(arr)
}

func updateSlice(arr []int){
    arr[0] = 100
}

The old cat wrote a function to update the value of the first position of slice. You can first think about the results before and after execution, and then look at the following answers.

In fact, the final implementation result is:

arr[2:6] [3 4 5 6]
arr[2:6] [100 4 5 6]
[1 2 100 4 5 6 7]

So why? In fact, arr[2:6] it is easy to understand 3456 above, and the second one is also easy to understand. When the first value of slice is updated to 100, so the second one is programmed, why does the original data become 100? In fact, it needs to be tasted carefully, because we said that slice is a view of the original array. When we see that slice has been updated to 100 in the second way, the underlying data must have changed to 100. (note here that no one says that the operation of the view will not react on the original array). Here is still more important. I hope you can taste it carefully.

Relax and extension

When it comes to relax, to put it bluntly, it is to take another slice value for the original slice. Let's look at the following example.

func main() {
    arr := [...]int{1,2,3,4,5,6,7}
    s1 := arr[:]
    s2 := s1[2:4]
    fmt.Println(s2)
}

It can be seen from the above example that s1 is the full slice of the array, and then we slice s1 again. It is easy to calculate that the result we get in the second time is [3,4]. Such behavior is called reply, which is easy to understand.

Next, let's deepen the difficulty on this basis. We'll do the resilce again on the basis of S2, as follows:

func main() {
    arr := [...]int{1,2,3,4,5,6,7}
    s1 := arr[:]
    s2 := s1[2:4]
    s3 := s2[1:3]
    fmt.Println(s3)
}

We all know that the value of s2 is [3,4]. When we restore it for the second time, because we take [1:3], we find that it is easier to calculate the first position from the first position to the third position. Based on [3,4], the first position should be 4. What about the later? What is the result? Let's tell you the results directly here. In fact, the results obtained after the old cat runs are

[4 5]

So why is there such a result? Where did it come from?

Let's take a look at a diagram arranged under the old cat.

  1. An array of arr, and its length is 7, and seven numbers are stored in it.
  2. Next s1, we slice it completely, so we get a complete number of 7.
  3. It should be noted that at this time, we use the subscript representation. When s2 slices s1 here, we find that its essence is to start taking values for the second element of the array. Because it is the concept of view, in fact, s2 will illusory two other positions of view arr, that is, the gray 3 and 4 subscripts we represent.
  4. Similarly, we express s3, so we slice s3 again on the basis of s2. Theoretically, there are three subscript values, namely 0, 1 and 2 subscript values. However, we find that position 3 of s2 indicates the imaginary position, and there is no real value corresponding to it. Therefore, after taking the intersection, we can only take out two corresponding to the array arr, that is, the final [4,5].

It's still difficult to understand here. I hope you can understand it well, then write code and deduce it yourself. In fact, this knowledge point is the extension of slice. Let's take a look at the underlying implementation of slice below.

In fact, slice generally contains three concepts. The bottom layer of slice is actually an empty array structure. ptr is the pointer to the first position of the array. Len represents the available length of a specific slice, and cap represents the length that can be extended.

In fact, we have functions for len and cap that can be called and obtained directly. Let's take a look at the above example, and then print its length and expand cap. The specific printed codes are as follows.

func main() {
    arr := [...]int{1,2,3,4,5,6,7}
    s1 := arr[:]
    s2 := s1[2:4]
    s3 := s2[1:3]
    fmt.Printf("arr=%v\n",arr)
    fmt.Printf("s1=%v,len(s1)=%d,cap(s1)=%d\n",s1,len(s1),cap(s1))
    fmt.Printf("s2=%v,len(s2)=%d,cap(s2)=%d\n",s2,len(s2),cap(s2))
    fmt.Printf("s3=%v,len(s3)=%d,cap(s3)=%d\n",s3,len(s3),cap(s3))
}

The output of the above code is

arr=[1 2 3 4 5 6 7]
s1=[1 2 3 4 5 6 7],len(s1)=7,cap(s1)=7
s2=[3 4],len(s2)=2,cap(s2)=5
s3=[4 5],len(s3)=2,cap(s3)=4

When our value exceeds cap, an error will be reported. For example, s2 is now s2:=[2:4]. Now we find that its cap is 5. If we exceed 5, s2 can be written as s2:=[2:8], and the following exception will be reported

panic: runtime error: slice bounds out of range [:8] with capacity 7

goroutine 1 [running]:
main.main()
    E:/project/godemo/part6-slice.go:8 +0x7f

Besides, if we take this value

fmt.Printf("s3=%v",s3[4])

If s3 has exceeded the length of len, an error will be reported as follows

panic: runtime error: index out of range [4] with length 2

goroutine 1 [running]:
main.main()
    E:/project/godemo/part6-slice.go:14 +0x49f

To sum up, we can actually draw these conclusions.

  1. slice can be extended backward, not forward.
  2. s[i] cannot exceed len(s), and backward expansion cannot exceed the underlying array cap(s)

The above expansion of slice is actually a headache and difficult to understand, but it's OK to really understand the algorithm inside. I hope you can also understand the above explanation. Old cat has done its best. If there are still unclear, you are welcome to talk about old cat in private.

Slice operation

How to add elements to slice? Look at the old cat's code, as follows:

func main() {
    arr :=[...]int{0,1,2,3,4,5,6,7}
    s1 :=arr[2:6]
    s2 :=s1[3:5]
    s3 := append(s2,10) //[5,6,10]
    s4 := append(s3,11) //[5,6,10,11]
    s5 := append(s4,12)
    fmt.Printf("arr=%v\n",arr)
    fmt.Printf("s2=%v,len(s2)=%d,cap(s2)=%d\n",s2,len(s2),cap(s2))
    fmt.Printf("s2=%v\n",s2)
    fmt.Printf("s3=%v\n",s3)
    fmt.Printf("s4=%v\n",s4)
    fmt.Printf("s5=%v\n",s5)
}

As shown above, when we add operations to the slice, we use the append function. You can calculate the final output without looking at the actual results of the old cat. Combined with the slicing operation previously described by the old cat. The results are as follows:

arr=[0 1 2 3 4 5 6 10]
s2=[5 6],len(s2)=2,cap(s2)=3
s2=[5 6]
s3=[5 6 10]
s4=[5 6 10 11]
s5=[5 6 10 11 12]

From the above, we will find that the append operation will have such a conclusion

  • If the cap is exceeded when adding elements, the system will reallocate a larger underlying array
  • Due to the value passing relationship, the return value of append must be received

slice creation and copy

The slice shared by the old cat seems to be based on arr. In fact, the bottom layer of slice is indeed based on arry. Do we need to create a new array every time we create slice? Actually, there are many ways to create slice. Let's take a look at the following creation methods

func main() {
    var s []int //1. The creation method of empty slice is actually based on the array of Nil values
    for i := 0;i<100;i++ {
        s = append(s,2*i+1)
    }
    fmt.Println(s)
    s1 :=[]int {2,4,5,6} //2. Create a slice with initialization value
    s2 :=make([]int ,16) //3. Use the make built-in function to create a slice with a length of 16
    s3 :=make([]int,10,32) //4. Use the make built-in function to create a slice with a length of 10, but the cap is 32
    //slice copy is also quite simple. You can directly use the built-in function, as shown below
    copy(s2,s1) //This mainly means that s1 is copied to s2. Here, we should pay attention not to reverse it
}

Delete slice element

The reason why delete operations should be shared separately is mainly because these operations have relatively convenient built-in functions to use, but there is no delete operation. We can only evaluate by the characteristics of the slice. The following example

func main() {
    s1 :=[] int{2,3,4,5,6}
    s2 :=append(s1[:2],s1[3:]...)
    fmt.Println(s2)
}

There is a slice from 2 to 6 above. If we want to remove the 4 elements, we have to use this slice combination to remove the elements inside. I believe you can understand that the form of "s1[3:]..." is actually a way of writing in go language, which means to take all the remaining elements from position 3.

In the end, we got the result

[2 3 5 6]

The above is the sharing of all the knowledge of slice. It took the old cat a lot of time to sort it out. The old cat also tried to make some of his understanding clear. Slice is still important in language.

Write at the end

Review the go language container above. In fact, the focus is to share with you the relevant definitions, operations and underlying principles of slice. It's easier to get started if you find out. Of course, the containers of go language are more than these. Due to space constraints, old cats will not share other containers. I believe they will have no patience to read them after writing. The later container will mainly share the map and the processing of characters and strings.

I am a cat, more content. Welcome to search for official account of cat.

Posted by nikefido on Fri, 19 Nov 2021 13:46:23 -0800