【Golang】类型转换 - 获取变量类型

Posted by 西维蜀黍 on 2020-01-30, Last Modified on 2021-09-21

如果某个函数的入参的类型是 interface{},有下面几种方法可以获取入参的类型:

获取入参类型的方法

1 反射

import (
    "reflect"
    "fmt"
)
func main() {
    v := "hello world"
    fmt.Println(typeof(v))
}
func typeof(v interface{}) string {
    return reflect.TypeOf(v).String()
}

Example

其实,fmt.Sprintf("%T") 底层也是用反射实现的:

func (p *pp) printArg(arg interface{}, verb rune) {
	...
	// Special processing considerations.
	// %T (the value's type) and %p (its address) are special; we always do them first.
	switch verb {
	case 'T':
		p.fmt.fmtS(reflect.TypeOf(arg).String())
		return
  ...
  }
}  

Usage:

import "fmt"
func main() {
    v := "hello world"
    fmt.Println(typeof(v))
}
func typeof(v interface{}) string {
    return fmt.Sprintf("%T", v)
}

2 类型断言(Type Assertion)

安全的类型断言(Type Assertion)

func main() {
    v := "hello world"
    fmt.Println(typeof(v))
}
func typeof(v interface{}) string {
    switch t := v.(type) {
    case int:
        return "int"
    case float64:
        return "float64"
    //... etc
    default:
        _ = t
        return "unknown"
    }
}

注意,这里的 v 必须为 initerface{} 类型才可以进行类型断言。比如下面代码就会报错:

s := "BrainWu"
if v, ok := s.(string); ok {  // invalid type assertion: s.(string) (non-interface type string on left)
	fmt.Println(v)
}

在这里只要是在声明时或函数传进来的参数不是 interface{} 类型,那么做类型断言都是会报 non-interface 错误的。

所以我们只能通过将 s 先转换为一个 interface{}类型,然后才能进行类型断言:

s := "BrainWu"
if v, ok := interface{}(s).(string); ok {
	fmt.Println(v)
}

分析

reflect.TypeOf() 的参数是 i interface{},返回一个Type 类型的struct:

// 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 interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
	size       uintptr
	ptrdata    uintptr  // number of bytes in the type that can contain pointers
	hash       uint32   // hash of type; avoids computation in hash tables
	tflag      tflag    // extra type information flags
	align      uint8    // alignment of variable with this type
	fieldAlign uint8    // alignment of struct field with this type
	kind       uint8    // enumeration for C
	alg        *typeAlg // algorithm table
	gcdata     *byte    // garbage collection data
	str        nameOff  // string form
	ptrToThis  typeOff  // type for pointer to this type, may be zero
}

Main:

package main

import (
	"fmt"
	"reflect"
)

type AAA struct {
	bb string
}

func main() {
	a := AAA{bb: "temp"}
	fmt.Printf("%v", reflect.TypeOf(a))
}

我们来看看 Golang 的反射是怎么做到的?

在 Golang中,interface也是一个结构体,包含两个attribute(分别是 1 个指针):

  • 指针1:指向该变量的类型
  • 指针2:指向该变量的value

如下,空 interface (即未定义任何内容的interface)对应的结构体如下所示:

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

第一个指针的类型是type rtype struct

非空 interface 由于需要携带的信息更多(例如该接口实现了哪些方法),所以第一个 attribute 是 itab,它是一个 struct 指针类型,在这个 struct 中记录了该变量的动态类型 typ *rtype


// nonEmptyInterface is the header for a interface value with methods.
type nonEmptyInterface struct {
	// see ../runtime/iface.go:/Itab
	itab *struct {
		ityp   *rtype // static interface type
		typ    *rtype // dynamic concrete type
		link   unsafe.Pointer
		bad    int32
		unused int32
		fun    [100000]unsafe.Pointer // method table
	}
	word unsafe.Pointer
}

我们来看看 reflect.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 interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

可以看到,调用 TypeOf 时传入的是 interface{},它将变量的地址转换为空接口,然后将得到的 rtype 实例转为 Type 接口返回。

需要注意,当调用 reflect.TypeOf 的之前,其实已经发生了一次隐式的类型转换,即将具体类型的向空接口转换。这个过程比较简单,只要拷贝typ *rtypeword unsafe.Pointer就可以了。

例如w := os.Stdout,该变量的接口值在内存里是这样的:

那么,类型断言是怎么判断是不是某个接口呢?回到最初,在golang中,接口是一个松耦合的概念,一个类型是不是实现了某个接口,就是看该类型是否实现了该接口要求的所有函数,所以,类型断言判断的方法就是检查该类型是否实现了接口要求的所有函数。

Reference