原文链接 http://www.limerence2017.com/2019/10/11/golang15/
interface 意义?
golang 为什么要创造interface这种机制呢?我个人认为最主要的就是做约束,定义一种规范,大家可以按照同一种规范实现各自的功能,从而实现多态。
同时当interface做函数形参,可以很好地限制传入参数,并且根据不同的实参调用达到多态的效果。多态的意思就是多种多样的功能,比如我们定义了一
个接口
1 |
type IOInter interface{ |
定义了一个IOInter的接口,只要别人实现了write和read方法,都可以转化为这个接口。至于具体怎么读,读什么,网络IO还是文件IO取决于具体的实现,
这就形成了多样化的功能,从而实现多态。同时IOInter做函数的形参,
1 |
func WriteFunc(io IOInter){ |
还达到了安全限制的功能。比如没有实现读写功能的类实例无法传给WriteFunc,WriteFunc内部调用io的Write函数会根据实参具体的实现完成特定的读写。
我们通过一个小例子实战下接口做形参的意义。golang中sort包提供了几个排序api,我们先实现一个int类型slice排序功能。
1 |
arrayint := []int{6, 1, 0, 5, 2, 7} |
sort.Ints可以完成对整形slice的排序,同样sort.Strings可以完成对string类型slice的排序
1 |
arraystring := []string{"hello", "world", "Alis", "and", "Bob"} |
如果有这样一个需求,游戏中有很多个英雄,每个英雄有四个属性,攻击,防御,名字,出生时间,对这些英雄排序,从小到大,优先按照攻击排序,其次攻击相等的按
照防御排序,如果防御相等的按照出生时间排序。这是比较规则,我们根据sort包的Sort函数可以实现这个功能。
首先我们先定义英雄的结构
1 |
type Hero struct { |
其次我们再定义一个英雄列表
1 |
type HeroList []*Hero |
接下来,我们看看golang的sort包实现的Sort源码
1 |
func Sort(data Interface) { |
可以看出,Sort函数有一个Interface类型的形参,我们继续查看Interface的类型
1 |
type Interface interface { |
可以看出Interface是一个接口,内部声明了三个方法Len,Less,Swap.
我们需要给自己的英雄列表实现这三个方法,就可以调用Sort排序了。先实现Len方法。
1 |
func (hl HeroList) Len() int { |
Len功能很简单,就是实现了列表大小的获取。接下来实现比较函数Less,Less是按照我们之前说的英雄比较规则实现
1 |
func (hl HeroList) Less(i, j int) bool { |
优先判断攻击是否相等,不相等就按照攻击力大小排序,小于返回true,从而实现从小到大排序
其次,攻击相等按照防御排序,以此类推。
接下来实现交换函数,交换英雄列表中的两个英雄
1 |
func (hl HeroList) Swap(i, j int) { |
这样,我们的英雄列表功能和结构都设计好了,写个main函数测试下
1 |
//自定义类型排序用sort.Sort |
我们通过for循环,每一秒生成一个英雄,攻击力和防御力随机,然后调用sort排序,接下来打印下看看效果
1 |
Hero1570780749 11 45 1570780749 |
看的出来,优先按照攻击力排序,攻击力相同则按照防御值排序。这样,这个排序的小功能就做好了。
interface万能接口
interface{}空接口可以接受任何类型的变量,从而可以实现类似于泛型编程的功能。golang本身并不支持泛型,原作者说泛型编程太过复杂,
以后会更新进来。
interface{}类型的变量在使用时要转化为具体的类型,否则会报错。转化方法前文提起过,现在复习一遍
1 |
var ife interface{} |
herolists是上个例子定义的英雄列表,将它赋值给ife后,ife为interface{}类型,不能直接使用,所以用ife.(HeroList)来转换为HeroList
类型。
当然interface{}还可以做类型判断
1 |
func JudgeType(itf interface{}) { |
interface实现万能类型双向链表
基于上面的知识,我们再做一个例子,实现双向链表,支持头部插入,尾部插入,指定位置插入,指定位置删除,可存储任意类型数据
我们先定义链表的基本结构
1 |
type LinkList struct { |
LinkeEle是链表中的每个节点类型,包含Data数据域,其为interface{}类型,以及Pre指向前一个节点的指针,Next指向后一个节点的指针。
LinkList是链表结构,包含头和尾部节点的指针
好的,为了方便取出节点数据域,我们给LinkEle实现一个GetData方法。
1 |
func (le *LinkEle) GetData() interface{} { |
接下来我们实现插入操作,实现头部插入,基本思路为在头部节点前插入新节点,然后将新节点和头部节点连接,更新新节点为头部节点
1 |
func (ll *LinkList) InsertHead(le *LinkEle) { |
上面先判断链表是否为空,如果链表为空,那么直接更新头尾信息即可,否则就需要执行连接操作。同样的道理我们实现尾部插入
尾部插入实在尾部节点的后边插入
1 |
func (ll *LinkList) InsertTail(le *LinkEle) { |
我们测试下实现的功能
1 |
ll := &LinkList{nil, nil} |
我们初始化了一个LinkList类型的链表变量ll,然后在头部插入两个节点,在尾部插入两个节点,看看效果
1 |
insert head ..................... |
头部插入先插入81,然后插入87,所以列表变为87,81
接着尾部插入47,59,列表变为87,81,47,59
接下来实现在指定位置的节点后插入节点
1 |
func (ll *LinkList) InsertIndex(le *LinkEle, index int) { |
首先判断链表是否为空,如果为空,则直接更新节点为列表头尾节点。否则判断插入位置是否越界,如果越界则直接返回。
如果不越界,则将节点插入,并判断插入节点是否为最后位置,如果为最后位置,则更新其为尾结点。
接下来我们测试在第三个节点后边插入节点。补充代码如下,前边的插入不变,看下效果
1 |
fmt.Println("insert after third element........") |
结果如下
1 |
insert head ..................... |
前边是头部和尾部插入的输出,接着我们在第三个位置节点后插入81,打印看到确实插入在了47的后边。
同样我们接下来实现删除操作,删除指定位置的节点
1 |
func (ll *LinkList) DelIndex(index int) { |
和插入操作类似,判断是否为空链表,是否只有一个节点等情况,接着判断删除的是否为头结点,是否为尾节点,否则就执行删除后的连接操作。
继续上边的测试代码,我们添加如下测试代码补充测试
1 |
fmt.Println("delete second element, its index is 1") |
输出如下
1 |
87 |
我们删除了index为1,也就是第二个节点81,测试成功了。
由于链表的数据域Data为空接口类型,所以可以存储各种类型的数据,只需要在GetData时做具体类型转换即可。
接下来读者可以自己考虑实现删除头部节点,删除尾部节点等。
可以下载我的源码 :
https://github.com/secondtonone1/golang-/tree/master/day26