GO Language Interview Essence - What is the difference between a value receiver and a pointer receiver?

method

Methods can add new behaviors to user-defined types. The difference between it and a function is that a method has a receiver. If you add a receiver to a function, it becomes a method. The recipient can be 值接收者or be 指针接收者.

When calling a method, the value type can either call 值接收者the method or 指针接收者the method called; the pointer type can both call 指针接收者the method or 值接收者the method called.

That is to say, no matter what type the receiver of the method is, values ​​and pointers of this type can be called without strictly conforming to the type of the receiver.

Let’s see an example:

package main

import "fmt"

type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func (p *Person) growUp() {
	p.age += 1
}

func main() {
	// qcrao 是值类型
	qcrao := Person{age: 18}

	// 值类型 调用接收者也是值类型的方法
	fmt.Println(qcrao.howOld())

	// 值类型 调用接收者是指针类型的方法
	qcrao.growUp()
	fmt.Println(qcrao.howOld())

	// ----------------------

	// stefno 是指针类型
	stefno := &Person{age: 100}

	// 指针类型 调用接收者是值类型的方法
	fmt.Println(stefno.howOld())

	// 指针类型 调用接收者也是指针类型的方法
	stefno.growUp()
	fmt.Println(stefno.howOld())
}

The output of the above example is:

18
19
100
101

After calling a function, its value changes growUpregardless of whether the caller is a value type or a pointer type .Age

In fact, when the receiver types of types and methods are different, the compiler actually does some work behind the scenes and presents it in a table:

- value receiver pointer receiver
value type caller The method will use a copy of the caller, similar to "pass by value" Use a reference to the value to call the method. In the above example, qcrao.growUp()it is actually(&qcrao).growUp()
Pointer type caller The pointer is dereferenced into a value, which in the above example stefno.howOld()is actually(*stefno).howOld() In fact, it is also "passing by value". The operations in the method will affect the caller. It is similar to passing parameters by pointer. A copy of the pointer is copied.

Value receivers and pointer receivers

As mentioned before, no matter whether the receiver type is a value type or a pointer type, it can be called through a value type or a pointer type. This actually works through syntactic sugar.

Let’s talk about the conclusion first: Implementing a method where the receiver is a value type is equivalent to automatically implementing a method where the receiver is a pointer type; and implementing a method where the receiver is a pointer type will not automatically generate a method corresponding to the receiver being a value type. .

Take a look at an example and you will understand completely:

package main

import "fmt"

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (p Gopher) code() {
	fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
	fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
	var c coder = &Gopher{"Go"}
	c.code()
	c.debug()
}

The above code defines an interface coder, which defines two functions:

code()
debug()

Then a structure is defined Gopher, which implements two methods, a value receiver and a pointer receiver.

Finally, we maincalled the two defined functions through the interface type variables in the function.

Run it, the result is:

I am coding Go language
I am debuging Go language

But if we mainchange the first statement of the function:

func main() {
	var c coder = Gopher{"Go"}
	c.code()
	c.debug()
}

Run it and get an error:

src/main.go:23:6: cannot use Gopher literal (type Gopher) as type coder in assignment:
	Gopher does not implement coder (debug method has pointer receiver)

Do you see the difference between these two codes? The first time was &Gopherassigned to coder; the second time was Gopherassigned to coder.

The second error was that Gopherit was not implemented coder. It's obvious, because Gopherthe type does not implement debugthe method; on the surface, *Gopherthe type does not implement codethe method, but because Gopherthe type implements codethe method, *Gopherthe type automatically has codethe method.

Of course, the above statement has a simple explanation: the receiver is a method of pointer type, and it is likely that the properties of the receiver will be changed in the method, thereby affecting the receiver; and for a method whose receiver is a value type, in The method has no effect on the receiver itself.

Therefore, when you implement a method whose receiver is a value type, you can automatically generate a method whose receiver is the corresponding pointer type, because neither will affect the receiver. However, when a method whose receiver is a pointer type is implemented, if a method whose receiver is a value type is automatically generated at this time, the original expectation of changing the receiver (implemented through a pointer) cannot be realized now, because the value type will generate A copy that doesn't really affect the caller.

Finally, just remember this:

If you implement a method whose receiver is a value type, you will implicitly implement a method whose receiver is a pointer type.

When are they used?

If the receiver of the method is a value type, regardless of whether the caller is an object or an object pointer, what is modified is a copy of the object and does not affect the caller; if the receiver of the method is a pointer type, the caller modifies the object pointed to by the pointer. itself.

Reasons to use pointers as receivers of methods:

  • Methods can modify the value pointed to by the receiver.
  • Avoid copying the value each time the method is called, which is more efficient when the type of the value is a large structure.

Whether to use a value receiver or a pointer receiver is not determined by whether the method modifies the caller (that is, the receiver), but should be based on the type 本质.

If the type has "primitive nature", that is to say, its members are all primitive types built into the Go language, such as strings, integer values, etc., then define methods of the value receiver type. Like built-in reference types, such as slice, map, interface, channel, these types are quite special. When you declare them, you actually create one. For them, it is also a headermethod to directly define the value receiver type. In this way, when calling the function, these types are copied directly header, and headerthe type itself is designed for copying.

If the type has a non-primitive nature and cannot be safely copied, the type should always be shared, then define methods on pointer receivers. For example, the file structure (struct File) in the go source code should not be copied, there should be only one copy 实体.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132795614