Go parameter passing: difference between value, reference and pointer?

Change parameters


Suppose you define a function and modify the parameters in the function, so that the caller can get your latest modified value through the parameters. I still use the person structure used in the previous course as an example, as follows:

func main() {

   p:=person{name: "张三",age: 18}

   modifyPerson(p)

   fmt.Println("person name:",p.name,",age:",p.age)

}

func modifyPerson(p person)  {

   p.name = "李四"

   p.age = 20

}

type person struct {

   name string

   age int

}

In this example, I expect to change the name in the parameter p to Li Si and the age to 20 through the modifyPerson function. The code has no errors, but run it and you will see the following printout:

person name: 张三 ,age: 18

Why is it still Zhang San and 18? I try to replace it with a pointer parameter. I can modify the pointed object data through the pointer, as shown below:

modifyPerson(&p)

func modifyPerson(p *person)  {

   p.name = "李四"

   p.age = 20

}

These codes are used to satisfy the modification of pointer parameters, to change the received parameters to pointer parameters, and to pass a pointer through the & address character when calling the modifyPerson function. Now run the program again, and you should see the expected output, like this:

person name: 李四 ,age: 20

value type


In the subsection above, I defined a normal variable p of type person. In the Go language, person is a value type, and the pointer obtained by &p is of type *person , which is a pointer type. So why can't value types be modified in parameter passing? This also starts with memory.

We already know that the value of a variable is stored in memory, and memory has a number called a memory address . So if you want to modify the data in the memory, you must find this memory address . Now, let me compare the memory addresses of value type variables inside and outside the function as follows:

func main() {

   p:=person{name: "张三",age: 18}

   fmt.Printf("main函数:p的内存地址为%p\n",&p)

   modifyPerson(p)

   fmt.Println("person name:",p.name,",age:",p.age)

}

func modifyPerson(p person)  {

   fmt.Printf("modifyPerson函数:p的内存地址为%p\n",&p)

   p.name = "李四"

   p.age = 20

}

Among them, I changed the original sample code to print out the memory address of the variable p in the main function and the memory address of the parameter p in the modifyPerson function. Running the above program, you can see the following results:

main函数:p的内存地址为0xc0000a6020

modifyPerson函数:p的内存地址为0xc0000a6040

person name: 张三 ,age: 18

You will find that their memory addresses are different, which means that the parameter p modified in the modifyPerson function is not the same as the variable p in the main function, which is also the parameter p modified in the modifyPerson function, but in the main function After printing, it was found that there was no reason for the modification.

The reason for this result is  that the function parameters in the Go language are passed by value .  Pass-by-value refers to passing a copy of the original data, not the original data itself.

                              (The main function calls the modifyPerson function to pass the parameter memory diagram)

Taking the modifyPerson function as an example, when calling the modifyPerson function to pass the variable p, the Go language will copy a p and put it in a new memory, so that the memory address of the new p is different from the original , but the name and age inside It's the same, or Zhang San and 18. This is the meaning of copy , the data in the variable is the same, but the memory address stored is different.

In addition to struct, there are floats, integers, strings, booleans, arrays, which are all value types.

pointer type


The value stored by the pointer type variable is the memory address corresponding to the data , so under the principle that function parameter transfer is by value, the copied value is also the memory address. Now slightly modify the above example, the modified code is as follows:

func main() {

   p:=person{name: "张三",age: 18}

   fmt.Printf("main函数:p的内存地址为%p\n",&p)

   modifyPerson(&p)

   fmt.Println("person name:",p.name,",age:",p.age)

}

func modifyPerson(p *person)  {

   fmt.Printf("modifyPerson函数:p的内存地址为%p\n",p)

   p.name = "李四"

   p.age = 20

}

Run this example, you will find that the printed memory address is consistent, and the data has been modified successfully, as shown below:

main函数:p的内存地址为0xc0000a6020

modifyPerson函数:p的内存地址为0xc0000a6020

person name: 李四 ,age: 20

Therefore, the parameters of the pointer type can always modify the original data, because when the parameters are passed, the memory address is passed.

Tip: The value passed is a pointer, which is also a memory address. The memory of the original data can be found through the memory address, so modifying it is equivalent to modifying the original data.

reference type


The following are reference types, including map and chan.

map

For the above example, if I don't use a custom person structure and pointer, can I use map to achieve the purpose of modification?

Let me try it out, as follows:

func main() {

   m:=make(map[string]int)

   m["飞雪无情"] = 18

   fmt.Println("飞雪无情的年龄为",m["飞雪无情"])

   modifyMap(m)

   fmt.Println("飞雪无情的年龄为",m["飞雪无情"])

}

func modifyMap(p map[string]int)  {

   p["飞雪无情"] =20

}

I define a variable m of type map[string]int, store a key-value pair whose Key is Feixueruheng and Value is 18, and then pass this variable m to the function modifyMap. What the modifyMap function does is modify the corresponding value to 20. Now run this code and see if the modification is successful by printing the output. The result is as follows:

飞雪无情的年龄为 18

飞雪无情的年龄为 20

The modification was indeed successful. Do you have many doubts? No pointers are used, but parameters of type map are used. According to the principle of value transfer in Go language, the map in the modifyMap function is a copy. How can the modification be successful?

To answer this question, let's start with make, a built-in function of the Go language. In Go, any code that creates a map (whether a literal or a make function) ultimately calls the runtime.makemap function.

Tip: Create a map in the form of a literal or make function, and convert it into a call to the makemap function. This conversion is automatically done by the Go language compiler for us.

As you can see from the code below, the makemap function returns a *hmap type, which means that it returns a pointer, so the map we created is actually a *hmap.

// makemap implements Go map creation for make(map[k]v, hint).

func makemap(t *maptype, hint int, h *hmap) *hmap{

  //省略无关代码

}

Because the map type of Go language is essentially *hmap , so according to the principle of replacement, the modifyMap(p map) function I just defined is actually modifyMap(p *hmap). Is this the same as the parameter call of pointer type mentioned in the previous section? This is also the reason why the original data can be modified through a parameter of type map, because it is essentially a pointer.

In order to further verify that the created map is a pointer, I modified the above example to print the memory addresses corresponding to the variables and parameters of the map type, as shown in the following code:

func main(){

  //省略其他没有修改的代码

  fmt.Printf("main函数:m的内存地址为%p\n",m)

}

func modifyMap(p map[string]int)  {

   fmt.Printf("modifyMap函数:p的内存地址为%p\n",p)

   //省略其他没有修改的代码

}

The two lines of printing code in the example are newly added, and the other codes have not been modified, so they will not be posted here. Running the modified program, you can see the following output:

飞雪无情的年龄为 18

main函数:m的内存地址为0xc000060180

modifyMap函数:p的内存地址为0xc000060180

飞雪无情的年龄为 20

As you can see from the output, their memory addresses are exactly the same, so the original data can be modified to get the result that the age is 20. And when I print the pointer, I use the variables m and p directly, and I don't use the address character &, because they are pointers, so there is no need to use & to take the address.

So here, the Go language saves us from pointer manipulation by wrapping the make function or literal, allowing us to use map more easily. In fact, it is syntactic sugar, which is an old tradition in the programming world.

Note: The map here can be understood as a reference type, but it is essentially a pointer, it can just be called a reference type. When parameters are passed, it is still pass-by-value, not the so-called pass-by-reference in other programming languages.

chan


Remember the channel we learned about in Go's concurrency module? It can also be understood as a reference type, and it is essentially a pointer.

As you can see from the source code below, the chan created is actually a *hchan, so it is the same as map in parameter passing.

func makechan(t *chantype, size int64) *hchan {

    //省略无关代码

}

Strictly speaking, the Go language does not have reference types , but we can call map and chan reference types, which is easy to understand. In addition to map and chan, functions, interfaces, and slices in the Go language can all be called reference types.

Tip: The pointer type can also be understood as a reference type.

Guess you like

Origin blog.csdn.net/qq_34556414/article/details/123395653