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 growUp
regardless 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 main
called 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 main
change 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 &Gopher
assigned to coder
; the second time was Gopher
assigned to coder
.
The second error was that Gopher
it was not implemented coder
. It's obvious, because Gopher
the type does not implement debug
the method; on the surface, *Gopher
the type does not implement code
the method, but because Gopher
the type implements code
the method, *Gopher
the type automatically has code
the 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 header
method to directly define the value receiver type. In this way, when calling the function, these types are copied directly header
, and header
the 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 实体
.