设为首页 加入收藏

TOP

Go基础系列:Go接口(一)
2018-11-02 20:08:31 】 浏览:210
Tags:基础 系列 接口

接口用法简介

接口(interface)是一种类型,用来定义行为(方法)。

type Namer interface {
    my_method1()
    my_method2(para)
    my_method3(para) return_type
    ...
}

但这些行为不会在接口上直接实现,而是需要用户自定义的方法来实现。所以,在上面的Namer接口类型中的方法my_methodN都是没有实际方法体的,仅仅只是在接口Namer中存放这些方法的签名(签名 = 函数名+参数(类型)+返回值(类型))。

当用户自定义的类型实现了接口上定义的这些方法,那么自定义类型的值(也就是实例)可以赋值给接口类型的值(也就是接口实例)。这个赋值过程使得接口实例中保存了用户自定义类型实例。

例如:

package main

import (
    "fmt"
)

// Shaper 接口类型
type Shaper interface {
    Area() float64
}

// Circle struct类型
type Circle struct {
    radius float64
}

// Circle类型实现Shaper中的方法Area()
func (c *Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}

// Square struct类型
type Square struct {
    length float64
}

// Square类型实现Shaper中的方法Area()
func (s *Square) Area() float64 {
    return s.length * s.length
}

func main() {
    // Circle类型的指针类型实例
    c := new(Circle)
    c.radius = 2.5

    // Square类型的值类型实例
    s := Square{3.2}

    // Sharpe接口实例ins1,它只能是值类型的
    var ins1 Shaper
    // 将Circle实例c赋值给接口实例ins1
    // 那么ins1中就保存了实例c
    ins1 = c
    fmt.Println(ins1)

    // 使用类型推断将Square实例s赋值给接口实例
    ins2 := s
    fmt.Println(ins2)
}

上面将输出:

&{2.5}
{3.2}

从上面输出结果中可以看出,两个接口实例ins1和ins2被分别赋值后,分别保存了指针类型的Circle实例c和值类型的Square实例s

另外,从上面赋值ins1和ins2的赋值语句上看:

ins1 = c
ins2 := s

是否说明接口实例ins就是自定义类型的实例?实际上接口是指针类型(指向什么见下文)。这个时候,自定义类型的实例c、s称为具体实例,ins实例是抽象实例,因为ins接口中定义的行为(方法)并没有具体的行为模式,而c、s中的行为是具体的。

因为接口实例ins也是自定义类型的实例,所以当接口实例中保存了自定义类型的实例后,就可以直接从接口上调用它所保存的实例的方法。例如:

fmt.Println(ins1.Area())   // 输出19.625
fmt.Println(ins2.Area())   // 输出10.24

这里ins1.Area()调用的是Circle类型上的方法Area(),ins2.Area()调用的则是Square类型上的方法Area()。这说明Go的接口可以实现面向对象中的多态:可以按需调用名称相同、功能不同的方法

接口实例中存的是什么

前面说了,接口类型是指针类型,但是它到底存放了什么东西?

接口类型的数据结构是2个指针,占用2个机器字长。

当将类型实例c赋值给接口实例ins1后,用println()函数输出ins1和c,比较它们的地址:

println(ins1)
println(c)

输出结果:

(0x4ceb00,0xc042068058)
0xc042068058

从结果中可以看出,接口实例中包含了两个地址,其中第二个地址和类型实例c的地址是完全相同的。而第二个地址c是Circle的指针类型实例,所以ins中的第二个值也是指针。

ins中的第一个是指针是什么?它所指向的是一个内部表结构iTable,这个Table中包含两部分:第一部分是实例c的类型信息,也就是*Circle,第二部分是这个类型(Circle)的方法集,也就是Circle类型的所有方法(此示例中Circle只定义了一个方法Area())。

所以,如图所示:

注意,上图中的实例c是指针,是指针类型的Circle实例。

对于值类型的Square实例s,ins2保存的内容则如下图:

方法集(Method Set)规则

官方手册对Method Set的解释:https://golang.org/ref/spec#Method_sets

实例的method set决定了它所实现的接口,以及通过receiver可以调用的方法。

方法集是类型的方法集合,对于非接口类型,每个类型都分两个Method Set:值类型实例是一个Method Set,指针类型的实例是另一个Method Set。两个Method Set由不同receiver类型的方法组成:

实例的类型       receiver
--------------------------------------
 值类型:T       (T Type)
 指针类型:*T    (T Type)或(T *Type)

也就是说:

  • 值类型的实例的Method Set只由值类型的receiver(T Type)组成
  • 指针类型的实例的Method Set由值类型和指针类型的receiver共同组成,即(T Type)(T *Type)

这是什么意思呢?从receiver的角度去考虑:

receiver        实例的类型
---------------------------
(T Type)        T 或 *T
(T *Type)       *T

上面的意思是:

  • 如果某类型实现接口的方法的receiver是(T *Type)类型的,那么只有指针类型的实例*T才算是实现了这个接口
  • 如果某类型实现接口的方法的receiver是(T Type)类型的,那么值类型的实例T和指针类型的实例*T都算实现了这个接口

举个例子。接口方法Area(),自定义类型Circle有一个receiver类型为(c *Circle)的Area()方法时,说明实现了接口的方法,但只有Circle实例的类型为指针类型时,这个实例才算是实现了接口,才能赋值给接口实例,才能当作一个接口参数。如下:

package main

import "fmt"

// Shaper 接口类型
type Shaper interface {
    Area() float64
}

// Circle struct类型
type Circle struct {
    radius float64
}

// Circle类型实现Shaper中的方法Area()
// receiver类型为指针类型
func (c *Circle) Area() float64 {
    return 3.14 * c.radius * c.radius
}

func main() {
    // 声明2个接口实例
    var ins1, ins2 Shaper
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go环境安装 下一篇Go中多个返回值的技巧

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目