接口

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. Fiddler2 下断点修改HTTP报文

    一 Fiddler中设置断点修改HTTP请求 方法1:全局断点.Rules-->Automatic BreakPoint-->Before Requests(或快捷键F11),这种方法会拦 ...

  2. 从原子类和Unsafe来理解Java内存模型,AtomicInteger的incrementAndGet方法源码介绍,valueOffset偏移量的理解

    众所周知,i++分为三步: 1. 读取i的值 2. 计算i+1 3. 将计算出i+1赋给i 可以使用锁来保持操作的原子性和变量可见性,用volatile保持值的可见性和操作顺序性: 从一个小例子引发的 ...

  3. Peek和Pop功能开发(3D Touch开发之一)

    1.哪些设备支持3D Touch iPhone 6s/Plus.iPhone 7s/Plus.iPhone 8s/Plus.iPhone X (系统必须是iOS9或者更新的版本) 2.怎么实现Peek ...

  4. 五分钟学Java:一篇文章搞懂spring和springMVC

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 什么是Spring,为什么你要学习spring? 你第一次接触spring框架是在什么时候?相信很多人和我一样,第一次了 ...

  5. mysql存储过程简单用法

    show procedure status 查看所有存储过程 <!--  简单存储过程  --> 先将结束符改成// delimiter // create procedure query ...

  6. 洛谷1880 区间dp+记忆化搜索 合并石子

    题目网址:https://www.luogu.com.cn/problem/P1880 题意是:给定一个序列,最小规则是相邻两个值的合并,开销是他们的和,将整个序列合并成一个值的情况下,求解该值的最小 ...

  7. 老板说,Vim宏都不会用,你的工作效率太低啦~

    工作中,对于文本文件的编辑我们经常有这样的需求: 多次重复输入一段相同文本: 生成一段序列化的文本: 每行文本插入一句相同的文本. 除此之外,还有很多需要重复操作的动作.对于这些需求,如果我们人工去操 ...

  8. [JVM教程与调优] 了解JVM 堆内存溢出以及非堆内存溢出

    在上一章中我们介绍了JVM运行时参数以及jstat指令相关内容:[JVM教程与调优] 什么是JVM运行时参数?.下面我们来介绍一下jmap+MAT内存溢出. 首先我们来介绍一下下JVM的内存结构. J ...

  9. VMware使用总结

    1.处理器设置释疑 比如一个8核16线程处理器 处理器数量最多设置为8,而每个处理器的内核数量*处理器个数必须小于等于16. 2.虚拟网络编辑器 NAT模式中可通过NAT设置将内部端口映射到主机端口. ...

  10. GB2312,GBK和UTF-8的区别

    GBK GBK包含全部中文字符, GBK的文字编码是双字节来表示的,即不论中.英文字符均使用双字节来表示,只不过为区分中文,将其最高位都定成1.至于UTF-8编码则是用以解决国际上字符的一种多字节编码 ...