💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
Go中的方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者。 在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。 也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。 ~~~go package main import "fmt" type Person struct { age int } func (p Person) Elegance() int { return p.age } func (p *Person) GetAge() { p.age += 1 } func main() { // p1 是值类型 p := Person{age: 18} // 值类型 调用接收者也是值类型的方法 fmt.Println(p.howOld()) // 值类型 调用接收者是指针类型的方法 p.GetAge() fmt.Println(p.GetAge()) // ---------------------- // p2 是指针类型 p2 := &Person{age: 100} // 指针类型 调用接收者是值类型的方法 fmt.Println(p2.GetAge()) // 指针类型 调用接收者也是指针类型的方法 p2.GetAge() fmt.Println(p2.GetAge()) } ~~~ 运行 ~~~go 18 19 100 101 ~~~ | 函数和方法 | 值接收者 | 指针接收者 | | --- | --- | --- | | 值类型调用者 | 方法会使用调用者的一个副本,类似于“传值” | 使用值的引用来调用方法,上例中,p1.GetAge() 实际上是 (&p1).GetAge(). | | 指针类型调用者 | 指针被解引用为值,上例中,p2.GetAge()实际上是 (\*p1).GetAge() | 实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针 | 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法。 如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。 通常我们使用指针作为方法的接收者的理由: * 使用指针方法能够修改接收者指向的值。 * 可以避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。 因而呢,我们是使用值接收者还是指针接收者,不是由该方法是否修改了调用者(也就是接收者)来决定,而是应该基于该类型的本质。 如果类型具备“原始的本质”,也就是说它的成员都是由 Go 语言里内置的原始类型,如字符串,整型值等,那就定义值接收者类型的方法。像内置的引用类型,如 slice,map,interface,channel,这些类型比较特殊,声明他们的时候,实际上是创建了一个 header, 对于他们也是直接定义值接收者类型的方法。这样,调用函数时,是直接 copy 了这些类型的 header,而 header 本身就是为复制设计的。 如果类型具备非原始的本质,不能被安全地复制,这种类型总是应该被共享,那就定义指针接收者的方法。比如 go 源码里的文件结构体(struct File)就不应该被复制,应该只有一份实体。 接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil。