Go 接口 interface 面试案例

55 阅读3分钟

案例一:

type Base interface {
	do()
}

type App struct {
}

func (a *App) do() {}

func GetApp() *App {
	return nil
}

func GetApp2() Base {
 	var app *App
	return app
}


func GetApp3() *App {
	return nil
}

func TestGetApp(t *testing.T) {
	var base Base
	base = GetApp()

	fmt.Println("GetApp()1", base)        // <nil>
	fmt.Println("GetApp()1", base == nil) // false

	base2 := GetApp2()

	fmt.Println("GetApp()2", base2)        // <nil>
	fmt.Println("GetApp()2", base2 == nil) // false
    
    var base3 *App
	base3 = GetApp3()

	fmt.Println("GetApp()3", base3)        // <nil>
	fmt.Println("GetApp()3", base3 == nil) // true 
    //  因为GetApp()3返回值只是一个普通的指针结构体,不是接口Base
}

Go底层对于非空接口的定义,使用 iface结构体来定义:

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

// itab 结构体定义
type itab struct {
    inter  *interfacetype   // 接口自身的元信息
    _type  *_type           // 具体类型的元信息
    link   *itab
    bad    int32
    hash   int32            // _type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
    fun    [1]uintptr       // 函数指针,指向具体类型所实现的方法
}

app 是一个指向nil的空指针,但是最后return app 会触发匿名变量 App = app值拷贝动作

所以最后GetApp2() 返回给上层的是一个 Base insterface{}类型,也就是一个iface struct{}类型。

app为nil,只是 iface 中的data 为nil而已。 但是iface struct{}本身并不为nil.

因为iface结构体中tab *itab 字段是有值的,具体包含了:

  1. _type 表示具体化的类型,有值,具体内容指向: type App struct{}
  1. fun 表示具体类型所实现的方法,有值,具体内容指向: func (a *App) do() {}

但是 data unsafe.Pointer 字段是 nil: 为*App = nil

案例二:空接口判断nil

func Foo(x interface{}) {
	if x == nil {
		fmt.Println("empty interface")
		return
	}
	fmt.Println("non-empty interface")
}
func TestFoo(t *testing.T) {
	var p *int = nil
	Foo(p) // non-empty interface
}

Foo()的形参x interface{}是一个空接口类型eface struct{}。

空接口定义:

type eface struct {
	_type *_type         // 类型信息
	data  unsafe.Pointer // 指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}

在执行Foo(p)的时候,触发x interface{} = p语句,所以此时 x结构如下。

  1. _type *type  表示数据类型的描述,字段有值,指向*int
  2. data unsafe.Pointer  表示具体的数据类型或者说实现类,值为nil:p = nil

所以 x 结构体本身不为 nil,而是 data指 针指向的p为nil。

**案例三:error 接口为nil

**

type MyErr struct {
	Msg string
}

func GetErr() *MyErr {
	return nil
}

func (m *MyErr) Error() string {
	return ""
}

func TestGetErr(t *testing.T) {
	var e error
	e = GetErr()
	fmt.Println(e == nil)  // false
}

背后的原因本质上还是对 Go 语言中 interface 的基本原理的理解。

在案例三中,虽然 GetErr 方法确实是返回了 nil,返回的类型也是具体的 *MyErr 类型。但是其接收的变量却不是具体的结构类型,而是 error 类型:

var e error
e = GetErr()

在 Go 语言中, error 类型本质上是 interface:

type error interface {
    Error() string
}

因此兜兜转转又回到了 interface 类型的问题,interface 不是单纯的值,而是分为类型和值

所以传统认知的此 nil 并非彼 nil,必须得类型和值同时都为 nil 的情况下,interface 的 nil 判断才会为 true

在案例三中,结合代码逻辑,更符合场景的是:

var e *MyErr
e = GetErr()
log.Println(e == nil)

输出结果就会是 true。

在案例二中,也是一样的结果,原因也是 interface。不管是 error 接口(interface),还是自定义的接口,背后原理一致,自然也就结果一致了。