preface
As a Go novice, I was curious to see any “weird” code. For example, there are several methods THAT I’ve seen recently; The pseudocode is as follows:
func FindA(a) ([]*T,error){}func FindB(a) ([]T,error){}func SaveA(data *[]T) error{}func SaveB(data *[]*T) error{}Copy the code
I believe that most beginners in Go are confused when they see this kind of code. The most confusing part is:
[]*T
*[]T
*[]*T
Copy the code
This is a declaration of slicing, and I don’t want to write it either way; The []*T section contains all the memory addresses of T, which is more efficient than storing T itself. In addition, []*T can be modified inside the method, whereas []T cannot be modified.
func TestSaveSlice(t *testing.T) {
a := []T{{Name: "1"}, {Name: "2"}}
for _, t2 := range a {
fmt.Println(t2)
}
_ = SaveB(a)
for _, t2 := range a {
fmt.Println(t2)
}
}
func SaveB(data []T) error {
t := data[0]
t.Name = "1233"
return nil
}
type T struct {
Name string
}
Copy the code
For example, the above example printed is
{1}
{2}
{1}
{2}
Copy the code
Only change the method to
func SaveB(data []*T) error {
t := data[0]
t.Name = "1233"
return nil
}
Copy the code
To change the value of T:
& {1} and {2} and {1233} and {2}Copy the code
The sample
[]*T = *[] *T = *[] *T = *[] *T
func TestAppendA(t *testing.T) {
x:=[]int{1.2.3}
appendA(x)
fmt.Printf("main %v\n", x)
}
func appendA(x []int) {
x[0] =100
fmt.Printf("appendA %v\n", x)
}
Copy the code
If we look at the first one, the output is:
appendA [1000 2 3]
main [1000 2 3]
Copy the code
During function transfer, changes inside the function can affect the outside.
Here’s another example:
func appendB(x []int) {
x = append(x, 4)
fmt.Printf("appendA %v\n", x)
}
Copy the code
The end result:
appendA [1 2 3 4]
main [1 2 3]
Copy the code
It doesn’t affect the outside.
And when we adjust it a little bit, it’s different:
func TestAppendC(t *testing.T) {
x:=[]int{1.2.3}
appendC(&x)
fmt.Printf("main %v\n", x)
}
func appendC(x *[]int) {
*x = append(*x, 4)
fmt.Printf("appendA %v\n", x)
}
Copy the code
The final result:
appendA &[1 2 3 4]
main [1 2 3 4]
Copy the code
You can see that if you pass a pointer to a slice, appending the data using the append function will affect the outside.
Slice the principle
Before looking at these three scenarios, let’s look at slice’s data structure.
A direct look at the source code reveals that Slice is actually a structure, but not directly accessible.
Source address runtime/slice.go
There are three important attributes:
attribute | meaning |
---|---|
array | The underlying array of data, which is a pointer. |
len | Section length |
cap | Section capacitycap>=len |
When you think about slices, you have to think about arrays.
Slicing is an abstraction of arrays, and arrays are the underlying implementation of slicing.
It’s not hard to tell by the name slice, it’s just a slice from the array; As opposed to a fixed size array, slices can be expanded based on actual usage.
So slicing can also be obtained by “slicing” the array:
x1:=[6]int{0.1.2.3.4.5}
x2 := x[1:4]
fmt.Println(len(x2), cap(x2))
Copy the code
Where x1 has a length and a capacity of 6.
The length and capacity of x2 are 3 and 5.
- The length of x2 is easy to understand.
- Capacity equal to 5 can be interpreted as the maximum length that can be used for the current slice.
Since slice X2 is a reference to array X1, the underlying array excluding the unreferenced position on the left is the maximum size of the slice, which is 5.
Same underlying array
As an example:
func TestAppendA(t *testing.T) {
x:=[]int{1.2.3}
appendA(x)
fmt.Printf("main %v\n", x)
}
func appendA(x []int) {
x[0] =100
fmt.Printf("appendA %v\n", x)
}
Copy the code
During function passing, the x in main and the X slice in appendA refer to the same array.
If x[0]=100, you can get it from main.
You’re essentially modifying the same piece of in-memory data.
Misunderstandings caused by value passing
In the example above, the append function that calls append in appendB does not affect the main function. Here I have adjusted the example code a little:
func TestAppendB(t *testing.T) {
/ / x: int [] = {1, 2, 3}
x := make([]int.3.5)
x[0] = 1
x[1] = 2
x[2] = 3
appendB(x)
fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
}
func appendB(x []int) {
x = append(x, 444)
fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x))
}
Copy the code
The main reason is that the initialization mode of slices is modified, so that the capacity is larger than the length. The specific reasons will be explained later.
The output is as follows:
appendB [1 2 3 444] len=4,cap=5
main [1 2 3] len=3,cap=5
Copy the code
The data in main does seem to be unaffected; But attentive friends should notice that the x in the appendB function becomes 4 after append() with length +1.
And in main, the length goes back to 3.
This detail difference is why append() doesn’t seem to work; As for why “looks like”, I adjusted the code again:
func TestAppendB(t *testing.T) {
/ / x: int [] = {1, 2, 3}
x := make([]int.3.5)
x[0] = 1
x[1] = 2
x[2] = 3
appendB(x)
fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
y:=x[0:cap(x)]
fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y))
}
Copy the code
On top of that, I make another slice based on the x after append; The slice ranges from all the data in the array referenced by x.
Let’s take a look at the results:
appendB [1 2 3 444] len=4,cap=5
main [1 2 3] len=3,cap=5
y [1 2 3 444 0] len=5,cap=5
Copy the code
It would be amazing to see that y prints out all the data. The data appended in appendB is actually written to the array, but why is x not fetching it?
It’s easy to understand when you look at the picture:
- in
appendB
It does append data to the original array and its length increases. - But since it’s value passing, so
slice
Even if the length of this structure is changed to 4, it’s only changing the length of the object that was copied,main
The length of PI is still 3. - Since the underlying array is the same, you can see the appended data by regenerating a full-length slice based on the underlying array.
So the fundamental reason for this is that slice is a structure that passes a value, and no matter how much length is changed in the method, it doesn’t affect the original data (in this case, the attributes of length and capacity).
Section capacity
One more note:
The example was changed a little bit. The size of the slice was set to exceed the size of the array.
What happens if you don’t do this special setting?
func TestAppendB(t *testing.T) {
x:=[]int{1.2.3}
/ / : x = make (int [], 3, 5)
x[0] = 1
x[1] = 2
x[2] = 3
appendB(x)
fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
y:=x[0:cap(x)]
fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y))
}
func appendB(x []int) {
x = append(x, 444)
fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x))
}
Copy the code
Output results:
appendB [1 2 3 444] len=4,cap=6
main [1 2 3] len=3,cap=3
y [1 2 3] len=3,cap=3
Copy the code
The y slice in main has not changed. Why?
This is because the x slice was initialized with size and capacity of 3, and when data is appented in the appendB function, there is no place left.
This is where we expand:
- Copy the old data into the new array.
- Append data.
- Returns the new data memory address to
appendB
The x.
Similarly, since this is a value pass, a slice in appendB replacing the underlying array has no effect on the slice in main, leaving the data in main unchanged.
Transfer slice pointer
Is there any way to make an impact on the outside even as you expand?
func TestAppendC(t *testing.T) {
x:=[]int{1.2.3}
appendC(&x)
fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x))
}
func appendC(x *[]int) {
*x = append(*x, 4)
fmt.Printf("appendC %v\n", x)
}
Copy the code
The output is:
appendC &[1 2 3 4]
main [1 2 3 4] len=4,cap=6
Copy the code
And then the external slice can be affected, and the reason is very simple;
As I said before, since slice itself is a structure, when we pass a pointer, we use the same principle as when we pass a custom struct inside a function to modify data through a pointer.
Finally, the pointer to x in appendC points to the enlarged structure, and because we passed a pointer to x in main, the x in main also points to that structure.
conclusion
So to summarize:
- A slice is an abstraction of an array, and the slice itself is a structure.
- The argument is passed to the same array that is referenced inside and outside the function, so changes to the slice affect the outside of the function.
- If capacity expansion occurs, the situation will change and data will be copied. So try to estimate the slice size and avoid copying data.
- When reslicing slices or arrays, it is important to be aware that the data will affect each other because you are sharing the same underlying array.
- Slices can also pass Pointers, but in fewer Settings and can be unnecessarily confusing; The recommended value is passed, the length and capacity do not take up much memory.
I believe that the use of slices will be found very similar to the ArrayList in Java, is also based on the array implementation, will also expand the occurrence of data copy; It seems that the language is just a choice for the upper level use, and some common low-level implementations are common to everyone.
Look at []*T *[]T *[]T *[]*T in the title.