This article originated from personal public account: TechFlow, original is not easy, for attention


Today is the fifth installment of our Series on Golang, in which we will look at the use of arrays and slicing in Golang.

Arrays and slices

Arrays in golang are defined in much the same way as in C++, except that variable types are written at the end.

For example, if we wanted to declare an array of int length 10, we would write it like this:

var a [10]int
Copy the code

The length of an array cannot be changed after it is defined, just like in C++ and Java. But in our daily use, we don’t use arrays unless we are very sure that the array length won’t change. Instead, we use slices.

Slicing is a bit like an array reference, its size can be dynamic and therefore more flexible. So in our everyday use, it’s much more than arrays.

The declaration of slices is derived from arrays, similar to list slices in Python, where we declare a slice by specifying the range of the left and right ranges. Ranges are closed on the left and open on the right, as in Python. Let’s look at an example:

var a [10]int
var s []int = a[0:4]
Copy the code

This is the standard declaration. Instead of using var, we can use an array to assign a value to a slice. For example, the statement above can be written like this:

s := a[:4]
Copy the code

In Python, when we use slicing, the interpreter makes a copy of the corresponding data for us. So it’s different after the slice than before, but it’s different in golang. Slice and data correspond to the same piece of data. Slice is only a reference to the array. If the data in the original array changes, it will change along with the data in the slice.

The same example:

var a [10]int
var s []int = a[0:4]
fmt.Println(s)
Copy the code

This gives us the output [0, 0, 0], because array initialization defaults to 0. If we modify an element of A and print s, we get a different result:

var a [10]int
var s []int = a[0:4]
a[0] = 4
fmt.Println(s)
Copy the code

In this way, the result is [4 0 0 0 0]. Although we have not modified the data in S, since S is essentially a reference of A, changes in A will be accompanied by changes in S.

Advanced usage

As mentioned earlier, slicing is more convenient than arrays, so we tend to use slicing rather than arrays in our daily use. However, according to the current syntax, slicing is generated from an array, which means that if we want to use slicing, we must first create a corresponding array.

Golang’s designers have taken this problem into account, and for our convenience, Golang has designed a method for defining slices directly.

This is a declaration of an array. We fixed the length of the array and initialized it with the specified element.

var a = [3]int{0.1.2}
Copy the code

If we remove the length declaration, it becomes a sliced declaration:

var a = []int{0.1.2}
Copy the code

This also works. Inside golang, the following statement also creates an array, and the a we get is a slice of that array. But the array is invisible to us, and the Golang compiler omits this logic for us.

Length and capacity

Now that we understand the relationship between slices and arrays, we can look at the concepts of length and capacity.

Length refers to the number of elements contained in the slice itself, while capacity refers to the number of elements contained in the array corresponding to the slice from the beginning to the end. We can use len to get the length of the slice and cap to get its capacity.

Let’s look at an example. First we create a slice and then write a function to print out the length and capacity of the slice:

package main

import "fmt"

func main(a) {
 s := []int{1.2.3.4.5.6}  printSlice(s)  }  func printSlice(s []int) {  fmt.Printf("len=%d cap=%d %v\n".len(s), cap(s), s) }  Copy the code

When we run it, we get something like this:


This should be exactly what I expected, we created a slice of six elements, and of course it should be six in size and length, but the following operation might be a bit different.

We slice the slice again and output the capacity and length after the slice:

s = s[:2]
printSlice(s)
Copy the code

Running this results in the following result:


We see that its length has become 2, but its capacity is still 6, which is not too hard to understand. Since the current slice length is smaller, its corresponding array hasn’t changed at all, so its capacity should still be 6.

We continue, we continue to slice:

s := []int{1.2.3.4.5.6}
s = s[:2]
s = s[:4]
printSlice(s)
Copy the code

The result looks like this:


Things are starting to change a little bit, and there are two interesting points. One is that the length of s after the previous slice is 2, but we were able to slice it to 4. This shows that when we slice, the object is not the slice itself, but the corresponding array behind the slice. This is very important, and if you don’t understand it, a lot of slicing can seem bizarre and incomprehensible.

The second point is that the capacity of slicing still does not change, so it will not change, so let’s try another slicing method, and see if there is any difference.

s = s[2:]
printSlice(s)
Copy the code

This time, the result is different. It looks like this:


This time it changed, the size of the slice became 4, which means it got smaller. Why is that?

The reason is simply that the position of the array’s head pointer has been moved. The array was originally 6, so I moved it two places to the right, so I’m left with 4. But the remaining question is, why does the array’s head pointer move?

Because the array’s head pointer is linked to the position of the slice, the previous slice operation changes the element and its length, but does not change the position of the slice pointer. This time we slice [2:], and when we perform this operation, we essentially move the pointer position to the right to 2.

This is why the capacity of a slice is defined by the number of elements from the beginning to the end of its corresponding array, not the number of elements in the corresponding array. Because moving the pointer to the right changes the size of the array, the length of the array itself does not change.

Let’s look at an example:

var a = [6]int{1.2.3.4.5.6}
 s := a[:]
 //printSlice(s)
 s = s[:2]
 printSlice(s)
 s = s[2:]  printSlice(s)  //s[0] = 4  fmt.Println(a) Copy the code

We use explicit slices this time, and after a series of slices of S, its capacity becomes 4, but the number of elements in A remains 6, unchanged. So you can’t just think of the capacity as the length of the array, but the length of the slice position to the end of the array. Because slicing changes the position of the slicing pointer, it changes the capacity, but the size of the array does not change.

Make the operation

In general, when we use slicing, we use it as a dynamic array, which is a list in Python. So, on the one hand, we don’t want to care about the array behind slicing, on the other hand, we want to have a more differentiated constructor, which is distinct from creating an array.

So with that in mind, Golang provides a make method for creating slices. Since make can also be used to create other types, such as maps, we need to pass in the type of variable we want to create when we use make. Here we want to create a slice, so we pass in the type of the slice, which is []int, or []float, etc. After that, we need to pass in the length and capacity of the slice.

Such as:

s := make([]int.0.5)
Copy the code

We have a slice of length 0 and capacity 5. We can also pass a single argument, which means that the length and capacity of the slice are equal.

Something like this:

s := make([]int.5)
Copy the code

If we print this s, we get [0, 0, 0, 0], which means golang will fill the slice with zero values for us.

Append method

Slicing is more flexible than arrays, meaning that the length of the slice is variable, and we can append elements to the slice using the append method.

The Append method in Golang is different from Python and other languages in that it takes two arguments, the slice itself and the element to be added, and returns a slice.

So we should write it like this:

s := make([]int.4)
s = append(s, 4)
Copy the code

The purpose of this is also simple, because the length of the slice is dynamic, which means that the length of the array corresponding to the slice is also variable, or at least can be increased. If the current array is not large enough to store slices, Golang allocates a larger array and returns a slice pointing to the new array. This means that the append method cannot be an inplace because of the underlying implementation mechanism of slicing, so it must be returned. I guess this is also due to performance considerations.

Two-dimensional slices

Finally, let’s take a look at how two-dimensional slicing is implemented in Golang. You have to be able to understand only two dimensions and extend to multiple dimensions.

Golang creates 2d slices in much the same way that C++ creates a 2d vector. We start by defining a 2d slice directly and then fill it with a loop. The method for defining two-dimensional slices is similar to that for one-dimensional slices, except that a square bracket is added. Then we fill several one-dimensional slices with a loop:

mat := make([] []int.10)
for i := 0; i < 10; i++ {
  mat[i] = make([]int.10)
}
Copy the code

At the end

That concludes the common uses of golang for arrays and slicing. Not only that, but we also have a slight understanding of the underlying implementation of slicing. First contact section of this concept may feel a bit strange, always feel like before and we learn language in, about the concept of capacity is not too easy to understand, this is very normal, in essence, it doesn’t look normal or is an uncomfortable place, there are the creator of thinking, as well as for performance trade-offs. So, if you feel confused, you can think more about this aspect, maybe there will be a different harvest.

Today’s article is here, the original is not easy, scan code to pay attention to me, get more wonderful articles.