当前位置:网站首页>Is parameter passing in go language transfer value or reference?

Is parameter passing in go language transfer value or reference?

2020-11-08 17:23:00 dog

Preface

hello , Hello everyone , I am a asong. Today, my girlfriend asked me , Small pine nuts , You know, Go Is language parameter passing value or reference ? Oh, ha! , I was looked down upon , I have a quick operation , I told him plainly , Little girl film , It's still too tender , Listen to me in detail ~~~.​

Real parameters and shape parameters

We use go Parameters can be defined when defining methods . For example, the following methods :

func printNumber(args ...int)

there args Is the parameter . Parameters are divided into formal parameters and actual parameters in programming language .

Formal parameters : Is the parameter used when defining the function name and body , The purpose is to receive the parameters passed in when the function is called .

The actual parameter : When calling a function with parameters , There is a data transfer relationship between the main function and the called function . When calling a function in the keynote function , The arguments in parentheses after the function name are called “ The actual parameter ”.

Examples are as follows :

func main()  {
 var args int64= 1
 printNumber(args)  // args Is the actual parameter 
}

func printNumber(args ...int64)  { // What is defined here args It's a formal parameter 
    for _,arg := range args{
        fmt.Println(arg) 
    }
}

What is value transfer

Value passed , We analyze its literal meaning : It's the value that is passed . Value transfer means : Function always passes a copy of the original , A copy . Let's pass on a int Parameters of type , What is passed is actually a copy of this parameter ; Pass a parameter of type pointer , In fact, it is a copy of this pointer , Instead of the value that this pointer points to . Let's draw a picture to explain :

What is reference passing

Students who have studied other languages , You should be familiar with this reference passing , such as C++ Users , stay C++ in , Function parameters can be passed by reference . So-called reference It refers to passing the address of the actual parameter to the function when calling the function , So the changes to the parameters in the function , It will affect the actual parameters .

golang It's worth passing on

Let's write a simple example to verify :

func main()  {
 var args int64= 1
 modifiedNumber(args) // args Is the actual parameter 
 fmt.Printf(" Address of the actual parameter  %p\n", &args)
 fmt.Printf(" The changed value is   %d\n",args)
}

func modifiedNumber(args int64)  { // What is defined here args It's a formal parameter 
    fmt.Printf(" Formal parameter address  %p \n",&args)
    args = 10
}

Running results :

 Formal parameter address  0xc0000b4010 
 Address of the actual parameter  0xc0000b4008
 The changed value is   1

This just proves that go It's worth passing on , But it's not entirely certain go It's just value passing , We are writing an example to verify :

func main()  {
 var args int64= 1
 addr := &args
 fmt.Printf(" The memory address of the original pointer is  %p\n", addr)
 fmt.Printf(" Pointer to the variable addr Storage address  %p\n", &addr)
 modifiedNumber(addr) // args Is the actual parameter 
 fmt.Printf(" The changed value is   %d\n",args)
}

func modifiedNumber(addr *int64)  { // What is defined here args It's a formal parameter 
    fmt.Printf(" Formal parameter address  %p \n",&addr)
    *addr = 10
}

Running results :

 The memory address of the original pointer is  0xc0000b4008
 Pointer to the variable addr Storage address  0xc0000ae018
 Formal parameter address  0xc0000ae028 
 The changed value is   10

So through the output, we can see , This is a copy of the pointer , Because the memory address of the two pointers is different , Although the value of the pointer is the same , But it's two different pointers .

Through the graph above , We can better understand . We declared a variable args, Its value is 1, And his memory address is 0xc0000b4008, Through this address , We can find the variable args, This address is the variable args The pointer to addr. The pointer addr It's also a pointer type variable , It also needs memory for it , What's its memory address ? yes 0xc0000ae018. When we pass pointer variables addr to modifiedNumber Function , Is a copy of the pointer variable , So the pointer variable of the new copy addr, Its memory address has changed , It's new 0xc0000ae028. therefore , Whether it's 0xc0000ae018 still 0xc0000ae028, We can all call it a pointer , They point to the same pointer 0xc0000b4008, This 0xc0000b4008 And it points to variables args, That's why we can change variables args Value .

Through the above analysis , We can be sure of go It's value passing , Because we are modifieNumber The memory address printed out in the method has changed , So it's not reference passing , It's a real blow, brothers , irrefutable evidence ~~~. wait , It seems that something has been left behind , Well said go Only value passing in , Why? chanmapslice Type passing can change the value in it ? Bai is in a hurry , Let's verify in turn .

slice Is it also value passing ?

Let's start with a piece of code :

func main()  {
 var args =  []int64{1,2,3}
 fmt.Printf(" section args The address of : %p\n",args)
 modifiedNumber(args)
 fmt.Println(args)
}

func modifiedNumber(args []int64)  {
    fmt.Printf(" The address of the parameter slice  %p \n",args)
    args[0] = 10
}

Running results :

 section args The address of : 0xc0000b8000
 The address of the parameter slice  0xc0000b8000 
[10 2 3]

Wow , What's going on? , The speed of light hits the face , How come the address is the same ? And the value has been changed ? What's going on? , How to explain , You bastard , Cheat my feelings ... Sorry to go to the wrong set . Let's move on to this question . We don't use it here & Symbol address conversion , Just put slice The address is printed out , Let's add a line of code to test :

func main()  {
 var args =  []int64{1,2,3}
 fmt.Printf(" section args The address of : %p \n",args)
 fmt.Printf(" section args The address of the first element : %p \n",&args[0])
 fmt.Printf(" Direct slice args Address fetch %v \n",&args)
 modifiedNumber(args)
 fmt.Println(args)
}

func modifiedNumber(args []int64)  {
    fmt.Printf(" The address of the parameter slice  %p \n",args)
    fmt.Printf(" Shape parameter slice args The address of the first element : %p \n",&args[0])
    fmt.Printf(" Slice the parameters directly args Address fetch %v \n",&args)
    args[0] = 10
}

Running results :

 section args The address of : 0xc000016140 
 section args The address of the first element : 0xc000016140 
 Direct slice args Address fetch &[1 2 3] 
 The address of the parameter slice  0xc000016140 
 Shape parameter slice args The address of the first element : 0xc000016140 
 Slice the parameters directly args Address fetch &[1 2 3] 
[10 2 3]

Through this example we can see , Use & The operator represents slice The address of is invalid , And use %p The output memory address and slice The address of the first element of is the same , So why does this happen ? Could it be fmt.Printf What special treatment does the function do ? Let's take a look at the source code :

fmt package ,print.go Medium printValue This method , Intercept the key part , because `slice` Is also a reference type , So it's going to get into this `case`:
case reflect.Ptr:
        // pointer to array or slice or struct? ok at top level
        // but not embedded (avoid loops)
        if depth == 0 && f.Pointer() != 0 {
            switch a := f.Elem(); a.Kind() {
            case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
                p.buf.writeByte('&')
                p.printValue(a, verb, depth+1)
                return
            }
        }
        fallthrough
    case reflect.Chan, reflect.Func, reflect.UnsafePointer:
        p.fmtPointer(f, verb)

p.buf.writeByte('&') This line of code is why we use & Print address output results with & The voice of . Because what we're going to print is a slice type , Will call p.printValue(a, verb, depth+1) Recursively retrieve the contents of the slice , Why are the slices printed [] Surrounded by , Let me see printValue The source code of this method :

case reflect.Array, reflect.Slice:
// Omitted code 
} else {
            p.buf.writeByte('[')
            for i := 0; i < f.Len(); i++ {
                if i > 0 {
                    p.buf.writeByte(' ')
                }
                p.printValue(f.Index(i), verb, depth+1)
            }
            p.buf.writeByte(']')
        }

This is the top fmt.Printf(" Direct slice args Address fetch %v \\n",&args) Output Direct slice args Address fetch &[1 2 3] Why . This problem has been solved , Let's take a look at using %p The output memory address and slice The address of the first element of is the same . In the source code above , There is such a line of code fallthrough, Represents the next fmt.Poniter It will also be carried out , Let me take a look at the source code :

func (p *pp) fmtPointer(value reflect.Value, verb rune) {
    var u uintptr
    switch value.Kind() {
    case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
        u = value.Pointer()
    default:
        p.badVerb(verb)
        return
    }
......  Omitted code 
// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0.  If the slice is empty but non-nil the return value is non-zero.
 func (v Value) Pointer() uintptr {
    // TODO: deprecate
    k := v.kind()
    switch k {
    case Chan, Map, Ptr, UnsafePointer:
        return uintptr(v.pointer())
    case Func:
        if v.flag&flagMethod != 0 {
 .......  Omitted code 

Here we can see that there is a note above :If v's Kind is Slice, the returned pointer is to the first. Translated into Chinese is if it is slice type , return slice The address of the first element in this structure . This explains exactly why fmt.Printf(" section args The address of : %p \\n",args) and fmt.Printf(" The address of the parameter slice %p \\n",args) The printed address is the same , because args Is a reference type , So they all went back slice The address of the first element in this structure , Why are these two slice The first element has the same structure , This is to say slice The underlying structure of .

Let's see slice The underlying structure :

//runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

slice It's a structure , His first element is a pointer type , This pointer points to the first element of the underlying array . So when it's slice When it comes to type ,fmt.Printf Return is slice The address of the first element in this structure . At the end of the day , And then it's pointer processing , It's just that the pointer is slice The memory address of the first element in .

Said so much , Finally, let's make a conclusion , Why? slice It's also worth passing on . The reason for the transfer of reference type can modify the data of the original content , This is because the pointer of the reference type is used by default at the bottom level , But it also uses a copy of the pointer , It's still value passing . therefore slice Passing is a copy of the pointer to the first element , because fmt.printf The reason is that the printed address is the same , It gives people a sense of confusion .

map Is it also value passing ?

map and slice It's the same kind of confusing behavior , hum , Cinderella .map We can modify its content in a way that , And it has no obvious pointer . Take this example :

func main()  {
    persons:=make(map[string]int)
    persons["asong"]=8

    addr:=&persons

    fmt.Printf(" original map The memory address of is :%p\n",addr)
    modifiedAge(persons)
    fmt.Println("map Value modified , The new value is :",persons)
}

func modifiedAge(person map[string]int)  {
    fmt.Printf(" Received in function map The memory address of is :%p\n",&person)
    person["asong"]=9
}

Take a look at the running results :

 original map The memory address of is :0xc00000e028
 Received in function map The memory address of is :0xc00000e038
map Value modified , The new value is : map[asong:9]

Take a meow first , Oh dear , The address of actual parameter and formal parameter is different , It should be value passing , wait ....map How has the value been modified ? A look of doubt .....

To solve our doubts , Let's start with the source code , Take a look at the principle :

//src/runtime/map.go
// makemap implements Go map creation for 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 h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
    mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
    if overflow || mem > maxAlloc {
        hint = 0
    }

    // initialize Hmap
    if h == nil {
        h = new(hmap)
    }
    h.hash0 = fastrand()

From the above source code , We can see that , Use make The function returns a hmap Pointer to type *hmap. Back to the example above , our func modifiedAge(person map[string]int) function , In fact, it is equal to func modifiedAge(person *hmap), In fact, when it is used as a passing parameter, a copy of the pointer is used for passing , It belongs to value passing . ad locum ,Go Language passing make function , Literal packaging , It saves us the operation of pointer , So we can use it more easily map. there map Can be understood as a reference type , But remember that the reference type is not a pass reference .

chan Is it value passing ?

The same as usual , Let's look at an example :

func main()  {
    p:=make(chan bool)
    fmt.Printf(" original chan The memory address of is :%p\n",&p)
    go func(p chan bool){
        fmt.Printf(" Received in function chan The memory address of is :%p\n",&p)
        // Simulation takes time 
        time.Sleep(2*time.Second)
        p<-true
    }(p)

    select {
    case l := <- p:
        fmt.Println(l)
    }
}

Let's take a look at the running results :

 original chan The memory address of is :0xc00000e028
 Received in function chan The memory address of is :0xc00000e038
true

What's the matter with this , The address of actual parameter and formal parameter is different , But how does this value come back , What about good value passing ? Bai is in a hurry , Iron seed , We're like analysis map like that , Let's analyze chan. First look at the source code :

// src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
    elem := t.elem

    // compiler checks this but be safe.
    if elem.size >= 1<<16 {
        throw("makechan: invalid channel element type")
    }
    if hchanSize%maxAlign != 0 || elem.align > maxAlign {
        throw("makechan: bad alignment")
    }

    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    if overflow || mem > maxAlloc-hchanSize || size < 0 {
        panic(plainError("makechan: size out of range"))
    }

From the above source code , We can see that , Use make The function returns a hchan Pointer to type *hchan. It's not about map There is a truth , Back to the example above , Actually our fun (p chan bool) And fun (p *hchan) It's the same , In fact, when it is used as a passing parameter, a copy of the pointer is used for passing , It belongs to value passing .

Is it here , It's basically certain that go Is the value passed ? The last one is not tested , That's it struct, Let's finally verify struct.

struct It's value passing

you 're right , I'll start with the answer ,struct It's value passing , If you don't believe it, look at this example :

func main()  {
    per := Person{
        Name: "asong",
        Age: int64(8),
    }
    fmt.Printf(" original struct The address is :%p\n",&per)
    modifiedAge(per)
    fmt.Println(per)
}

func modifiedAge(per Person)  {
    fmt.Printf(" Received in function struct The memory address of is :%p\n",&per)
    per.Age = 10
}

We found that , We defined it ourselves Person type , When a function passes a parameter, it is also a value transfer , But its value is (Age Field ) It hasn't been modified , We want to change it to 10, It turns out that the final result is still 8.

In conclusion

My brothers, I'm really impressed ,go It's value passing , What can be confirmed is Go All parameters in a language are value passing ( Pass value ), It's all a copy , a copy . Because the copied content is sometimes non referential (int、string、struct Such as these ), In this way, the original content data cannot be modified in the function ; There are reference types ( The pointer 、map、slice、chan Such as these ), In this way, the original content data can be modified .

Whether the original content data can be modified , Passing value 、 There is no necessary relationship between biography and quotation . stay C++ in , It is certainly possible to modify the original content data , stay Go In language , Although only the transmission value , But we can also modify the original content data , Because the parameter is a reference type .

Some of my friends will be here or confused , Because you think of reference type and pass reference as a concept , These are two concepts , Bear in mind !!!

Let's test you with a question

Welcome to leave your answer in the comment area ~~~

Now that you all know golang Only value passing , So this code to help me analyze it , The value here can be modified successfully , Why use append There will be no expansion ?

func main() {
    array := []int{7,8,9}
    fmt.Printf("main ap brfore: len: %d cap:%d data:%+v\n", len(array), cap(array), array)
    ap(array)
    fmt.Printf("main ap after: len: %d cap:%d data:%+v\n", len(array), cap(array), array)
}

func ap(array []int) {
    fmt.Printf("ap brfore:  len: %d cap:%d data:%+v\n", len(array), cap(array), array)
  array[0] = 1
    array = append(array, 10)
    fmt.Printf("ap after:   len: %d cap:%d data:%+v\n", len(array), cap(array), array)
}

Postscript

All right. , This is the end of this article , See you next time ~~. I hope it worked for you , What's wrong is welcome to point out , You can add my golang Communication group , Let's learn and communicate .

At the end, I will send you a small welfare , I've been watching it lately [ Microservice architecture design pattern ] This book , It's very good. , I also collected a copy of PDF, If you need it, you can download it by yourself . Access method : Official account :[Golang DreamWorks ], The background to reply :[ Microservices ], Can get .

I translated a copy of GIN Chinese document , It will be maintained regularly , There is a need for small partners background reply [gin] You can download it. .

Translated a copy of Machinery Chinese document , It will be maintained regularly , The partners who need to reply backstage [machinery] Can get .

I am a asong, An ordinary procedural ape , Give Way gi Let me grow stronger together . I built one myself golang Communication group , If you need me, please add me vx, I'll pull you into the group . Welcome to your attention , See you next time ~~~

I recommend previous articles :

版权声明
本文为[dog]所创,转载请带上原文链接,感谢