接口

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. TCP IP Socket In C, 2e-chapter 1 Introduction

    本章是基础概念,建议补计算机网络基础,这里不全. 目录 1 网络,数据包,协议 2 关于地址(address) 2.1 IP地址格式 2.2 IPv4和IPv6共存 2.3 端口号 2.4 特殊地址 ...

  2. C++类复习及新的认识 6.1.1+6.1.2内容(适合看过一遍书的新手)

    作者水平有限,文字表述大多摘抄课本,源码部分由课本加自己改编而成,所有代码均在vs2019中编译通过 定义类操作 class Tdate { public: void Set(int m, int d ...

  3. Cisco二层交换机命令

    1.二层交换机基本配置 Switch >Switch >enable   # 进入特权模式 Switch#configure terminal   # 进入全局配置模式 Switch(co ...

  4. Javascript之封装运动函数

    @ 目录 阶段一.仅适用单位带px属性的匀速运动 阶段二.可适用单位不带px属性(如opacity)的匀速运动 阶段三.适用于多元素单一属性的匀速运动 阶段四.适用于多元素单一属性的匀速或缓冲运动 阶 ...

  5. JSP九大内置对象及其作用以及四大域对象

    一,什么是内置对象? 在jsp开发中会频繁使用到一些对象,如ServletContext HttpSession PageContext等.如果每次我们在jsp页面中需要使用这些对象都要自己亲自动手创 ...

  6. 使用PostgreSQL注意事项

    一.大小写特别敏感 大写字段需要用“”引号(pg字段名使用“”,MySQL字段名使用``) ******表名以及字段名如果是小写但是为关键字,比如name,则也需使用"": 二.分 ...

  7. 面试刷题26:新冠攻击人类?什么攻击java平台?

    可恶的新冠病毒攻击人类,搞得IT就业形势相当不好?好在有钟南山院士带领我们提前开展好了防护工作! java作为基础平台安装在各种移动设备,PC,小型机,分布式服务器集群,各种不同的操作系统上.所以,对 ...

  8. window的三种系统弹框介绍

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

  9. CUDA编程入门

    CUDA是一个并行计算框架.用于计算加速.是nvidia家的产品.广泛地应用于现在的深度学习加速. 一句话描述就是:cuda帮助我们把运算从cpu放到gpu上做,gpu多线程同时处理运算,达到加速效果 ...

  10. 基于Andriod的简易计算器

    这学期有安卓这门课,这里做了一个简易的计算器,实现了两位数加减乘除的基本功能,比较简单适合用来入门学习. 运行效果 预备知识 实现这个计算器之前要先了解实现计算器需要的基本组件 1.TextView ...