Golang Interface小结

本文详细介绍了Go语言的接口特性,包括接口的非侵入式定义、鸭子类型的概念以及接口在静态和动态类型中的应用。通过实例解析了接口的结构,如eface和iface,以及类型断言和类型转换的区别。同时,文章探讨了动态和静态类型的差异,并展示了如何在运行时检查接口的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概述

1.1 简介

接口是一种规范,描述了类的行为和功能,而不做具体实现

C++定义接口的方式称为“侵入式”,而Go采用的是“非侵入式”,不需要显式声明,只需要实现了接口定义的函数,编译器自动识别

1.2 鸭子类型

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Duck Typing: 鸭子类型,是动态编程语言的一种对象推断策略,它更关注对象能如何被使用,而不是对象的类型本身。Go语言作为一门静态语言,它通过接口方式完美支持鸭子类型。

1.3 接口特性

接口代表一种调用契约,是多个方法声明的集合。它把所有具有共性的方法定义在一起,任何其他类型只要全部实现了这些方法,就实现了该接口

  • 一个或多个方法签名的集合
  • 只要某类型拥有改接口的所有方法签名,即算实现该接口,无需显示声明实现了那些接口,此称为Structural Typing
  • 接口中只有方法声明,没有实现
  • 接口可匿名嵌入其他接口,或嵌入到结构中
  • 将对象赋值给接口,会发生拷贝,而接口内部存储指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针
  • 只有当接口存储的类型和对象均为nil时,接口才能有nil
  • 接口调用不会做receiver的自动转换
  • 接口同样支持匿名字段方法
  • 接口也可实现类似OOP中的多态
  • 空接口可以作为任何类型数据的容器

2. 接口结构

接口src/runtime/runtime2.go由两个指针构成:

  • 指向变量的类型
  • 指向变量的值

2.1 eface

空接口:不包含任何方法,即 interface{}
在这里插入图片描述

// src/runtime/runtime2.go
type eface struct {
	_type *_type
	data  unsafe.Pointer
}

type _type struct {
  size         uintptr
  ptrdata      uintptr
  hash         uint32
  tflag        tflag
  align        uint8
  fieldalign   uint8
  kind         uint8
  alg          *typeA1g
  gcdata       *byte
  str          nameOff
  ptrToThis    typeOff
}

2.2 iface

非空接口:包含了一组方法集
在这里插入图片描述

// src/runtime/runtime2.go
type iface struct {
	tab  *itab
	data unsafe.Pointer
}

// 非空接口的类型信息
type itab struct {
    inter  *interfacetype  // 静态类型
    _type  *_type          // 动态类型
    link   *itab  
    bad    int32
    inhash int32
    unused [2]byte
    fun    [1]uintptr   // 接口方法实现列表,即函数地址列表,按字典序排序
}

// runtime/type.go
// 非空接口类型,接口定义,包路径等。
type interfacetype struct {
   typ     _type
   pkgpath name
   mhdr    []imethod        // 接口方法声明列表,按字典序排序
}

// 接口的方法声明 
type imethod struct {
   name nameOff                // 方法名
   ityp typeOff                // 描述方法参数返回值等细节
}

类型断言:

// src/rumtime/iface.go
func assertI2I(inter *interfacetype, tab *itab) *itab {
	if tab == nil {
		// explicit conversions require non-nil interface value.
		panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
	}
	if tab.inter == inter {
		return tab
	}
	return getitab(inter, tab._type, false)
}

func assertI2I2(inter *interfacetype, i iface) (r iface) {
	tab := i.tab
	if tab == nil {
		return
	}
	if tab.inter != inter {
		tab = getitab(inter, tab._type, true)
		if tab == nil {
			return
		}
	}
	r.tab = tab
	r.data = i.data
	return
}

func assertE2I(inter *interfacetype, t *_type) *itab {
	if t == nil {
		// explicit conversions require non-nil interface value.
		panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
	}
	return getitab(inter, t, false)
}

func assertE2I2(inter *interfacetype, e eface) (r iface) {
	t := e._type
	if t == nil {
		return
	}
	tab := getitab(inter, t, true)
	if tab == nil {
		return
	}
	r.tab = tab
	r.data = e.data
	return
}

3. 获取类型

3.1 TypeOf

// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

在调用 reflect.TypeOf 函数之前,已发送了一次隐式转换,将具体类型转换为 空接口(intreface{}) ,该过程比较简单,只是 *rtypeunsafe.Pointer 两个指针

在反射包/src/reflect/value.go中:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}

// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
	// see ../runtime/iface.go:/Itab
	itab *struct {
		ityp *rtype // static interface type
		typ  *rtype // dynamic concrete type
		hash uint32 // copy of typ.hash
		_    [4]byte
		fun  [100000]unsafe.Pointer // method table
	}
	word unsafe.Pointer
}

3.2 类型断言

与类型转换的不同:

  • 类型转换:转换前后两个类型要相互兼容才行
  • 类型断言:对接口变量进行操作
func main() {
	var a interface{}

	var x float32 = 1.23
	a = x

	y, ok := a.(float32) // 类型断言
	if ok {
		fmt.Println(y)
	} else {
		fmt.Println("转换失败")
	}
}

3.3 总结

// 方法一:本质使用反射,p.fmt.fmtS(reflect.TypeOf(arg).String())
func typeOf(v interface{}) string {
	return fmt.Sprintf("%T\n", v)
}

// 方法二:反射
func typeOf(v interface{}) string {
	return reflect.TypeOf(v).String()
}

// 方法三:类型断言
func typeOf(v interface{}) string {
	switch v.(type) {
	case int:
		return "int"
	case string:
		return "string"
	default:
		return "unknown"
	}
}

4. 静态和动态类型

  • 静态类型: static type,变量声明时的类型。
  • 动态类型: concrete type,程序运行时的具体类型
var i interface{}   // 静态类型为interface

i = 8     // 动态类型为int
i = "abc" // 动态类型为string

4.1 零值接口

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

ifaceeface 多了一层itab结构。 itab 存储 _type类型信息[]fun方法集,因此接口的 data 指向了nil 并不意味着 interface 等于 nil

零值接口:

  • 动态类型为 nil
  • 值为 nil

示例1:值为nil,但类型不为nil

type Stringer interface {
	String() string
}

type Point struct {
	x, y int
}

func (p *Point) String() string {
	return fmt.Sprintf("(%d,%d)", p.x, p.y)
}

func main() {
	var x Stringer
	fmt.Printf("%T, %v, %t\n", x, x, x == nil) // <nil>, <nil>, true

	x = &Point{1, 2}
	fmt.Printf("%T, %v, %t\n", x, x, x == nil) // *main.Point, (1,2), false

	x = (*Point)(nil)
	fmt.Printf("%T, %v, %t\n", x, x, x == nil) // *main.Point, <nil>, false
	fmt.Println(x.String())                    // panic
}

示例2:非空接口,接收集体的实现

type Reader interface {
	read()
}

type FileReader struct {
	name string
}

func (r FileReader) read() {
	fmt.Printf("read file %s\n", r.name)
}

func main() {
    // 仅仅是一个未初始化的指针,即空指针
	var r1 Reader
	fmt.Println(r1 == nil)            // true
	fmt.Printf("%T, %v\n", r1, r1)    // nil, nil

	var r2 *FileReader
	fmt.Println(r2 == nil)            // true
	fmt.Printf("%T, %v\n", r2, r2)    // *main.FileReader, nil

    // 非空接口iface,数据对象为空指针,但它还包含方法集等信息,不为nil
	r1 = r2
	fmt.Println(r1 == nil)            // false
	fmt.Printf("%T, %v\n", r1, r1)    // *main.FileReader, nil
}

4.2 动态接口

调用fn函数前,具体类型的 dataitab._type两个指针被隐式转换成了 空接口interface {}data_type 指针,此时空接口的类型不为nil

func fn(v interface{}) string {
	if v == nil {
		return "empty-interface"
	}

	return "non-empty-interface"
}

func main() {
    // i is nil
	var i *int

	// non-empty-interface
	fmt.Println(fn(i))

	// not work
	if i != nil {
		fmt.Printf("%v\n", i)
	}
}

4.3 动态类型和值

func main() {
	var a interface{} = nil
	var b interface{} = (*int)(nil)

	x := 1
	var c interface{} = (*int)(&x)

	fmt.Println(a == nil) // true
	fmt.Println(b == nil) // false
	fmt.Println(c == nil) // false

	ia := *(*iface)(unsafe.Pointer(&a))
	ib := *(*iface)(unsafe.Pointer(&b))
	ic := *(*iface)(unsafe.Pointer(&c))

	fmt.Println(ia) // {0 0}
	fmt.Println(ib) // {17457856 0}
	fmt.Println(ic) // {17457856 824634117808}

	fmt.Println(*(*int)(unsafe.Pointer(ic.data))) // 1
}

5. 接口实现检查

type Animal interface {
	eat()
}

type Dog struct{}

func (Dog) eat() {
	fmt.Println("dog eat")
}

type Cat struct{}

func (Cat) walk() {
	fmt.Println("cat walk")
}

func main() {
	// 编译时检查
	var _ Animal = new(Dog) // 编译通过,接口已实现
	var _ Animal = new(Cat) // 编译失败,接口未实现

	// 运行时检查
	var _ Animal = (*Dog)(nil)
	var _ Animal = (*Cat)(nil) // 失败
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值