体现封装的特点,本质也是函数,是接受特定类型的函数,就是方法

回忆点

  1. 理解什么是方法?
    绑定了类型的函数,就是方法;方法+接受者 = 函数(就是目录的方法表达式,这样定义函数,就好像给了函数初始值)

(其余的区别我根本听不懂)

  1. 基本语法注意点
    1. 值传递还是引用传递取决于,方法接受者这个形参
    2. 是在给方法接受者实参的时候,可以go可以将<font style="color:#AD1A2B;">实参</font>进行自动类型转换;而非定义方法or定义方法表达式的时候,那时候go要知道你是值传递还是引用传递
    3. 调用用.,与结构体调用字段的符号相同
  2. 关系纠纷
    1. 一个包里定义了诸多方法,绑定与包,即:包→类型(类型绑定了诸多方法,形成<font style="color:#AD1A2B;">方法集</font>
    2. 结构体嵌套
      1. 方法会继承
      2. 若出现嵌套内外的方法同名,则服从<font style="color:#AD1A2B;">就近原则</font>
  3. 工厂模式?

基本语法

:::info 定义

:::

func (recevier <font style="color:rgb(0, 134, 179);">type</font>) methodName(参数列表)(返回值列表){}

与函数的区别

方法——是面向对象的;函数是面向流程的

  1. 方法是与对象实例绑定的特殊函数。
    方法是面向对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。
  2. 普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。
  3. 换句话说,方法是有关联状态的,而函数通常没有。方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。在某些语言里,尽管没有显式定义,但会在调用时隐式传递this 实例参数。
  4. 可以为当前包,以及除接口和指针以外的任何类型定义方法。

定义

func (recevier <font style="color:rgb(0, 134, 179);">type</font>) methodName(参数列表)(返回值列表){}

  1. package main
  2. type Test struct{}
  3. // 无参数、无返回值
  4. func (t Test) method0() {
  5. }
  6. // 单参数、无返回值
  7. func (t Test) method1(i int) {
  8. }
  9. // 多参数、无返回值
  10. func (t Test) method2(x, y int) {
  11. }
  12. // 无参数、单返回值
  13. func (t Test) method3() (i int) {
  14. return
  15. }
  16. // 多参数、多返回值
  17. func (t Test) method4(x, y int) (z int, err error) {
  18. return
  19. }
  20. // 无参数、无返回值
  21. func (t *Test) method5() {
  22. }
  23. // 单参数、无返回值
  24. func (t *Test) method6(i int) {
  25. }
  26. // 多参数、无返回值
  27. func (t *Test) method7(x, y int) {
  28. }
  29. // 无参数、单返回值
  30. func (t *Test) method8() (i int) {
  31. return
  32. }
  33. // 多参数、多返回值
  34. func (t *Test) method9(x, y int) (z int, err error) {
  35. return
  36. }
  37. func main() {}

要求

  1. 省略
    参数和返回值可以省略,receiver可以省略名,但不能省略类型
  2. receiver
receiver 要求
范围 1. 不管是什么类型,T都必须经过**type 类型别名 类型**,这意味着T不可以直接是int、float、。。。
2. 只能为当前包内命名类型定义方法。
省略 参数 receiver 可任意命名。如方法中未曾使用 ,可省略参数名(虽然我感觉要是没使用recevier那和一般函数有什么去呗呢)
类型要求 (=可以为什么类型变量定义方法?) 1. 具体 封装--方法 - 图1 封装--方法 - 图2 由以上例子可得:
1. 基类型 T本身不能是接口或指针。
2. 但是可以加“外壳*****,使得参数 receiver 类型可以是 *T。 ➡参数 receiver 类型可以是 T*T
不支持方法重载 解释:
1. 不支持接收者与方法名字都相同的两种方法,即使有不同参数
2. 但支持,接收者类型不同,方法同名的两个方法,因为只要接收者类型不一样,就算方法同名,也是不同方法,不会出现重复定义函数的错误 封装--方法 - 图3
  1. 调用时的自动转换(详情
    用实例 value 或 pointer 调用全部方法时,不受方法约束,编译总是查找全部方法,并自动转换recevier实参!
    主要体现于,指针的加持上——在运用成员运算符时候,无需**&a/*prt.方法**,只需**a/prt.方法**,仅适用于变量

调用

:::info 基本调用方法

:::

语法变量.方法,调用的有明显和函数的差别

普通类型的方法

封装--方法 - 图4

结果:封装--方法 - 图5

结构体类型的方法

go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对

象里面有类方法等概念。我们也可以声明一些方法,属于某个结构体。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. //结构体
  6. type User struct {
  7. Name string
  8. Email string
  9. }
  10. //方法
  11. func (u User) Notify() {
  12. fmt.Printf("%v : %v \n", u.Name, u.Email)
  13. }
  14. func main() {
  15. // 值类型调用方法
  16. u1 := User{"golang", "golang@golang.com"}
  17. u1.Notify()
  18. // 指针类型调用方法
  19. u2 := User{"go", "go@go.com"}
  20. u3 := &u2
  21. u3.Notify()
  22. }

封装--方法 - 图6

  1. package main
  2. import "fmt"
  3. type Person struct {
  4. name string
  5. sex string
  6. age int
  7. }
  8. func main() {
  9. man_1 := Person{"justin", "boy", 24}
  10. man_1.myfunc_1()
  11. man_1.myfunc_2("June", "girl", 24) //(&man_1).myfunc_2("June", "girl", 24)也是一样
  12. man_1.myfunc_1()
  13. }
  14. //下面为两个方法
  15. func(tmp Person) myfunc_1() {
  16. fmt.Println("tmp = ", tmp)
  17. }
  18. func(p *Person) myfunc_2(n string, s string, a int) { //接收者为结构体指针,可以直接对接收者进行修改
  19. p.name = n
  20. p.sex = s
  21. p.age = a
  22. }

封装--方法 - 图7

结果:

封装--方法 - 图8

  • 接收者为指针(本例为结构体指针)(包括引用类型),可以直接对接收者进行修改
  • 接受者为值类型,就无法对接受者修改

封装--方法 - 图9

值类型or引用类型讨论

  1. 接受者类型:基础类型T / 指针类型 T ,*是值传递还是引用传递,决定于形参
  2. 值类型复制,指针类型不复制→接受者为值类型,就无法对接受者修改;接受者为指针类型,可以对接受者修改
  3. 在某包中,定义了一个类型,这个类型所能调用的所有方法,就是该类型的方法集; 一般我们会分成:类型T的方法集 / 类型*T的方法集
值传递 引用传递
形参 对于基础类型T(值类型的) 1. 基础类型T里,如果是切片、集合…引用类型变量定义的类型
2. T不可以是指针,但是可加壳子,指针类型*T
自动转换 1. 某变量A,类型为T,变量A调用方法(含匿名字段), 无需担心接受者类型是T or *T,皆可自动转化:receiver实参类型→receiver形参类型
2. 很类似的是,结构体指针,在访问内部结构成员的时候,叫解引用,也这样结构体
实参 1. 实参不论是该T类型还是该T类型的指针,都是值传递
2. 不能修改另一个作用域的值
1. 实参不论是该T类型还是该T类型的指针,都是引用传递
2. 可以修改另一个作用域的值
例子 封装--方法 - 图10 封装--方法 - 图11

加面粉

方法,绑定于类型;那么类型之间的关系,如嵌套,

方法的继承

针对结构体的匿名字段

  1. 一句话搞定:子类可使用父类结构体的方法
  2. 具体:
    有结构体A,绑定A的方法a,现在定义结构体B,A成了B的匿名字段,那么结构体B会继承方法a,即可B.a

:::info 实例:

:::

封装--方法 - 图12

方法的重写

有结构体A,绑定A的方法a,现在定义结构体B,A成了B的匿名字段,那么结构体B会继承方法a;现在定义绑定B的方法b,若恰好,方法a与方法b同名,怎么办?——这种同名的现象就叫,方法的重写

Answer:就近原则

  1. 由于接受类型不同,方法a与b就算同名,那也是不同的方法
  2. 调用方法的原则:就近原则
    先找本作用域的方法,找不到,再找继承的方法
    这意味着,B.a/B.b其实调用的都是方法a
  3. 若非要调用匿名字段B的方法b,则B.A.b

:::info 实例分析

:::

上方例子

封装--方法 - 图13

封装--方法 - 图14

  1. //这是父类
  2. type Person struct{
  3. name string
  4. sex byte
  5. age int
  6. }
  7. //这是子类,父类嵌套在子类里
  8. type Student struct{
  9. Person
  10. id int
  11. addr string
  12. }

所谓方法的重写还有一份理解:

父亲对事件A有相应的处理方法X,但孩子自己对事件A也有相应的处理方法X,孩子颠覆了父亲的方法X,这就是“方法的重写”,所以我们也可以看到,方法的重写的讨论基础是:
  • 在结构体上,有父子继承
  • 父类结构体和子类结构体有同名的方法

这同样意味着:孩子可以使用父亲的方法,但父亲不能使用孩子的方法X

方法表达式(进一步封装)

隐式传参/方法值

总结:进一步的打包,封装,隐藏接受者与方法,实现了“方法 → 函数”

具体:

有结构体A,绑定A的值传递的方法a,绑定A的引用传递的方法a',那么传统调用是A.a/ A.a'

现在定义一个类型为结构体A的变量p

现在传递给一个变量:pFunc := p.a/pFunc_1 := p.a' ,这就是方法值

那么调用此方法,就像调用函数,整合了接收者和方法,只需传参,无论是是值传递还是引用传递,那都可以:
pFunc(...)/ pFunc_1(...)

值传递 引用传递
有结构体A, 现绑定方法 绑定A的值传递的方法a 绑定A的引用传递的方法a'
传统调用 A.a A.a'
现在传递给一个变量—方法值 pFunc := p.a pFunc_1 := p.a'
调用 pFunc(...) pFunc_1(...)

显式传参/方法表达式

区别于方法值,方法表达式隐藏方法,但保留接收者接口,显式传递接收者

值传递 引用传递
有结构体A, 现绑定方法 绑定A的值传递的方法a 绑定A的引用传递的方法a'
传统调用 A.a A.a'
现在传递给一个变量—方法表达式 f1 :=(A).a f2:=(<font style="color:#E8323C;">*</font>A).a'
调用 f1(...接收者, ...实参) f2(...<font style="color:#E8323C;">&</font>接收者,...实参)
  1. package main
  2. import "fmt"
  3. type person struct{
  4. name string
  5. age int
  6. }
  7. func(p person)myfunc_1(){
  8. fmt.Println(p)
  9. }
  10. func(p *person)myfunc_2(n string, a int){
  11. p.name=n
  12. p.age =a
  13. fmt.Println(p)
  14. }
  15. func main() {
  16. man_1 := person{"Bob", 24}
  17. f1 := man_1.myfunc_1
  18. f2 := man_1.myfunc_2
  19. f1()
  20. f2("justin", 24)
  21. f3:=(person).myfunc_1
  22. f4:=(*person).myfunc_2 //错解:f4:=(person).myfunc_2
  23. f3(man_1)
  24. f4(&man_1,"Tom", 24) //错解:f4(man_1,"Tom", 24)
  25. }

封装--方法 - 图15

修改:

封装--方法 - 图16

?工厂模式

说实话还不怎么懂

  1. 说明:
    Golang没有构造函数,要想获得实例,要通过工厂模式来解决问题
  2. 什么时候需要?——小写+别的包引用此类型创建实例
    封装--方法 - 图17
  3. 解决方法
    1. 结构体类型改成大写
    2. 有些情况下,不能改成大写,则需要工厂模式
      1. 在原包model,创建一个函数Newstudent,大写,输入的为结构体成员的值,输出为结构体指针
        封装--方法 - 图18
      2. 在新包,创建实例,stu是一个结构体指针
        封装--方法 - 图19
  4. 问:若在原包model中的结构体类型student中,若结构体成员小写(实例中是score)怎么办?
    1. 原包
      封装--方法 - 图20
    2. 新包
      封装--方法 - 图21
  5. 个人理解:
    没大写不能直接调用,就在原包A创建一个方法或者函数,返回这个值得指针,在新包B内皆可以创建实例