15.方法
2023年1月6日约 1867 字大约 6 分钟
15.方法
1,概述
在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,这种带有接收者的函数,我们称之为方法(method)
。本质上,一个方法则是一个和特殊类型关联的函数。
一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。
在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。
方法总是绑定对象实例,并隐式将实例作为第一实参(receiver),方法的语法如下:
func (receiver ReceiverType) funcName(parameters) (results)
- 参数receiver可任意命名。如方法中未曾使用,可省略参数名。
- 参数receiver类型可以是T或*T。基类型T不能是接口或指针,否则将编译失败。
- 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
- 只要接收者类型不一样,即便出现两个方法同名的情况,也表示不同的方法,不会出现重复定义函数的错误。
2,面向对象和面向过程
package main
import "fmt"
//实现2数相加
//面向过程
func Add01(a, b int) int {
return a + b
}
//面向对象,通过方法,就是给某个类型绑定一个函数
type long int
//tmp叫接收者,接收者就是传递一个参数
func (tmp long) Add02(other long) long {
return tmp + other
}
func main() {
var s1 int
s1 = Add01(2, 3)
fmt.Println("s1 = ", s1)
//定义一个变量
var s2 long = 2
//调用方法格式: 变量名.函数(所需参数)
r := s2.Add02(3)
fmt.Println("r = ", r)
//面向对象只是换了一种表现形式
}
3,结构体类型添加方法
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
//带有接收者的函数叫做方法
func (tmp Person) PrintInfo() {
fmt.Println("tmp = ", tmp)
}
//通过一个函数,利用指针,给成员赋值
func (p *Person) SetInfo(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
}
//注意,如果基础变量时指针类型,那么不能被方法调用
//long为接收者类型,他本身不能够是指针类型。
//type long *int
//func (a long) test(){
//
//}
func main() {
//定义同时初始化
p := Person{"mike", 'm', 18}
//这里的p对应tmp,.PrintInfo表示调用后边的函数
p.PrintInfo()
//定义一个结构体变量
var p1 Person
(&p1).SetInfo("yoyo", 'g', 22)
p1.PrintInfo()
}
4,值语义和指针语义
其实就是前边一直有提到的普通引用传递,以及指针引用传递。
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
//修改成员变量的值
//接收者为普通变量,非指针,也叫值语义,相当于拷贝一份,与原来的互不相干
func (p Person) SetInfoValue(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
fmt.Println("p = ", p)
fmt.Printf("SetInfoValue &p = %p\n", &p)
}
//接收者为指针变量,就是引用传递,也叫引用语义
func (p *Person) SetInfoPointer(n string, s byte, a int) {
p.name = n
p.sex = s
p.age = a
fmt.Println("p = ", p)
fmt.Printf("SetInfoPointer p = %p\n", p)
}
func main() {
s1 := Person{"go", 'm', 12}
fmt.Printf("&s1 = %p\n", &s1) //打印s1的地址
//值语义验证,首先调用上边值语义定义的方法进行重新赋值,然后对比两者的地址以及值
// s1.SetInfoValue("mike", 'b', 18)
// fmt.Println("s1 = ", s1) //打印内容,地址以及值都不一样
//引用语义验证
(&s1).SetInfoPointer("mike", 'b', 18)
fmt.Println("s1 = ", s1) //打印内容,地址以及值都一样
}
5,方法集
类型的方法集是指可以被该类型的值调用的所有方法的集合。
用实例value和pointer调用方法(含匿名字段)不受方法集约束,编译器总是查找全部方法,并自动转换receiver实参。
1,指针类型方法集
一个纸箱自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
如果在指针上电调用一个接受值的方法,Go语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者。
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
func (p Person) SetInfoValue() {
fmt.Println("SetInfoValue")
}
func (p *Person) SetInfoPointer() {
fmt.Println("SetInfoPointer")
}
func main() {
//结构体变量是一个指针变量,它能够调用哪些方法,那么这些方法就是一个集合,简称方法集
p := &Person{"mike", 'm', 18}
p.SetInfoPointer() //func (p Person) SetInfoValue()
(*p).SetInfoPointer() //把(*p)转换成p后再调用,等价于上面
//内部做的转换,先把指针p转换成*p后再调用
(*p).SetInfoValue()
p.SetInfoValue()
}
2,普通类型方法集
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
func (p Person) SetInfoValue() {
fmt.Println("SetInfoValue")
}
func (p *Person) SetInfoPointer() {
fmt.Println("SetInfoPointer")
}
func main() {
p := Person{"mike", 'm', 18}
p.SetInfoPointer() //func (p Person) SetInfoValue()
//内部先把p转换成(*p)后再调用
p.SetInfoValue()
}
说白了,所谓方法集就是Go语言内置了智能的类型匹配功能,也就是说,当我们需要调用某个类型的函数的时候,可以使用通用的某一个方式,编译器会自动进行匹配识别。
6,匿名字段
1,方法的继承
package main
import "fmt"
type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
}
//Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s,sex=%c,age=%d\n", tmp.name, tmp.sex, tmp.age)
}
//有个学生,继承Person字段,成员和方法都继承了
type Student struct {
Person //匿名字段
id int
addr string
}
func main() {
s := Student{Person{"erya", 'm', 12}, 123, "ny"}
s.PrintInfo()
}
2,方法的重写
package main
import "fmt"
type Person struct {
name string //名字
sex byte //性别,字符类型
age int //年龄
}
//Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
fmt.Printf("name=%s,sex=%c,age=%d\n", tmp.name, tmp.sex, tmp.age)
}
//有个学生,继承Person字段,成员和方法都继承了
type Student struct {
Person //匿名字段
id int
addr string
}
//Student 也实现了一个方法,这个方法和Person方法同名,这种方法叫重写
func (tmp *Student) PrintInfo() {
fmt.Println("Student : tmp=", tmp)
}
func main() {
s := Student{Person{"erya", 'm', 12}, 123, "ny"}
//就近原则:先找本作用域的方法,找不到再用继承的方法
s.PrintInfo() //到底是调用的是Person,还是Student呢?答案是Student
//继承的方法可以通过显式调用
s.Person.PrintInfo()
}
3,方法值
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
func (p Person) SetInfoValue() {
fmt.Printf("SetInfoValue: %p,%v\n", &p, p)
}
func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer: %p,%v\n", p, p)
}
func main() {
p := Person{"erya", 'm', 12}
fmt.Printf("SetInfoPointer: %p,%v\n", &p, p)
p.SetInfoPointer() //传统调用方式
//保存函数入口地址
pFunc := p.SetInfoPointer //这个就是方法值,调用函数时,无需再传递接收者,隐藏了接受者
pFunc()
vFunc := p.SetInfoValue
vFunc()
}
4,方法表达式
package main
import "fmt"
type Person struct {
name string
sex byte
age int
}
func (p Person) SetInfoValue() {
fmt.Printf("SetInfoValue: %p,%v\n", &p, p)
}
func (p *Person) SetInfoPointer() {
fmt.Printf("SetInfoPointer: %p,%v\n", p, p)
}
func main() {
p := Person{"erya", 'm', 12}
fmt.Printf("SetInfoPointer: %p,%v\n", &p, p)
//方法值 f := p.SetInfoPointer 隐藏了接收者
//方法表达式
f := (*Person).SetInfoPointer
f(&p) //显式把接收者传递过去 ===》 p.SetInfoPointer()
f2 := (Person).SetInfoValue
f2(p) //显式把接收者传递过去 ===》 p.SetInfoValue()
}
Powered by Waline v2.9.1