一、 概述
在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称为方法(method)。本质上,一个方法则是一个和特殊类型关联的函数。
一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。
在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。
⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:
func (receiver ReceiverType) funcName (parameters) (results)
- 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
- 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
- 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
二、 为类型添加方法
1. 基础类型作为接收者
// 自定义类型,给int改名为MyInt
type MyInt int// 在函数定义时,在其名字之前放上一个变量,即是一个方法
func (a MyInt) Add(b MyInt) MyInt {return a + b
}//传统方式的定义
func Add(a, b MyInt) MyInt {//面向过程return a + b
}func main() {var a MyInt=1var b MyInt=1//调用func (aMyInt) Add(bMyInt)fmt.Println("a.Add(b)=",a.Add(b))//a.Add(b)=2//调用func Add(a,bMyInt)fmt.Println("Add(a,b)=",Add(a,b))//Add(a,b)=2
}
通过上面的例子可以看出,面向对象只是换了一种语法形式来表达。方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数。
注意:虽然方法的名字一模一样,但是如果接收者不一样,那么方法就不一样。
2. 结构体作为接收者
方法里面可以访问接收者的字段,调用方法通过点.访问,就像`struct``里面访问字段一样:
type Person struct {name stringsex byteage int
}func (p Person) PrintInfo(){//给Person添加方法fmt.Println(p.name,p.sex,p.age)
}func main() {p:=Person{"mike",'m',18}//初始化p.PrintInfo()//调用func(pPerson)PrintInfo()
}
打印结果为mike,m,18,你方法写的是Person那么这个方法只能传Person,不能传别的类型。
type student struct { //学生name stringsex stringage int
}func (s student) Print() {s.age = 10fmt.Println(s)
}
func main() {stu := student{"赵云", "男", 30}stu.Print()fmt.Println(stu)
}{赵云 男 10}
{赵云 男 30}
三、 值语义和引用语义
type Person struct {name stringsex byteage int
}// 指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){// 给成员赋值(*p).name = "yoyo"p.sex = 'f'p.age = 22
}// 值作为接收者,值语义
func (p Person) SetInfoValue(){// 给成员赋值p.name = "yoyo"p.sex = 'f'p.age = 22
}func main() {// 指针作为接收者,引用语义p1 := Person{"mike",'m',18} // 初始化fmt.Println("函数调用前=",p1) // 函数调用前={mike10918}(&p1).SetInfoPointer()fmt.Println("函数调用后=",p1) // 函数调用后={yoyo10222}fmt.Println("==========================")p2 := Person{"mike",'m',18} // 初始化// 值作为接收者,值语义fmt.Println("函数调用前=",p2) // 函数调用前={mike10918}p2.SetInfoValue()fmt.Println("函数调用后=",p2) // 函数调用后={mike10918}
}
四、方法集
类型的方法集是指可以被该类型的值调用的所有方法的集合。
用实例实例value和pointer调用方法(含匿名字段)不受⽅法集约束,编译器编总是查找全部方法,并自动转换receiver实参。
1.类型*T方法集
一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者。
类型*T⽅法集包含全部receiver T + *T⽅法:
type Person struct{name stringsex byteage int
}// 指针作为接收者,引用语义
func (p *Person) SetInfoPointer(){(*p).name="yoyo"p.sex='f'p.age=22
}// 值作为接收者,值语义
func (p Person) SetInfoValue(){p.name="xxx"p.sex='m'p.age=33
}func main() {// p为指针类型var p*Person = &Person{"mike",'m',18}p.SetInfoPointer() // func (p)SetInfoPointer()p.SetInfoValue() // func (*p)SetInfoValue()(*p).SetInfoValue() // func (*p)SetInfoValue()
}
2. 类型T方法集
一个自定义类型值的方法集则由为该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。
但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现。
package mainimport "fmt"type Student struct {name stringage int
}// 指针作为接收者 引用语义
func (s *Student) SetStuPointer() {s.name = "Bob"s.age = 18
}// 值作为接收者 值语义
func (s Student) SetStuValue() {s.name = "Peter"s.age = 18
}func main() {// 指针作为接收者,引用语义s1 := Student{"Miller", 18} // 初始化fmt.Println("函数调用前 = ", s1) // 函数调用前 = {Miller 18}(&s1).SetStuPointer()fmt.Println("函数调用后 = ", s1) // 函数调用后 = {Bob 18}fmt.Println("==========================")s2 := Student{"mike", 18} // 初始化//值 作为接收者,值语义fmt.Println("函数调用前 = ", s2) // 函数调用前 = {mike 18}s2.SetStuValue() fmt.Println("函数调用后 = ", s2) // 函数调用后 = {mike 18}
}
// 总结 : (引用语义:会改变结构体内容) (值语义:不会改变结构体内容)
五、 匿名字段
1. 方法的继承
如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该方法。
type Person struct {name stringsex byteage int
}//Person定义了方法
func (p *Person) PrintInfo() {fmt.Printf("%s,%c,%d\n",p.name,p.sex,p.age)
}type Student struct {Person//匿名字段,那么Student包含了Person的所有字段id intaddr string
}func main() {p := Person{"mike",'m',18}p.PrintInfo()s := Student{Person{"yoyo",'f',20},2,"sz"}s.PrintInfo()
}
也就是说我用student继承了person那么我就拥有了person的一切不管是字段,还是方法,我都能调用。
子类可以继承父类 可以继承属性和方法,但是父类不可继承子类的
// 子类
type student01 struct {studentclass int
}func (s *student) PrintInfo() {fmt.Printf("编号%d\n", s.id)fmt.Printf("姓名%s\n", s.name)fmt.Printf("年龄%d\n ", s.age)
}
func main() {s := student{"赵云", 110, 30}s.PrintInfo()// 子类可以继承父类 可以继承属性和方法s1 := student01{student{"李白", 100, 20}, 9}s1.PrintInfo()
}
2. 方法的重写
type Person struct {name stringsex byteage int
}
//Person定义了方法
func (p *Person) PrintInfo() {fmt.Printf("Person:%s,%c,%d\n",p.name,p.sex,p.age)
}
type Student struct {Person//匿名字段,那么Student包含了Person的所有字段id intaddr string
}
//Student定义了方法
func (s *Student) PrintInfo() {fmt.Printf("Student:%s,%c,%d\n",s.name,s.sex,s.age)
}func main() {p:=Person{"mike",'m',18}p.PrintInfo() //Person:mike,m,18s:=Student{Person{"yoyo",'f',20},2,"sz"}s.PrintInfo() //Student:yoyo,f,20s.Person.PrintInfo() //Person:yoyo,f,20
}
也就是说我调用了Person的方法,但是我觉得这个方法不行,然后我自己又重新写了个方法,最后调用student方法的时候就只会调用我这个方法,而不会调用person的方法了
六、 方法值和方法表达式
类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。
根据调用者不同,方法分为两种表现形式:方法值和方法表达式。两者都可像普通函数那样赋值和传参,区别在于方法值绑定实例,⽽方法表达式则须显式传参。