Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

In the following code, the value type address implements the interface FMt.stringer as the receiver, and its pointer type Address implements the interface FMt.stringer as well.

type address struct { province string city string } func (addr address) String() string{ return fmt.Sprintf("the addr is  %s%s",addr.province,addr.city) }Copy the code

In the following code example, I define the variable add of value type and pass it and its pointer &add as arguments to the function printString to find that both work and the code runs successfully. This also proves that when a value type implements an interface as a receiver, its pointer type also implements that interface.

Func main() {add := address{type: "", city: PrintString (add) printString(&add)} func printString(s FMT.Stringer) {FMT.Println(s.string ())}Copy the code

Based on this conclusion, let’s go ahead and see if we can define a pointer to the interface. As follows:

Var FMT.Stringer =address{type: "",city:" "} printString(si) :=&si printString(sip)Copy the code

In this example, because the type address already implements the interface fmt.stringer, its value can be assigned to the variable si, and SI can also be passed as an argument to the function printString.

You can then use operations like SIP :=&si to get a pointer to the interface, and that’s fine. You can’t pass sip to the interface as an argument to printString. The Go compiler will give you the following error message:

./main.go:42:13: cannot use sip (type *fmt.Stringer) as type fmt.Stringer in argument to printString:

	*fmt.Stringer is pointer to interface, not interface
Copy the code

To sum up: while a pointer to a specific type can implement an interface, a pointer to an interface can never implement that interface.

So you almost never need a pointer to an interface, forget about it, don’t let it appear in your code.

Modify the parameters

Suppose you define a function and modify its parameters so that the caller can retrieve the value of your latest modification. I’m still using the person construct I used in previous classes as an example:

Func main() {p:=person{name: "",age: Println("person name:",p.name,",age:",p.age)} func modifyPerson(p person) {p.name = "p.age"  = 20 } type person struct { name string age int }Copy the code

In this example, I expect the modifyPerson function to change the name in parameter P to Li Si and the age to 20. There is no error in the code, but run it and you should see the following printout:

Person Name: Zhang SAN, Age: 18Copy the code

Why is it still Three and eighteen? Let me try using a pointer argument, because we already saw in the last class that we can modify the object data to be pointed to by a pointer, as follows:

Func modifyPerson(p *person) {p.name = "p.age" p.age = 20}Copy the code

This code is used to accommodate changes to pointer parameters, change the received parameters to pointer parameters, and pass a pointer through the ampersand address when the modifyPerson function is called. Now run the program again and you should see the expected output, as follows:

Person name: Li Si, Age: 20Copy the code

Value types

In the previous section, THE ordinary variable P I defined is of type person. In the Go language, person is a value type, and the pointer fetched by &p is of type *person, or pointer type. So why can’t the value type be changed in parameter passing? It also starts with memory.

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

Func main() {p:=person{name: "",age: 18} FMT.Printf("main: %p\n",&p) modifyPerson(p) fmt.Println("person name:",p.name,",age:",p.age)} func modifyPerson(p person) { Printf("modifyPerson function: p memory address is %p\n",&p) p.name = "" p.age = 20}Copy the code

Here, I changed the original sample code to print the memory address of the variable P in the main function and the parameter P in the modifyPerson function. Run the above program, you can see the following result:

ModifyPerson function: P memory address is 0xC0000a6040 Person name: Joe,age: 18Copy the code

You’ll notice that they all have different memory addresses, which means that the parameter P in modifyPerson is not the same as the variable P in main, which is why we’re modifying p in modifyPerson, However, after printing in the main function, it was found that there was no reason for the modification.

The reason for this is that function arguments in the Go language are passed by value. Value passing refers to passing a copy of the original data rather than the original data itself.

For example, when modifyPerson is called to pass the variable P, Go will copy a P and place it in a new memory, so that the memory address of the new P is different, but the name and age are the same. It’s still John and 18. That’s what a copy means. The variables have the same data, but they have different memory locations.

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

Pointer types

The value of a pointer type variable is the memory address corresponding to the data. Therefore, in the principle of passing function parameters as values, the copied value is also the memory address. Now to modify the above example, the modified code looks like this:

Func main() {p:=person{name: "",age: 18} FMT.Printf("main: Println("person name:",p.name,",age:",p.age)} func modifyPerson(p *person) {%p\n",&p) modifyPerson(&p) fmt.Println("person name:",p.name,",age:",p.age)} func modifyPerson(p *person) { Printf("modifyPerson function: p = %p\n",p) p.name = "" p.age = 20}Copy the code

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

ModifyPerson function: p memory address is 0xC0000a6020 person name: Li4,age: 20Copy the code

So a pointer parameter can always modify the original data, because when the parameter is passed, the memory address is passed.

Tip: values pass Pointers, which are also memory addresses. The block of memory where the original data can be found through the memory address, so modifying it is also equivalent to modifying the original data.

Reference types

Here are the reference types, including Map and chan.

map

For the example above, if I don’t use a custom Person structure and pointer, can I use a map to change it?

Let me try it out, as follows:

Func main() {m:=make(map[string]int) m[" FMT "] = 18 FMT.Println(" FMT ",m[" FMT "]) modifyMap(m) Func modifyMap(p map[string]int) {p[" FMT.Println(" FMT.Println(" FMT.Println(" FMT.Println(" FMT.Println(" FMT.Println ",m[" FMT "])} func modifyMap(p map[string]int) {p[" FMT.Println ",m[" FMT "])}Copy the code

I define a map[string]int variable m, store a key-value pair with a Key of 18, and pass this variable m to modifyMap. All the modifyMap function does is change the value to 20. Now run the code and print the output to see if the modification succeeded. The result is as follows:

Snow ruthless age is 18 snow ruthless age is 20Copy the code

And it worked. Do you have a lot of doubts? No pointer was used, only parameters of map type were used. According to the value transfer principle of Go language, map in modifyMap function is a copy, how can it be modified successfully?

To answer this question, start with make, a function built into the Go language. In Go, any code that creates a map (literal or make) ends up calling the Runtime.makemap function.

Tip: Create a map as a literal or make function and convert it to a makemap call. The Go compiler does this automatically for us.

As you can see from the following code, the makemap function returns a *hmap, that is, 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{// omit irrelevant code}Copy the code

Because the map type of the Go language is essentially *hmap, the modifyMap(p map) function I just defined is actually modifyMap(P *hmap) according to the substitution principle. Is this the same as the pointer type parameter call described in the previous section? This is why raw data can be modified with a parameter of type map, which is essentially a pointer.

To further verify that the map created is a pointer, I modify the example above to print the memory address for the maptype variables and parameters, as shown in the code below:

Func main(){// omit other unmodified code fmt.printf ("main function: Func modifyMap(p map[string]int) {fmt.Printf("modifyMap function: p with %p\n",p) // omit other unmodified code}Copy the code

The two lines of printed code in this example are new, the rest of the code is not modified and will not be posted here. Run the modified program and you should see the following output:

ModifyMap function: P memory address is 0xC000060180 Flying snow ruthless age is 20Copy the code

As you can see from the output, they have exactly the same memory address, so you can modify the original data to get an age of 20. And when I print Pointers, I use the variables m and p directly, and I don’t use the ampersand, because they are Pointers, so I don’t need to use the ampersand anymore.

So in this case, Go saves us Pointers by wrapping up make functions or literals, making it easier to use maps. Syntax candy, it’s an old programming tradition.

Note: the map here can be understood as a reference type, but it is essentially a pointer that can be called a reference type. When a parameter is passed, it is still passed by value, not by reference as in other programming languages.

The difference between values, references, and Pointers is verified for reference only.