方法就是函数,但它是属于某一个类型。
方法的语法为
你可以给任意类型定义方法
package main
import (
"fmt"
)
type MyInt int
// 转换为string类型
func (i MyInt) Output() string {
return fmt.Sprintf("%v", i)
}
方法实际上是函数,上述方法等价于下列函数
package main
import (
"fmt"
)
type MyInt int
// 转换为string类型
func Output(i MyInt) string {
return fmt.Sprintf("%v", i)
}
方法其实就是把函数参数移动到前面作为函数的接受者。
方法的定义必须要与类型的定义在同一个包里,例如你不能给int
类型创建方法,因为int
类型的定义不在main
包中:
package main
import (
"fmt"
)
// 编译期错误: cannot define new methods on non-local type int
func (i int) Output() string {
return fmt.Sprintf("%v", i)
}
但你可以给int
起一个别名,给别名定义方法, 例如上一个例子。
定义一个counter类型,它有一个值接收者的递增方法(increment):
package main
import "fmt"
// 给int起一个别名,新的类型counter
type counter int
// 值接受者的方法,递增1
func (c counter) increment() {
c = c + 1
}
func main() {
// 新建一个counter
c := counter(0)
// 调用递增的方法
c.increment()
// 打印c的值
fmt.Println(c)
}
输出
为什么, 是0,而不是1呢?我们说过,方法其实就是函数
func (c counter) increment() {
c = c + 1
}
等价于
func increment(c counter) {
c = c + 1
}
Go中一切传递都是进行复制,索引increment
函数中的c只是复制了传入的0
,和main
函数的c
没有关系。
我们把increment方法改为指针接收者,结果就会大不一样:
package main
import "fmt"
// 给int起一个别名,新的类型counter
type counter int
// 指针接受者的方法,递增1
func (c *counter) increment() {
*c = *c + 1 // 指针解引用,*c 取出指向的变量
}
func main() {
// 新建一个counter
c := counter(0)
// 调用递增的方法
c.increment()
// 打印c的值
fmt.Println(c)
}
输出
为什么呢?
指针接受者的方法
// 指针接受者的方法,递增1
func (c *counter) increment() {
*c = *c + 1
}
等价于
func increment(c *counter) {
*c = *c + 1
}
传入的是指针地址,复制的是指针地址,main
函数中的c
的地址存的值发送了改变,所以打印1。
这就是指针接受者。当我们想改变c
时,使用指针介绍者。或者当c
内存占用很大时,我们使用指针接受者,减少函数传递时复制的开销,因为指针占用的内存永远是8个字节。
不知道你注意没有,counter
调用指针接受者是这样的:
...
func main() {
// 新建一个counter
c := counter(0)
// 调用递增的方法
c.increment()
// 打印c的值
fmt.Println(c)
}
...
c 是counter
类型,而不是*counter
类型,为什么可以直接调用increment
方法呢?
func (c *counter) increment() {
*c = *c + 1
}
这是Go提供的指针语法糖, Go会自动帮我们取地址:
等价于
这能简化我们的操作。
同样,Go也会自动地解引用,考虑下列值接受者
package main
import "fmt"
// 给int起一个别名,新的类型counter
type counter int
// 值接受者
func (c counter) increment() {
c = c + 1
}
func main() {
// 新建一个counter
temp := counter(0)
c := &temp // c: *counter类型
// 调用递增的方法
c.increment()
// 打印c的值
fmt.Println(*c) // 解引用取出c指向的变量
}
c是指针类型(*counter)的变量,increment
是值接受者。
等价于
Go帮我们自动解引用。
聪明的你,上面的代码输出0还是1?
运行,输出
为什么呢?因为increment
传入的是值类型counter
,而不是指针类型(*counter)。
结构体(类型)就像一个人, 他有姓名,年龄等属性(字段), 也会唱,跳和rap等技能(方法)。
package main
import "fmt"
type person struct {
name string
age int
}
// 唱
func (p person) sing() {
fmt.Printf("%s 在跳\n", p.name)
}
// 跳
func (p person) dance() {
fmt.Printf("%s 在唱\n", p.name)
}
// rap
func (p person) rap() {
fmt.Printf("%s 在rap\n", p.name)
}
func main() {
p := person{
name: "lang",
age: 222,
}
p.sing()
p.dance()
p.rap()
}
运行,输出
lang 在跳
lang 在唱
lang 在rap
如何更好地复用代码呢?其他语言通过面向对象来实现,Go特立独行,使用组合来实现类似面向对象的功能。
以上面的person
结构体为例,如果我想在person
的基础上创建新的结构体smartPerson
,如何做呢?这时可以使用嵌套:
type smartPerson struct {
person
iQ int
}
嵌套结构体,这时不用使用字段名,直接把类型写入即可。smartPerson
自己有一个iQ
字段
新的结构体可以访问person的字段和方法
func main() {
p := person{
name: "lang",
age: 222,
}
sm := smartPerson{
person: p,
iQ: 100,
}
fmt.Println(sm.age) // 222
fmt.Println(sm.name) // "lang"
sm.sing() // lang 在跳
sm.dance() // lang 在唱
}
smartPerson
也可以实现和person
相同的方法
func (sm smartPerson) sing() {
sm.person.sing()
fmt.Println("smartPerson 在唱")
}
这时调用sing()
结果是哪个呢?
func main() {
p := person{
name: "lang",
age: 222,
}
sm := smartPerson{
person: p,
iQ: 100,
}
sm.sing()
}
输出
是自身的sing
方法。通过结构体的嵌套,我们可以“重写”被嵌入的结构体的方法。
如果想调用被嵌套的sing
方法,可以使用
指明字段
运行,输出:
好的。我们实现了类似“继承”的功能,但它是组合。组合优于继承!
完整代码
package main
import "fmt"
type person struct {
name string
age int
}
// 唱
func (p person) sing() {
fmt.Printf("%s 在跳\n", p.name)
}
// 跳
func (p person) dance() {
fmt.Printf("%s 在唱\n", p.name)
}
type smartPerson struct {
person
iQ int
}
func (sm smartPerson) sing() {
sm.person.sing()
fmt.Println("smartPerson 在唱")
}
func main() {
p := person{
name: "lang",
age: 222,
}
sm := smartPerson{
person: p,
iQ: 100,
}
// 调用字段
fmt.Println(sm.age) // 222
fmt.Println(sm.name) // "lang"
// 调用方法
sm.sing() // lang 在跳
sm.dance() // lang 在唱
}
输出
222
lang
lang 在跳
smartPerson 在唱
lang 在唱