0

0

深析golang中interface接口

藏色散人

藏色散人

发布时间:2021-01-28 17:36:45

|

2209人浏览过

|

来源于jb51

转载

下面由golang教程栏目给大家深析golang中interface接口,希望对需要的朋友有所帮助!

一 接口介绍

如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度。Go语言在编程哲学上是变革派,而不是改良派。这不是因为Go语言有gorountine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口。Go语言的编程哲学因为有接口而趋于完美。C++,Java 使用"侵入式"接口,主要表现在实现类需要明确声明自己实现了某个接口。这种强制性的接口继承方式是面向对象编程思想发展过程中一个遭受相当多质疑的特性。Go语言采用的是“非侵入式接口",Go语言的接口有其独到之处:只要类型T的公开方法完全满足接口I的要求,就可以把类型T的对象用在需要接口I的地方,所谓类型T的公开方法完全满足接口I的要求,也即是类型T实现了接口I所规定的一组成员。这种做法的学名叫做Structural Typing,有人也把它看作是一种静态的Duck Typing。

要这个值实现了接口的方法。

type Reader interface { 
 Read(p []byte) (n int, err os.Error) 
} 
 
// Writer 是包裹了基础 Write 方法的接口。 
type Writer interface { 
 Write(p []byte) (n int, err os.Error) 
} 
 
var r io.Reader 
r = os.Stdin 
r = bufio.NewReader(r) 
r = new(bytes.Buffer)

有一个事情是一定要明确的,不论 r 保存了什么值,r 的类型总是 io.Reader ,Go 是静态类型,而 r 的静态类型是 io.Reader。接口类型的一个极端重要的例子是空接口interface{},它表示空的方法集合,由于任何值都有零个或者多个方法,所以任何值都可以满足它。也有人说 Go 的接口是动态类型的,不过这是一种误解。 它们是静态类型的:接口类型的变量总是有着相同的静态类型,这个值总是满足空接口,只是存储在接口变量中的值运行时可能被改变。对于所有这些都必须严谨的对待,因为反射和接口密切相关。

立即学习go语言免费学习笔记(深入)”;

二  接口类型内存布局

在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。一个接口变量可以存储任意实际值(非接口),只要这个值实现了接口的方法。interface在内存上实际由两个成员组成,如下图,tab指向虚表,data则指向实际引用的数据。虚表描绘了实际的类型信息及该接口所需要的方法集。

type Stringer interface { 
 String() string 
} 
 
type Binary uint64 
 
func (i Binary) String() string { 
 return strconv.FormatUint(i.Get(), 2) 
} 
 
func (i Binary) Get() uint64 { 
 return uint64(i) 
} 
 
func main() { 
 var b Binary = 32 
 s := Stringer(b) 
 fmt.Print(s.String()) 
}

  

观察itable的结构,首先是描述type信息的一些元数据,然后是满足Stringger接口的函数指针列表(注意,这里不是实际类型Binary的函数指针集哦)。因此我们如果通过接口进行函数调用,实际的操作其实就是s.tab->fun[0](s.data) 。是不是和C++的虚表很像?但是他们有本质的区别。先看C++,它为每个类创建了一个方法集即虚表,当子类重写父类的虚函数时,就将表中的相应函数指针改为子类自己实现的函数,如果没有则指向父类的实现,当面临多继承时,C++对象结构里就会存在多个虚表指针,每个虚表指针指向该方法集的不同部分。我们再来看golang的实现方式,同C++一样,golang也为每种类型创建了一个方法集,不同的是接口的虚表是在运行时专门生成的,而c++的虚表是在编译时生成的(但是c++虚函数表表现出的多态是在运行时决定的).例如,当例子中当首次遇见s := Stringer(b)这样的语句时,golang会生成Stringer接口对应于Binary类型的虚表,并将其缓存。那么为什么go不采用c++的方式来实现呢?这根c++和golang的对象内存布局是有关系的。

首先c++的动态多态是以继承为基础的,在对象构造初始化的时首先会初始化父类,其次是子类,也就是说一个对象的内存布局是虚表,父类部分,子类部分(编译器不同可能会有差异),当一个父类指针指向子类时,会发生内存的截断,截断子类部分(内存地址偏移),但是此时子类的虚表中的函数指针实际上还是指向了自己的实现,所以此时的指针才会调用到子类的虚函数,如果不是虚函数,因为内存已经截断没有子类的非虚函数信息了,所以只能调用父类的了,这种继承关系让c++的虚表的初始化非常清晰,在一个对象初始化时先调用父类的构造此时虚表跟父类是一样的,接下来初始化子类,此时编译器就会去识别子类有没有覆盖父类的虚函数,如果有则虚表中相应的函数指针改成自己的虚函数实现指针。

那么go有什么不同呢,首先我们很清楚go是没有严格意义上的继承的,go的接口不存在继承关系,只要实现了接口定义的方法都可以成为接口类型,这给go的虚表初始化带来很大的麻烦,到底有多少类型实现了这个接口,一个类型到底实现了多少接口这让编译器很confused。举个例子,某个类型有m个方法,某接口有n个方法,则很容易知道这种判定的时间复杂度为O(mXn),不过可以使用预先排序的方式进行优化,实际的时间复杂度为O(m+n)这样看来其实还行那为什么要在运行时生成虚表呢,这不是会拖慢程序的运行速度吗,注意我们这里是某个类型,某个接口,是1对1的关系,如果有n个类型,n个接口呢,编译器难道要把之间所有的关系都理清吗?退一步说就算编译器任劳任怨把这事干了,可是你在写过程中你本来就不想实现那个接口,而你无意中给这个类型实现的方法中包含了某些接口的方法,你根本不需要这个接口(况且go的接口机制会导致很多这种无意义的接口实现),你欺负编译器就行了,这也太欺负人了吧。如果我们放到运行时呢,我们只要在需要接口的去分析一下类型是否实现了接口的所有方法就行了很简单的一件事。

三 空接口

接口类型的一个极端重要的例子是空接口:interface{} ,它表示空的方法集合,由于任何值都有零个或者多个方法,所以任何值都可以满足它。 注意,[]T不能直接赋值给[]interface{}

//t := []int{1, 2, 3, 4} wrong 
//var s []interface{} = t 
t := []int{1, 2, 3, 4} //right 
s := make([]interface{}, len(t)) 
for i, v := range t { 
 s[i] = v 
}
str, ok := value.(string) 
if ok { 
 fmt.Printf("string value is: %q\n", str) 
} else { 
 fmt.Printf("value is not a string\n") 
}

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

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() 
}

也可以使用“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") 
}

四 接口赋值

package main 
 
import ( 
"fmt" 
) 
 
type LesssAdder interface { 
  Less(b Integer) bool 
  Add(b Integer) 
} 
 
type Integer int 
 
func (a Integer) Less(b Integer) bool { 
  return a < b 
} 
 
func (a *Integer) Add(b Integer) { 
  *a += b 
} 
 
func main() { 
 
  var a Integer = 1 
  var b LesssAdder = &a 
  fmt.Println(b) 
 
  //var c LesssAdder = a 
  //Error:Integer does not implement LesssAdder  
  //(Add method has pointer receiver) 
}

go语言可以根据下面的函数:

func (a Integer) Less(b Integer) bool

自动生成一个新的Less()方法

func (a *Integer) Less(b Integer) bool

这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。 而根据

菜鸟学PHP之Smarty入门
菜鸟学PHP之Smarty入门

  刚开始接触模版引擎的 PHP 设计师,听到 Smarty 时,都会觉得很难。其实笔者也不例外,碰都不敢碰一下。但是后来在剖析 XOOPS 的程序架构时,开始发现 Smarty 其实并不难。只要将 Smarty 基础功练好,在一般应用上就已经相当足够了。当然基础能打好,后面的进阶应用也就不用怕了。  这篇文章的主要用意并非要深入探讨 Smarty 的使用,这在官方使用说明中都已经写得很完整了。笔

下载
func (a *Integer) Add(b Integer)

这个函数无法生成以下成员方法:

func(a Integer) Add(b Integer) { 
  (&a).Add(b) 
}

因为(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响(值传递),这不符合用户的预期。所以Go语言不会自动为其生成该函数。因此类型Integer只存在Less()方法,缺少Add()方法,不满足LessAddr接口。(可以这样去理解:指针类型的对象函数是可读可写的,非指针类型的对象函数是只读的)将一个接口赋值给另外一个接口 在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就等同的,可以相互赋值。 如果A接口的方法列表时接口B的方法列表的子集,那么接口B可以赋值给接口A,但是反过来是不行的,无法通过编译。

五 接口查询

接口查询是否成功,要在运行期才能够确定。他不像接口的赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。

var file1 Writer = ...
if file5,ok := file1.(two.IStream);ok {
...
}

这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执行特定的代码。

在Go语言中,你可以询问它指向的对象是否是某个类型,比如,

var file1 Writer = ...
if file6,ok := file1.(*File);ok {
...
}

这个if语句判断file1接口指向的对象实例是否是*File类型,如果是则执行特定的代码。

slice := make([]int, 0)
slice = append(slice, 1, 2, 3)

var I interface{} = slice


if res, ok := I.([]int);ok {
  fmt.Println(res) //[1 2 3]
}

这个if语句判断接口I所指向的对象是否是[]int类型,如果是的话输出切片中的元素。

func Sort(array interface{}, traveser Traveser) error {

  if array == nil {
    return errors.New("nil pointer")
  }
  var length int //数组的长度
  switch array.(type) {
  case []int:
    length = len(array.([]int))
  case []string:
    length = len(array.([]string))
  case []float32:
    length = len(array.([]float32))

  default:
    return errors.New("error type")
  }

  if length == 0 {
    return errors.New("len is zero.")
  }

  traveser(array)

  return nil
}

通过使用.(type)方法可以利用switch来判断接口存储的类型。

小结: 查询接口所指向的对象是否为某个类型的这种用法可以认为是接口查询的一个特例。接口是对一组类型的公共特性的抽象,所以查询接口与查询具体类型区别好比是下面这两句问话的区别:

你是医生么?

是。

你是莫莫莫


第一句问话查询的是一个群体,是查询接口;而第二个问句已经到了具体的个体,是查询具体类型。

除此之外利用反射也可以进行类型查询,会在反射中做详细介绍。

更多golang相关技术文章,请访问go语言栏目!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

182

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

229

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

343

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

394

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

220

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

193

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

398

2025.06.17

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
golang socket 编程
golang socket 编程

共2课时 | 0.1万人学习

nginx浅谈
nginx浅谈

共15课时 | 0.8万人学习

golang和swoole核心底层分析
golang和swoole核心底层分析

共3课时 | 0.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号