Uncle is a visual cleanliness person, after writing the article, I found that the character limit exceeded the gold digging, very sad~~ if you want to see better visual effects or pictures, you can see the public account article:portal

Golang slicing parameters are passed by reference or value. Golang slicing parameters are passed by reference or 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.

The story begins a long, long time ago……

The illusion of slice transfer

Legend has it that a long time ago there was an uncle in the yamen as a programmer, specializing in dealing with the folk who have no problems.

One day, the yamen arrested two grass people two dogs and lai Fu. The uncle asked and found out that these two people were doing it because they were arguing about a problem! Two dog leg was interrupted a, lai Fu leg hair were bitten light, very miserable. How come, uncle, you have to ask what the problem is?

It turned out that they were arguing about whether slicing as a parameter in the Golang function should be passed by value or by reference. Two dogs insist that is pass value, arrogance is very arrogant; Ruffles quote, saying that the ancestral secrets are written in black paper and white paper, it can’t be wrong.

Uncle in the mind muttered: I rub (Dan), this problem I also can’t ah! Do how? If this case is not solved, in case the leader asks for tea, the KPI of this quarter will be fed to the dog again!

Something had to be done, the uncle thought and decided to go to a fortune-teller to find out. Uncle spent ten dollars to find ten fortune-tellers to solve the problem. Eight of them thought it was a quote, and the other two thought it was a value. In the principle of majority rule, is the answer quoting? Uncle secretly pleased ~

On the way back, the more the uncle thought, the more he felt wrong. Anyway, I was also working in the government office. Serving the people wholeheartedly is my working purpose. The uncle kicked the coke bottle tens of meters away and vowed not to let his professional integrity be broken.

Back to the yamen apartment, uncle opened his laptop, relying on god is better than relying on their own, simply have a try, so uncle opened vscode, write down the following test code:

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

Compile run print output:

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

The changeSlice function has changed the slice function, and the slice variable has changed as well. Is this guy really quoting? The uncle murmured to his conscience. Faithful to his conscience, the uncle typed the following example on the mechanical keyboard:

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

The above code is roughly the same as the previous example, except this time the address of the slice is printed out. The address of the slice parameter should be the same as the address of the slice variable in the main function. Based on this conclusion, the uncle runs the code again, this time with an unexpected discovery:

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

What??? The uncle froze for two seconds. The address of the slice variable in main is not the same as the address of the slice variable in main. Why is the address different?

The uncle took out the root lotus King from his trouser pocket, lit the fire, slowly inhale two sips, eyebrow lock thought: this goods really is a bit of a problem!

Let the slices “show their true shape”

Slice passed as a function parameter must not be passed as a reference, otherwise the address of the printed parameter in the function should be the same as the address of the sliced variable outside. The uncle shook his ash and took two puffs, thinking: If it’s not a quote, is it a value? If the value is passed, then why does changing a slice in changeSlice directly affect the slice variable in main? That doesn’t make any sense again. Are they passing Pointers? All of a sudden value transfer, pointer transfer, reference transfer these concepts to the uncle confused, uncle think it is necessary to go to the technical school when the notes out to confirm these concepts.

The uncle went to the corner of the wall and found a parchment book with a moldy and yellow cover. He blew on the dust two centimeters thick.

Here’s something new. The notes go like this:

To highlight

Technical school official website clear statement: Go inside function parameters only value transfer a way, details can refer to technical school documents: golang.org/ref/spec#Ca…

What is Value passing (value passing),Pointer passedPass 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. In this way, the actual parameters are not affected by changes in the function.
  • Pointer passedA parameter is a pointer to the address of the argument. When we operate on the pointer to the parameter, we operate on the argument itself.
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

Run output:

value: 0xc000016088
addr: 0xc00000e028Value within a function:0xc000016088Function addr:0xc00000e038The value of a has been modified. The new value is:1
Copy the code

The above code defines a variable A and stores the address in the pointer variable PA. Since only value is passed in Go, the parameter p passed to pa is actually a copy of the pointer variable PA. They will have different addresses, but the value of the two is the same. 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.

As you can see from the output above, this is a copy of the pointer, pa and P have different addresses, are two different Pointers, but both have the same value.

  • Pass a reference (pass a reference): Indicates that the address of the actual parameter is passed to the function when the function is called. The modification of the parameter in the function affects the actual parameter.Since reference passing does not exist in Go, folk rumors that reference passing in Go is for Slice, Map, and channel are wrong.

Since there is no reference to pass in Go, why do changes to the parameters of slice, map, and channel affect the actual parameters? Do all three data types follow the same path as Pointers? It’s a pointer at creation time, right?

We all know that map and chan are created using make function, the source code provided by technical school is as follows:

/ / source path: / usr/local/go/SRC/runtime/map. Go
// makemap implements a Go map creation make(map[k]v, hint)
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h ! = nil, the map can be created directly in h.
// If bucket ! = nil, bucket can be used as the first bucket.
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
    // omit extraneous code
}
Copy the code
/ / source path: / usr/local/go/SRC/runtime/chan. Go
func makechan(t *chantype, size int64) *hchan {
    // omit extraneous code
}
Copy the code

Sure enough, for both map and chan data types, the make function returns a pointer at creation time. What about the slice type? In fact, slice is one of the odder data types, so let’s take a look at the structure of slice:

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.

For example, an array arr := [5]int{0,1,2,3,4} generates a slice slice := arr[1:4], resulting in the following slice := arr[1:4]

Let’s look at an 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.

Sometimes we create slices with make, and actually go creates an anonymous array underneath. If sliced from a new slice, the newly created slices share the underlying anonymous array.

func main(a) {
    slice := make([]int.5)
    for i:=0; i<len(slice); i++{ slice[i] = i } fmt.Printf("slice %v \n", slice)

    slice2 := slice[1:4]
    fmt.Printf("slice %v, slice2 %v \n", slice, slice2)

    slice[1] = 1111
    fmt.Printf("slice %v, slice2 %v \n", slice, slice2)
}
Copy the code

Printout:

slice [0 1 2 3 4] 
slice [0 1 2 3 4], slice2 [1 2 3] 
slice [0 1111 2 3 4], slice2 [1111 2 3]
Copy the code

If you print slice directly with the fmt.printf () function, you’ll notice that the memory address of slice can be printed directly with %p, but it’s important to note that printing slice directly and printing &slice are two different things. The former prints the address where the pointer in the slice structure points to the array element, while the latter prints the address where the slice is stored

func main(a) {
    arr := [5]int{0.1.2.3.4}
    slice := arr[0:3]
    fmt.Printf("slice pointer addr: %p\n", slice)
    fmt.Printf("arr[0] addr: %p\n", &arr[0])}Copy the code

Printout:

slice pointer addr: 0xc0000ac030
arr[0] addr: 0xc0000ac030
Copy the code

As we can see from the previous content, there is only one way to pass the value of the function in Go, which is a copy. Pointer passing is essentially a copy of a pointer. Parameters and arguments have different addresses, but their values are the same, so modifying parameters can affect the arguments. We call the types passed by Pointers reference types.

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.

When slice is passed into a function as a parameter, it is essentially a copy of the slice value, but the slice contains data of reference type (pointer array to the underlying array). Therefore, modifying the elements stored in the function’s parameters can achieve the purpose of modifying the elements stored in the slice argument.

When he saw this, the uncle swallowed his mouth water, the words above his head: when did I ever write this shit? Suck on the Hibiscus King quickly.

No matter what time to write, this note is a point to wake up the uncle, the original is so one and the same thing, two dogs and lai Fu these two simple and simple fight is in vain.

Does modifying a parameter slice inside a function necessarily affect external arguments

Seeing this, the uncle felt that the strange knowledge points were again under the bag, so he was eager to write up the test demo:

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[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 an element to the slice and then changes the value of the second element of the slice.

After writing, the uncle has to straighten up ah:

  • Slice: [0 1] and this slice contains the address to which the pointer points. What does the pointer point toThe underlying array arr is: [0, 1]
  • Then call the function changeSlice, which appends elements to the slice, so the pointer to the slice S points toThe underlying array arr becomes [0, 1, 3], and then change the value of the second element of the slice to 111, soThe underlying array arR is changed to [0, 111, 3]
  • After executing the changeSlice function, print the external slice at last. Since the start and end positions of the slice pointer to the underlying array are the 0th unit (ARr [0]) and the 1st unit (ARr [1]), the final printed slice will be [0, 111].
  • When printing the slice address, since the parameter slice S is a copy of the argument slice, their values are the same, so the slice address printed above should be the same

After whispering, excited heart trembling hands, uncle can’t wait to run the code:

slice: [0 1], addr: 0xc000138000 
func s: [0, 3] 111,addr: 0xc000138000 
slice: [0 111], addr: 0xc000138000
Copy the code

When he saw that the output was the same as he had predicted, he looked proud

As the saying goes, life must be full of joy, do not make the golden bottle empty to the moon, since you know its routine, how to come, just play, just change! So the uncle made another change in the above code:

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

This time the uncle adds s = append(s, 4) to the changeSlice function to continue adding elements to the slice. Uncle thought, the final output as the above, no problem! So I happily ran the code:

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

Seeing the output, the uncle froze and gulped twice. What the hell? Isn’t the underlying array shared? Why can’t you change it? Even the address is different. Look at the calendar, today is not July 14 ah, is appropriate to write code ah!

How does that work? The uncle could not understand, so his eyes went back to the worn note. The note went on:

Note that 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.

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 to slice.

If the array is out of bounds when slicing append an element, append does something like this:

  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.
  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:

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:

slice Slice literal Space left in array The length of the capacity
s[1:3] [1, 2] 2 2 4
s[1:1] [] 4 0 4
s[4:4] [] 1 0 1
s[4:5] [4] 0 1 1

Understanding may refer to: studygolang.com/articles/98…

After executing s = append(s, 4), the slice is expanded. Slice S refers to the new underlying array after expansion. Therefore, modifying the element of slice S again will not affect the external slice.

homework

The uncle stared at the ceiling for five minutes as his brain circuits frantically sorted out the notes he had just read. After a long struggle, the uncle finally understood. Immediately write the following code:

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

After writing, uncle heart secretly pleased: tomorrow two dogs and lai Fu if not up, each fine five dollars, as if fortune telling money to come back, ha ha ~ (what will the final output results? Please write your answers in the comments.)

So the next day……

The above story is pure fiction, hope you take its essence can.

Follow public accountUncle said codeGet more dry goods, and that’s it for today’s sharing. Let’s take a look

Reference:

1, www.flysnow.org/2018/02/24/…

2, segmentfault.com/a/119000001…

3, studygolang.com/articles/98…