接口

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. 学习笔记----C语言的面向对象

    2020-03-26    21:27:17 面向对象的编程语言都有一个类的概念,像Java.python等.类是对特定数据的特定操作的集合体.它包含两个范畴:数据和操作.C语言是没有类的概念的,但是 ...

  2. 脏牛提权CVE-2016-5195

    gcc -pthread dirtyc0w.c -o dirtyc0w 尝试使用gcc -pthread dirtyc0w.c -o dirtyc0w 编译该POC文件 gcc命令是一个编译器套件,可 ...

  3. HDOJ 1301最小生成树的Kruskal算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1301 将结点的字符信息处理成点信息即可,代码如下: #include<bits/stdc++.h ...

  4. wr720n v4 折腾笔记(四):安装inode客户端njitclient

    前记: 既然折腾到这里,那就不怕再折腾一下了,之前说过最终还是安装南浦月大神的固件,折腾了一圈,怎么不直接在官方界面上安装呢,这里给出直接安装的方法,就是修改固件头为wr720nv4. 0x01 修改 ...

  5. P1969 积木大赛 题解

    原题链接 简要题意: 每次把一段区间 \(+1\),问得到 \(a\) 数组的最小次数. 我们可以把 \(+1\) 得到 \(a\) 换成,从 \(a\) 依次 \(-1\) 得到 \(0\). 算法 ...

  6. html5 window.postMessage 传递数据的使用

    window.postMessage(图片介绍): 发送方(图片介绍): 接收方(图片介绍): 个人测试一(iframe): 发送方,地址为:http://localhost:63342/HelloH ...

  7. 牛客网剑指offer【Python实现】——part2

    不用加减乘除做加法 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.*./四则运算符号. 两个数异或:相当于每一位相加,而不考虑进位: 两个数相与,并左移一位:相当于求得进位: 将上述两步的 ...

  8. WordPress 版本升级、主题升级记录

    版本升级 升级很简单,但是以防万一,先备份数据. 一.备份数据库 mysqldump -u root -p --database myblog > myblog.sql 若需要还原可执行如下操作 ...

  9. mysql打开general log的办法

    mysql打开general log的办法   mysql打开general log之后,所有的查询语句都可以在general log 文件中以可读的方式得到,但是这样general log文件会非常 ...

  10. TensorFlow 实战卷积神经网络之 LeNet

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识! LeNet 项目简介 1994 年深度学习三巨头之一的 Yan L ...