0
点赞
收藏
分享

微信扫一扫

18.Go面向对象-方法

18.Go面向对象-方法

前言

在上一篇章我们使用匿名字段实现了继承,下面来看看如何设置面向对象-方法。

2 方法

2.1 基本方法创建

在介绍面向对象时,讲过可以通过属性和方法(函数)来描述对象。

什么是方法呢?

方法,大家可以理解成就是函数,但是在定义使用方面与前面讲解的函数还是有区别的。

我们先定义一个传统的函数:

func Test(a, b int) int {
return a + b
}

func main() {
result := Test(1, 2)
fmt.Println(result)
}

这个函数非常简单,下面定义一个方法,看一下在语法与传统的函数有什么区别:

方法的定义:

type Integer int // 为 int 定义别名 Integer

func (a Integer) Test(b Integer) Integer {
return a + b
}

func main() {
var result Integer = 3
r := result.Test(3)
fmt.Println(r)
}

​type Integer int​​​:表示的意思是给​​int​​​类型指定了一个别名叫​​Integer​​,别名可以随便起,只要符合GO语言的命名规则就可以。

指定别名后,后面可以用​​Integer​​​来代替​​int​​ 来使用。

func (a Integer) Test(b Integer) Integer {
return a + b
}

方法的定义与函数的区别

表示定义了一个方法,方法的定义与函数的区别

第一:在关键字后面加上 ​​( a Integer)​​​, 这个在方法中称之为接收者,所谓的接受者就是接收传递过来的第一个参数,然后复制​​a​​​, ​​a​​​的类型是​​Integer​​​ ,由于​​Integer​​​是​​int​​​的别名,所以​​a​​​的类型为​​int​

第二:在表示参数的类型时,都使用了对应的别名。

通过方法的定义,可以看出方法其实就是给某个类型绑定的函数。在该案例中,是为整型绑定的函数,只不过在给整型绑定函数(方法)时,一定要通过​​type​​​来指定一个别名,因为​​int​​类型是系统已经规定好了,无法直接绑定函数,所以只能通过别名的方式。

第三:调用方式不同

var result Integer = 3

表示定义一个整型变量​​result​​,并赋值为3.

result.Test(3)

通过​​result​​变量,完成方法的调用。

因为​​Test()​​​方法,是为​​int​​​类型绑定的函数,而​​result​​​变量为​​int​​​类型,所以可以调用​​Test()​​方法。

​result​​​变量的值会传递给​​Test()​​​方法的接受者,也就是参数​​a​​​,而实参​​Test(3)​​​,会传递形参​​b​​.

当然,我们也可以将​​Test()​​​方法,理解成是为​​int​​​类型扩展了,追加了的方法。因为系统在​​int​​类型时,是没有该方法的。

通过以上的定义,发现方法其实就是函数的语法糖。

在以上案例中,​​Test()​​​方法是为​​int​​类型绑定的函数,所以任何一个整型变量,都可以调用该方法。

例如:

var sum Integer = 6
t := sum.Test(8)
fmt.Println(t)

// 执行
14

2.2 给结构体添加方法

上面给整型创建了一个方法,那么直接通过整型变量加上“点”,就可以调用该方法了。

大家想一下,如果给结构体(类)加上了方法,那么根据结构体(类)创建完成对象后,是不是就可以通过对象加上“点”,就可以完成方法的调用,这与调用类中定义的属性的方式是完全一样的。这样就完成了通过方法与属性来描述一个对象的操作。(大家自己回想一下,前面在讲解对象的概念时,一直在说其具有的属性与行为(方法))

结构体添加方法

给结构体添加方法,语法如下:

type Student struct {
id int
name string
age int
score float64
}

func (stu Student) PrintShow() { // 定义Student结构体的方法PrintShow
fmt.Println(stu)
}

func main() {
student := Student{101, "zhangsan", 19, 90}
student.PrintShow() // 调用结构体的方法
}

给结构体添加方法的方式与前面给​​int​​类型添加方法的方式,基本一致。

唯一不同的是,不需要给结构体指定别名,因为结构体​​Student​​​就是相当于其所有成员属性的别名​​(id,name,score)​​​,所以这里不要在给结构体​​Student​​创建别名。

调用方式:根据结构体(类)创建的对象,完成了方法的调用。

修改结构体方法的接收者类型为指针,则允许修改对应的值

​PrintShow()​​方法的作用,只是将结构体的成员(属性)值打印出来,如果要修改其对应的值,应该怎么做呢?

这时,大家肯定想起来指针,将方法的接收者,修改成对应的指针类型。

具体修改如下:

type Student struct {
id int
name string
age int
score float64
}

// 定义Student结构体的方法PrintShow
func (stu Student) PrintShow() {
fmt.Println(stu)
}

// 接收者类型为指针 (p *Student)
func (p *Student) EditInfo(id int, name string, age int, score float64) {
p.id = id
p.name = name
p.age = age
p.score = score
}

func main() {
//student := Student{101, "zhangsan", 19, 90}
var student Student
(&student).EditInfo(102,"lisi",19,88) // 将结构体的指针传递给接收者,调用修改数据的方法
student.PrintShow() // 调用结构体的方法
}

在创建方法时,接收者类型为指针类型,所以在调用方法时,创建一个结构体变量,同时将结构体变量的地址,传递给方法的接收者,然后调用​​EditInfo()​​方法,完成要修改的数据传递。

在使用方法是,要注意如下几个问题:

第一:只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误

type long int

func (tmp long) test() {

}

type char byte

func (tmp char) test() {

}

但是,如果接收者类型一样,但是方法的参数不一样,是会出现错误的。

18.Go面向对象-方法_class

也就是,在GO中没有方法重载(所谓重载,指的是方法名称一致,参数类型,个数不一致)。

第二:关于接收者不能为指针类型。

type long int

func (tmp *long) test() {

}

以上定义正确


18.Go面向对象-方法_ios_02

image-20210531082214366

第三:接收者为普通变量,非指针,值传递

type Student struct {
id int
name string
age int
score float64
}

// 定义Student结构体的方法PrintShow
func (stu Student) PrintShow(id int, name string, age int, score float64) {
stu.id = id
stu.name = name
stu.age = age
stu.score = score
}

func main() {
var stu Student
stu.PrintShow(102,"lisi",19,88)
fmt.Println(stu)
}

// 执行:因为传递的不是内存地址,所以无法修改原 stu 的值
{0 0 0}

接收者为指针变量,引用传递

func (p *Student) EditInfo(id int, name string, age int, score float64) {
p.id = id
p.name = name
p.age = age
p.score = score
}

func main() {
var stu Student
(&stu).EditInfo(102,"lisi",19,88) // 将结构体的指针传递给接收者,调用修改数据的方法
fmt.Println(stu)
}

// 执行:
{102 lisi 19 88}

2.3  指针变量的方法值

在上面的案例中,我们定义了两个方法,一个是​​PrintShow()​​​, 该方法的接收者为普通方法,一个​​EditInfo()​​​方法,该方法的接收者为指针变量,那么大家思考这么一个问题:定义一个结构体指针变量,能否调用​​PrintShow()​​方法呢?

如下所示:

type Student struct {
id int
name string
age int
score float64
}

func (stu Student) PrintShow(id int, name string, age int, score float64) { // 定义Student结构体的方法PrintShow
stu.id = id
stu.name = name
stu.age = age
stu.score = score
fmt.Println("PrintShow:", stu)
}

func (p *Student) EditInfo(id int, name string, age int, score float64) {
p.id = id
p.name = name
p.age = age
p.score = score
fmt.Println("EditInfo:", *p)
}

func main() {
var stu Student // 定义一个结构体类型的变量
(&stu).PrintShow(101,"zhangsan",19,90) // 为什么结构体指针变量,可以调用`PrintShow()`方法呢?
(&stu).EditInfo(102,"lisi",20,67)
}

// 执行如下:
PrintShow: {101 zhangsan 19 90}
EditInfo: {102 lisi 20 67}

通过测试,发现是可以调用的。

结构体指针 可以 调用 普通结构体变量 的方法

为什么结构体指针变量,可以调用​​PrintShow()​​方法呢?

原因是:先将指针​​stu​​​,转换成​​*stu​​在调用。

等价如下代码:

(*(&stu)).PrintShow(101,"zhangsan",19,90)

所以,如果结构体变量是一个指针变量,它能够调用哪些方法,这些方法就是一个集合,简称方法集

普通结构体变量 可以 调用 结构体指针 的方法

如果是普通的结构体变量能否调用​​EditInfo()​​方法。

type Student struct {
id int
name string
age int
score float64
}

func (stu Student) PrintShow(id int, name string, age int, score float64) {
stu.id = id
stu.name = name
stu.age = age
stu.score = score
fmt.Println("PrintShow:", stu)
}

func (p *Student) EditInfo(id int, name string, age int, score float64) {
p.id = id
p.name = name
p.age = age
p.score = score
fmt.Println("EditInfo:", *p)
}

func main() {
var stu Student
stu.EditInfo(102,"lisi",20,67) // 普通结构体变量可以调用 EditInfo
}

是可以调用的,原因是:将普通的结构体类型的变量转换成​​(&stu)​​​在调用​​EditInfo()​​方法。

这样的好处是非常灵活,创建完对应的对象后,可以随意调用方法,不需要考虑太多指针的问题。

2.4 练习题

下面进行面向对象编程的练习

练习1:

定义一个学生类,有六个属性,分别为姓名、性别、年龄、语文、数学、英语成绩。

有2个方法:

一个打招呼的方法:介绍自己叫XX,今年几岁了。是男同学还是女同学。

两个计算自己总分数和平均分的方法。{显示:我叫XX,这次考试总成绩为X分,平均成绩为X分}

  • 1.结构体定义如下:
type Student struct {
name string // 姓名
sex byte // 性别
age int // 年龄
chinese float64 // 语文成绩
math float64 // 数学成绩
english float64 // 英语成绩
}
  • 2.为结构体定义相应的方法,并且在方法中可以完成对传递过来的数据的校验
func (p *Student) SayHello(name string, age int, sex byte) {
p.name = name
p.age = age
p.sex = sex
// 对年龄与性别进行判断
if p.age < 0 && p.age > 100 {
p.age = 0
}
if p.sex != 'm' && p.sex != 'w' {
p.sex = 'm'
}
fmt.Printf("我叫%s, 我今年%d岁了,我是%c生。", p.name, p.age, p.sex)
}

func (p *Student) ShowScore(chinese float64, math float64, english float64) {
p.chinese = chinese
p.math = math
p.english = english
var sum float64
sum = p.chinese + p.math + p.english
fmt.Printf("我叫%s, 我的总成绩是%.2f, 平均成绩是%.2f", p.name, sum, sum/3)
}
  • 3.完成方法的调用
var student Student
student.SayHello("zhangsan", 11,'m')
student.ShowScore(90,85,97)

// 执行:
我叫zhangsan, 我今年11岁了,我是m生。我叫zhangsan, 我的总成绩是272.00, 平均成绩是90.67

在以上的案例中,​​SayHello()​​​方法中已经完成了​​name​​​属性的赋值,所以在​​ShowScore()​​方法中,可以直接使用,因为我们使用指针指向了同一个结构体内存。

在调用的过程中,也能体会出确实很方便,,不需要考虑太多指针的问题

练习2:

写一个Ticket类,有一个距离属性, 不能为负数,有一个价格属性, 并且根据距离distance计算价格Price (1元/公里):

0-100公里    票价不打折

101-200公里   总额打9.5折

201-300公里   总额打9折

300公里以上   总额打8折

实现如下:

/**
Ticket类
- 0-100公里 票价不打折
- 101-200公里 总额打9.5折
- 201-300公里 总额打9折
- 300公里以上 总额打8折
*/
type Ticket struct {
distance float64 // 距离
price float64 // 价格
}

func (p *Ticket) GetPrice(price float64, distance float64) {
if distance < 0 {
distance = 0
}
p.distance = distance
p.price = price
if p.distance > 0 && p.distance <= 100 {
p.price = p.price * 1.0
} else if p.price >= 101 && p.price < 200 {
p.price = p.price * 0.95
} else if p.price >= 201 && p.price < 300 {
p.price = p.price * 0.9
} else {
p.price = p.price * 0.8
}
fmt.Println(p.price)
}

func main() {
var ticket Ticket
ticket.GetPrice(190, 200)
}

// 执行:
180.5


举报

相关推荐

0 条评论