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? chan
、map
、slice
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 :
- machinery-go Asynchronous task queue
- Teach my sister to write a message queue
- The cache avalanche of common interview questions 、 Cache penetration 、 Cache breakdown
- Detailed explanation Context package , Just read this one !!!
- go-ElasticSearch It's enough to read this one at the beginning ( One )
- interviewer :go in for-range Have you ever used ? Can you explain the reasons for these questions
- Learn to wire Dependency injection 、cron The timing task is so simple !
- I hear you don't know jwt and swagger- I don't want to eat any more. I'll come with my practice project
- Master these Go Linguistic characteristics , Your level will improve N A class ( Two )