当前位置: 首页 > news >正文

哪个网站是教人做淘宝客的首页关键词优化价格

哪个网站是教人做淘宝客的,首页关键词优化价格,高端广告公司网站建设价格,房产公司网站模板1、接口 1.1 类型 Golang 中的接口是一组方法的签名,是实现多态和反射的基础。 type 接口名 interface {method1(参数列表) 返回值列表method2(参数列表) 返回值列表 }不同于 Java 语言,使用 implements 关键字显示的实现接口。Golang 接口的实现都是…

1、接口

1.1 类型

Golang 中的接口是一组方法的签名,是实现多态和反射的基础。

type 接口名 interface {method1(参数列表) 返回值列表method2(参数列表) 返回值列表
}

不同于 Java 语言,使用 implements 关键字显示的实现接口。Golang 接口的实现都是隐式的,只需要实现了接口类型中的所有方法就实现了接口。

func (t 自定义类型) method1(参数列表) (返回值列表) {//方法实现
}func (t 自定义类型) method2(参数列表) (返回值列表) {//方法实现
}

这里来看个示例

type Speaker interface {Speak() string
}type Dog struct{}func (d *Dog) Speak() string {return "Woof!"
}type Cat struct{}func (c *Cat) Speak() string {return "Meow!"
}func PrintSpeak(s Speaker) {fmt.Println(s.Speak())
}func TestSpeaker(t *testing.T) {dog := &Dog{}cat := &Cat{}PrintSpeak(dog)PrintSpeak(cat)
}

在这个例子中,DogCat 类型都实现了 Speaker 接口的 Speak 方法。PrintSpeak 函数接受一个 Speaker 接口类型的参数,因此可以接受任何实现了 Speaker 接口的类型。这展示了多态性的强大之处:不同的类型可以通过相同的接口进行交互。

这里可以再展开下,利用接口多态的特点,服务可以对外提供一个元数据接口,依据传参的不同,返回不同的数据模板。比如,type 字段来做区分,rider 代表返回骑手的相关信息,order 代表返回订单的相关信息,delivery 代表返回运单的相关。服务内部就可以依据 type 的不同,来分别封装实现。

另外,Golang 接口不能包含任何变量,且允许为空。空接口 interface{} 没有任何方法,所以所有类型都实现了空接口。

func TestGeneric(t *testing.T) {var values []interface{}values = append(values, 42)values = append(values, "hello")values = append(values, 3.14)for _, value := range values {fmt.Println(value)}
}

上述代码展示了如何使用空接口 (interface{}) 来存储不同类型的值,并通过循环遍历这些值进行打印,仔细看是不是像泛型?

因为接口在定义一组方法时没有对实现的接收者做限制,所以在这一节的最后,来探讨下结构体实现接口和结构体指针实现接口的不同。

  • 示例一
type Animal interface {Say()
}type Cat struct{}// 接收者类型为结构体指针
func (c *Cat) Say() {fmt.Println("miu")
}func TestStruct(t *testing.T) {var animal Animal = &Cat{}animal.Say()
}

程序正常运行。

  • 示例二
type Animal interface {Say()
}type Cat struct{}// 接收者类型为结构体
func (c Cat) Say() {fmt.Println("miu")
}func TestStruct(t *testing.T) {var animal Animal = &Cat{}animal.Say()
}

程序正常运行。

  • 示例三
type Animal interface {Say()
}type Cat struct{}// 接收者类型为结构体
func (c Cat) Say() {fmt.Println("miu")
}func TestStruct(t *testing.T) {var animal Animal = Cat{}animal.Say()
}

程序正常运行。

  • 示例四
type Animal interface {Say()
}type Cat struct{}// 接收者类型为结构体指针
func (c *Cat) Say() {fmt.Println("miu")
}func TestStruct(t *testing.T) {var animal Animal = Cat{}animal.Say()
}

运行失败,输出

cannot use Cat{} (value of type Cat) as type Animal in variable declaration:Cat does not implement Animal (Say method has pointer receiver)

编译器提示 Cat 没有实现 Animal 接口,Say 方法接受的是指针。

针对上述四个示例汇总如下

结构体实现接口结构体指针实现接口
结构体初始化变量通过不通过
结构体指针初始化变量通过通过

为什么会出现这种情况呢?我们知道 Golang 中传递参数都是值传递

  • 对于 &Cat{} 来说,这意味着拷贝一个新的 &Cat{} 指针,不过这个指针与原来的指针指向一个相同并且唯一的结构体,所以编译器可以隐式的对变量解引用(dereference)获取指针指向的结构体
  • 而对于 Cat{} 来说,这意味着 Say 方法会接受一个全新的 Cat{},因为方法的参数是 *Cat,编译器不会无中生有创建一个新的指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用该方法的结构体;

这里可以看出,当接受者为结构体时,那么在方法调用的时候需要传值,拷贝参数,这里会有性能损失,因此建议在实际项目中,接受者使用结构体指针来实现。

1.2 数据结构

golang 版本 1.19.12

Golang 中有两种略微不同的接口,一种是带有一组方法的接口,另一种是不带任何方法的接口,下面就分别介绍下其底层实现。

1.2.1 空接口

空接口的实现如下

type EmptyInterface {}

其底层的数据结构如下

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

eface 的结构体由两个属性构成,一个是类型信息 _type,一个是数据信息 data,占 16 个字节。

  • _type 属性,存放的是类型、方法等信息。
// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
// ../internal/reflectlite/type.go:/^type.rtype.
type _type struct {size       uintptr // 类型占用内存大小ptrdata    uintptr // 包含所有指针的内存前缀大小hash       uint32  // 类型 hash,用于比较两个类型是否相等tflag      tflag   // 标记位,主要用于反射align      uint8   // 对齐字节信息fieldAlign uint8   // 当前结构字段的对齐字节数kind       uint8   // 基础类型枚举值equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等gcdata    *byte    // GC 类型的数据str       nameOff  // 类型名称字符串在二进制文件段中的偏移量ptrToThis typeOff  // 类型元信息指针在二进制文件段中的偏移量
}

其中可以关注下 kind,这个字段描述的是如何解析基础类型。在 Golang 中,基础类型是一个枚举常量,有 26 个基础类型,如下。枚举值通过 kindMask 取出特殊标记位。

// runtime/typekind.go
const (kindBool = 1 + iotakindIntkindInt8kindInt16kindInt32kindInt64kindUintkindUint8kindUint16kindUint32kindUint64kindUintptrkindFloat32kindFloat64kindComplex64kindComplex128kindArraykindChankindFunckindInterfacekindMapkindPtrkindSlicekindStringkindStructkindUnsafePointerkindDirectIface = 1 << 5kindGCProg      = 1 << 6kindMask        = (1 << 5) - 1
)

这里再做个简单的展开, kindMask 的值为 31,对应的二进制为 00011111,也就是低五位都为 1,再看下 Golang 中有 26 个基础类型,也就是都比 kindMask 值要小,这时利用位与运算的特性(如果两个对应位都是 1,则结果位为 1,否则为 0),二者做与运算可以获取对应类型的种类信息。

  • data 属性,指向原始数据的指针,是一个 unsafe.Pointer 类型

下面用个示例演示空接口 eface 数据到底是如何存储的。

type nameOff int32 // offset to a name
type typeOff int32 // offset to an *rtype
type tflag uint8type eface struct {_type *_typedata  unsafe.Pointer
}type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32tflag      tflagalign      uint8fieldAlign uint8kind       uint8// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal func(unsafe.Pointer, unsafe.Pointer) bool// 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
}func TestEFace(t *testing.T) {var i interface{} = 3.14e := (*eface)(unsafe.Pointer(&i))fmt.Println(e)fmt.Println(e._type)fmt.Println(e.data)}

打印输出一目了然。

&{0x1049dc640 0x1049ce618}
&{8 0 2472095124 7 8 8 14 0x1048bafd0 0x1049ce678 7052 34048}
0x1049ce618

从上述 eface 结构的两个属性可以推断出,Golang 的任意类型都可以转换成 interface{}

1.2.2 非空接口

空接口的实现如下

type NoEmptyInterface {Say()
}

其底层的数据结构如下

type iface struct {tab  *itabdata unsafe.Pointer
}

iface 结构体也是占 16 个字节,这里的 data 属性和空接口的 eface 里的 data 作用相同,这里就不做赘述。下面来看看 tab 属性。

// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.WriteTabs.
type itab struct {inter *interfacetype // 存的是 interface 自己的静态类型_type *_type // 存的是 interface 对应具体对象的类型hash  uint32 // 是对 _type.hash 的拷贝_     [4]bytefun   [1]uintptr // 是一个函数指针,它指向的是具体类型的函数方法
}

itab 结构体是非空接口的核心组成部分,占 32 字节,着重看下 inter_type 属性。

type imethod struct {name nameOffityp typeOff
}type interfacetype struct {typ     _type // 类型元信息pkgpath name  // 包路径和描述信息等等mhdr    []imethod // 方法
}

inter 存储的是非空接口自己类型相关数据,因为 Golang 中函数方法是以包为单位隔离的。所以 interfacetype 除了保存 _type 还需要保存包路径等描述信息。mhdr 存的是各个 interface 函数方法在段内的偏移值 offset,知道偏移值以后才方便调用。

_type 在上一节空接口已详细介绍过,存储的是接口类型的元信息,这里就不展开。

这里还要说下 tabfun 属性,存储的是指向实现非空接口类型的方法数组。

type iface struct {tab  *itabdata unsafe.Pointer
}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 imethod struct {name nameOffityp typeOff
}type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}type name struct {bytes *byte
}type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32tflag      tflagalign      uint8fieldAlign uint8kind       uint8// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal func(unsafe.Pointer, unsafe.Pointer) bool// 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
}type People interface{Say()
}type Student struct {}func (s *Student) Say() {fmt.Println("hello world")
}func TestNoEmptyInterface(t *testing.T) {s := &Student{}var i People = snoEmpty := (*iface)(unsafe.Pointer(&i))fmt.Println(noEmpty)fmt.Println(noEmpty.tab)fmt.Println(noEmpty.tab.inter)fmt.Println(noEmpty.tab._type)fmt.Println(noEmpty.tab.fun)fmt.Println(noEmpty.data)}

打开 Goland 的调试模式,可以很清晰的看到非空接口内部字段都是如何存储的。

type Student struct{}func (stu *Student) Show() {}func live() People {var stu *Studentreturn stu
}func TestNil(t *testing.T) {stu := live()if stu == nil {fmt.Println("nil")} else {fmt.Println("not nil")}}

看完这一节,上面输出应该就简单了吧,输出 not nil。至于原因嘛,在运行时,非空接口只是 datanil,但是 tab 可不为 nil

这里再做个展开,简单说说动态派发。

Gointerface 可以动态派发方法,实现类似面向对象语言中的多态的特性。

func TestDynamic(t *testing.T) {var stu People = &Student{}stu.Show()
}

这里为了正常实现 stu.Show() 方法调用,需要构建 iface 结构,之后再依据 *tab 里面存的 fun 指针做一次寻址,接着才能调用。

func TestDynamic(t *testing.T) {var stu := &Student{}stu.Show()
}

这里直接调用结构体的方法,少了构建 iface 结构以及寻址的时间,性能应该比动态派发要好。不过,指针实现的动态派发造成的性能损失非常小,相对于一些复杂逻辑的处理函数,这点性能损失几乎可以忽略不计。

2、反射

Golang 中的反射是用标准库中的 reflect 包实现,reflect 包实现了 runtime (运行时)的反射能力,能够让程序操作不同的对象。

reflect 包中有两个非常重要的函数:

  • reflect.TypeOf 能获取类型信息;
  • reflect.ValueOf 能获取数据的运行时表示;

2.1 TypeOf

先来看看 TypeOf 函数

// reflect/type.go
// 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)
}// 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
}

其中入参 i 的类型 any 就是 interface{} 的别名,anyGolang 1.18 中引入,表示任意类型。

type any = interface{}

TypeOf 函数实现很简单,只是将一个 interface{} 变量转换成了内部的 emptyInterface 表示,然后从中获取相应的类型信息。里面有个强制转换,把 any 转成了 emptyInterface。下面来看看 emptyInterface 的数据结构:

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}// 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 C// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal     func(unsafe.Pointer, unsafe.Pointer) boolgcdata    *byte   // garbage collection datastr       nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero
}

仔细看看 emptyInterface 结构,发现其属性和上一章节的空接口是一模一样的,只是名称改了下, rtype 用于表示变量的类型, word 指向内部封装的数据。

TypeOf 参数 i any 中可以看出,入参是两种类型

  • 一种是具体类型变量,TypeOf() 返回的具体类型信息
  • 一种是 interface 类型变量
    • 如果 i 绑定了具体类型对象实例,返回的是 i 绑定具体类型的动态类型信息;
    • 如果 i 没有绑定任何具体的类型对象实例,返回的是接口自身的静态类型信息。
type Forest interface{}type Tree struct{}func TestBasicType(t *testing.T) {ifa := &Tree{}rfa := reflect.TypeOf(ifa)fmt.Println("第一组输出:", rfa.Elem().Name(), rfa.Elem().Kind().String())var ifb Forest = &Tree{}ifc := new(Forest)rfb := reflect.TypeOf(ifb)rfc := reflect.TypeOf(ifc)fmt.Println("第二组输出:", rfb.Elem().Name(), rfb.Elem().Kind().String())fmt.Println("第二组输出:", rfc.Elem().Name(), rfc.Elem().Kind().String())
}

输出

第一组输出: Tree struct
第二组输出: Tree struct
第二组输出: Forest interface
  • 第一组输出中 ifa 是具体的类型,所以返回本身的类型 Tree,对应的 kindstruct
  • 第二组输出中
    • ifbinterface 类型变量,绑定具体的类型对象实例,所以返回的是绑定的具体类型 Forest,对应的 kindstruct
    • ifcinterface 类型变量,且没有绑定任何具体的类型对象实例,所以返回的是本身的类型 Forest,对应的 kindinterface

2.2 ValueOf

再来看看 ValueOf 函数

// reflect/type.go
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i any) 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)
}// Dummy annotation marking that the value x escapes,
// for use in cases where the reflect code is so clever that
// the compiler cannot follow.
func escapes(x any) {if dummy.b {dummy.x = x}
}var dummy struct {b boolx any
}// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) 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}
}

ValueOf 实现也很简单,若 inil 返回零值 Value{}。否则就先调用 escapes(i) 确保值逃逸到堆上,然后调用 reflect.unpackEface ,先把 i interface{} 转换成 emptyInterface,然后将具体类型和指针包装成 reflect.Value 结构体后返回。

这里对内存逃逸做个简单的说明。在 Golang 中,内存逃逸通常发生在以下情况:

1.变量的地址被返回给调用者。
2.变量的地址被赋值给全局变量或者其他包级变量。
3.变量的地址被传递给不透明的函数或方法(例如接口方法)。

上面的 dump 是个全局变量,当 x 赋值给全部变量的 x 属性时就会发生逃逸。其实,在变量 i 传给函数 escapes 时就已经发生了逃逸(见上述第三种情况),笔者在本机环境中(Golang 版本为 1.19.12)测试也是如此:

package _0240619import "testing"func escapes(x any) {if dummy.b {dummy.x = x}
}var dummy struct {b boolx any
}func TestEscapes(t *testing.T) {i := 11escapes(i)
}

执行 go tool compile -m escapes_test.go ,输出如下

escapes_test.go:5:6: can inline escapes
escapes_test.go:16:6: can inline TestEscapes
escapes_test.go:19:9: inlining call to escapes
escapes_test.go:5:14: leaking param: x
escapes_test.go:16:18: t does not escape
escapes_test.go:19:9: i escapes to heap

这里的 19 行就是 escapes(i),也就是传参的时候由于此函数入参是个 interface{} 因此发生了内存逃逸。

那什么时候仍然需要 escapes 函数?如果在某些特定情况下,编译器无法自动判定变量需要逃逸到堆上,而又需要强制变量逃逸,那么 escapes 函数仍然是有用的。它可以明确告诉编译器这个变量需要在堆上分配。

那为什么要在 ValueOf 中把变量的内存逃逸到堆上呢?源码上写的原因是 这样标记是为了防止反射代码写的过于高级,以至于编译器跟不上了。

这里我理解为 ValueOf 通常用于反射操作,在反射中我们经常需要确保被反射的对象在堆上,以便反射包能够安全地访问和修改这些对象。例如:

  • 反射可能需要长时间持有对象的引用。
  • 反射可能会传递对象到其他地方(例如,作为返回值)。
  • 反射可能会在不同的 Goroutine 之间传递对象。

2.3 反射三定律

Golang 开派祖师之一 Rob Pike2011 年写的 The Laws of Reflection,提出了反射三大定律

  1. 反射可以从接口值到反射对象
  2. 反射可以从反射对象中获得接口值
  3. 要修改反射对象,其值必须可设置

2.3.1 反射可以从接口值得到反射对象

反射第一定律即通过一个接口值,我们可以获取其对应的反射对象。可以通过 reflect.TypeOfreflect.ValueOf 函数来实现。因为二者的入参都是 interface{},传参的时候会发生类型转换。

func TestReflectFirstLaw(t *testing.T) {var x float64 = 3.4tp := reflect.TypeOf(x) // 得到类型信息 reflect.Typev := reflect.ValueOf(x) // 得到值信息 reflect.Valuefmt.Println("type:", tp) // 输出 float64fmt.Println("value:", v) // 3.4}

2.3.2 反射可以从反射对象中获得接口值

即通过一个反射对象,我们可以还原出其对应的接口值。可以通过 reflect.ValueInterface 方法来实现。

不过调用 reflect.Value.Interface 方法只能获得 interface{} 类型的变量,如果想要将其还原成最原始的状态还需要经过如下所示的显式类型转换:

func TestReflectSecondLaw(t *testing.T) {var x float64 = 3.4v := reflect.ValueOf(x)y := v.Interface().(float64) // 将反射值转回为接口值fmt.Println(y) // 3.4
}

仔细看,发现第一定律和第二定律是互为逆向的过程:

  • 从接口值得到反射对象:
    • 从基本类型到接口类型的类型转换(这里是隐式转换);
    • 从接口类型到反射对象的转换;
  • 从反射对象的到接口值:
    • 反射对象转换成接口类型;
    • 通过显式类型转换变成原始类型;

2.3.3 要修改反射对象,其值必须可设置

如果我们想要更新一个 reflect.Value,那么它持有的值一定是可以被更新的。

func TestReflectThirdLaw(t *testing.T) {f := 3.14v := reflect.ValueOf(f)v.SetFloat(6.52)
}

运行后报如下错误

panic: reflect: reflect.Value.SetFloat using unaddressable value [recovered]panic: reflect: reflect.Value.SetFloat using unaddressable value

这里给的提示信息是使用了不可寻址(unaddressable)的 Value。

我们知道 Golang 的函数的参数都是值传递的,这里 reflect.ValueOf(f) 传值后,会拷贝一个 f 的副本,与原来的 f 就没关系了,此时再改 f 的值就会报不可寻址。

修复也简单,传指针即可,虽然是值拷贝,但是指向的都是同一块内存地址。

func TestReflectThirdLaw(t *testing.T) {f := 3.14v := reflect.ValueOf(f)if !v.CanSet() {v = reflect.ValueOf(&f)v = v.Elem()}v.SetFloat(6)fmt.Println(v)}

2.4 实例

首先通过一个综合的示例,来把上面列举的 TypeOfValueOf 给串起来。

type Monster struct {Name  string `json:"name"`Age   int    `json:"monster_age"`Score float32Sex   string
}func (m *Monster) Print() {fmt.Println("---start---")fmt.Println(m)fmt.Println("---end---")
}func (m *Monster) GetSum(n1, n2 int) int {return n1 + n2
}func (m *Monster) Set(name string, age int, score float32, sex string) {m.Name = namem.Age = agem.Score = scorem.Sex = sex
}func ProcStruct(a interface{}) {typ := reflect.TypeOf(a)val := reflect.ValueOf(a)valMethod := valif typ.Kind() == reflect.Ptr {typ = typ.Elem()}if typ.Kind() != reflect.Struct {fmt.Println("expect struct")return}if val.Kind() == reflect.Ptr {val = val.Elem()}num := val.NumField()fmt.Printf("struct has %d field\n", num)for i := 0; i < num; i++ {fmt.Printf("Field %d: 属性为 = %v\n", i, typ.Field(i).Name)fmt.Printf("Field %d: 值为 = %v\n", i, val.Field(i))tagVal := typ.Field(i).Tag.Get("json")if tagVal != "" {fmt.Printf("Field %d: tag 为 %v\n", i, tagVal)}}numOfMethod := valMethod.NumMethod()fmt.Printf("struct has %d methods\n", numOfMethod)valMethod.Method(1).Call(nil)var params []reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(40))res := valMethod.Method(0).Call(params)fmt.Println("res = ", res[0].Int())}func TestReflectDemo(t *testing.T) {monster := &Monster{Name:  "Cat",Age:   400,Score: 30.8,Sex:   "10",}ProcStruct(monster)}

打印输出

struct has 4 field
Field 0: 属性为 = Name
Field 0: 值为 = Cat
Field 0: tag 为 name
Field 1: 属性为 = Age
Field 1: 值为 = 400
Field 1: tag 为 monster_age
Field 2: 属性为 = Score
Field 2: 值为 = 30.8
Field 3: 属性为 = Sex
Field 3: 值为 = 10
struct has 3 methods
---start---
&{Cat 400 30.8 10}
---end---
res =  50

这里需要关注下:

  • NumField(),获取结构体字段的数量
  • NumMethod, 获取 reflect.Value 可以访问的方法数量,这包括值接收者和指针接收者的方法
    • 值接收器:方法定义在值类型上。例如:func (m Monster) Print()
    • 指针接收器:方法定义在指针类型上。例如:func (m *Monster) Print()

来看看下面这段代码

type Foo struct {Name string
}func (f *Foo) Method1() {fmt.Println("Method1 called")
}func TestNum(t *testing.T) {f := &Foo{}v := reflect.ValueOf(f)fmt.Println(v)fmt.Println(v.Elem())fmt.Println(v.Elem().NumField())fmt.Println(v.NumMethod())
}

打印输出

&{}
{}
1
1

再结合上面列出的关注点,就很清楚二者要获取数量的注意点了。

  • NumField(),针对的是结构体本身的字段数量,若接收器(receiver)是结构体指针,需要解引用指针,获取指针指向的 reflect.Value,也就是结构体
  • NumMethod(),用于获取某个 reflect.Value 所代表的类型的方法数量,即使类型为指针,也无需使用 Elem() 进行解引用。

其次,再来个依据反射动态调用方法

type DynamicStruct struct {Name stringAge  intAddr string
}func (d *DynamicStruct) PrintName(ctx context.Context) error {fmt.Println(d.Name)return nil
}func (d *DynamicStruct) PrintAge(ctx context.Context) error {fmt.Println(d.Age)return nil
}func (d *DynamicStruct) PrintAddr(ctx context.Context) error {fmt.Println(d.Addr)return nil
}func TestDynamic(t *testing.T) {dynamic := &DynamicStruct{Name: "molaifeng",Age:  18,Addr: "beijing",}ctx := context.Background(){method, exits := dynamic.GetMethod("Name")if !exits {return}_ = method(ctx)}{method, exits := dynamic.GetMethod("hobby")if !exits {return}_ = method(ctx)}}func (d *DynamicStruct) GetMethod(name string) (func(ctx context.Context) error, bool) {methodName := "Print" + namemethod := reflect.ValueOf(d).MethodByName(methodName)if !method.IsValid() {fmt.Println(fmt.Printf("%v not exist", methodName))return nil, false}return method.Interface().(func(ctx context.Context) error), true
}

打印输出

molaifeng
Printhobby not exist20 <nil>

这里主要用到了 MethodByName 获取反射对象的方法,有了前面例子打底,这里就不做过多介绍。另外一个就是 Interface() 方法了,这个方法可以获取反射对象,但是呢,要还原成原本的类型,还需显示的转换,于是就有下面这个

method.Interface().(func(ctx context.Context) error), true

其实这个很好理解,看看具体 *DynamicStruct 具体绑定的方法,返回值是不是就是上面括号里的。

再来个简化版的

func TestInt(t *testing.T) {num := 99v := reflect.ValueOf(num)i := v.Interface().(int)fmt.Println(i)}

最后,来个校验参数的例子

type Params struct {Name *stringAge  *intAddr *string
}func TestValidParam(t *testing.T) {requiredParam := map[string]bool{"Name": true,"Age":  true,}params := &Params{}err := CheckParams(params, requiredParam)if err != nil {fmt.Println(err)return}}func CheckParams(params *Params, requiredParam map[string]bool) error {val := reflect.ValueOf(params).Elem()for fieldName := range requiredParam {field := val.FieldByName(fieldName)if isFieldNil(field) {return fmt.Errorf("%v is required,actual nil", fieldName)}}return nil}func isFieldNil(v reflect.Value) bool {k := v.Kind()switch k {case reflect.Slice, reflect.Map, reflect.Ptr:return v.IsNil()default:return false}
}

打印如下

Name is required,actual nil
http://www.qdjiajiao.com/news/7641.html

相关文章:

  • 网上接效果图平台优化关键词排名提升
  • asp动态网站设计做直销去哪里找客户
  • 自做装逼头像网站站长之家是什么
  • 做混剪素材网站精准客户信息一条多少钱
  • 汕头自助建站系统百度竞价被点击软件盯上
  • 制作企业网站的代码海淀区seo搜索引擎
  • 设计事务所重庆网页优化seo公司
  • 广东省消防建设工程申报网站360免费做网站
  • 做视频哪个网站收入高nba最新排名公布
  • 网站建设中的注册和登录页面新品牌推广策划方案
  • 网站推广方法汇总重大军事新闻
  • wordpress文章会员廊坊seo整站优化软件
  • 网站定位代码电商营销
  • 汕头网站制作网页百度推广app下载安卓版
  • 网站上传修改限制网络软件开发
  • 推广手段有哪些方式结构优化是什么意思
  • 网站建设公司 提成长沙网络公关公司
  • 设计制作安全提示牌河北网站seo地址
  • 成都手机wap网站制作网页制作软件推荐
  • 微信开发平台开发seo检测优化
  • 天助网的网站青岛网站快速排名提升
  • hao123网站模板2022年适合小学生的新闻
  • 如何建立自己的商城网站网络营销策划书的结构是什么
  • 安徽做网站的公司资源企业网站排名优化价格
  • wordpress是静态页面seo推广外包企业
  • 钢筋网片每平米重量惠州seo按天计费
  • 怎么通过局域网建设网站深圳网络营销推广方案
  • ebay有做deal的网站吗设计公司排名
  • 武汉专业网站做网页重庆网站搜索引擎seo
  • 景区网站的作用自己怎么免费做网站