不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用
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()) // 18
// 值类型 调用 接收者是指针类型 的方法
qcrao.growUp()
fmt.Println(qcrao.howOld()) // 19
// ----------------------
// stefno 是指针类型
stefno := &Person{age: 100}
// 指针类型 调用接收者是值类型的方法
fmt.Println(stefno.howOld()) // 100
// 指针类型 调用接收者也是指针类型的方法
stefno.growUp()
fmt.Println(stefno.howOld()) // 101
}
实际上,当类型和方法的接收者类型不同时,其实是编译器在背后做了一些工作:
值接收者 | 指针接收者 | |
---|---|---|
值类型调用者 | 方法会使用调用者的一个副本,类似于“传值” | 使用值的引用来调用方法,上例中,qcrao.growUp() 实际上是 (&qcrao).growUp() |
指针类型调用者 | 指针被解引用为值,上例中,stefno.howOld() 实际上是 (*stefno).howOld() | 实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针 |
因此,不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用,这里面实际上通过语法糖起作用的
但需要注意的是,虽然go允许解引或者取引来实现调用,我们仍然需要关注里面的安全问题:
- 指针可以调用值接收器方法,这不会产生安全问题,因为指针被解引用为值,该值被拷贝一份副本传给接收器;
- 而值调用指针接收器方法,有可能产生安全问题,因为值被取了指针,接收器获取了该指针,就可能对原对象造成破坏
go interface 对 receiver 的安全限制
go的interface就进行了严格的安全限制,不允许接口提供的方法对接口本身产生任何可能的破坏
就像我们在使用公牛插座的时候,插座本身需要保证插座提供的功能不会破坏插座本身
因此,以下的接口实现是非法的
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"} // var c coder = Gopher{"Go"} will err: Gopher does not implement coder (debug method has pointer receiver). this happens because a value calling a pointer receiver's function is unsafe
c.code()
c.debug() // c被取引用以调用debug(),是不安全的
}
而var c coder = &Gopher{"Go"}
则可以正常实现coder接口,因为它调用任何接口方法都是安全的
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被解引用以调用code(),是安全的
c.debug()
}
抛弃了接口后的以下常规代码,Gopher可以正常调用*Gopher.debug
,这是因为上面提到的,编译器自动对c做了引用。常规代码的安全,go交给了开发者
package main
import "fmt"
type Gopher struct {
language string
}
func (p Gopher) code() {
fmt.Printf("I am coding %s language\n", p.language)
}
func (p *Gopher) debug() {
p.language = "go1.18"
fmt.Printf("I am debuging %s language\n", p.language)
}
func main() {
var c = Gopher{"Go"}
c.code() // I am coding Go language
c.debug() // I am debuging go1.18 language
// 值调用指针接收器方法,产生安全问题,因为值被取了指针,接收器获取了该指针,对原对象造成修改
fmt.Println("final language: ", c.language) // final language: go1.18, which means c has been changed.
}
小结
从语法角度讲,不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用
而实际上,我们在编程实践中,应该遵循interface一样的规范:
避免隐式地取引调用,保证安全
网友评论