go学习总结(十二)面向对象

匿名字段

学生类(结构体),讲师类(结构体)等都有共同的成员(属性和方法),这样就存在重复,所以我们把这些重复的成员封装到一个父类(结构体)中。然后让学生类(结构体)和讲师类(结构体)继承父类(结构体)
接下来,我们可以先将公共的属性,封装到父类(结构体)中实现继承,关于方法(函数)的继承后面再讲。

匿名字段创建与初始化

那么怎样实现属性的继承呢?
可以通过匿名字段(也叫匿名组合)来实现,什么是匿名字段呢?通过如下使用,大家就明白了。
在这里插入图片描述
以上代码通过匿名字段实现了继承,将公共的属性封装在Person中,在Student中直接包含Person,那么Student中就有了Person中所有的成员,Person就是匿名字段。注意:Person匿名字段,只有类型,没有名字。
那么接下来说我们就可以给Student赋值了,具体初始化的方式如下:
在这里插入图片描述
以上代码中创建了一个结构体变量s1, 这个s1我们可以理解为就是Student对象,但是要注意语法格式,以下的写法是错误的:
var s2 Student=Student{101,”mike”,18,98.5}
其它初始化方式如下:
(1)自动推导类型
在这里插入图片描述
(2)指定初始化成员
在这里插入图片描述
接下来还可以对Person中指定的成员进行初始化。
在这里插入图片描述

成员操作

创建完成对象后,可以根据对象来操作对应成员属性,是通过“.”运算符来完成操作的。具体案例如下:
在这里插入图片描述
由于Student继承了Person,所以Person具有的成员,Student也有,所以根据Student创建出的对象可以直接对age成员项进行修改。
由于在Student中添加了匿名字段Person,所以对象s1,也可以通过匿名字段Person来获取age,进行修改。
当然也可以进行如下修改:
在这里插入图片描述
直接给对象s1中的Person成员(匿名字段)赋值
通过以上案例我们可以总结出,根据类(结构体)可以创建出很多的对象,这些对象的成员(属性)是一样的,但是成员(属性)的值是可以完全不一样的。

同名字段

现在将Student结构体与Person结构体,进行如下的修改:
在这里插入图片描述
在Student中也加入了一个成员name,这样与Person重名了,那么如下代码是给Student中name赋值还是给Person中的name 进行赋值?
在这里插入图片描述
输出的结果如下:
在这里插入图片描述
通过结果发现是对Student中的name进行赋值,所以在操作同名字段时,有一个基本的原则:如果能够在自己对象所属的类(结构体)中找到对应的成员,那么直接进行操作,如果找不到就去对应的父类(结构体)中查找。这就是所谓的就近原则。

指针类型匿名字段

结构体(类)中的匿名字段的类型,也可以是指针。
例如:
在这里插入图片描述
输出结果:
在这里插入图片描述
输出了结构体的地址。如果要取值,可以进行如下操作:
在这里插入图片描述
在定义对象s时,完成初始化,然后通过”.”的操作完成成员的操作。
但是,注意以下的写法是错误的:
在这里插入图片描述
大家可以思考一下,以上代码为什么会出错?
会出错,错误信息如下:
invalid memory address or nil pointer dereference
翻译成中文:无效的内存地址或nil指针引用
意思是Person没有指向任何的内存地址,那么其默认值为nil.
也就是指针类型匿名字段
Person没有指向任何一个结构体,所以对象s也就无法操作Person中的成员。

具体的解决办法如下:
在这里插入图片描述

多重继承

在上面的案例,Student类(结构体)继承了Person类(结构体),那么Person是否可以在继承别的类(结构体)呢?
可以,这就是多重继承。
多重继承指的是一个类可以继承另外一个类,而另外一个类又可以继承别的类,比如A类继承B类,而B类又可以继承C类,这就是多重继承。
具体案例如下:
在这里插入图片描述
接下来,看一下怎样对多重继承中的成员进行操作:
在这里插入图片描述
操作的方式,与前面讲解的是一样的。
注意:尽量在程序中,减少多重继承,否则会增加程序的复杂度。

通过以上内容的讲解,大家能够体会出面向对象编程中继承的优势了,接下来会给大家介绍
面向对象编程中另外的特性:封装性,其实关于封装性,在前面的编程中,大家也已经能够体会到了,就是通过函数来实现封装性。
大家仔细回忆一下,当初在讲解函数时,重点强调了函数的作用,就是将重复的代码封装来,用的时候,直接调用就可以了,不需要每次都写一遍,这就是封装的优势。(超级玛丽案例)
在面向对象编程中,也有封装的特性。面向对象中是通过方法来实现。下面,将详细的给大家讲解一下方法的内容。

方法

基本方法创建

在介绍面向对象时,讲过可以通过属性和方法(函数)来描述对象。
什么是方法呢?
方法,大家可以理解成就是函数,但是在定义使用方面与前面讲解的函数还是有区别的。
我们先定义一个传统的函数:
在这里插入图片描述
这个函数非常简单,下面定义一个方法,看一下在语法与传统的函数有什么区别:
方法的定义:
在这里插入图片描述
type Integer int :表示的意思是给int类型指定了一个别名叫Integer,别名可以随便起,只要符合GO语言的命名规则就可以。
指定别名后,后面可以用Integer来代替int 来使用。
func (a Integer) Test(b Integer) Integer{
}
表示定义了一个方法,方法的定义与函数的区别
第一:在关键字后面加上( a Integer), 这个在方法中称之为接收者,所谓的接受者就是接收传递过来的第一个参数,然后复制a, a的类型是Integer ,由于Integer是int的别名,所以a的类型为int

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

第三:调用方式不同
var result Interger=3
表示定义一个整型变量result,并赋值为3.
result.Test( 3)
通过result变量,完成方法的调用。因为,Test( )方法,是为int类型绑定的函数,而result变量为int类型。所以可以调用Test( )方法。result变量的值会传递给Test( )方法的接受者,也就是参数a,而实参Test( 3),会传递形参b.
当然,我们也可以将Test( )方法,理解成是为int类型扩展了,追加了的方法。因为系统在int类型时,是没有该方法的。

通过以上的定义,发现方法其实方法就是函数的语法糖。
在以上案例中,Test( )方法是为int类型绑定的函数,所以任何一个整型变量,都可以调用该方法。
例如:
在这里插入图片描述

给结构体添加方法

上面给整型创建了一个方法,那么直接通过整型变量加上“点”,就可以调用该方法了。
大家想一下,如果给结构体(类)加上了方法,那么根据结构体(类)创建完成对象后,是不是就可以通过对象加上“点”,就可以完成方法的调用,这与调用类中定义的属性的方式是完全一样的。这样就完成了通过方法与属性来描述一个对象的操作。(大家自己回想一下,前面在讲解对象的概念时,一直在说其具有的属性与行为(方法))
给结构体添加方法,语法如下:
在这里插入图片描述
给结构体添加方法的方式与前面给int类型添加方法的方式,基本一致。唯一不同的是,不需要给结构体指定别名,因为结构体Student就是相当于其所有成员属性的别名(id,name,score),所以这里不要在给结构体Student创建别名。
调用方式:根据结构体(类)创建的对象,完成了方法的调用。
PrintShow( )方法的作用,只是将结构体的成员(属性)值打印出来,如果要修改其对应的值,应该怎么做呢?
这时,大家肯定像到了指针,将方法的接收者,修改成对应的指针类型。
具体修改如下:
在这里插入图片描述
在使用方法时,要注意以下问题:
第一:只要接收者类型不一样,这个方法就算同名,也是不同方法,不会出现重复定义函数的错误。
在这里插入图片描述
但是,如果接收者类型一样,但是方法的参数不一样,是会出现错误的。‘
在这里插入图片描述
也就是说,再go中没有方法重载一说

第二:关于接收者不能为指针类型
在这里插入图片描述
第三:普通变量能够调用指针方法,指针变量能否调用普通方法?
在这里插入图片描述
通过结果可以发现,不管是普通变量还是指针变量都可以调用所有方法,非常方便。

方法继承

现在我们已经实现了为结构体添加成员(属性),和方法,并且实现了成员属性的继承,那么方法能否继承呢?
具体如下:
在这里插入图片描述
子类可以调用父类的方法

方法重写

在前面的案例中,子类(结构体)可以继承父类中的方法,但是,如果父类中的方法与子类的方法是重名方法会怎样呢?
在这里插入图片描述
结果:
在这里插入图片描述
如果子类(结构体)中的方法名与父类(结构体)中的方法名同名,在调用的时候是先调用子类(结构体)中的方法,这就方法的重写。所谓的重写:就是子类(结构体)中的方法,将父类中的相同名称的方法的功能重新给改写了。

为什么要重写父类(结构体)的方法呢?
通常,子类(结构体)继承父类(结构体)的方法,在调用对象继承方法的时候,调用和执行的是父类的实现。但是,有时候需要
对子类中的继承方法有不同的实现方式。例如,假设动物存在“跑”的方法,从中继承有狗类和马类两个子类,但是它们的跑是不一样的。
例如以下案例:
在这里插入图片描述
在改案例中,定义了一个动物类(结构体),并且有一个叫的方法,接下来小狗的类(结构体)继承动物类,小猫的类继承动物类,它们都有了叫的方法,但是动物类中的叫的方法无法满足小猫的叫的要求,只能重写。

方法值与方法表达式

在前面的案例中,我们调用结构体(类)中的方法,一般都是通过如下的方式:
在这里插入图片描述
或者是指针变量,现在,在给大家补充另外一种方式。
如下所示:
在这里插入图片描述
以上调用的方式称为方法值。这种方式隐藏了接收者。

还有一种调用的方式是通过方法表达式,如下所示:
在这里插入图片描述

接口

在讲解具体的接口之前,先看如下问题。
使用面向对象的方式,设计一个加减的计算器
代码如下:
在这里插入图片描述
以上实现非常简单,但是有个问题,在main( )函数中,当我们想使用减法操作时,创建减法类的对象,调用其对应的减法的方法。但是,有一天,系统需求发生了变化,要求使用加法,不再使用减法,那么需要对main( )函数中的代码,做大量的修改。将原有的代码注释掉,创建加法的类对象,调用其对应的加法的方法。有没有一种方法,让main( )函数,只修改很少的代码就可以解决该问题呢?有,要用到接下来给大家讲解的接口的知识点。
(1)什么是接口
接口就是一种规范与标准,在生活中经常见接口,例如:笔记本电脑的USB接口,可以将任何厂商生产的鼠标与键盘,与电脑进行链接。为什么呢?原因就是,USB接口将规范和标准制定好后,各个生产厂商可以按照该标准生产鼠标和键盘就可以了。
在程序开发中,接口只是规定了要做哪些事情,干什么。具体怎么做,接口是不管的。这和生活中接口的案例也很相似,例如:USB接口,只是规定了标准,但是不关心具体鼠标与键盘是怎样按照标准生产的.

在企业开发中,如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口告诉开发人员你需要实现那些功能。
(2)接口定义
接口定义的语法如下:
在这里插入图片描述
怎样具体实现接口中定义的方法呢?
在这里插入图片描述
具体的调用如下:
在这里插入图片描述
只要类(结构体)实现对应的接口,那么根据该类创建的对象,可以赋值给对应的接口类型。
接口的命名习惯以er结尾。
(3)多态
接口有什么好处呢?实现多态。
使用接口实现多态的方式如下:
在这里插入图片描述

接口继承与转换

接口也可以实现继承
在这里插入图片描述
接口继承后,可以实现“超集”接口转换“子集”接口,代码如下:
在这里插入图片描述

空接口

空接口(interface{})不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。
例如:
在这里插入图片描述
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:
在这里插入图片描述

类型断言

我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
comma-ok断言
Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。
如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false
具体案例如下:
在这里插入图片描述
switch实现:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vegetablesssss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值