接口

Golang世界中,有一种叫interface的东西,很是神奇。

一、数据类型 interface{}

如果你事前并不知道变量是哪种数据类型,不知道它是整数还是字符串,但是你还是想要使用它。

Golang就产生了名为interface{}的数据类型,表示并不知道它是什么类型。举例子:

package main

import (
"fmt"
"reflect"
) func print(i interface{}) {
fmt.Println(i)
} func main() {
// 声明一个未知类型的 a,表明不知道是什么类型
var a interface{}
a = 2
fmt.Printf("%T,%v\n", a, a) // 传入函数
print(a)
print(3)
print("i love you") // 使用断言,判断是否是 int 数据类型
v, ok := a.(int)
if ok {
fmt.Printf("a is int type,value is %d\n", v)
} // 使用断言,判断变量类型
switch a.(type) {
case int:
fmt.Println("a is type int")
case string:
fmt.Println("a is type string")
default:
fmt.Println("a not type found type")
} // 使用反射找出变量类型
t := reflect.TypeOf(a)
fmt.Printf("a is type: %s", t.Name())
}

输出:

int,2
2
3
i love you
a is int type,value is 2
a is type int
a is type: int

1.1.基本使用

我们使用interface{},可以声明一个未知类型的变量a

    // 声明一个未知类型的 a,表明不知道是什么类型
var a interface{}
a = 2
fmt.Printf("%T,%v\n", a, a)

然后给变量赋值一个整数:a=2,这时a仍然是未知类型,使用占位符%T可以打印变量的真实类型,占位符%v打印值,这时fmt.Printf在内部会进行类型判断。

我们也可以将函数的参数也定为interface,和变量的定义一样:

func print(i interface{}) {
fmt.Println(i)
}

使用时:

    // 传入函数
print(a)
print(3)
print("i love you")

我们传入print函数的参数可以是任何类型,如整数3或字符串i love you等。进入函数后,函数内变量i丢失了类型,是一个未知类型,这种特征使得我们如果想处理不同类型的数据,不需要写多个函数。

当然,结构体里面的字段也可以是interface{}

type H struct {
A interface{}
B interface{}
}

1.2.判断具体类型

我们定义了interface{},但是实际使用时,我们有判断类型的需求。有两种方法可以进行判断。

使用断言:

    // 使用断言,判断是否是 int 数据类型
v, ok := a.(int)
if ok {
fmt.Printf("a is int type,value is %d\n", v)
}

直接在变量后面使用.(int),有两个返回值v, ok会返回。ok如果是true表明确实是整数类型,这个整数会被赋予v,然后我们可以拿v愉快地玩耍了。否则,okfalsev为空值,也就是默认值 0。

如果我们每次都这样使用,会很难受,因为一个interface{}类型的变量,数据类型可能是.(int),可能是.(string),可以使用switch来简化:

    // 使用断言,判断变量类型
switch a.(type) {
case int:
fmt.Println("a is type int")
case string:
fmt.Println("a is type string")
default:
fmt.Println("a not type found type")
}

swicth中,断言不再使用.(具体类型),而是a.(type)

最后,还有一种方式,使用的是反射包reflect来确定数据类型:

    // 使用反射找出变量类型
t := reflect.TypeOf(a)
fmt.Printf("a is type: %s", t.Name())

这个包会直接使用非安全指针来获取真实的数据类型:

func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}

一般日常开发,很少使用反射包。

二. 接口结构 interface

我们现在都是函数式编程,或者是结构体方法式的编程,难道没有其他语言那种面向对象,对象继承的特征吗?有,Golang语言叫做面向接口编程。

package main

import (
"fmt"
"reflect"
) // 定义一个接口,有一个方法
type A interface {
Println()
} // 定义一个接口,有两个方法
type B interface {
Println()
Printf() int
} // 定义一个结构体
type A1Instance struct {
Data string
} // 结构体实现了Println()方法,现在它是一个 A 接口
func (a1 *A1Instance) Println() {
fmt.Println("a1:", a1.Data)
} // 定义一个结构体
type A2Instance struct {
Data string
} // 结构体实现了Println()方法,现在它是一个 A 接口
func (a2 *A2Instance) Println() {
fmt.Println("a2:", a2.Data)
} // 结构体实现了Printf()方法,现在它是一个 B 接口,它既是 A 又是 B 接口
func (a2 *A2Instance) Printf() int {
fmt.Println("a2:", a2.Data)
return 0
} func main() {
// 定义一个A接口类型的变量
var a A // 将具体的结构体赋予该变量
a = &A1Instance{Data: "i love you"}
// 调用接口的方法
a.Println()
// 断言类型
if v, ok := a.(*A1Instance); ok {
fmt.Println(v)
} else {
fmt.Println("not a A1")
}
fmt.Println(reflect.TypeOf(a).String()) // 将具体的结构体赋予该变量
a = &A2Instance{Data: "i love you"}
// 调用接口的方法
a.Println()
// 断言类型
if v, ok := a.(*A1Instance); ok {
fmt.Println(v)
} else {
fmt.Println("not a A1")
}
fmt.Println(reflect.TypeOf(a).String()) // 定义一个B接口类型的变量
var b B
//b = &A1Instance{Data: "i love you"} // 不是 B 类型
b = &A2Instance{Data: "i love you"}
fmt.Println(b.Printf())
}

输出:

a1: i love you
&{i love you}
*main.A1Instance
a2: i love you
not a A1
*main.A2Instance
a2: i love you
0

我们可以定义一个接口类型,使用type 接口名 interface,这时候不再是interface{}

// 定义一个接口,有一个方法
type A interface {
Println()
} // 定义一个接口,有两个方法
type B interface {
Println()
Printf() int
}

可以看到接口AB是一种抽象的结构,每个接口都有一些方法在里面,只要结构体struct实现了这些方法,那么这些结构体都是这种接口的类型。如:

// 定义一个结构体
type A1Instance struct {
Data string
} // 结构体实现了Println()方法,现在它是一个 A 接口
func (a1 *A1Instance) Println() {
fmt.Println("a1:", a1.Data)
} // 定义一个结构体
type A2Instance struct {
Data string
} // 结构体实现了Println()方法,现在它是一个 A 接口
func (a2 *A2Instance) Println() {
fmt.Println("a2:", a2.Data)
} // 结构体实现了Printf()方法,现在它是一个 B 接口,它既是 A 又是 B 接口
func (a2 *A2Instance) Printf() int {
fmt.Println("a2:", a2.Data)
return 0
}

我们要求结构体必须实现某些方法,所以可以定义一个接口类型的变量,然后将结构体赋值给它:

    // 定义一个A接口类型的变量
var a A
// 将具体的结构体赋予该变量
a = &A1Instance{Data: "i love you"}
// 调用接口的方法
a.Println()

如果结构体没有实现该方法,将编译不通过,无法编译二进制。

当然也可以使用断言和反射来判断接口类型是属于哪个实际的结构体struct

    // 断言类型
if v, ok := a.(*A1Instance); ok {
fmt.Println(v)
} else {
fmt.Println("not a A1")
}
fmt.Println(reflect.TypeOf(a).String())

Golang很智能判断结构体是否实现了接口的方法,如果实现了,那么该结构体就是该接口类型。我们灵活的运用接口结构的特征,使用组合的形式就可以开发出更灵活的程序了。

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook

数据结构和算法(Golang实现)(5)简单入门Golang-接口的更多相关文章

  1. 数据结构和算法(Golang实现)(1)简单入门Golang-前言

    数据结构和算法在计算机科学里,有非常重要的地位.此系列文章尝试使用 Golang 编程语言来实现各种数据结构和算法,并且适当进行算法分析. 我们会先简单学习一下Golang,然后进入计算机程序世界的第 ...

  2. 数据结构和算法(Golang实现)(2)简单入门Golang-包、变量和函数

    包.变量和函数 一.举个例子 现在我们来建立一个完整的程序main.go: // Golang程序入口的包名必须为 main package main // import "golang&q ...

  3. 数据结构和算法(Golang实现)(3)简单入门Golang-流程控制语句

    流程控制语句 计算机编程语言中,流程控制语句很重要,可以让机器知道什么时候做什么事,做几次.主要有条件和循环语句. Golang只有一种循环:for,只有一种判断:if,还有一种特殊的switch条件 ...

  4. 数据结构和算法(Golang实现)(4)简单入门Golang-结构体和方法

    结构体和方法 一.值,指针和引用 我们现在有一段程序: package main import "fmt" func main() { // a,b 是一个值 a := 5 b : ...

  5. 数据结构和算法(Golang实现)(6)简单入门Golang-并发、协程和信道

    并发.协程和信道 Golang语言提供了go关键字,以及名为chan的数据类型,以及一些标准库的并发锁等,我们将会简单介绍一下并发的一些概念,然后学习这些Golang特征知识. 一.并发介绍 我们写程 ...

  6. 数据结构和算法(Golang实现)(7)简单入门Golang-标准库

    使用标准库 一.避免重复造轮子 官方提供了很多库给我们用,是封装好的轮子,比如包fmt,我们多次使用它来打印数据. 我们可以查看到其里面的实现: package fmt func Println(a ...

  7. CQRS简单入门(Golang)

    一.简单入门之入门 CQRS/ES和领域驱动设计更搭,故整体分层沿用经典的DDD四层.其实要实现的功能概要很简单,如下图. 基础框架选择了https://github.com/looplab/even ...

  8. Java数据结构和算法之数组与简单排序

    一.数组于简单排序 数组 数组(array)是相同类型变量的集合,可以使用共同的名字引用它.数组可被定义为任何类型,可以是一维或多维.数组中的一个特别要素是通过下标来访问它.数组提供了一种将有联系的信 ...

  9. 《Java数据结构与算法》笔记-CH3简单排序

    class ArrayBub { private long[] arr; private int nElement; public ArrayBub(int size) { arr = new lon ...

随机推荐

  1. 面向对象第三单元博客(JML)

    // demo/Graph.java package demo; ​ import java.util.ArrayList; ​ public class Demo { /*@ public norm ...

  2. 网络安全从入门到精通 ( 第二章-5) 后端基础PHP—简介及基本函数-下

    本文内容: 循环语句 PHP获取表单信息 PHP操作Mysql语句 语法SQL注入 1,循环语句: for循环: 语法:for($x=0,$x<10;$x++){执行语句;} 注意:$x++,先 ...

  3. Mol. Cell. Proteomics | 癌细胞衍生的小细胞外囊体通过促进HGF-Met途径促进受体细胞转移

    题目:Cancer cell derived small extracellular vesicles contribute to recipient cell metastasis through ...

  4. hdu1045 炮台的配置 dfs

    只要炮台在同一行或者同一列,就可以互相摧毁,遇到墙则无法对墙后的炮台造成伤害,可以通过dfs搜索n*n的方格,全部搜完算一轮,计算炮台数,并保存其最大值. 其中对于t编号的炮台,位置可以计算出是(t/ ...

  5. ML-Agents(三)3DBall例子

    ML-Agents(三)3DBall例子 前一周忙着公司的考试,都没有怎么学新的,今天补上~ 之后的记录,我准备先只研究官方的示例,主要是把研究过程中的疑惑和想法记下来.首先我先补充一下如何利用GPU ...

  6. Anaconda常用命令收藏----后期还会更新

    一.更换jupyter notobook的打开目录 一般载在安装好Anaconda的时候,打开jupyter指向的目录一般是系统的根目录,如C:\Users\25282,但是这样的话对C盘是个不小的负 ...

  7. C# NAudio录音和播放音频文件及实时绘制音频波形图(从音频流数据获取,而非设备获取)

    下午写了一篇关于NAudio的录音.播放和波形图的博客,不太满意,感觉写的太乱,又总结了下 NAudio是个相对成熟.开源的C#音频开发工具,它包含录音.播放录音.格式转换.混音调整等功能.本次介绍主 ...

  8. HDU - 1999 不可摸数,快速求因子和

    题意:定义s[m]为m内的因子的和,给定一个n,判断是否有s[m]==n,若没有,则是不可摸数. 思路:首先要打表求出s[m]的值,标记这些出现过的值. 打表求因子和: for(int i=1;i&l ...

  9. Reactor模式和Proactor模式

    Reactor 主线程往epoll内核事件表中注册socket上的读就绪事件 主线程调用epoll_wait等待socket上有数据可读 当socket上有数据可读时,epoll_wait通知主线程, ...

  10. 汇编学习-三(VB)

    闲来无事做了一下160个crackme,因为是VB程序,所以将得到的一点心得记录如下(OD加载注释) push eax ; Andréna.004018A8 call dword ptr ds:[&l ...