原文链接 http://www.limerence2017.com/2019/10/11/golang15/

interface 意义?

golang 为什么要创造interface这种机制呢?我个人认为最主要的就是做约束,定义一种规范,大家可以按照同一种规范实现各自的功能,从而实现多态。
同时当interface做函数形参,可以很好地限制传入参数,并且根据不同的实参调用达到多态的效果。多态的意思就是多种多样的功能,比如我们定义了一
个接口

1
2
3
4
type IOInter interface{
write()int
read()int
}

定义了一个IOInter的接口,只要别人实现了write和read方法,都可以转化为这个接口。至于具体怎么读,读什么,网络IO还是文件IO取决于具体的实现,
这就形成了多样化的功能,从而实现多态。同时IOInter做函数的形参,

1
2
3
func WriteFunc(io IOInter){
io.Write()
}

还达到了安全限制的功能。比如没有实现读写功能的类实例无法传给WriteFunc,WriteFunc内部调用io的Write函数会根据实参具体的实现完成特定的读写。
我们通过一个小例子实战下接口做形参的意义。golang中sort包提供了几个排序api,我们先实现一个int类型slice排序功能。

1
2
3
   arrayint := []int{6, 1, 0, 5, 2, 7}
sort.Ints(arrayint)
fmt.Println(arrayint)

sort.Ints可以完成对整形slice的排序,同样sort.Strings可以完成对string类型slice的排序

1
2
3
   arraystring := []string{"hello", "world", "Alis", "and", "Bob"}
sort.Strings(arraystring)
fmt.Println(arraystring)

如果有这样一个需求,游戏中有很多个英雄,每个英雄有四个属性,攻击,防御,名字,出生时间,对这些英雄排序,从小到大,优先按照攻击排序,其次攻击相等的按
照防御排序,如果防御相等的按照出生时间排序。这是比较规则,我们根据sort包的Sort函数可以实现这个功能。
首先我们先定义英雄的结构

1
2
3
4
5
6
type Hero struct {
Name string
Attack int
Defence int
GenTime int64
}

其次我们再定义一个英雄列表

1
type HeroList []*Hero

接下来,我们看看golang的sort包实现的Sort源码

1
2
3
4
func Sort(data Interface) {
n := data.Len()
quickSort(data, 0, n, maxDepth(n))
}

可以看出,Sort函数有一个Interface类型的形参,我们继续查看Interface的类型

1
2
3
4
5
6
7
8
9
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}

可以看出Interface是一个接口,内部声明了三个方法Len,Less,Swap.
我们需要给自己的英雄列表实现这三个方法,就可以调用Sort排序了。先实现Len方法。

1
2
3
func (hl HeroList) Len() int {
return len(hl)
}

Len功能很简单,就是实现了列表大小的获取。接下来实现比较函数Less,Less是按照我们之前说的英雄比较规则实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (hl HeroList) Less(i, j int) bool {
if i < 0 || j < 0 {
return true
} lenth := len(hl)
if i >= lenth || j >= lenth {
return true
} if hl[i].Attack != hl[j].Attack {
return hl[i].Attack < hl[j].Attack
} if hl[i].Defence != hl[j].Defence {
return hl[i].Defence < hl[j].Defence
} return hl[i].GenTime < hl[j].GenTime
}

优先判断攻击是否相等,不相等就按照攻击力大小排序,小于返回true,从而实现从小到大排序
其次,攻击相等按照防御排序,以此类推。
接下来实现交换函数,交换英雄列表中的两个英雄

1
2
3
4
5
6
7
8
9
10
11
12
13
func (hl HeroList) Swap(i, j int) {
if i < 0 || j < 0 {
return
} lenth := len(hl)
if i >= lenth || j >= lenth {
return
} hl[i], hl[j] = hl[j], hl[i] }

这样,我们的英雄列表功能和结构都设计好了,写个main函数测试下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//自定义类型排序用sort.Sort
var herolists HeroList
for i := 0; i < 10; i++ {
generate := time.Now().Unix()
name := fmt.Sprintf("Hero%d", generate)
hero := Hero{
Name: name,
Attack: rand.Intn(100),
Defence: rand.Intn(200),
GenTime: generate,
}
herolists = append(herolists, &hero)
time.Sleep(time.Duration(1) * time.Second)
} sort.Sort(herolists)
for _, value := range herolists {
fmt.Print(value.Name, " ", value.Attack, " ", value.Defence, " ", value.GenTime, "\n")
}

我们通过for循环,每一秒生成一个英雄,攻击力和防御力随机,然后调用sort排序,接下来打印下看看效果

1
2
3
4
5
6
7
8
9
10
Hero1570780749 11 45 1570780749
Hero1570780744 25 140 1570780744
Hero1570780748 28 74 1570780748
Hero1570780750 37 106 1570780750
Hero1570780742 47 59 1570780742
Hero1570780745 56 100 1570780745
Hero1570780747 62 89 1570780747
Hero1570780741 81 87 1570780741
Hero1570780743 81 118 1570780743
Hero1570780746 94 111 1570780746

看的出来,优先按照攻击力排序,攻击力相同则按照防御值排序。这样,这个排序的小功能就做好了。

interface万能接口

interface{}空接口可以接受任何类型的变量,从而可以实现类似于泛型编程的功能。golang本身并不支持泛型,原作者说泛型编程太过复杂,
以后会更新进来。
interface{}类型的变量在使用时要转化为具体的类型,否则会报错。转化方法前文提起过,现在复习一遍

1
2
3
4
5
6
7
8
   var ife interface{}
ife = herolists
val, ok := ife.(HeroList)
if !ok {
fmt.Println("ife can't transfer to HeroList!")
return
}
fmt.Println("herolist's len is ", val.Len())

herolists是上个例子定义的英雄列表,将它赋值给ife后,ife为interface{}类型,不能直接使用,所以用ife.(HeroList)来转换为HeroList
类型。
当然interface{}还可以做类型判断

1
2
3
4
5
6
7
8
9
10
11
12
func JudgeType(itf interface{}) {
switch itf.(type) {
case string:
fmt.Println("type is string")
case int:
fmt.Println("type is int")
case HeroList:
fmt.Println("type is HeroList")
default:
fmt.Println("unknown type ")
}
}

interface实现万能类型双向链表

基于上面的知识,我们再做一个例子,实现双向链表,支持头部插入,尾部插入,指定位置插入,指定位置删除,可存储任意类型数据
我们先定义链表的基本结构

1
2
3
4
5
6
7
8
9
10
type LinkList struct {
Head *LinkEle
Tail *LinkEle
} type LinkEle struct {
Data interface{}
Pre *LinkEle
Next *LinkEle
}

LinkeEle是链表中的每个节点类型,包含Data数据域,其为interface{}类型,以及Pre指向前一个节点的指针,Next指向后一个节点的指针。
LinkList是链表结构,包含头和尾部节点的指针
好的,为了方便取出节点数据域,我们给LinkEle实现一个GetData方法。

1
2
3
func (le *LinkEle) GetData() interface{} {
return le.Data
}

接下来我们实现插入操作,实现头部插入,基本思路为在头部节点前插入新节点,然后将新节点和头部节点连接,更新新节点为头部节点

1
2
3
4
5
6
7
8
9
10
11
12
13
func (ll *LinkList) InsertHead(le *LinkEle) {

	if ll.Tail == nil && ll.Head == nil {
ll.Tail = le
ll.Head = ll.Tail
return
} ll.Head.Pre = le
le.Pre = nil
le.Next = ll.Head
ll.Head = le
}

上面先判断链表是否为空,如果链表为空,那么直接更新头尾信息即可,否则就需要执行连接操作。同样的道理我们实现尾部插入
尾部插入实在尾部节点的后边插入

1
2
3
4
5
6
7
8
9
10
11
func (ll *LinkList) InsertTail(le *LinkEle) {
if ll.Tail == nil && ll.Head == nil {
ll.Tail = le
ll.Head = ll.Tail
return
}
ll.Tail.Next = le
le.Pre = ll.Tail
le.Next = nil
ll.Tail = le
}

我们测试下实现的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
   ll := &LinkList{nil, nil}
fmt.Println("insert head .....................")
for i := 0; i < 2; i++ {
num := rand.Intn(100)
node1 := &LinkEle{Data: num, Next: nil, Pre: nil}
ll.InsertHead(node1)
fmt.Println(num)
}
fmt.Println("after insert head .................")
for node := ll.Head; node != nil; node = node.Next {
val, ok := node.GetData().(int)
if !ok {
fmt.Println("interface transfer error")
break
}
fmt.Println(val)
} fmt.Println("insert tail .....................")
for i := 0; i < 2; i++ {
num := rand.Intn(100)
node1 := &LinkEle{Data: num, Next: nil, Pre: nil}
ll.InsertTail(node1)
fmt.Println(num)
} fmt.Println("after insert tail .................")
for node := ll.Head; node != nil; node = node.Next {
val, ok := node.GetData().(int)
if !ok {
fmt.Println("interface transfer error")
break
}
fmt.Println(val)
}

我们初始化了一个LinkList类型的链表变量ll,然后在头部插入两个节点,在尾部插入两个节点,看看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
insert head .....................
81
87
after insert head .................
87
81
insert tail .....................
47
59
after insert tail .................
87
81
47
59

头部插入先插入81,然后插入87,所以列表变为87,81
接着尾部插入47,59,列表变为87,81,47,59
接下来实现在指定位置的节点后插入节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func (ll *LinkList) InsertIndex(le *LinkEle, index int) {
if index < 0 {
return
} if ll.Head == nil {
ll.Head = le
ll.Tail = ll.Head
return
} node := ll.Head
indexfind := 0
for ; indexfind < index; indexfind++ {
if node.Next == nil {
break
}
node = node.Next
} if indexfind != index {
fmt.Println("index is out of range")
return
}
//node 后边的节点缓存起来
nextnode := node.Next //node 和le连接起来
node.Next = le
le.Pre = node if node == ll.Tail {
ll.Tail = le
return
} //le和next node 连接起来
if nextnode != nil {
nextnode.Pre = le
le.Next = nextnode
}
}

首先判断链表是否为空,如果为空,则直接更新节点为列表头尾节点。否则判断插入位置是否越界,如果越界则直接返回。
如果不越界,则将节点插入,并判断插入节点是否为最后位置,如果为最后位置,则更新其为尾结点。
接下来我们测试在第三个节点后边插入节点。补充代码如下,前边的插入不变,看下效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fmt.Println("insert after third element........")
{
num := rand.Intn(100)
node1 := &LinkEle{Data: num, Next: nil, Pre: nil}
ll.InsertIndex(node1, 2)
fmt.Println(num)
} fmt.Println("after insert index .................")
for node := ll.Head; node != nil; node = node.Next {
val, ok := node.GetData().(int)
if !ok {
fmt.Println("interface transfer error")
break
}
fmt.Println(val)
}

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
insert head .....................
81
87
after insert head .................
87
81
insert tail .....................
47
59
after insert tail .................
87
81
47
59
insert after third element........
81
after insert index .................
87
81
47
81
59

前边是头部和尾部插入的输出,接着我们在第三个位置节点后插入81,打印看到确实插入在了47的后边。
同样我们接下来实现删除操作,删除指定位置的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func (ll *LinkList) DelIndex(index int) {
if index < 0 {
return
} if ll.Head == nil {
return
} node := ll.Head
indexfind := 0
for ; indexfind < index; indexfind++ {
if node.Next == nil {
break
}
node = node.Next
} if indexfind != index {
fmt.Println("index is out of range")
return
} if ll.Head == ll.Tail {
ll.Tail = nil
ll.Head = ll.Tail
return
} //如果是头节点
if node == ll.Head {
ll.Head = node.Next
node.Next.Pre = nil
return
} //如果是尾结点
if node == ll.Tail {
ll.Tail = node.Pre
ll.Tail.Next = nil
return
}
//将前后连接起来
node.Pre.Next = node.Next
node.Next.Pre = node.Pre
}

和插入操作类似,判断是否为空链表,是否只有一个节点等情况,接着判断删除的是否为头结点,是否为尾节点,否则就执行删除后的连接操作。
继续上边的测试代码,我们添加如下测试代码补充测试

1
2
3
4
5
6
7
8
9
10
11
fmt.Println("delete second element, its index is 1")
ll.DelIndex(1)
fmt.Println("after delete second element, its index is 1")
for node := ll.Head; node != nil; node = node.Next {
val, ok := node.GetData().(int)
if !ok {
fmt.Println("interface transfer error")
break
}
fmt.Println(val)
}

输出如下

1
2
3
4
5
6
7
8
9
10
11
87
81
47
81
59
delete second element, its index is 1
after delete second element, its index is 1
87
47
81
59

我们删除了index为1,也就是第二个节点81,测试成功了。
由于链表的数据域Data为空接口类型,所以可以存储各种类型的数据,只需要在GetData时做具体类型转换即可。
接下来读者可以自己考虑实现删除头部节点,删除尾部节点等。
可以下载我的源码 :
https://github.com/secondtonone1/golang-/tree/master/day26

总结

本文通过两个小例子实战,演示了interface常用的用法,为接下来讲解反射做铺垫。
感谢关注我的公众号

golang(10)interface应用和复习的更多相关文章

  1. Golang的Interface是个什么鬼

    问题概述 Golang的interface,和别的语言是不同的.它不需要显式的implements,只要某个struct实现了interface里的所有函数,编译器会自动认为它实现了这个interfa ...

  2. golang 关于 interface 的学习整理

    Golang-interface(四 反射) go语言学习-reflect反射理解和简单使用 为什么在Go语言中要慎用interface{} golang将interface{}转换为struct g ...

  3. golang的interface剖析

      背景: golang的interface是一种satisfied式的.A类只要实现了IA interface定义的方法,A就satisfied了接口IA.更抽象一层,如果某些设计上需要一些更抽象的 ...

  4. Golang 的 `[]interface{}` 类型

    Golang 的 []interface{} 类型 我其实不太喜欢使用 Go 语言的 interface{} 类型,一般情况下我宁愿多写几个函数:XxxInt, XxxFloat, XxxString ...

  5. Golang接口(interface)三个特性(译文)

    The Laws of Reflection 原文地址 第一次翻译文章,请各路人士多多指教! 类型和接口 因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍. go是一个静态性语言,每个变量都 ...

  6. Golang-interface(四 反射)

    github:https://github.com/ZhangzheBJUT/blog/blob/master/reflect.md 一 反射的规则 反射是程序执行时检查其所拥有的结构.尤其是类型的一 ...

  7. golang之interface

    一.概述 接口类型是对 "其他类型行为" 的抽象和概况:因为接口类型不会和特定的实现细节绑定在一起:很多面向对象都有类似接口概念,但Golang语言中interface的独特之处在 ...

  8. golang将interface{}转换为struct

    项目中需要用到golang的队列,container/list,需要放入的元素是struct,但是因为golang中list的设计,从list中取出时的类型为interface{},所以需要想办法把i ...

  9. Golang-interface(二 接口与nil)

    github: https://github.com/ZhangzheBJUT/blog/blob/master/nil.md 一 接口与nil 前面解说了go语言中接口的基本用法,以下将说一说nil ...

随机推荐

  1. Java语言基础(11)

    1 构造方法 构造方法是一种特殊的方法,只有在创建对象的时候才被调用,用来执行初始化的操作,比如给属性赋值... 1) 构造方法名字跟类名一致,没有返回值也就没有返回值类型 2) 格式: 类名(参数列 ...

  2. HTML符号代码速查表

    HTML实体符号被用作实现保留字符(reserved characters)或者表达键盘无法输入的一些常用字符.在大多数浏览器中默认的字符集为ISO-8859-1.HTML实体符号使我们在网页设计中经 ...

  3. Windows安装Redis并添加本地自启动服务

    概况 在windows本地搭建redis缓存,添加到本地计算机的服务中,保证每次开机自动启动服务. 第一步:下载redis(我的是计算机win10,64位) https://github.com/Mi ...

  4. web页面ios浏览器img图片的坑

    大家都知道ios浏览器有个默认的内置事件,就是长按屏幕,会放大预览你点击的内容. 不同浏览器效果各异,有的浏览器弹出提示框,预览图片或保存图片.主要呈下面2中形式.体验最差的就是后者,会直接将页面中的 ...

  5. 服务消费(LoadBalancerClient、Ribbon、Feign)

    转自:https://www.jianshu.com/p/562045489d9d 4.1使用LoadBalancerClient 在Spring Cloud Commons中提供了大量的与服务治理相 ...

  6. 部署flask到阿里云服务器ECS

    比较难的一点是:部署到云服务器上之后,通过公网没法访问. 这就要说回道 本地开发时的一个小细节:通过http://127.0.0.1:5000是可以访问的,但通过http://[本地ip]:5000是 ...

  7. Linux下使用telnet测试端口号是否开放

    telnet 127.0.0.1 80调用后,若提示bash: telnet: command not found,那么进行以下步骤: 1.检查telnet是否已经安装,或者有部分未安装: rpm - ...

  8. C/C++字符串转换函数;

    字符串转数字: char 转 int 函数: atoi   wchar  转 int 函数: _wtoi 数字转字符串: std::to_string(): std::to_wsting();

  9. httpclient个人理解

    httpclient:模拟浏览器发送请求,服务器会响应数据,用心区域网内 不同系统间的请求调用 依赖  httpclient.jar和httpcore.jar需要同时纯在 <dependency ...

  10. Android源码开发利器——Java源码调试(基于4.1.2)

     原文地址:http://blog.csdn.net/jinzhuojun/article/details/8868038     调试Android Java源码 草帽的后花园——Neo 写在之前的 ...