golang interface 与 反射

  • golang interface 使用场景
  • golang interface 数据结构
  • golang interface 一些使用场景原理
    • 函数参数是 interface 的成本
    • interface{}和带方法的interface的赋值过程
    • 动态类型与动态分发是如何实现的,动态分发什么时候进行,并且有什么样的调用成本
    • 如何进行类型转换
    • 如何进行断言,断言的成本有多高
  • 为什么用反射
  • 反射实现原理以及与interface{}关系
    • 反射的Type和interface
    • reflect.TypeOf 函数解析
    • reflect.ValueOf 函数解析
  • 反射的性能损耗原因以及性能评估

base go 1.13.5

golang interface 使用场景

这里先简单描述一下 interface 的使用场景。 我们通常有两种方式使用interface,一种是带方法的interface,一种是空interface。

我们一般用带方法的interface作为一个通用的抽象。用空的interface{} 来作为一种泛型使用。

具体的使用姿势的形式上,一般也就是作为函数入参,返回值,属性域等等。

除了要会用、用对以外,我觉得有必要搞清楚内部原理。比如作为函数入参,返回值,值和指针接受者的函数调用等的性能损耗。

golang interface 数据结构

interface变量前面说了有两种,一种是带方法的,一种是不带方法的。编译器会自动映射成底层的两种结构:iface 和 eface。区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}。

下面看一下源码的定义: runtime/runtime2.go

type iface struct {tab  *itabdata unsafe.Pointer
}type eface struct {_type *_typedata  unsafe.Pointer
}// 描述带方法的interface的类型信息以及接口信息
type itab struct {inter *interfacetype_type *_typehash  uint32 // copy of _type.hash. Used for type switches._     [4]bytefun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}// 描述接口的方法信息
type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}// 描述interface存储的实际对象的类型信息
type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32tflag      tflagalign      uint8fieldalign uint8kind       uint8alg        *typeAlg// gcdata stores the GC type data for the garbage collector.// If the KindGCProg bit is set in kind, gcdata is a GC program.// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.gcdata    *bytestr       nameOffptrToThis typeOff
}

从eface和iface的定义可知道,interface的portal层的定义实际上是2个指针,一个类型相关的信息,一个是指向实际存储对象的数据指针。也就是16个字节。itab的fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。

另外,你可能会觉得奇怪,为什么 fun 数组的大小为 1,要是接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针,没什么影响。顺便提一句,这些方法是按照函数名称的字典序进行排列的。

再看一下 interfacetype 类型,它描述的是接口的类型:

type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}

可以看到,它包装了 _type 类型,_type 实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个 mhdr 字段,表示接口所定义的函数列表, pkgpath 记录定义了接口的包名。

下面用一张图描述 iface 的全貌:

下面可以看一个实例:

package mainimport "fmt"func main() {x := 100var inter interface{} = xfmt.Println(inter)g := Gopher{"Go"}var c coder = gfmt.Println(c)
}type coder interface {code()debug()
}type Gopher struct {language string
}func (p Gopher) code() {fmt.Printf("I am coding %s language\n", p.language)
}func (p Gopher) debug() {fmt.Printf("I am debuging %s language\n", p.language)
}

通过 go tool compile -S 输出汇编代码,可以看到,main 函数里调用了两个函数:

func convT64(val uint64) (x unsafe.Pointer)
func convTstring(val string) (x unsafe.Pointer)

这里编译器可以自动识别数据的类型,并转换成对应的值。

上面的convTXXX函数定义在 runtime/iface.go 里面,这个文件里面有一段注释:

// The conv and assert functions below do very similar things.
// The convXXX functions are guaranteed by the compiler to succeed.
// The assertXXX functions may fail (either panicking or returning false,
// depending on whether they are 1-result or 2-result).
// The convXXX functions succeed on a nil input, whereas the assertXXX
// functions fail on a nil input.下面的conv和assert函数做的事情非常类似。
编译器保证了convXXX函数的成功。
assertXXX函数可能失败(panic或返回false,这取决于它们是1-结果,还是2-结果)。
convXXX函数在nil输入时成功,而assertXXX则失败

这里列出所有的函数:

//下面的这些方法是将指定的类型转换成interface类型,但是下面的这些方法返回的仅仅是返回data指针// 转换对象成一个 interface{}
func convT2E(t *_type, elem unsafe.Pointer) (e eface)
// 转换uint16成一个interface的data指针
func convT16(val uint16) (x unsafe.Pointer)
// 转换uint32成一个interface的data指针
func convT32(val uint32) (x unsafe.Pointer)
// 转换uint64成一个interface的data指针
func convT64(val uint64) (x unsafe.Pointer)
// 转换string成一个interface的data指针
func convTstring(val string) (x unsafe.Pointer)
// 转换slice成一个interface的data指针
func convTslice(val []byte) (x unsafe.Pointer)
// 转换t类型的元素到interface{}, 这里的t不是指针类型
func convT2Enoptr(t *_type, elem unsafe.Pointer) (e eface)
// 指定类型的到 interface 的转换
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)
// 指定类型到 interface 的转换,不是指针
func convT2Inoptr(tab *itab, elem unsafe.Pointer) (i iface)
// interface到interface的转换。
func convI2I(inter *interfacetype, i iface) (r iface) // 下面是断言调用的一些函数
func assertI2I(inter *interfacetype, i iface) (r iface) func assertI2I2(inter *interfacetype, i iface) (r iface, b bool)func assertE2I(inter *interfacetype, e eface) (r iface)func assertE2I2(inter *interfacetype, e eface) (r iface, b bool)

这些函数在将指定类型转换成 interface 和 interface做类型断言时候会调用。在我当前go版本1.13.5中还做了一些优化,对于一些特定类型的,比如int等基本数字数据类型、String、slice等等,只需要做调用mallocgc 申请一片新内存,然后做赋值。但是对于具体类型准换成interface{}等场景,除了调用mallocgc 申请内存,还需要内存的拷贝。

具体场景看下面的内容。

golang interface 一些使用场景原理

函数参数是 interface 的成本

我们经常使用的一个场景就是函数的参数是 interface{}或则是一个由函数的interface。比如:

func m1(p interface{}){}

这个时候我们传递参数,参数是一个具体的数据类型,比如是一个struct或则是一个基本类型,那么就需要将这个具体的类型转换成 interface{}, 这个时候是有性能损耗的。如果我们在函数内部想要获得具体的类型做类型断言,这个时候也是有性能损耗的。

具体性能损耗对比,可以参考golang type assertion and unsafe.Pointer 性能对比

interface{}和带方法的interface的赋值过程

赋值过程其实就是类型转换的过程,具体就是调用 conVxxxx 函数。过程也比较简单,细节可以参考源码。

动态类型与动态分发是如何实现的,动态分发什么时候进行,并且有什么样的调用成本

首先说一下动态类型是怎么实现的。对于interface{}来说,动态类型用 _type 来描述。对于非空interface来说,动态类型由itab 来描述。

我们看一个例子来验证对象的动态类型。

package mainimport ("fmt""reflect""unsafe"
)type iface struct {itab, data uintptr
}func main() {var a interface{} = nilbi := new(int)*bi = 10var b interface{} = bix := 5var c interface{} = (*int)(&x)ia := *(*iface)(unsafe.Pointer(&a))ib := *(*iface)(unsafe.Pointer(&b))ic := *(*iface)(unsafe.Pointer(&c))fmt.Println(ia, ib, ic)fmt.Println(reflect.TypeOf(b) == reflect.TypeOf(c))
}

看看输出的结果:

{0 0} {17454368 824634166904} {17454368 824634166896}
true

对于ib和ic的类型字段指针地址是一样的,也就是说两个是同一个对象。通过调用reflect.TypeOf也能得以验证。

如何进行类型转换

通过前面的 iface 的源码可以看到,实际上它包含接口类型 interfacetype 和 实体类型 _type,这两个都是 iface 的字段 itab 的成员。也就是说生成一个 itab 同时需要接口的类型和实体的类型。

interfacetype的结构再贴一次:

type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}type imethod struct {name nameOffityp typeOff
}

我们在判断一种类型是否满足某个接口时,Go 使用类型的方法集和接口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型实现了该接口。

比如:某个类型有 m 个方法,某个接口有 n 个方法,则很容易知道这种判定的时间复杂度为 O(mn),Go 会对方法集的函数按照函数名的字典序进行排序,所以实际的时间复杂度为 O(m+n)。

实际的类型转换实现是通过调用 runtime/iface.go 里面的方法:
func convI2I(inter *interfacetype, i iface) (r iface)
将一个 interface 转换成另外一个 interface。

具体实现如下:

func convI2I(inter *interfacetype, i iface) (r iface) {tab := i.tabif tab == nil {return}if tab.inter == inter {r.tab = tabr.data = i.datareturn}r.tab = getitab(inter, tab._type, false)r.data = i.datareturn
}

这里面最重要的就是 getitab 函数的源码,这里源码和细节就不说了,感兴趣可以看源码。简单说就是 getitab 函数会根据 interfacetype 和 _type 去全局的 itab 哈希表中查找,如果能找到,则直接返回;否则,会根据给定的 interfacetype 和 _type 新生成一个 itab,并插入到 itab 哈希表,这样下一次就可以直接拿到 itab。

如何进行断言,断言的成本有多高

断言的实现,实际上也是调用 runtime/iface.go 里面的 assertXXX方法,具体实现参考源码。

为什么用反射

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

关于为什么使用反射,这里列出两个常用场景:

  1. 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

但是注意,使用反射是有有很多缺点的。比较重要的就是:性能损耗,以及代码的安全性。

  1. Go语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
  2. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

反射实现原理以及与interface{}关系

前面讲了,interface 是 Go 描述对象的一个非常强大的抽象。当向接口变量赋值一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

反射的Type和interface

Go是一个强类型的语言,每个类型都有一个静态类型,并且这个静态类型在编译阶段就能够确认。比如int, int[],string等等,需要注意的是,这个类型是声明时候的类型,不是底层数据类型。

比如:

type TestInt int
var i int
var j TestInt

这里i和j的存储类型虽然都是int, 但是对于Go来说,i和j却是两个不同的静态类型,也不能用于互相赋值,除非做类型转换。

理解Go的反射,就必须理解interface的结构,这两种息息相关。前面已经描述了 interface 的底层结构,这里再来复习一下:

type iface struct {tab  *itabdata unsafe.Pointer
}
// 描述带方法的interface的类型信息以及接口信息
type itab struct {inter *interfacetype_type *_typehash  uint32 // copy of _type.hash. Used for type switches._     [4]bytefun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}// 描述接口的方法信息
type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}// 描述interface存储的实际对象的类型信息
type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32tflag      tflagalign      uint8fieldalign uint8kind       uint8alg        *typeAlg// gcdata stores the GC type data for the garbage collector.// If the KindGCProg bit is set in kind, gcdata is a GC program.// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.gcdata    *bytestr       nameOffptrToThis typeOff
}


iface 描述的是非空接口,它包含方法;与之相对的是 eface,描述的是空接口,不包含任何方法,Go 语言里有的类型都 “实现了” 空接口。

我们再看看reflect里面的基本数据类型和接口。reflect 包里定义了一个接口和一个结构体,即 reflect.Type 和 reflect.Value,它们提供很多函数来获取存储在接口里的类型信息。

reflect.Type 是一个接口,提供了很多方法老获取关于类型相关的信息,rtype 实现了 Type 接口。我们可以看下图,对于Go的其余类型,比如sliceType也默认实现了reflect.Type接口。实际上sliceType等都是组合了rtype和一个类型特有的信息。

看下 rtype 的定义:

// 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       uintptrptrdata    uintptr  // number of bytes in the type that can contain pointershash       uint32   // hash of type; avoids computation in hash tablestflag      tflag    // extra type information flagsalign      uint8    // alignment of variable with this typefieldAlign uint8    // alignment of struct field with this typekind       uint8    // enumeration for Calg        *typeAlg // algorithm tablegcdata     *byte    // garbage collection datastr        nameOff  // string formptrToThis  typeOff  // type for pointer to this type, may be zero
}

rtype 是Go里面其余类型的基础类型,会被内嵌在很多其余类型struct里面。也就是说所有的类型都会包含 rtype 这个字段,表示各种类型的公共信息;另外,不同类型包含自己的一些独特的部分。比如下面的:

// arrayType represents a fixed array type.
type arrayType struct {rtypeelem  *rtype // array element typeslice *rtype // slice typelen   uintptr
}// chanType represents a channel type.
type chanType struct {rtypeelem *rtype  // channel element typedir  uintptr // channel direction (ChanDir)
}
.....
funcType
ptrType
sliceType
structType
......

此外rtype必须和…/runtime/type.go里面的 _type 保持一致。这里肯定用于和interface里面的类型指针做指针类型转换的。

再来看看 reflect.Value的结构:

// reflect/value.go
type Value struct {// typ holds the type of the value represented by a Value.typ *rtype// Pointer-valued data or, if flagIndir is set, pointer to data.// Valid when either flagIndir is set or typ.pointers() is true.ptr unsafe.Pointer// flag holds metadata about the value.// The lowest bits are flag bits://  - flagStickyRO: obtained via unexported not embedded field, so read-only//   - flagEmbedRO: obtained via unexported embedded field, so read-only//    - flagIndir: val holds a pointer to the data//   - flagAddr: v.CanAddr is true (implies flagIndir)//  - flagMethod: v is a method value.// The next five bits give the Kind of the value.// This repeats typ.Kind() except for method values.// The remaining 23+ bits give a method number for method values.// If flag.kind() != Func, code can assume that flagMethod is unset.// If ifaceIndir(typ), code can assume that flagIndir is set.flag......
}

可以看到Value里面实际上是包含类型信息的,然后也包含一个指向实际value的指针。

reflect 包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

TypeOf 函数用来提取一个接口中值的类型信息。由于它的输入参数是一个空的 interface{},调用此函数时,实参会先被转化为 interface{}类型。这样,实参的类型信息、方法集、值信息都存储到 interface{} 变量里了。

ValueOf 函数返回值 reflect.Value 表示 interface{} 里存储的实际变量,它能提供实际变量的各种信息。相关的方法常常是需要结合类型信息和值信息。例如,如果要提取一个结构体的字段信息,那就需要用到 _type (具体到这里是指 structType) 类型持有的关于结构体的字段信息、偏移信息,以及 *data 所指向的内容 —— 结构体的实际值。

这里引用老钱《快学Go语言第十五课——反射》的一张图:

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)
}// emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}// toType converts from a *rtype to a Type that can be returned
// to the client of package reflect. In gc, the only concern is that
// a nil *rtype must be replaced by a nil Type, but in gccgo this
// function takes care of ensuring that multiple *rtype for the same
// type are coalesced into a single Type.
func toType(t *rtype) Type {if t == nil {return nil}return t
}

当我们调用 reflect.TypeOf 函数时候,首先会将入参实际类型转换成 interface{},然后通过非类型安全的指针转换成emptyInterface。 最后获取实际的类型对象 rtype (rtype实现了reflect.Type接口)。最后实际返回的是接口,reflect.Type, 所以可以通过调用 reflect.Type的各种接口函数获取类型信息。

reflect.ValueOf 函数解析

func ValueOf(i interface{}) Value {if i == nil {return Value{}}// TODO: Maybe allow contents of a Value to live on the stack.// For now we make the contents always escape to the heap. It// makes life easier in a few places (see chanrecv/mapassign// comment below).escapes(i)return unpackEface(i)
}func escapes(x interface{}) {if dummy.b {dummy.x = x}
}// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))// NOTE: don't read e.word until we know whether it is really a pointer or not.t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, e.word, f}
}

reflect.ValueOf 函数返回的是反射的 Value 对象。主要主干流程如下:

  1. 首先会调用escapes函数确保输入对象分配在堆上;
  2. 做非类型安全指针转换成*emptyInterface
  3. 封装emptyInterface里面的 type 和 value 到 reflect.Value

通过reflect.Value 可以读写对象。

反射的性能损耗原因以及性能评估

reflect.TypeOfreflect.ValueOf 的损耗并不多,涉及到主要是 interface{} 的装箱/拆箱操作,或者是创建新的Value对象。

装箱拆箱带来的性能影响可以参考 golang type assertion and unsafe.Pointer 性能对比

下面测试:

  1. 通过反射和直接New创建对象性能;
  2. 通过反射获取对象设置值、通过field的name设置值、通过index设置值、原生的直接设置值的性能;

测试环境:Mac2015款,2核心,8G内存。Go1.13.5

测试代码:

package mainimport ("reflect""testing"
)func BenchmarkReflect_New(b *testing.B) {var s *Studentsv := reflect.TypeOf(Student{})b.ResetTimer()for i := 0; i < b.N; i++ {sn := reflect.New(sv)s, _ = sn.Interface().(*Student)}_ = s
}func BenchmarkDirect_New(b *testing.B) {var s *Studentb.ResetTimer()for i := 0; i < b.N; i++ {s = new(Student)}_ = s
}func BenchmarkReflect_Set(b *testing.B) {var s *Studentsv := reflect.TypeOf(Student{})b.ResetTimer()for i := 0; i < b.N; i++ {sn := reflect.New(sv)s = sn.Interface().(*Student)s.Name = "Jerry"s.Age = 18s.Class = "20005"s.Score = 100}
}
func BenchmarkReflect_SetFieldByName(b *testing.B) {sv := reflect.TypeOf(Student{})b.ResetTimer()for i := 0; i < b.N; i++ {sn := reflect.New(sv).Elem()sn.FieldByName("Name").SetString("Jerry")sn.FieldByName("Age").SetInt(18)sn.FieldByName("Class").SetString("20005")sn.FieldByName("Score").SetInt(100)}
}
func BenchmarkReflect_SetFieldByIndex(b *testing.B) {sv := reflect.TypeOf(Student{})b.ResetTimer()for i := 0; i < b.N; i++ {sn := reflect.New(sv).Elem()sn.Field(0).SetString("Jerry")sn.Field(1).SetInt(18)sn.Field(2).SetString("20005")sn.Field(3).SetInt(100)}
}
func BenchmarkDirect_Set(b *testing.B) {var s *Studentb.ResetTimer()for i := 0; i < b.N; i++ {s = new(Student)s.Name = "Jerry"s.Age = 18s.Class = "20005"s.Score = 100}
}type Student struct {Name  stringAge   intClass stringScore int
}

测试结果:

goos: darwin
goarch: amd64
pkg: study_golang/study/basic/reflect
BenchmarkReflect_New-4                  17330691                89.2 ns/op            48 B/op          1 allocs/op
BenchmarkDirect_New-4                   26311687                43.1 ns/op            48 B/op          1 allocs/op
BenchmarkReflect_Set-4                  15889159                68.0 ns/op            48 B/op          1 allocs/op
BenchmarkReflect_SetFieldByName-4        2906097               438 ns/op              80 B/op          5 allocs/op
BenchmarkReflect_SetFieldByIndex-4      11250272               105 ns/op              48 B/op          1 allocs/op
BenchmarkDirect_Set-4                   25542271                52.6 ns/op            48 B/op          1 allocs/opPASS
coverage: 0.0% of statementsProcess finished with exit code 0

Case1 测试创建对象:
可以看到通过反射创建对象是89.2ns, 直接new是43.1ns, 有接近一倍的性能差距。

Case2 测试给对象成员赋值:

  1. 直接赋值和通过反射获取interface{}之后做类型转换之后再赋值,这两种差距不是很大;
  2. 但是通过field name或则 index 来赋值,性能就非常差了,特别是通过field name, 有接近10倍的性能差距。

比较有趣的是,FieldByName方式赋值是FieldByIndex方式赋值的好几倍, 原因在于FieldByName会有额外的循环进行字段的查找,虽然最终它还是调用FieldByIndex进行赋值。

func (v Value) FieldByName(name string) Value {v.mustBe(Struct)if f, ok := v.typ.FieldByName(name); ok {return v.FieldByIndex(f.Index)}return Value{}
}

golang interface 与 反射相关推荐

  1. Golang interface 接口详细原理和使用技巧

    文章目录 Golang interface 接口详细原理和使用技巧 一.Go interface 介绍 interface 在 Go 中的重要性说明 interface 的特性 interface 接 ...

  2. golang interface to string_Golang 反射

    原文作者:OhBonsai 来源:简书 基本了解 在Go语言中,大多数时候值/类型/函数非常直接,要的话,定义一个.你想要个Struct 1type Foo struct {2 A int 3 B s ...

  3. golang中的反射

    变量的内在机制 类型信息,这部分是元信息,是预先定义好的 值类型,这部分是程序运行过程中,动态改变的 反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关的,只要包含 ...

  4. golang interface 转 int string slice struct 类型

    在golang中,interface{}允许接纳任意值,int, string, struct,slice等,因此我可以很简单的将值传递到interface{} package main import ...

  5. golang interface 类型转换_Golang面试题41道

    Golang面试题41道 大家好,这一期呢,我们来说一下golang的面试题. 第1题什么是golang? go是一个开源的编程语言,由谷歌开发的.这门语言是设计用来做系统级的编程的. 第2题为什么要 ...

  6. golang interface{} 转 struct结构体

    1.使用断言,强制转换 p, ok := (Value).(user) if ok {fmt.Println("id:" + p.Id)fmt.Println("name ...

  7. golang interface 转 string,int,float64

    inter 是interface类型,转化为string类型是: str := inter.(string) 转为其他类型也类似 testInt := inter.(int) testFloat := ...

  8. golang interface传结构体

    package mainimport "fmt"type test struct {pre stringnext string }func printInfo(te interfa ...

  9. golang interface 类型转换_无符号Golang程序逆向方法解析

    在去年的inctf2018中,出现了一道Go语言编写的进程通信逆向题,无论是从题目整体设计还是解题思路上来说都独树一帜,自己在解题过程中遇到了很多问题,但我这不打算做过多探讨,网上也有大佬的解题过程, ...

  10. golang interface 类型变量当作某个具体类型使用

    比如,我们定义了一个 struct type person struct {Name string `json:"name"`Age int `json:"age&quo ...

最新文章

  1. Docker不香吗,为啥还要K8s?
  2. 仅需6步,教你轻易撕掉app开发框架的神秘面纱(1):确定框架方案
  3. GridSearchCV 与 RandomizedSearchCV 调参
  4. docx.opc.exceptions.PackageNotFoundError: Package not found at ‘文件名.docx‘ 问题解决
  5. Java NIO系列教程(五)Buffer
  6. (原创)cocos2dx-lua TableView官方demo分析
  7. 分类VS标签,一文带你看懂数据中台为什么要建标签体系?
  8. 社交网站将推动手游发展
  9. php开源问答_PHP基础知识能力问答
  10. 堆栈在DNA计算机中的应用,堆栈和二叉树数据结构在DNA计算机中的设计与实现
  11. 数据 3 分钟 | ShardingSphere 核心团队获融资、巨杉数据库发布湖仓一体架构多款产品...
  12. 优麒麟Ubuntu20.04安装各种问题
  13. 顶级外语学习资源[转] 近600个教学学习资料链接
  14. ssh 命令连接服务器
  15. 企业CIS 系统的收集方法分析
  16. java编程实现;猜单词游戏
  17. 莫生气,一切对镜皆是考验,对面若不识,还需从头练
  18. 10.Audio音频
  19. Dunn检验的介绍和python实现
  20. cogs 1341 永无乡

热门文章

  1. 企业10大HR软件分析对比(精)
  2. python---之scipy.ndimage.measurements.label
  3. linux7.6安装gcc,Centos7.6 安装gcc9
  4. 临床数据库挖掘系列2-使用SEER.stat软件提取数据
  5. 获取level2行情接口的功能详解
  6. 河海大学2021年硕士研究生招考公告
  7. msl3等级烘烤时间_MSL(湿气敏感性等级)
  8. H3C网络设备模拟器配置VLAN-Hybrid
  9. SCDPM2019服务器恢复数据
  10. Python脚本系列:拳皇13一键出招以及连段实现!谁打得过我?