接口

概述

如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键。在Go语言的实际编程中,几乎所有的数据结构都围绕接口展开,接口是Go语言中所有数据结构的核心。

Go语言中的接口是一些方法的集合(method set),它指定了对象的行为:如果它(任何数据类型)可以做这些事情,那么它就可以在这里使用。

type Reader interface {
Read(p []byte) (n int, err error)
} type Writer interface {
Write(p []byte) (n int, err error)
} type Closer interface {
Close() error
} type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}

上面的代码定义了4个接口。

假设我们在另一个地方中定义了下面这个结构体:

type File struct { // ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error

我们在实现File的时候,可能并不知道上面4个接口的存在,但不管怎样,File实现了上面所有的4个接口。我们可以将File对象赋值给上面任何一个接口。

var file1 Reader = new(File)
var file2 Writer = new(File)
var file3 Closer = new(File)
var file4 Seeker = new(File)

因为File可以做这些事情,所以,File就可以在这里使用。File在实现的时候,并不需要指定实现了哪个接口,它甚至根本不知道这4个接口的存在。这种“松耦合”的做法完全不同于传统的面向对象编程。

实际上,上面4个接口来自标准库中的io package。

接口赋值

我们可以将一个实现接口的对象实例赋值给接口,也可以将另外一个接口赋值给接口。

(1)通过对象实例赋值

将一个对象实例赋值给一个接口之前,要保证该对象实现了接口的所有方法。考虑如下示例:

type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
} type LessAdder interface {
Less(b Integer) bool
Add(b Integer)
} var a Integer = 1
var b1 LessAdder = &a //OK
var b2 LessAdder = a //not OK

b2的赋值会报编译错误,为什么呢?还记得<类型方法>一章中讨论的Go语言规范的规定吗?

The method set of any other named type T consists of all methods with receiver type T. The method set of the corresponding pointer type *T is the set of all methods with receiver T or T (that is, it also contains the method set of T).

也就是说
Integer实现了接口LessAdder的所有方法,而Integer只实现了Less方法,所以不能赋值。

(2)通过接口赋值

        var r io.Reader = new(os.File)
var rw io.ReadWriter = r //not ok var rw2 io.ReadWriter = new(os.File)
var r2 io.Reader = rw2 //ok

因为r没有Write方法,所以不能赋值给rw。

接口嵌套

我们来看看io package中的另外一个接口:

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
Reader
Writer
}

该接口嵌套了io.Reader和io.Writer两个接口,实际上,它等同于下面的写法:

type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

注意,Go语言中的接口不能递归嵌套,

// illegal: Bad cannot embed itself
type Bad interface {
Bad
} // illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface {
Bad2
}
type Bad2 interface {
Bad1
}

空接口(empty interface)

空接口比较特殊,它不包含任何方法:

interface{}

在Go语言中,所有其它数据类型都实现了空接口。

var v1 interface{} = 1
var v2 interface{} = "abc"
var v3 interface{} = struct{ X int }{1}

如果函数打算接收任何数据类型,则可以将参考声明为interface{}。最典型的例子就是标准库fmt包中的Print和Fprint系列的函数:

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{})
func Fprintln(w io.Writer, a ...interface{})
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{})
func Println(a ...interface{}) (n int, err error)

注意,[]T不能直接赋值给[]interface{}

        t := []int{1, 2, 3, 4}
var s []interface{} = t

编译时会输出下面的错误:

cannot use t (type []int) as type []interface {} in assignment

我们必须通过下面这种方式:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
s[i] = v
}

类型转换(Conversions)

类型转换的语法:

Conversion = Type "(" Expression [ "," ] ")" .

当以运算符*或者<-开始,有必要加上括号避免模糊:

*Point(p)        // same as *(Point(p))
(*Point)(p) // p is converted to *Point
<-chan int(c) // same as <-(chan int(c))
(<-chan int)(c) // c is converted to <-chan int
func()(x) // function signature func() x
(func())(x) // x is converted to func()
(func() int)(x) // x is converted to func() int
func() int(x) // x is converted to func() int (unambiguous)

Type switch与Type assertions

在Go语言中,我们可以使用type switch语句查询接口变量的真实数据类型,语法如下:

switch x.(type) {
// cases
}

x必须是接口类型。

来看一个详细的示例:

type Stringer interface {
String() string
} var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
return str //type of str is string
case Stringer: //type of str is Stringer
return str.String()
}

语句switch中的value必须是接口类型,变量str的类型为转换后的类型。

If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It's also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.

如果我们只关心一种类型该如何做?如果我们知道值为一个string,只是想将它抽取出来该如何做?只有一个case的类型switch是可以的,不过也可以用类型断言(type assertions)。类型断言接受一个接口值,从中抽取出显式指定类型的值。其语法借鉴了类型switch子句,不过是使用了显式的类型,而不是type关键字,如下:

x.(T)

同样,x必须是接口类型。

str := value.(string)

上面的转换有一个问题,如果该值不包含一个字符串,则程序会产生一个运行时错误。为了避免这个问题,可以使用“comma, ok”的习惯用法来安全地测试值是否为一个字符串:

str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}

如果类型断言失败,则str将依然存在,并且类型为字符串,不过其为零值,即一个空字符串。

我们可以使用类型断言来实现type switch的中例子:

if str, ok := value.(string); ok {
return str
} else if str, ok := value.(Stringer); ok {
return str.String()
}

这种做法没有多大实用价值。

深入学习golang(5)—接口的更多相关文章

  1. [golang note] 接口使用

    侵入式接口 √ 在其他一些编程语言中,接口主要是作为不同组件之间的契约存在,即规定双方交互的规约. √ 对契约的实现是强制的,即必须确保用户的确实现了该接口,而实现一个接口,需要从该接口继承. √ 如 ...

  2. golang(08)接口介绍

    原文链接 http://www.limerence2017.com/2019/09/12/golang13/#more 接口简介 golang 中接口是常用的数据结构,接口可以实现like的功能.什么 ...

  3. 学习Golang语言(6):类型--切片

    学习Golang语言(1): Hello World 学习Golang语言(2): 变量 学习Golang语言(3):类型--布尔型和数值类型 学习Golang语言(4):类型--字符串 学习Gola ...

  4. FastAPI(七十四)实战开发《在线课程学习系统》接口开发-- 删除留言

    之前文章FastAPI(七十三)实战开发<在线课程学习系统>接口开发-- 回复留言,那么我们这次分享删除留言接口的开发 可以对留言进行删除,这里的删除,我们使用的是逻辑的删除,不是物理删除 ...

  5. FastAPI(七十三)实战开发《在线课程学习系统》接口开发-- 回复留言

    之前文章分享FastAPI(七十二)实战开发<在线课程学习系统>接口开发-- 留言列表开发,这次我们分享如何回复留言 按照惯例,我们还是去分析这里面的逻辑. 1.判断用户是否登录 2.用户 ...

  6. FastAPI(七十二)实战开发《在线课程学习系统》接口开发-- 留言列表开发

    之前我们分享了FastAPI(七十一)实战开发<在线课程学习系统>接口开发-- 查看留言,这次我们分享留言列表开发. 列表获取,也需要登录,根据登录用户来获取对应的留言.逻辑梳理如下. 1 ...

  7. FastAPI(七十一)实战开发《在线课程学习系统》接口开发-- 查看留言

    之前FastAPI(七十)实战开发<在线课程学习系统>接口开发--留言功能开发分享了留言开发,这次我们分享查看留言 梳理这里的逻辑,这个接口要依赖登录. 1.判断用户是否登录 2.判断对应 ...

  8. FastAPI(七十)实战开发《在线课程学习系统》接口开发--留言功能开发

    在之前的文章:FastAPI(六十九)实战开发<在线课程学习系统>接口开发--修改密码,这次分享留言功能开发 我们能梳理下对应的逻辑 1.校验用户是否登录 2.校验留言的用户是否存在 3. ...

  9. FastAPI(六十九)实战开发《在线课程学习系统》接口开发--修改密码

    之前我们分享了FastAPI(六十八)实战开发<在线课程学习系统>接口开发--用户 个人信息接口开发.这次我们去分享实战开发<在线课程学习系统>接口开发--修改密码 我们梳理一 ...

随机推荐

  1. TextFile 类的创写

    TextFile 作为自写的方法,继承自List<String>.像统计文本中出现的哪些单词,不重复等等,适合用Set集合完成统计. class TextFile extends Arra ...

  2. servers中添加server时,看不到运行环境的选择。

    servers中添加server时,看不到运行环境的选择. 主要原因是tomcat目录中的配置文件格式不对.

  3. html5新特性之音频、视频

    1.视频 标签video video标签的属性 属性 描述 autoplay 视频就绪后自动播放 preload 视频在页面加载时加载 loop 视频播放完成后循环播放 controls 显示控件 s ...

  4. 关于GP的理解

    连续式中,CMMI有5个GG:阶段式只有3个.多出来的2个GG,一个对应的是阶段4的量化,一个是阶段5的持续优化,确保了阶段式和连续式在范围上的一致性. GG就是讲各个过程中的共有元素抽取出来,形成的 ...

  5. centos 安装flash插件

    方法一: 1.选择合适的yum源http://get.adobe.com/cn/flashplayer/进入此网址选择 “YUM,适用于Linux(YUM)”,下载adobe源http://101.9 ...

  6. 【二分答案】【贪心】bzoj3969

    http://www.cnblogs.com/mmlz/p/4497118.html #include<cstdio> #include<algorithm> using na ...

  7. 斯坦福第七课:正则化(Regularization)

    7.1  过拟合的问题 7.2  代价函数 7.3  正则化线性回归 7.4  正则化的逻辑回归模型 7.1  过拟合的问题 如果我们有非常多的特征,我们通过学习得到的假设可能能够非常好地适应训练集( ...

  8. android listview用adapter.notifyDataSetChanged()无法刷新每项的图标

    http://blog.csdn.net/caizhegnhao/article/details/41318575 今天在开发中遇到一个很奇怪的listview的问题. 这个问题情景是我的应用需要做一 ...

  9. iOS push过去的时候界面不能完全退出

    iOS push过去的时候界面不能完全退出 解决方法:设置self.view.backgroundcolor 1. initWithFrame方法是什么?  initWithFrame方法用来初始化并 ...

  10. golang 远程传输文件

    概述 之前有一篇介绍如何使用 golang 通过SSH协议来执行远程命令:golang 执行远程命令 同样,通过SSH协议也可以使用 golang 来远程传输文件. 除了 SSH 的库,为了传输文件, ...