@@ -1,58 +0,0 @@ 15.方法 | 凤凰涅槃进阶之路

15.方法

Abel sun2023年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