Golang-interface(一 基本使用)

github: https://github.com/ZhangzheBJUT/blog/blob/master/interface.md

一 接口概述

如果说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

Go 是静态类型的。每一个变量有一个静态的类型,也就是说,有一个已知类型并且在编译时就确定下来了

type MyInt int
var i int
var j MyInt

那么 i 的类型为 int 而 j 的类型为 MyInt。即使变量 i 和 j 有相同的底层类型,它们仍然是有不同的静态类型的。未经转换是不能相互赋值的。

二 接口类型

在类型中有一个重要的类别就是接口类型,表达了固定的一个方法集合。一个接口变量可以存储任意实际值(非接口),只要这个值实现了接口的方法。

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 的接口是动态类型的,不过这是一种误解。 它们是静态类型的:接口类型的变量总是有着相同的静态类型,这个值总是满足空接口,只是存储在接口变量中的值运行时可能被改变。

对于所有这些都必须严谨的对待,因为反射和接口密切相关。

三 接口的特色

接口类型的变量存储了两个内容:赋值给变量实际的值和这个值的类型描述。更准确的说,值是底层实现了接口的实际数据项目,而类型描述了这个项目完整的类型。例如下面,

var r io.Reader
tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil { return nil, err }
r = tty

用模式的形式来表达 r 包含了的是 (value, type) 对,如 (tty, *os.File)。

注意: 类型 *os.File 除了 Read 方法还实现了其他方法:尽管接口值仅仅提供了访问 Read 方法的可能(即通过r 只能访问Read方法),但是内部包含了这个值的完整的类型信息(反射的依据)。

这也就是为什么可以这样做:

var w io.Writer
w = r.(io.Writer) //接口查询

在这个赋值中的断言是一个类型断言:它断言了 r 内部的条目同时也实现了 io.Writer,因此可以赋值它到 w。在赋值之后,w 将会包含 (tty, *os.File),跟在 r 中保存的一致。

接口的静态类型决定了哪个方法可以通过接口变量调用,即便内部实际的值可能有一个更大的方法集。

接下来,可以这样做:

var empty interface{}
empty = w

而空接口值 e 也将包含同样的 (tty, *os.File)。这很方便:空接口可以保存任何值同时保留关于那个值的所有信息。

注:这里无需类型断言,因为 w 肯定满足空接口的。在上面的个例子中,将一个值从 Reader 变为 Writer,由于 Writer 的方法不是 Reader 的子集,所以就必须明确使用类型断言。

一个很重要的细节是接口内部的对总是 (value, 实际类型) 的格式,而不会有 (value, 接口类型) 的格式。接口不能保存接口值。

四 接口赋值

接口赋值在Go语言中分为两种情况: 1.将对象实例赋值给接口 2.将一个接口赋值给另外一个接口

  • 将对象实例赋值给接口

    看下面的例子:

      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接口。 而根据

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来判断接口存储的类型。

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

你是医生么?

是。

你是莫莫莫

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

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

参考: http://research.swtch.com/interfaces

Golang-interface(一 基本使用)

时间: 2024-10-22 20:44:01

Golang-interface(一 基本使用)的相关文章

golang:interface{}类型测试

在golang中空的interface即interface{}可以看作任意类型, 即C中的void *. 对interface{}进行类型测试有2种语法: 1. Comma-ok断言: value, ok = element.(T), 其中T是具体类型. 2. Switch测试: switch element.(type) { case T1: case T2: default: } 其中T1, T2是具体类型. 注意: element.(type)语法不能在switch外的任何逻辑里使用. 在

golang - interface的作用

多态.struct 可以赋值给 interface.interface 可以转换成子接口,或者 struct. 请看go中的一段的源代码: listener, _ := net.Listen("tcp", "localhost:8000") tcpListener := listener.(*net.TCPListener) conn,_ := tcpListener.Accept() 仔细拜读源码可知: net.Listen() 返回了一个 Listener接口,

[golang] 基于 golang interface 特性衍生的任务流处理思维

在设计程序的许多应用场景中我们会遇到大体分为三个阶段的任务流. 第一.入口 一个或多个入口,等待阻塞的.或者主动请求方式的. ============================== 比如任务流需要接受来自于 HTTP 和 FTP 的应用请求,后续还有可能增加别的方式的接受请求. 第二.处理 多个入口可以对应一个处理程序,也可以对应多个处理程序. ================================== 比如 HTTP 和 FTP 的多个入口程序需要对应一个和多个处理逻辑,同样也

golang interface

什么是接口? 简单的说,interface是一组method签名的组合,我们通过interface来定义对象的一组行为.interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口 .     Go中的接口为指定对象的行为提供了一种方式:如果事情可以这样做,那么它就可以在这里使用.我们已经看到一些简单的例子:自定义的打印可以通过String() 方法来实现,而Fprintf 可以通过Write 方法输出到任意的地方.只有一个或两个方法的接口在Go代码中很常见

golang interface接口

package main import "fmt" type Shaper interface { Area() float32 } type Square struct { side float32 } func (sq *Square) Area() float32 { return sq.side * sq.side } func main() { sq1 := new(Square) sq1.side = 5 var areaIntf Shaper areaIntf = sq1

Golang学习笔记(1)---go程序一般结构

Go程序是通过 package来组织的(与python的库类似) 只有package名称为main的包可以包涵main函数(同时main函数也是一个程序的入口) 一个可执行程序有且仅有一个main包 通过import关键字来引入其他非main包 通过const关键字来进行常量的定义 通过在函数体外部使用var关键字来进行全局变量的声明与赋值 通过type关键字来进行结构(struct)或接口(interface)的声明----一般(自定义)类型 通过func关键字来进行函数的声明 一般格式为:

Golang 笔记 1

一.Go语言基础 1. 基础 Go语言中的标识符必须以字母(Unicode字母,PHP/JS可以用中文作为变量名)下划线开头.大写字母跟小写字母是不同的:Hello和hello是两个不同的名字.  Go中有25个关键字: break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for imp

Golang 接口与反射知识要点

目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() Interface() 6. 反射对象的可设置性 SetXXX(), CanSet() Elem() 7. Struct 的反射 NumField(), Type.Field(i int) Value.Field(i int) 参考文档 Golang 接口与反射知识要点 这篇文章以 Go 官方经典博

Go语言的学习

1.配置环境变量 2.本地阅读报的说明和文档 不用FQ window+R  出现黑窗口   执行    godoc -http :8080 在本地浏览器 localhost:8080 回车 3多行注释  /**/ 4关键字 25 const  PI =3.14     //常量的声明 type newType //一般类型 var name ="gopher" //全局变量的声明 //结构声明 type gopher struct {} //接口 type golang interfa

深度解密Go语言之Slice

目录 当我们在说 slice 时,到底在说什么 slice 的创建 直接声明 字面量 make 截取 slice 和数组的区别在哪 append 到底做了什么 为什么 nil slice 可以直接 append 传 slice 和 slice 指针有什么区别 总结 参考资料 Go 语言的 slice 很好用,不过也有一些坑.slice 是 Go 语言一个很重要的数据结构.网上已经有很多文章写过了,似乎没必要再写.但是每个人看问题的视角不同,写出来的东西自然也不一样.我这篇会从更底层的汇编语言去解