golang中接口接口的深度解析

  


  

  

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

  

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

        类型读者接口{   读(p[]字节)(n int,犯错os.Error)   }//作家是包裹了基础写方法的接口。   {作家接口类型   写(p[]字节)(n int,犯错os.Error)   }      var r io.Reader   r=os.Stdin   r=bufio.NewReader(右)   r=新(bytes.Buffer)      

有一个事情是一定要明确的,不论r保存了什么值,r的类型总是<代码> io。读者>   

  


  

  

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

        {斯特林格接口类型   字符串()字符串   }      类型的二进制uint64      func(二进制)字符串String () {   返回strconv.FormatUint (i.Get (), 2)   }      uint64 func(二进制)得到(){   返回uint64(我)   }      函数main () {   var b二进制=32   s:=斯特林格(b)   fmt.Print (s.String ())   }      

 golang中接口接口的深度解析”>,,<br/>
  </p>
  <p>观察itable的结构,首先是描述类型信息的一些元数据,然后是满足串接口的函数指针列表(注意,这里不是实际类型二进制的函数指针集哦)。因此我们如果通过接口进行函数调用,实际的操作其实就是<代码> s.tab→乐趣[0](s.data)> </代码。是不是和c++的虚表很像?但是他们有本质的区别。先看c++,它为每个类创建了一个方法集即虚表,当子类重写父类的虚函数时,就将表中的相应函数指针改为子类自己实现的函数,如果没有则指向父类的实现,当面临多继承时,c++对象结构里就会存在多个虚表指针,每个虚表指针指向该方法集的不同部分。我们再来看golang的实现方式,同c++一样,golang也为每种类型创建了一个方法集,不同的是接口的虚表是在运行时专门生成的,而c++的虚表是在编译时生成的(但是c++虚函数表表现出的多态是在运行时决定的)。例如,当例子中当首次遇见<代码> s:=斯特林格(b) </代码>这样的语句时,golang会生成斯金格接口对应于二进制类型的虚表,并将其缓存。那么为什么不去采用c++的方式来实现呢?这根c++和golang的对象内存布局是有关系的。<br/>
  </p>
  <p>首先c++的动态多态是以继承为基础的,在对象构造初始化的时首先会初始化父类,其次是子类,也就是说一个对象的内存布局是虚表,父类部分,子类部分(编译器不同可能会有差异),当一个父类指针指向子类时,会发生内存的截断,截断子类部分(内存地址偏移),但是此时子类的虚表中的函数指针实际上还是指向了自己的实现,所以此时的指针才会调用到子类的虚函数,如果不是虚函数,因为内存已经截断没有子类的非虚函数信息了,所以只能调用父类的了,这种继承关系让c++的虚表的初始化非常清晰,在一个对象初始化时先调用父类的构造此时虚表跟父类是一样的,接下来初始化子类,此时编译器就会去识别子类有没有覆盖父类的虚函数,如果有则虚表中相应的函数指针改成自己的虚函数实现指针。<h2 class=golang中接口接口的深度解析