方法
□ 概述
本质上,方法是一个和特殊类型关联的函数。
⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:
代码语言:javascript复制// 方法
func (receiver ReceiverType) funcName (parameters) (results)
// 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
// 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
// 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
方法与函数的区别:
- 函数是⼀段具有独⽴功能的代码,可以被反复多次调⽤,从⽽实现代码复⽤。⽽⽅法是⼀个类的⾏为功能,只有该类的对象才能调⽤。
- Go语⾔的⽅法method是⼀种作⽤于特定类型变量的函数,这种特定类型变量叫做Receiver(接受者、接收者、接收器);
- 接受者的概念类似于传统⾯向对象语⾔中的this或self关键字;
- ⼀个⽅法就是⼀个包含了接受者的函数;
- Go语⾔中, 接受者的类型可以是任何类型,不仅仅是结构体, 也可以是struct类型外的其他任何类型。
type Employee struct {
name, currency string
salary int
}
//参数为Employee类型 的函数
func displaySalary(e Employee) {
fmt.Printf("员工姓名: %s, 薪资: %s%dn", e.name, e.currency, e.salary)
}
//接收者类型为Employee 的方法
func (e Employee) displaySalary() {
fmt.Printf("员工姓名: %s, 薪资: %s%dn", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee{
name: "纱布",
salary: 2000,
currency: "$", // 货币单位
}
emp1.displaySalary() //调用方法 员工姓名: 纱布, 薪资: $2000
displaySalary(emp1) //调用函数 员工姓名: 纱布, 薪资: $2000
}
□ 为类型添加方法
基础类型作为接收者
代码语言:javascript复制type MyInt int //自定义类型,给int改名为MyInt
// 方法
func (a MyInt) Add(b MyInt) MyInt { //面向对象
return a b
}
// 传统方式的定义
func Add(a, b MyInt) MyInt { //面向过程
return a b
}
func main() {
// 基础类型作为接收者
var a MyInt = 1
var b MyInt = 1
//调用func (a MyInt) Add(b MyInt)
fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) = 2
//调用func Add(a, b MyInt)
fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) = 2
}
通过上面的例子可以看出,面向对象只是换了一种语法形式来表达。
方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数。
结构体作为接收者
代码语言:javascript复制type Person struct {
name string
sex byte
age int
}
func (p Person) PrintInfo() { //给Person添加方法
fmt.Println(p.name, p.sex, p.age)
}
func main() {
p := Person{"手嘎吧", '男', 18} //初始化
p.PrintInfo() //调用func (p Person) PrintInfo()
}
□ 值语义和引用语义
值语义 :值作为接收者,在方法中对对象的改变 出了方法就没用了。
引用语义:指针作为接收者,在方法中对对象的改变 出了方法仍然有效。
代码语言:javascript复制type Person34 struct {
name string
}
// 值作为接收者,引用语义
func (p34 Person34) printInfo01() {
p34.name = "张删"
}
// 指针作为接收者,引用语义
func (p34 *Person34) printInfo02() {
p34.name = "历时"
}
func main() {
p34:=Person34{"糊涂"}
p34.printInfo01()
fmt.Println(p34.name) // 糊涂
p34.printInfo02()
fmt.Println(p34.name) // 历时
}
□ 方法集
方法集是指可以被该类型的值可调用的所有方法的集合。
类型 *T 方法集(接受者为值或者指针 都可去调用)
一个 指向自定义类型的值 的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
如果在指针上调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值(那片内存)作为方法的接收者。
代码语言:javascript复制type Person34 struct {
name string
}
// 指针作为接收者,引用语义
func (p34 Person34) printInfoPrinter() {
p34.name = "张删"
}
// 值作为接收者,引用语义
func (p34 *Person34) printInfoValue() {
p34.name = "历时"
}
func main() {
p34:= &Person34{"糊涂"}
p34.printInfoPrinter()
fmt.Println(p34.name) // 糊涂
(*p34).printInfoValue()
fmt.Println(p34.name) // 历时
p34.printInfoValue()
fmt.Println(p34.name) // 历时
}
类型 T 方法集(不可直接调接受者类型为指针的方法)
一个 自定义类型值 的 方法集 则由为 该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。
但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于Go语言传值的地址能力实现。
代码语言:javascript复制p34:= Person34{"糊涂"}
(&p34).printInfoPrinter()
fmt.Println(p34.name) // 历时
□ 方法的继承与重写
方法的继承、
如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该方法。
代码语言:javascript复制type Person35 struct {
name string
}
func (p Person35) fatherMethod() {
fmt.Println("你继承了父类的方法")
}
type Student35 struct {
Person35
name string
}
func main() {
stu:=Student35{Person35{"糊涂啊"},"糊涂孩纸"}
// 继承父类的方法
stu.fatherMethod()
}
方法的重写
代码语言:javascript复制// 父类方法
func (p Person35) fatherMethod() {
fmt.Println(p.name)
}
// 重写 父类的方法
func (s Student35) rewriteFather() {
fmt.Println(s.name)
}
func main() {
per := Person35{"父名"}
stu := Student35{Person35{"子名"}, 12}
// 重写父类的方法
per.fatherMethod() // 父名
stu.fatherMethod() // 子名
}
□ 表达式
代码语言:javascript复制type Person36 struct {
id int
name string
age int
}
func (p Person36) PrintInfoValue() {
fmt.Printf("%p,%vn", &p, p)
}
// 建议使用这种指针类型的
func (p *Person36) PrintInfoPointer() {
fmt.Printf("%p,%vn", &p, *p)
}
方法值( 隐式传参 )
就是实例化之后的对象去调用 绑定该对象的方法名 这时候结果是一个func()类型的值,这个就是方法值,去赋值给一个变量。
代码语言:javascript复制func main() {
p := Person36{1, "condition", 18}
// 值传递
// 可以看出调用方法名的结果是个地址 说明也可以作为值去传递
fmt.Println(p.PrintInfoPointer) // 0x6f9a00
fmt.Printf("%Tn", p.PrintInfoPointer) // func()
//方法值,隐式传递 receiver,绑定实例(对象)
//(意思就是这还是个属于Person35的方法,且绑定了实例化对象p,但是你并没有显式的赋予给pFunc1)
pFunc1 := p.PrintInfoPointer
pFunc1() // 0xc000006038,{1 condition 18}
pFunc2 := p.PrintInfoValue
pFunc2() // 0xc000042440,{1 condition 18}
}
方法表达式(显示传参)
代码语言:javascript复制func main() {
p := Person36{1, "condition", 18}
// 方法表达式
pFunction1 := Person36.PrintInfoValue
fmt.Printf("%Tn",pFunction1) // func(main.Person36)
// pFunction1() 错误写法:因为没有绑定实例化的对象,需要传入实例化后的对象
pFunction1(p) // 0xc000042480,{1 condition 18}
// 不同于方法值的地方 对于参数的类型是严格要求的 要求参数为指针时,就必须为指针。
pFunction2 := (*Person36).PrintInfoPointer
fmt.Printf("%Tn",pFunction2) // func(*main.Person36)
pFunction2(&p) // 0xc000006038,{1 condition 18}
}