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

软件开发工具通常也称为什么工具/优化大师的三大功能

软件开发工具通常也称为什么工具,优化大师的三大功能,用ps切片做网站,做公益网站怎么赚钱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

相关文章:

说说 golang 中的接口和反射

1、接口 1.1 类型 Golang 中的接口是一组方法的签名&#xff0c;是实现多态和反射的基础。 type 接口名 interface {method1(参数列表) 返回值列表method2(参数列表) 返回值列表 }不同于 Java 语言&#xff0c;使用 implements 关键字显示的实现接口。Golang 接口的实现都是…...

小程序注册

【 一 】小程序注册 微信公众平台 https://mp.weixin.qq.com/ https://mp.weixin.qq.com/注册 邮箱激活 小程序账户注册 微信小程序配置 微信小程序开发流程 添加项目成员 【 二 】云服务 lass 基础设施服务&#xff08;组装机&#xff09; 你买了一大堆的电脑配件&#x…...

工作记录2

1. 要实现y轴超出部分滚动的效果&#xff0c;可以这样写 <div style"max-height: 384px; overflow-y: auto;"> </div> 2. 当后端接口还没好的时候&#xff0c;可以自己模拟一下接口返回的数据 export const getCommodityDetail (id) > Promise.re…...

linux挂载硬盘(解决linux不显示硬盘问题)

目录 1.查看系统有几块硬盘2.查看挂载情况3.格式化硬盘4.创建挂载目录用于挂载硬盘5.将硬盘挂载到指定的挂载目录6.随系统自启动挂载查看配置文件&#xff0c;看是否已经把这条命令加入配置 帮同门解决挂载失败问题记录 参考视频&#xff1a;只要6步&#xff01;Linux系统下挂载…...

运输标签扫描仪可简化运输和接收任务

Dynamic Web TWAIN 是一个专为Web应用程序设计的TWAIN扫描识别控件。你只需在TWAIN接口写几行代码&#xff0c;就可以用兼容TWAIN的扫描仪扫描文档或从数码相机/采集卡中获取图像。然后用户可以编辑图像并将图像保存为多种格式&#xff0c;用户可保存图像到远程数据库或者Share…...

Stable Diffusion 3 大模型文生图实践

windows教程2024年最新Stable Diffusion本地化部署详细攻略&#xff0c;手把手教程&#xff08;建议收藏!!)_stable diffusion 本地部署-CSDN博客 linux本地安装教程 1.前期准备工作 1&#xff09;创建conda环境 conda create --name stable3 python3.10 2&#xff09;下…...

Linux grep技巧 删除含有指定关键词的行,创建新文件

一. 需求 ⏹有如下文件&#xff0c;现要求 删除含有xuecheng关键字的行删除含有192.168.1.1关键字的行也就是说&#xff0c;最终只会留下127.0.0.1 license.sublimehq.com 127.0.0.1 www.xuecheng.com 127.0.0.1 img.xuecheng.com 192.168.1.1 www.test.com 127.0.0.1 video…...

ChatMoney还能写剧本杀?

本文由 ChatMoney团队出品 近年来&#xff0c;剧本杀作为一种新兴社交游戏&#xff0c;收到了越来越多人的喜爱&#xff0c;它不仅需要玩家们发挥自身演技&#xff0c;还需运用逻辑思维推理&#xff0c;分析所获得的线索&#xff0c;找出案件真凶。然而你是否想过&#xff0c;你…...

优化系统小工具

一款利用VB6编写的系统优化小工具&#xff0c;系统优化、桌面优化、清理垃圾、查找文件等功能。 下载:https://download.csdn.net/download/ty5858/89432367...

调幅信号AM的原理与matlab实现

平台&#xff1a;matlab r2021b 本文知识内容摘自《软件无线电原理和应用》 调幅就是使载波的振幅随调制信号的变化规律而变化。用音频信号进行调幅时&#xff0c;其数学表达式可以写为: 式中&#xff0c;为调制音频信号&#xff0c;为调制指数&#xff0c;它的范围在(0&…...

[MySql]两阶段提交

文章目录 什么是binlog使用binlog进行恢复的流程 什么是redolog缓冲池redologredolog结构 两阶段提交 什么是binlog binlog是二进制格式的文件&#xff0c;用于记录用户对数据库的修改&#xff0c;可以作用于主从复制过程中数据同步以及基于时间点的恢复&#xff08;PITR&…...

掌握rpc、grpc并探究内在本质

文章目录 rpc是什么&#xff1f;又如何实现服务通信&#xff1f;理解rpcRPC的通信过程通信协议的选择小结RPC VS Restful net_rpc实践案例net/rpc包介绍创建服务端创建client 看看net_rpc的通信调度实现的内部原理明确目标基于自己实现的角度分析我会怎么做代码分析 grpc介绍与…...

构造,析构,垃圾回收

构造函数 基本概念 在实例化对象时 会调用的用于初始化的函数 如果不写&#xff0c;默认存在一个无参构造函数 构造函数的写法 1.没有返回值 2.函数名和类名必须相同 3.没有特殊需求时&#xff0c;一般都是public的 4.构造函数可以被重载 5.this代表当前调用该函数的对…...

杂记 | 搭建反向代理防止OpenAI API被封禁(对于此次收到邮件提示7月9日后将被屏蔽的解决参考)

文章目录 重要声明&#xff08;免责&#xff09;01 OpenAI封禁API的情况02 解决方案及原理2.1 原因分析2.2 解决方案2.3 步骤概述 03 操作步骤3.1 购买一个海外服务器3.2 申请一个域名3.3 将域名指向代理服务器3.4 在代理服务器上安装nginx3.5 配置反向代理 重要声明&#xff0…...

利用ref实现防抖

结合vue的customRef function debounceRef(value,time1000){ let t return customRef((track,trigger)>{ return { get(){ track() return value; } set(val){ clearTimeout(t) tsetTimeout(()>{ trigger() valueval },time) } } }) }...

SAP ABAP 之OOALV

文章目录 前言一、案例介绍/笔者需求二、SE24 查看类 a.基本属性 Properties b.接口 Interfaces c.友元 Friends d.属性 Attributes e.方法 Methods f.事件 Events g.局部类型 Types …...

构建实用的Flutter文件列表:从简到繁的完美演进

前言&#xff1a;为什么我们需要文件列表&#xff1f; 在现代科技发展迅速的时代&#xff0c;我们的电脑、手机、平板等设备里积累了大量的文件&#xff0c;这些文件可能是我们的照片、文档、音频、视频等等。然而&#xff0c;当文件数量增多时&#xff0c;我们如何快速地找到…...

spring使用@PostConstruct踩得坑

情况说明&#xff1a; 在一个抽象类中使用PostConstruct注解方法init用于初始化操作。然后每个实现类在初始化时都会调用PostConstruct注解的init方法执行初始化操作。如下代码&#xff1a; public abstract class AbstractClass {/*** 存放各实例.*/public static final Map&…...

【Mac】XnViewMP for Mac(图片浏览查看器)及同类型软件介绍

软件介绍 XnViewMP 是一款多功能、跨平台的图像查看和管理软件&#xff0c;适用于 macOS、Windows 和 Linux 系统。它是经典 XnView 软件的增强版本&#xff0c;更加现代化且功能更强大。XnViewMP 支持数百种图像格式&#xff0c;并提供多种图像处理工具&#xff0c;使其成为摄…...

win10修改远程桌面端口,Windows 10下修改远程桌面端口及服务器关闭445端口的操作指南

Windows 10下修改远程桌面端口及服务器关闭445端口的操作指南 一、修改Windows 10远程桌面端口 在Windows 10系统中&#xff0c;远程桌面连接默认使用3389端口。为了安全起见&#xff0c;建议修改此端口以减少潜在的安全风险。以下是修改远程桌面端口的步骤&#xff1a; 1. 打…...

口感探险之旅:勇闯红酒世界,揭秘复杂风味的无尽奥秘

在葡萄酒的浩瀚海洋中&#xff0c;红酒如同一座深邃而迷人的岛屿&#xff0c;等待着勇敢的探险家们去发掘其背后隐藏的奥秘。每一次品尝红酒&#xff0c;都是一次口感的大冒险&#xff0c;让我们在味蕾的舞动中感受那千变万化的风味。今天&#xff0c;就让我们一起踏上这场探索…...

吉时利 Keithley2440 数字源表

Keithley2440吉时利SMU数字源表 Keithley2440 - 40V、5A、50W源表 吉时利数字源表系列专用于要求紧密结合源和测量 的测试应用。全部数字源表型号都提供精密电压源和电 流源以及测量功能。每款数字源表既是高度稳定的直流 电源也是真仪器级的6位半万用表。此电源的特性包括 低…...

PPT的精细化优化与提升策略

&#x1f44f;&#x1f44f;&#x1f44f;欢迎来到我的博客 ! 亲爱的朋友们&#xff0c;欢迎您们莅临我的博客&#xff01;这是一个分享知识、交流想法、记录生活的温馨角落。在这里&#xff0c;您可以找到我对世界独特视角的诠释&#xff0c;也可以与我一起探讨各种话题&#…...

awtk踩坑记录三:移植awtk-mvvm到Awtk Designer项目

从github下载并编译awtk, awtk-mmvm awtk: https://github.com/zlgopen/awtk/tree/master awtk-mvvm: https://github.com/zlgopen/awtk-mvvm 用awtk-designer新建项目并打开项目目录 首先修改project.json&#xff0c;使其awtk和awtk-mvvm指向上个步骤下载的路径&#xff0c…...

07 - matlab m_map地学绘图工具基础函数 - 绘制等高线

07 - matlab m_map地学绘图工具基础函数 - 绘制等高线 0. 引言1. 关于绘制m_contour2. 关于绘制m_contourf3. 关于绘制m_elev4. 结语 0. 引言 本篇介绍下m_map中添加绘制等高线的一系列函数及其用法&#xff0c;主要函数包括m_elev、m_contour、m_contourf还有一些函数也和绘制…...

Kotlin设计模式:享元模式(Flyweight Pattern)

Kotlin设计模式&#xff1a;享元模式&#xff08;Flyweight Pattern&#xff09; 在移动应用开发中&#xff0c;内存和CPU资源是非常宝贵的。享元模式&#xff08;Flyweight Pattern&#xff09;是一种设计模式&#xff0c;旨在通过对象重用来优化内存使用和性能。本文将深入探…...

java压缩pdf

<!-- PDF操作,itext7全家桶 --><dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>7.1.15</version><type>pom</type></dependency>package org.example; import…...

[AIGC] ClickHouse:一款高性能列式数据库管理系统

轮流探索数据库的世界&#xff0c;我们不得不提到一个重要的角色——ClickHouse。ClickHouse是一个开源的列式数据库管理系统(DBMS)&#xff0c;以其卓越的性能&#xff0c;高效的查询能力和易扩展性而被业界广泛关注&#xff0c;尤其在大数据分析方面。 文章目录 1. 什么是 Cl…...

深度学习21-30

1.池化层作用&#xff08;筛选、过滤、压缩&#xff09; h和w变为原来的1/2&#xff0c;64是特征图个数保持不变。 每个位置把最大的数字取出来 用滑动窗口把最大的数值拿出来&#xff0c;把44变成22 2.卷积神经网络 &#xff08;1&#xff09;conv&#xff1a;卷积进行特征…...

google浏览器无法访问大端口的处理方式

属性的目标中添加后缀内容或者修改后台端口为常用端口&#xff0c;比如8080等。 “C:\Program Files\Google\Chrome\Application\chrome.exe” --explicitly-allowed-ports8888...