As a Go novice, I’m curious to see any “weird” code; for example, I recently saw a few methods; the pseudo-code is as follows.
I believe that most newcomers to
Go are confused when they see this code, the most confusing of which is
This declaration of a slice, without looking at the next two ways of writing it, is quite understandable when looking at
The slice holds the memory addresses of all T, which is more space-efficient than storing T itself, and
*T can modify the value of T inside the method, while
For example, the above example prints
Only by modifying the method to
to modify the value of T:
Let’s focus on the difference between
*T, where two
append functions are written.
Looking at the first one first, the output is the result of
It shows that the modifications inside the function can affect the outside during function passing.
Here we look at another example.
The end result is
There is no impact to the outside.
And when we adjust it a bit more, we find that it is different again.
You can see that if you pass a pointer to a slice, using the
append function to append data will affect the outside.
Before analyzing the above three cases, let’s understand the data structure of
A direct look at the source code shows that slice is actually a structure, but it is not directly accessible to the public.
Source code address
There are three important attributes.
|array||The array that holds the data at the bottom is a pointer.|
|cap||Slicing capacity cap>=len|
When it comes to slicing, one has to think of arrays, which can be understood as follows.
Slicing is an abstraction of arrays, and arrays are the underlying implementation of slicing.
In fact, it is easy to see by the name slicing that it is cutting a part of the array; as opposed to the fixed size of the array, slicing can be expanded according to the actual usage.
So a slice can also be obtained by “slicing” the array.
where the length and capacity of
x1 are 6.
The length and capacity of
x2 are 3 and 5.
- The length of * x2 is easy to understand.
- The capacity is equal to 5, which is 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 left position that is not referenced is the maximum capacity of the slice, which is 5.
The same underlying array
Take the code just shown as an example.
During function passing, the x in
main is referenced by the same array as the x slice in the
x=100 in the function, the
main function can also get it.
Essentially the same block of memory data is being modified.
Misunderstandings caused by value passing
In the above example, after calling the
append function in
appendB to append data, you will find that the main function is not affected, here I have slightly adjusted the sample code.
The main reason is the modification of the slice initialization method, so that the capacity is greater than the length, the specific reason will be explained subsequently.
The output is as follows.
The data in the
main function does seem to be unaffected; however, those who are careful will notice that the length of x in the
appendB function changes from +1 to 4 after
main function, the length is changed back to 3.
This difference in detail is why
append() “seems” not to work; as to why it “seems”, the code has been adjusted again to
On top of that, a slice is made based on x after
append; the scope of the slice is all the data in the array referenced by x.
Let’s see what happens.
You will magically find that y prints out all the data, and the data appended in the
appendB function has actually been written to the array, but why doesn’t
x itself get it?
It is easy to understand by looking at the figure.
- It is true that in
appendBthe data is appended to the original array and the length is also increased.
- But since it is a value pass, the structure
slice, even if it changes the length to 4, only changes the length of the copied object, and the length in
mainis still 3.
- Since the underlying array is the same, we can see the additional data by regenerating a full-length slice based on this underlying array.
So the essential reason here is that
slice is a structure, which is passed as a value, and no matter how the length is modified in the method, it does not affect the original data (in this case, the length and capacity properties).
Expansion of slices
One more note.
It was specifically mentioned earlier that the example here is slightly altered, mainly by setting the capacity of the slice to exceed the length of the array.
What happens if this special setting is not made?
At this point, you will find that the y slice data in the main function has not changed either, why is this?
This is because the length and capacity of the initialized x slice is 3. When appending data in the
appendB function, we find that there is no more room.
This is when the expansion is done.
- Copy a copy of the old data into the new array.
- Append the data.
- Return the new data memory address to x in
Again, since this is value passing, slicing the underlying array in
appendB has no effect on the slices in the
main function, which results in no change to the data in the final
Passing a slice pointer
Is there any way to have an external impact even during expansion?
The output results are.
This is when external slices can be affected, and the reason is actually quite simple; the
As I said earlier, since
slice itself is a structure, when we pass a pointer, it’s the same principle as the usual custom
struct that modifies data inside a function via a pointer.
In the end, the pointer to x in
appendC points to the expanded structure, and since the pointer to x in the main function is passed, the same x in the main function also points to the structure.
So to summarize.
- A slice is an abstraction of an array, while a slice is itself a structure.
- The same array is referenced inside and outside the function when parameters are passed, so changes to the slice will affect the outside of the function.
- The situation will change if expansion occurs, and also expansion will lead to data copying; so try to anticipate the slice size to avoid data copying.
- When regenerating slices for slices or arrays, the data will affect each other because the same underlying array is shared, and this needs to be noted.
- Slices can also pass pointers, but the scenario is rare and will bring unnecessary misunderstandings; it is recommended that values be passed to values, as length and capacity do not take up much memory.
I believe that the use of slices will find very similar to
Java in the
ArrayList, the same is based on array implementation, will also be expanded to occur data copy; so it seems that the language is only the choice of the upper layer use, some common underlying implementation of everyone is similar.
At this point we then look at the title
*T *T **T will find that there is no connection between these, just look like easy to bluff.