体现封装的特点,本质也是函数,是接受特定类型的函数,就是方法
回忆点
- 理解什么是方法?
—绑定了类型的函数,就是方法;方法+接受者 = 函数(就是目录的方法表达式,这样定义函数,就好像给了函数初始值)
(其余的区别我根本听不懂)
- 基本语法注意点
- 值传递还是引用传递取决于,方法接受者这个形参
- 是在给方法接受者实参的时候,可以go可以将
<font style="color:#AD1A2B;">实参</font>
进行自动类型转换;而非定义方法or定义方法表达式的时候,那时候go要知道你是值传递还是引用传递 - 调用用
.
,与结构体调用字段的符号相同
- 关系纠纷
- 一个包里定义了诸多方法,绑定与包,即:包→类型(类型绑定了诸多方法,形成
<font style="color:#AD1A2B;">方法集</font>
) - 结构体嵌套
- 方法会继承
- 若出现嵌套内外的方法同名,则服从
<font style="color:#AD1A2B;">就近原则</font>
- 一个包里定义了诸多方法,绑定与包,即:包→类型(类型绑定了诸多方法,形成
- 工厂模式?
基本语法
:::info 定义
:::
func (recevier <font style="color:rgb(0, 134, 179);">type</font>) methodName(参数列表)(返回值列表){}
与函数的区别
方法——是面向对象的;函数是面向流程的
- 方法是与对象实例绑定的特殊函数。
方法是面向对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。 - 普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。
- 换句话说,方法是有关联状态的,而函数通常没有。方法和函数定义语法区别的在于前者有前置实例接收参数(receiver),编译器以此确定方法所属类型。在某些语言里,尽管没有显式定义,但会在调用时隐式传递this 实例参数。
- 可以为当前包,以及除接口和指针以外的任何类型定义方法。
定义
func (recevier <font style="color:rgb(0, 134, 179);">type</font>) methodName(参数列表)(返回值列表){}
package main
type Test struct{}
// 无参数、无返回值
func (t Test) method0() {
}
// 单参数、无返回值
func (t Test) method1(i int) {
}
// 多参数、无返回值
func (t Test) method2(x, y int) {
}
// 无参数、单返回值
func (t Test) method3() (i int) {
return
}
// 多参数、多返回值
func (t Test) method4(x, y int) (z int, err error) {
return
}
// 无参数、无返回值
func (t *Test) method5() {
}
// 单参数、无返回值
func (t *Test) method6(i int) {
}
// 多参数、无返回值
func (t *Test) method7(x, y int) {
}
// 无参数、单返回值
func (t *Test) method8() (i int) {
return
}
// 多参数、多返回值
func (t *Test) method9(x, y int) (z int, err error) {
return
}
func main() {}
要求
- 省略
参数和返回值可以省略,receiver可以省略名,但不能省略类型 - receiver
receiver 要求 | |
---|---|
范围 | 1. 不管是什么类型,T都必须经过**type 类型别名 类型** ,这意味着T不可以直接是int、float、。。。2. 只能为当前包内命名类型定义方法。 |
省略 | 参数 receiver 可任意命名。如方法中未曾使用 ,可省略参数名(虽然我感觉要是没使用recevier那和一般函数有什么去呗呢) |
类型要求 (=可以为什么类型变量定义方法?) | 1. 具体 由以上例子可得: 1. 基类型 T本身不能是接口或指针。 2. 但是可以加“外壳 ***** ,使得参数 receiver 类型可以是 *T 。 ➡参数 receiver 类型可以是 T 或*T 。 |
不支持方法重载 | 解释: 1. 不支持接收者与方法名字都相同的两种方法,即使有不同参数 2. 但支持,接收者类型不同,方法同名的两个方法,因为只要接收者类型不一样,就算方法同名,也是不同方法,不会出现重复定义函数的错误 |
- 调用时的自动转换(详情)
用实例 value 或 pointer 调用全部方法时,不受方法约束,编译总是查找全部方法,并自动转换recevier实参!
主要体现于,指针的加持上——在运用成员运算符时候,无需**&a/*prt.方法**
,只需**a/prt.方法**
,仅适用于变量
调用
:::info 基本调用方法
:::
语法变量.方法
,调用的有明显和函数的差别
普通类型的方法
结果:
结构体类型的方法
go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对
象里面有类方法等概念。我们也可以声明一些方法,属于某个结构体。
package main
import (
"fmt"
)
//结构体
type User struct {
Name string
Email string
}
//方法
func (u User) Notify() {
fmt.Printf("%v : %v \n", u.Name, u.Email)
}
func main() {
// 值类型调用方法
u1 := User{"golang", "golang@golang.com"}
u1.Notify()
// 指针类型调用方法
u2 := User{"go", "go@go.com"}
u3 := &u2
u3.Notify()
}
package main
import "fmt"
type Person struct {
name string
sex string
age int
}
func main() {
man_1 := Person{"justin", "boy", 24}
man_1.myfunc_1()
man_1.myfunc_2("June", "girl", 24) //(&man_1).myfunc_2("June", "girl", 24)也是一样
man_1.myfunc_1()
}
//下面为两个方法
func(tmp Person) myfunc_1() {
fmt.Println("tmp = ", tmp)
}
func(p *Person) myfunc_2(n string, s string, a int) { //接收者为结构体指针,可以直接对接收者进行修改
p.name = n
p.sex = s
p.age = a
}
结果:
- 接收者为指针(本例为结构体指针)(包括引用类型),可以直接对接收者进行修改
- 接受者为值类型,就无法对接受者修改
值类型or引用类型讨论
- 接受者类型:基础类型T / 指针类型 T ,*是值传递还是引用传递,决定于形参
- 值类型复制,指针类型不复制→接受者为值类型,就无法对接受者修改;接受者为指针类型,可以对接受者修改
- 在某包中,定义了一个类型,这个类型所能调用的所有方法,就是该类型的方法集; 一般我们会分成:类型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. 可以修改另一个作用域的值 |
例子 |
加面粉
方法,绑定于类型;那么类型之间的关系,如嵌套,
方法的继承
针对结构体的匿名字段
- 一句话搞定:子类可使用父类结构体的方法
- 具体:
有结构体A,绑定A的方法a,现在定义结构体B,A成了B的匿名字段,那么结构体B会继承方法a,即可B.a
:::info 实例:
:::
方法的重写
有结构体A,绑定A的方法a,现在定义结构体B,A成了B的匿名字段,那么结构体B会继承方法a;现在定义绑定B的方法b,若恰好,方法a与方法b同名,怎么办?——这种同名的现象就叫,方法的重写
Answer:就近原则
- 由于接受类型不同,方法a与b就算同名,那也是不同的方法
- 调用方法的原则:就近原则
先找本作用域的方法,找不到,再找继承的方法
这意味着,B.a/B.b
其实调用的都是方法a - 若非要调用匿名字段B的方法b,则
B.A.b
:::info 实例分析
:::
上方例子
//这是父类
type Person struct{
name string
sex byte
age int
}
//这是子类,父类嵌套在子类里
type Student struct{
Person
id int
addr string
}
所谓方法的重写还有一份理解:
父亲对事件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>接收者,...实参) |
package main
import "fmt"
type person struct{
name string
age int
}
func(p person)myfunc_1(){
fmt.Println(p)
}
func(p *person)myfunc_2(n string, a int){
p.name=n
p.age =a
fmt.Println(p)
}
func main() {
man_1 := person{"Bob", 24}
f1 := man_1.myfunc_1
f2 := man_1.myfunc_2
f1()
f2("justin", 24)
f3:=(person).myfunc_1
f4:=(*person).myfunc_2 //错解:f4:=(person).myfunc_2
f3(man_1)
f4(&man_1,"Tom", 24) //错解:f4(man_1,"Tom", 24)
}
修改:
?工厂模式
说实话还不怎么懂
- 说明:
Golang没有构造函数,要想获得实例,要通过工厂模式来解决问题 - 什么时候需要?——小写+别的包引用此类型创建实例
- 解决方法
- 结构体类型改成大写
- 有些情况下,不能改成大写,则需要工厂模式
- 在原包model,创建一个函数Newstudent,大写,输入的为结构体成员的值,输出为结构体指针
- 在新包,创建实例,stu是一个结构体指针
- 在原包model,创建一个函数Newstudent,大写,输入的为结构体成员的值,输出为结构体指针
- 问:若在原包model中的结构体类型student中,若结构体成员小写(实例中是score)怎么办?
- 原包
- 新包
- 原包
- 个人理解:
没大写不能直接调用,就在原包A创建一个方法或者函数,返回这个值得指针,在新包B内皆可以创建实例