go interface

Go不是一种典型的OO语言,它在语法上不支持类和继承的概念。 没有继承是否就无法拥有多态行为了呢?答案是否定的,Go语言引入了一种新类型—Interface,它在效果上实现了类似于C++的“多态”概念,虽然与C++的多态在语法上并非完全对等,但至少在最终实现的效果上,它有多态的影子。

虽然Go语言没有类的概念,但它支持的数据类型可以定义对应的method(s)。本质上说,所谓的method(s)其实就是函数,只不过与普通函数相比,这类函数是作用在某个数据类型上的,所以在函数签名中,会有个receiver(接收器)来表明当前定义的函数会作用在该receiver上。

Go语言支持的除Interface类型外的任何其它数据类型都可以定义其method(而并非只有struct才支持method),只不过实际项目中,method(s)多定义在struct上而已。

从这一点来看,我们可以把Go中的struct看作是不支持继承行为的轻量级的“类”。

An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil. 说实话,这段说明对新手来说比较晦涩,这正是本篇笔记试图解释清楚的地方。 从语法上看,Interface定义了一个或一组method(s),这些method(s)只有函数签名,没有具体的实现代码(有没有联想起C++中的虚函数?)。若某个数据类型实现了Interface中定义的那些被称为"methods"的函数,则称这些数据类型实现(implement)了interface。举个例子来说明。

package main



import (

    "fmt"

    "math"

)



type Abser interface {

    Abs() float64

}



type MyFloat float64



func (f MyFloat) Abs() float64 {

    if f < 0 {

        return float64(-f)

    }

    return float64(f)

}



func main() {

    var a Abser

    fmt.Println(float64(math.Sqrt2))

    f := MyFloat(-math.Sqrt2)

    a = f // a MyFloat implements Abser

    fmt.Println(a.Abs())

}



//输出

1.4142135623730951

1.4142135623730951

上面的代码中,第8-10行是通过type语法声明了一个名为Abser的interface类型(Go中约定的interface类型名通常取其内部声明的method名的er形式)。而第12-19行通过type语法声明了MyFloat类型且为该类型定义了名为Abs()的method。 根据上面的解释,Abs()是interface类型Abser定义的方法,而MyFloat实现了该方法,所以,MyFloat实现了Abser接口。

Interface类型的更通用定义可归纳如下:

type Namer interface {

    Method1(param_list) return_type

    Method2(param_list) return_type

    ...

}

上面的示例用type语法声明了一个名为Namer的interface类型(但Namer不是个具体的变量,此时内存中还没有它对应的对象)。interface类型是可以定义变量的,也即interface type can have values,例如:

  1. var ai Namer

此时,定义了一个变量名为ai的Namer类型变量,在Go的底层实现中,ai本质上是个指针,其内存布局如下(内存布局图引用自<The Way to Go - A Thorough Introduction to the Go Programming Language>一书第11.1节):

它的method table ptr是不是与C++中类的虚函数表非常类似?而这正是interface类型的变量具有多态特性的关键: ai共占2个机器字,1个为receiver字段,1个为method table ptr字段。ai可以被赋值为任何变量,只要这个变量实现了interface定义的method(s) set,赋值后,ai的receiver字段用来hold那个变量或变量副本的地址(若变量类型小于等于1个机器字大小,则receiver直接存储那个变量;若变量类型大于1个机器字,则Go底层会在堆上申请空间存储那个变量的副本,然后receiver存储那个副本的地址,即此时receiver是个指向变量副本的指针)。而由变量实现的接口method(s)组成的interface table的指针会填充到ai的method table ptr字段。当ai被赋值为另一个变量后,其receiver和method table ptr会更新为新变量的相关值。 关于interface类型内部实现细节,可以参考GoLang官网Blog推荐过的一篇文章“Go Data Structures: Interfaces”,写的很清楚,强烈推荐。 所以,如果某个函数的入参是个interface类型时,任何实现了该interface的变量均可以作为合法参数传入且函数的具体行为会自动作用在传入的这个实现了interface的变量上,这不正是类似于C++中多态的行为吗?

Interface“多态”特性实例 Go语言自带的标准Packages提供的接口很多都借助了Interface以具备“可以处理任何未知数据类型”的能力。例如被广泛使用的fmt包,其功能描述如下: Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler. 它除了可以格式化打印Go的built-in类型外,还可以正确打印各种自定义类型,只要这些自定义数据类型实现了fmt的Print API入参所需的interface接口。

以fmt包的Printf()函数为例,其函数签名格式如下:

  1. func Printf(format string, a ...interface{}) (n int, err error)

它的入参除了用以描述如何格式化的'format'参数外,还需要interface类型的可变长参数。该函数在实现底层的打印行为时,要求传入的可变长参数实现了fmt包中定义的Stringer接口,这个接口类型定义及描述如下:

type Stringer interface {

        String() string

}



复制代码
所以,自定义类型想要调用fmt.Printf()做格式化打印,那只需实现Stringer接口就行。
例如,下面是一段简单的打印代码:
package main



import "fmt"



type IPAddr [4]byte



func main() {

    addrs := map[string]IPAddr{

        "loopback":  {127, 0, 0, 1},

        "googleDNS": {8, 8, 8, 8},

    }

    for n, a := range addrs {

        fmt.Printf("%v: %v\n", n, a)

    }

}


http://studygolang.com/articles/2652

http://www.yiibai.com/go/go_printfs.html

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2017-08-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑白安全

关于PHP语言在内存中的分配(堆和栈的区别)

本文以PHP语言为例来分析计算机中各段存储区的区别,代码段、堆空间段、代码段、初始化静态常量段。

2992
来自专栏C语言及其他语言

【编程经验】变量的存储类型

在 C 语言中,变量是对程序中数据所占内存空间的一种抽象定义,定义变量时,用户定义变量的名、 变量的类型,这些都是变量的操作属性。不仅可以通过变量...

1071
来自专栏coding for love

JS原生引用类型解析7-Promise类型

ES6引入了一个全新的对象Promise,用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。Promise最直接的好处就是链式调用,另外在错误捕获上...

741
来自专栏AI研习社

最常见的 35 个 Python 面试题及答案(2018 版)

作为一个 Python 新手,你必须熟悉基础知识。在本文中我们将讨论一些 Python 面试的基础问题和高级问题以及答案,以帮助你完成面试。包括 Python ...

8033
来自专栏大数据钻研

Java基础语法

java 基 础 语 法 一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念...

2916
来自专栏HappenLee的技术杂谈

C++雾中风景6:拷贝构造函数与赋值函数

这里我们显式声明了拷贝构造函数与赋值构造函数,接下来我们用一小段代码测试一下上面定义的类。(其他函数的定义并不完整,读者可以之行补全)

912
来自专栏前端小叙

JS中函数声明与函数表达式的异同

相同点 注:函数声明和函数表达式的相同点包括但不限于以下几点 函数是一个值,所以和其他值一样,函数也可以进行被输出、被赋值、作为参数传给其他函数等相关操作,不...

2955
来自专栏java一日一条

Java常量池解析与字符串intern简介

  在Java应用程序运行时,Java虚拟机会保存一份内部的运行时常量池,它区别于class文件的常量池,是class文件常量池映射到虚拟机中的数据结构。 关于...

1002
来自专栏web前端-

函数基础知识回顾

  String    Number   Boolean   Null    undefined    //占有固定的内存大小,如数值型占八个字节,布尔类型...

753
来自专栏mathor

异常的捕获与处理

1742

扫码关注云+社区

领取腾讯云代金券