“The 11th issue” is 69 issues away from the uncle’s small goal of 80 issues, today uncle wants to talk to everyone’s Lao question is — golang function parameter for slice is pass reference or pass value. This is a very basic and classic interview, I believe that no matter which big factory you go to interview, when asked about the sliced question can be said to be a necessary question, in front of the cattle answer a little wrong may have to go home and so on notice. Let’s take a look at it.

Slice the illusion of passing references – passing references

Does golang pass a reference or a value when the function parameter is slice? For this question, when you search baidu round, you will find that a large number of people think it is a quote, usually they will post the following code to support:

pacakge main

func changeSlice(s []int) {
    s[1] = 111
}
 func main(a) {  slice := []int{0.1.2.3}  fmt.Printf("slice: %v \n", slice)   changeSlice(slice)  fmt.Printf("slice: %v\n", slice) } Copy the code

In the above code, we initialize a slice variable in the main function, and then call the changeSlice function with the slice variable. The main processing logic of the changeSlice function is to change the value of the second element of the slice. Let’s look at the result of running the print:

slice: [0 1 2 3] 
slice: [0 111 2 3]
Copy the code

From the output, we can see that the slice variable in the main function slice has been modified as well as the slice modification in the changeSlice function. At first glance, this is not the performance of reference passing?

But is it really true?

Clarify three important concepts

When discussing exactly how function slice parameters are passed, let’s first clarify three important concepts:

  • Value passing (value passing)
  • The pointer
  • Pass a reference (pass a reference)

Value passing (value passing)

When a function is called, a copy of the actual parameters is passed to the function so that changes to the parameters in the function do not affect the actual parameters. This simplicity needs no elaboration.

The pointer

A parameter is a pointer to an argument’s address, and when you operate on a reference to a parameter, you operate on the argument itself. Sounds tricky, right? Let’s take a look at an example:

func main(a) {
    a := 10
    pa := &a
    fmt.Printf("value: %p\n", pa)
    fmt.Printf("addr: %p\n", &pa)
 modify(pa)  fmt.Println("The value of a has been modified to read :", a) }  func modify(p *int) {  fmt.Printf("Value: %p\n", p)  fmt.Printf("Addr: %p\n", &p)  *p = 1 } Copy the code

The above code defines a variable a and stores the address in the pointer variable PA; Then print the value of pa and the address of PA, and call modify with the pointer variable pa; The modify function prints the value of the parameter p and the address of p, then changes the value of p to 1.

The result we printed out:

value: 0xc000016088
addr: 0xc00000e028
Value within a function:0xc000016088
Function addr:0xc00000e038
The value of a has been modified. The new value is:1
Copy the code

As you can see from the output, this is a copy of a pointer. The Pointers pa and P have the same value, but the memory address where they are stored is different, so these are two different Pointers.

Note: everything stored in memory has an address. Pointers are no exception. They point to something else, but there is also memory for that pointer.

Combined with the picture, I believe it will be a little clearer:

“See comment picture 1”

Pass a reference (pass a reference)

When a function is called, the address of the actual parameter is passed to the function. The modification of the parameter in the function will affect the actual parameter.

Assuming the example above, if the address of the pointer variable p is also 0xC00000e028 printed in modify, then we consider it passed by reference

But we can’t use go as an example here, for the reasons that follow.

The Go function passes only values

If you insist on passing a reference after reading the concepts of passing a value, passing a pointer, passing a reference, well, the uncle is going to crush you right here.

According to the official document of Go, there is only one way to pass a function parameter in Go. That is, in Go, there is only one way to pass parameters to a function. Official document portal

If you’re still not convinced, let’s go straight to an example:

package main

import "fmt"

func changeSlice(s []int) {
 fmt.Printf("func: %p \n", &s)  s[1] = 111 }  func main(a) {  slice := []int{0.1.2.3}  fmt.Printf("slice: %v slice addr %p \n", slice, &slice)   changeSlice(slice)  fmt.Printf("slice: %v slice addr %p \n", slice, &slice) } Copy the code

Printout:

slice: [0 1 2 3] slice addr 0xc0000a6020 
func: 0xc0000a6060 
slice: [0 111 2 3] slice addr 0xc0000a6020
Copy the code

In this example, the address of the slice printed in main should be the same as the address printed in changSlice if the slice argument was passed as a reference, but this is not the case.

Therefore, we can say with absolute certainty that the reference to slice parameters in the Go function is incorrect, as is the reference to slice, map, and channel data types

Slicing parameters are essentially value passing

From the above analysis, slice parameters are not passed by reference. Passing a pointer is actually a copy of a pointer. The parameter and the argument are two different Pointers, but they have the same value. It’s essentially passing values.

Is that true or not? The uncle questions are out, indicating that 90% is possible, and we will verify the remaining 10%.

Structure of slice

Let’s first look at what the structure of the slice looks like:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
 type Pointer *ArbitraryType Copy the code

A slice, as its name implies, is a cut part of an array. Its structure contains three parts. The first part is a pointer to the underlying array, followed by len, the size of the slice, and Cap, the capacity of the slice. (Sure enough, it contains pointer member variables.)

Arr := [5]int{0,1,2,3,4} generates a slice := arr[1:4] :

“See comment picture 2”

Here’s another example:

func main(a) {
    arr := [5]int{0.1.2.3.4}

    slice1 := arr[1:4]
    slice2 := arr[2:5]
  / / print  fmt.Printf("arr %v, slice1 %v, slice2 %v arr addr: %p, slice1 addr: %p, slice2 addr: %p\n", arr, slice1, slice2, &arr, &slice1, &slice2)   / / print  fmt.Printf("arr[2] addr: %p, slice1[1] addr: %p, slice2[0] addr: %p\n", &arr[2], &slice1[1], &slice2[0])   arr[2] = 2222   / / print three  fmt.Printf("arr: %v, slice1: %v, slice2: %v\n", arr, slice1, slice2)    slice1[1] = 1111   / / print four  fmt.Printf("arr: %v, slice1: %v, slice2: %v\n", arr, slice1, slice2) } Copy the code

In the above code we create an array and generate two slices. Print their values and corresponding addresses. In addition, modify the value of a cell in the array or slice and observe the change in the value of the cell in the array and slice:

arr [0 1 2 3 4], slice1 [1 2 3], slice2 [2 3 4]   arr addr: 0xc000014090, slice1 addr: 0xc00000c080, slice2 addr: 0xc00000c0a0
arr[2] addr: 0xc0000140a0, slice1[1] addr: 0xc0000140a0, slice2[0] addr: 0xc0000140a0
arr: [0 1 2222 3 4], slice1: [1 2222 3], slice2: [2222 3 4]
arr: [0 1 1111 3 4], slice1: [1 1111 3], slice2: [1111 3 4]
Copy the code
  • You can see from printing a result that two slices have been created, each with a different address
  • As can be seen from the result of printing two, slice1[1], slice2[0] and array element ARr [2] have the same address, indicating theseSlices share data in the array ARR
  • It can be seen from printing three and four that modifying the data of the common part of the array and slice has a direct effect on both of them, again confirming the conclusion of the second point.

From the above analysis, we can know that the main reason why two different slices can influence each other is that the Pointers inside the slices point to the same data source, and the Pointers of the two slices point to the intersection of the data sources.

Back to the subject of section as a function parameter, because Go inside function and a way to pass only value, so when the section as a parameter, is part of a section of a copy, but in a copy of the section, it contains a pointer to the member variable’s value is the same, meaning that they are pointing to the data source is the same, so in the calling function can modify parameters affect the argument.

Go function value transfer summary

In general, we refer to the data type that can directly modify the argument by modifying the parameter during value copy as a reference type.

So we can sum it up like this:

All pass arguments in Go are value pass (pass value), a copy, a copy. Because copying content is sometimes a non-reference type (int, string, struct, etc.), there is no way to modify the original content data in the function; There are reference types (Pointers, map, Slice, chan, etc.) that allow you to modify the original content data.

Note here that reference types and passing references are two different concepts.

Slice parameter, does modifying parameter affect the argument

Yes, looking at this, we should ask ourselves a question: for function slicing arguments, does modifying the parameter inside the function necessarily affect the external argument? (There must be a ghost after a question!)

Let’s look at an example:

func main(a) {
    slice := make([]int.2.3)
    for i := 0; i < len(slice); i++ {
        slice[i] = i
    }
  fmt.Printf("slice: %v, addr: %p \n", slice, slice)   changeSlice(slice)  fmt.Printf("slice: %v, addr: %p \n", slice, slice) }  func changeSlice(s []int){  s = append(s, 3)  s = append(s, 4)  s[1] = 111  fmt.Printf("func s: %v, addr: %p \n", s, s) } Copy the code

In the above code, an empty slice of length 2 and capacity 3 is created, and then two values are assigned to the slice. Then the function changeSlice is called, and the parameter is the assigned value of the slice. The changeSlice function appends two elements to the slice and then changes the value of the second element of the slice.

Let’s look at the print:

slice: [0 1], addr: 0xc00001a100 
func s: [0 111 3 4], addr: 0xc000014090 
slice: [0 1], addr: 0xc00001a100 
Copy the code

We can see from the output that the external argument slice does not change after the modification of parameter S. We also notice that the values of the pointer variables of parameter S and parameter slice are different, that is, they point to different data sources.

Why is that?

Section capacity

First of all, let’s understand a knowledge point:

Both arrays and slices are limited in length. That is, when appending slices, if the element is within the capacity range of slices, you can directly append an element to the end. If the maximum size is exceeded, the append element needs to be copied and expanded for the underlying array.

In other words:

  • When the append method is used to append elements to slice, the size of slice is not enough, so it is equivalent to extending the contents of the array pointed to by slice, which can be interpreted as cutting the contents of the array again and modifying the contents of the array.
  • When append is used to append elements to slice, if the slice is already full and the append exceeds the slice’s capacity, the array will be out of bounds and the expansion operation will occur

What does Append do when it needs to expand?

  1. Create a new temporary slice T. The length of t is the same as that of slice, but the capacity of T is twice that of slice. When creating a slice, an anonymous array is also created at the bottom. (It doesn’t have to be a double expansion, see The Slice expansion strategy in Golang for details)
  2. Copy the slice elements into t, i.e. fill them into an anonymous array. T is then assigned to slice, which now points to the underlying anonymous array.
  3. To a smaller-than-capacity append method.

For example, if arr = [3]int{0, 11, 22} generates a slice := arr[1:3], append element 33 to the slice using the append method, the following operation will occur:

“See comment picture 3”

To return to the example above, the external argument slice is not affected by the modification of the parameter slice variable s, because after the execution of the code s = append(s, 4), the pointer to the array of slice S has been expanded, and the pointer to the new array has been expanded. Therefore, when the value of the second element of slice S is changed again, Does not affect the external slice variable slice.

Slice capacity calculation

For specific section capacity calculation, please refer to the following example:

In the array [0, 1, 2, 3, 4], the array has five elements. If the slice s = [1, 2, 3], then the index of 3 in the array is 3, that is, the size of the last element left in the array, plus S already has 3 elements, so the capacity of the final S is 1 + 3 = 4. If the slice is s1 = [4], the index of 4 is the largest in the array, and the free element of the array is 0, then the capacity of s1 is 0 + 1 = 1. The details are as follows:

“See comment picture 4”

homework

Ok, this is the end of the sharing, and finally think it is necessary to leave an assignment for you to test your acceptance of the above knowledge:

func main(a) {
    slice := make([]int.2.3)
    for i := 0; i < len(slice); i++ {
        slice[i] = i
    }
  ret := changeSlice(slice)  ret[1] = 111   fmt.Printf("slice: %v, ret: %v \n", slice, ret) }  func changeSlice(s []int) []int {  s[0] = 10  s = append(s, 3)  s = append(s, 4)  s[1] = 100   return s }  // q: What will be printed out? Copy the code

Leave your answers in the comments

Follow the public account “Uncle said code” for more dry goods. That’s all for today’s sharing, and we’ll see you next time

Reference:

1、 https://www.flysnow.org/2018/02/24/golang-function-parameters-passed-by-value.html

2, https://segmentfault.com/a/1190000015246182

3, https://studygolang.com/articles/9876