Go语言——反射 上

简介: Go语言——反射 上

一、reflect-反射-浅析-重要

反射是众多编程语言中的一个非常实用的功能,它是一种能够自描述、自控制的应用,Go语言也对反射提供了友好的支持。

Go语言中使用反射可以在编译时不知道类型的情况下更新变量,在运行时查看值、调用方法以及直接对他们的布局进行操作。

由于反射是建立在类型系统(type system)上的,所以我们先来复习一下Go语言中的类型。

Go语言中的类型

Go语言是一门静态类型的语言,每个变量都有一个静态类型,类型在编译的时候确定下来。

type MyInt int
var i int
var j MyInt

变量 i 的类型是 int,变量 j 的类型是 MyInt,虽然它们有着相同的基本类型,但静态类型却不一样,在没有类型转换的情况下,它们之间无法互相赋值。

接口是一个重要的类型,它意味着一个确定的方法集合,一个接口变量可以存储任何实现了接口的方法的具体值(除了接口本身),例如 io.Reader 和 io.Writer:

// Reader是包装基本Read方法的接口。
type Reader interface {
  Read(p []byte) (n int, err error)
}
// Writer是包装基本Write方法的接口。
type Writer interface {
  Write(p []byte) (n int, err error)
}

如果一个类型声明实现了 Reader(或 Writer)方法,那么它便实现了 io.Reader(或 io.Writer),这意味着一个 io.Reader 的变量可以持有任何一个实现了 Read 方法的的类型的值。

// 例一:
var r io.Reader
r = os.Stdin
fmt.Printf("%T\n",r) // *os.File
r = bufio.NewReader(r)
fmt.Printf("%T\n",r) // *bufio.Reader
r = new(bytes.Buffer)
fmt.Printf("%T\n",r) // *bytes.Buffer
// 三者都是实现了io.Reader接口的结构体

必须要弄清楚的一点是,不管变量 r 中的具体值是什么,r 的类型永远是 io.Reader,由于Go语言是静态类型的,r 的静态类型就是 io.Reader。

// 例二:
type person interface {
  speak() string
}
type student struct {
  name string
}
func (h student) speak() string {
  return h.name
}
func main() {
  var per person
  fmt.Printf("%T\n",per) // <nil>
  per = student{name: "胡宇洋"}
  fmt.Printf("%T\n",per) // main.student
}

可以看到student 结构体实现了person接口,per的具体值是一个student结构体,但由于GO语言是静态类型的,per的静态类型就是person。

如果不知道咋回事(我就不知道)就先略过,这个需要看interface深度解析,里面涉及汇编等东西,在以后有时间可以看一下。

在接口类型中有一个极为重要的例子——空接口:

interface{}

它表示了一个空的方法集,一切值都可以满足它,因为它们都有零值或方法。

有人说Go语言的接口是动态类型,这是错误的,它们都是静态类型,虽然在运行时中,接口变量存储的值也许会变,但接口变量的类型是不会变的。

我们必须精确地了解这些,因为反射与接口是密切相关的。

关于接口我们就介绍到这里,下面我们看看Go语言的反射三定律。

反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”

即:TypeOf(i interface{})和ValueOf(i interface{})方法返回反射类型:reflect.Type类型和reflect.Value类型的变量

注:这里反射类型指 reflect.Type和 reflect.Value。

从使用方法上来讲,反射提供了一种机制,允许程序在运行时检查接口变量内部存储的 (value, type) 对。

在最开始,我们先了解下 reflect 包的两种类型 Type 和 Value,这两种类型使访问接口内的数据成为可能,它们对应两个简单的方法,分别是 reflect.TypeOf 和 reflect.ValueOf,分别用来读取接口变量的 reflect.Type 和 reflect.Value 部分。

当然,从 reflect.Value 也很容易获取到 reflect.Type,目前我们先将它们分开。

首先,我们下看 reflect.TypeOf:

func main() {
    var x float64 = 3.4    
    fmt.Println("type:", reflect.TypeOf(x))
}

运行结果如下

type: float64

大家可能会疑惑,为什么没看到接口?

这段代码看起来只是把一个 float64 类型的变量 x 传递给 reflect.TypeOf 并没有传递接口。

其实在 reflect.TypeOf 的函数签名里包含一个空接口:

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type

我们调用 reflect.TypeOf(x) 时,x 被存储在一个空接口变量中被传递过去,然后 reflect.TypeOf 对空接口变量进行拆解,恢复其类型信息。

函数 reflect.ValueOf 也会对底层的值进行恢复:

func main() {
    var x float64 = 3.4    
    fmt.Println("value:", reflect.ValueOf(x))
}

运行结果如下:

value: 3.4

类型reflect.Type 和reflect.Value都有很多方法,我们可以检查和使用它们,这里我们举几个例子。

类型reflect.Value有一个方法Type(),它会返回一个reflect.Type 类型的对象。

Type 和 Value 都有一个名为Kind ()的方法,它会返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice 等。

Value 类型也有一些类似于 Int、Float 的方法,用来提取底层的数据:

Int 方法用来提取 int64

Float 方法用来提取 float64,示例代码如下:

func main() {
  var x float64 = 3.4
  v := reflect.ValueOf(x) // 返回一个reflect.Value 类型的对象
  fmt.Println("type:", v.Type()) // v.Type返回一个reflect.Type类型的对象
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64)// kind()返回一个常量,表示底层数据类型
  fmt.Println("value:", v.Float()) // 提取底层的数据
}

运行结果如下:

type: float64
kind is float64: true
value: 3.4

还有一些用来修改数据的方法,比如 SetInt、SetFloat。

在介绍它们之前,我们要先理解“可修性”(settability),这一特性会在下面进行详细说明。

反射库提供了很多值得列出来单独讨论的属性,下面就来介绍一下。

首先是介绍下 reflect.Value类型 的 getter 和 setter 方法,为了保证 API 的精简,这两个方法操作的是某一组类型范围最大的那个。比如,处理任何含符号整型数,都使用 int64,也就是说 Value 类型的 Int 方法返回值为 int64 类型,SetInt 方法接收的参数类型也是 int64 类型。实际使用时,可能需要转化为实际的类型:

func main() {    
    var x uint8 = 'x'    
    v := reflect.ValueOf(x)    
    fmt.Println("type:", v.Type())  // uint8. 
    fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.    
    x = uint8(v.Uint())  // v.Uint()返回的是uint64类型.
}

运行结果如下:

type: uint8
kind is uint8: true

其次,反射对象的 Kind() 方法描述的是基础类型,而不是静态类型。如果一个反射对象包含了用户定义类型的值,如下所示:

func main() {
  type MyInt int
  var x MyInt = 7
  v := reflect.ValueOf(x)
  fmt.Println(v.Kind()) // int
  fmt.Println(v.Type()) // main.MyInt
}

上面的代码中,虽然变量 v 的静态类型是 MyInt,而不是 int,但 Kind 方法仍然会返回 reflect.Int。

换句话说 Kind ()方法不会像 Type() 方法一样区分 MyInt 和 int。

反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”

和物理学中的反射类似,Go语言中的反射也能创造自己反面类型的对象。

根据一个reflect.Value类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。

其函数声明如下:

// Interface returns v's value as an interface{}.
// 相当于:var i interface{}=(v的底层值)。
func (v Value) Interface() interface{}

然后,我们可以通过断言,恢复底层的具体值:

func main() {
  var x float64  = 12.1
  v:=reflect.ValueOf(x)
  y,ok:= v.Interface().(float64) // y will have type float64.
  fmt.Println(ok,y)// true 12.1
}

上面这段代码会打印出一个 float64 类型的值,也就是反射类型变量 v 所代表的值。

事实上,我们可以更好地利用这一特性,标准库中的 fmt.Println 和 fmt.Printf 等函数都接收空接口变量作为参数,fmt 包内部会对接口变量进行拆包,因此 fmt 包的打印函数在打印 reflect.Value 类型变量的数据时,只需要把 Interface 方法的结果传给格式化打印程序:

fmt.Println(v.Interface())

那么为什么不直接使用 fmt.Println(v)?

因为 v 的类型是 reflect.Value,我们需要的是它的具体值,由于值的类型是 float64,我们也可以用浮点格式化打印它:

fmt.Printf("value is %7.1e\n", v.Interface())

运行结果如下:

3.4e+00

同样,这次也不需要对 v.Interface() 的结果进行类型断言,空接口值内部包含了具体值的类型信息,Printf 函数会恢复类型信息。

func main() {
  var x float64  = 12.1
  v:=reflect.ValueOf(x)
  fmt.Printf("%T\n",v) // reflect.Value
  fmt.Printf("%T\n",v.Interface()) // float64
}

简单来说 Interface 方法和 ValueOf 函数作用恰好相反,唯一一点是,返回值的静态类型是 interface{}。

Go的反射机制可以将“接口类型的变量”转换为“反射类型的对象”,然后再将“反射类型对象”转换过去。

反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”

这条定律很微妙,也很容易让人迷惑,但是如果从第一条定律开始看,应该比较容易理解。

下面这段代码虽然不能正常工作,但是非常值得研究:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic

如果运行这段代码,它会抛出一个奇怪的异常:

panic: reflect: reflect.flag.mustBeAssignable using unaddressable value

这里问题不在于值7.1不能被寻址,而是因为变量 v 是“不可写的”,“可写性”是反射类型变量的一个属性,但不是所有的反射类型变量都拥有这个属性。

我们可以通过 CanSet() 方法检查一个 reflect.Value 类型变量的“可写性”,对于上面的例子,可以这样写:

func main() {
  var x float64  = 12.1
  v:=reflect.ValueOf(x)
  fmt.Println("settability of v:", v.CanSet())
}

运行结果如下:

settability of v: false

对于一个不具有“可写性”的 Value 类型变量,调用 Set 方法会报出错误。

首先我们要弄清楚什么是“可写性”,“可写性”有些类似于寻址能力,但是更严格,它是反射类型变量的一种属性,赋予该变量修改底层存储数据的能力。“可写性”最终是由一个反射对象是否存储了原始值而决定的。

示例代码如下:

var x float64 = 3.4
v := reflect.ValueOf(x)

这里我们传递给 reflect.ValueOf 函数的是变量 x 的一个拷贝,而非 x 本身,想象一下如果下面这行代码能够成功执行:

v.SetFloat(7.1)

如果这行代码能够成功执行,它不会更新 x,虽然看起来变量 v 是根据 x 创建的,相反它会更新 x 存在于反射对象 v 内部的一个拷贝,而变量 x 本身完全不受影响。这会造成迷惑,并且没有任何意义,所以是不合法的。“可写性”就是为了避免这个问题而设计的。

这看起来很诡异,事实上并非如此,而且类似的情况很常见。考虑下面这行代码:

f(x)

代码中,我们把变量 x 的一个拷贝传递给函数,因此不期望它会改变 x 的值。如果期望函数 f 能够修改变量 x,我们必须传递 x 的地址(即指向 x 的指针)给函数 f,如下所示:

f(&x)

反射的工作机制与此相同,如果想通过反射修改变量 x,就要把想要修改的变量的指针传递给反射库。

首先,像通常一样初始化变量 x,然后创建一个指向它的反射对象,命名为 p:

func main() {
  var x float64 = 3.4
  p := reflect.ValueOf(&x) // Note: take the address of x.
  fmt.Println("type of p:", p.Type())
  fmt.Println("settability of p:", p.CanSet())
}

运行结果如下:

type of p: *float64
settability of p: false

反射对象 p 是不可写的,但是我们也不想修改 p,事实上我们要修改的是 *p。为了得到 p 指向的数据,可以调用 Value 类型的 Elem 方法。Elem 方法能够对指针进行“解引用”,然后将结果存储到反射 Value 类型对象 v 中:

func main() {    
    var x float64 = 3.4    
    p := reflect.ValueOf(&x) // Note: take the address of x.    
    v := p.Elem()    
    fmt.Println("settability of v:", v.CanSet())
}

运行结果如下:

settability of v: true

由于变量 v 代表 x, 因此我们可以使用 v.SetFloat 修改 x 的值:

func main() {
  var x float64 = 3.4
  p := reflect.ValueOf(&x) // Note: take the address of x.
  v := p.Elem() // 获取指针&x指向的变量x,等同于*(&x),但是在反射中不可以那么写,有一个专门的方法Elem()获取指针指向的变量
  v.SetFloat(7.1)
  fmt.Println(v.Interface())
  fmt.Println(x)
}

运行结果如下:

7.1
7.1

Elem():专门用来获取指针指向的变量,返回仍然还是一个reflect.Vlaue类型

反射不太容易理解,reflect.Type 和 reflect.Value 会混淆正在执行的程序,但是它做的事情正是编程语言做的事情。只需要记住:只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。

结构体

我们一般使用反射修改结构体的字段,只要有结构体的指针,我们就可以修改它的字段。

下面是一个解析结构体变量 t 的例子,用结构体的地址创建反射变量,再修改它。然后我们对它的类型设置了 typeOfT,并用调用简单的方法迭代字段。

需要注意的是,我们从结构体的类型中提取了字段的名字,但每个字段本身是正常的 reflect.Value 对象。

func main() {
  type Student struct {
    Age int
    Name string
  }
  t := Student{19, "hyy"}
  s := reflect.ValueOf(&t).Elem() // 用结构体的地址创建反射变量s
  typeOfT := s.Type()
  fmt.Println(typeOfT) // main.Student
  // 迭代字段
  // s.NumField() 返回结构体的字段个数
  for i := 0; i < s.NumField(); i++ {
    f := s.Field(i) // 取第i个字段
    fmt.Printf(
      "字段名:%s 字段类型:%s = %v\n",
      typeOfT.Field(i).Name, // 获取字段名
      f.Type(), // 获取类型
      f.Interface(), // 获取值
    )
  }
}

运行结果如下:

main.Student
字段名:Age 字段类型:int = 19
字段名:Name 字段类型:string = hyy

Student 字段名之所以大写,是因为结构体中只有可导出的字段是“可设置”的。

// 如果设置成
type Student struct {
    Age int
    name string
}

会报错painc:

因为 s 包含了一个可设置的反射对象,我们可以修改结构体字段:

func main() {
  type Student struct {
    Age int
    Name string
  }
  t := Student{19, "hyy"}
  s := reflect.ValueOf(&t).Elem() // 用结构体的地址创建反射变量s
  s.Field(0).SetInt(123) // t.Age=123
  s.Field(1).SetString("123") // t.Name="123"
  fmt.Println("修改后:",t)
}

运行结果如下:

修改后: {123 123}

如果我们修改了程序让 s 由 t(而不是 &t)创建,程序就会在调用 SetInt 和 SetString 的地方失败,因为 t 的字段是不可设置的。

总结

反射规则可以总结为如下几条:

反射可以将“接口类型变量”转换为“反射类型对象”;

反射可以将“反射类型对象”转换为“接口类型变量”;

如果要修改“反射类型对象”,其值必须是“可写的”。

二、reflect-反射类型对象-获取类型信息

1. TypeOf()和reflect.Type类型

通过反射获取类型信息

在 Go语言中通过调用 reflect.TypeOf() 函数,我们可以从一个任何非接口类型的值创建一个 reflect.Type 值。

reflect.Type 值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。

当然,我们也可以将一个接口值传递给一个 reflect.TypeOf() 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type 值。

实际上,reflect.TypeOf() 函数的唯一参数的类型为 interface{},reflect.TypeOf() 函数将总是返回一个表示着此唯一接口参数值的动态类型的 reflect.Type值。

那如何得到一个表示着某个接口类型的 reflect.Type 值呢?

我们必须通过下面将要介绍的一些间接途径来达到这一目的。

类型 reflect.Type 为一个接口类型,它指定了若干方法(Type)。

通过这些方法,我们能够观察到一个 reflect.Type 值所表示的 Go类型的各种信息。这些方法中的有的适用于所有种类(Kind)的类型,有的只适用于一种或几种类型。

通过不合适的 reflect.Type 值调用某个方法将在运行时产生一个painc。

使用 reflect.TypeOf() 函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。下面通过例子来理解获取类型对象的过程:

func main() {    
    var a int
    // 通过 reflect.TypeOf() 取得变量 a 的类型对象 typeOfA,类型为 reflect.Type()。
    typeOfA := reflect.TypeOf(a)
    // 通过 typeOfA 类型对象的成员函数,
    // 可以分别获取到 typeOfA 变量的类型名为 int,种类(Kind)为 int。
    fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

代码输出如下:

int  int

1.1 理解反射的类型(Type)与种类(Kind)

在使用反射时,需要首先理解类型(Type)和种类(Kind)的区别。

编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。

例如,需要统一判断类型中的指针时,使用种类(Kind)信息就较为方便。

总结:

type Student struct{...}

type 类型是Student

kind 种类是struct

1.1.1 反射种类(Kind)的定义

Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。

例如使用 type A struct{}定义结构体时,A 就是 struct{} 的类型。

种类(Kind)指的是对象归属的品种,在 reflect 包中有如下定义:

type Kind uint
const (    
    Invalid Kind = iota  // 非法类型    
    Bool                 // 布尔型    
    Int                  // 有符号整型    
    Int8                 // 有符号8位整型    
    Int16                // 有符号16位整型    
    Int32                // 有符号32位整型    
    Int64                // 有符号64位整型    
    Uint                 // 无符号整型    
    Uint8                // 无符号8位整型    
    Uint16               // 无符号16位整型    
    Uint32               // 无符号32位整型    
    Uint64               // 无符号64位整型    
    Uintptr              // 指针    
    Float32              // 单精度浮点数    
    Float64              // 双精度浮点数    
    Complex64            // 64位复数类型    
    Complex128           // 128位复数类型    
    Array                // 数组    
    Chan                 // 通道    
    Func                 // 函数    
    Interface            // 接口    
    Map                  // 映射    
    Ptr                  // 指针    
    Slice                // 切片    
    String               // 字符串    
    Struct               // 结构体    
    UnsafePointer        // 底层指针
)

Map、Slice、Chan 属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。

type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。

1.1.2 从类型对象中获取类型名称和种类的例子

Go语言中的类型名称对应的反射获取方法是 reflect.Type 中的 Name() 方法,返回表示类型名称的字符串。

类型归属的种类(Kind)使用的是 reflect.Type 中的 Kind() 方法,返回 reflect.Kind 类型的常量。

下面的代码中会对常量和结构体进行类型信息获取。

// Enum 定义一个Enum类型
type Enum int
const (
  Zero Enum = 0
)
func main() {
  // 声明一个空结构体
  type cat struct{}
  // 获取结构体实例的反射类型对象
  typeOfCat := reflect.TypeOf(cat{})
  // 显示反射类型对象的名称和种类
  fmt.Println(typeOfCat.Name(), typeOfCat.Kind())
  // 获取Zero常量的反射类型对象
  typeOfA := reflect.TypeOf(Zero)
  // 显示反射类型对象的名称和种类
  fmt.Println(typeOfA.Name(), typeOfA.Kind())
}

代码输出如下:

cat struct
Enum int

1.2. reflect.Elem()

通过反射获取指针指向的元素类型。

func main() {
    // 声明一个空结构体
    type cat struct {
    }
    // 创建cat的实例
    ins := cat{}
    // 获取结构体实例的反射类型对象
    typeOfCat := reflect.TypeOf(&ins)
    // 显示反射类型对象的名称和种类
    fmt.Printf("name:'%v' kind:'%v'\n",typeOfCat.Name(), typeOfCat.Kind())
    // 取类型的元素
    typeOfCat = typeOfCat.Elem()
    // 显示反射类型对象的名称和种类
    fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfCat.Name(), typeOfCat.Kind())
}

对于指针一般都是直接

typeOfX := reflect.TypeOf(&x).Elem

取指针指向元素类型

1.3. 通过反射类型对象获取结构体的成员类型

任意值通过 reflect.TypeOf() 获得反射类型对象后,

如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。

与成员获取相关的 reflect.Type 的方法如下表所示。

image.png

1.3.1 结构体字段类型(StructField )

reflect.Type 的 Field() 方法返回 StructField 结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。

StructField 的结构如下:

type StructField struct {    
    Name string          // 字段名    
    PkgPath string       // 字段路径    
    Type      Type       // 字段反射类型对象    
    Tag       StructTag  // 字段的结构体标签    
    Offset    uintptr    // 字段在结构体中的相对偏移    
    Index     []int      // Type.FieldByIndex中的返回的索引值    
    Anonymous bool       // 是否为匿名字段
}

字段说明如下。

Name:为字段名称。

PkgPath:字段在结构体中的路径。

Type:字段本身的反射类型对象,类型为 reflect.Type,可以进一步获取字段的类型信息。

Tag:结构体标签,为结构体字段标签的额外信息,可以单独提取。

Index:FieldByIndex 中的索引顺序。

Anonymous:表示该字段是否为匿名字段。

func main() {
// 声明一个结构体
type cat struct {
   // 带有结构体tag的字段
   Type int `json:"type" id:"100"`
}
// 创建cat的实例
ins := cat{Name: "mimi", Type: 1}
// 获取结构体实例的反射类型对象
typeOfCat := reflect.TypeOf(&ins).Elem()
// 2. 通过字段名, 找到字段类型信息
catType, ok := typeOfCat.FieldByName("Type")
if ok {
   fmt.Println(catType.Name) //Type
   fmt.Println(catType.PkgPath) // ""
   fmt.Println(catType.Type) // int
   fmt.Println(catType.Tag) // json:"type" id:"100"
   fmt.Println(catType.Offset) // 16
   fmt.Println(catType.Index) // [1]
   fmt.Println(catType.Anonymous) // false
}
}

1.3.2 获取成员反射信息

下面代码中,实例化一个结构体并遍历其结构体成员,再通过 reflect.Type 的 FieldByName() 方法查找结构体中指定名称的字段,直接获取其类型信息。

反射访问结构体成员类型及信息:

func main() {
  // 声明一个空结构体
  type cat struct {
    Name string
    // 带有结构体tag的字段
    Type int `json:"type" id:"100"`
  }
  // 创建cat的实例
  ins := cat{Name: "mimi", Type: 1}
  // 获取结构体实例的反射类型对象
  typeOfCat := reflect.TypeOf(&ins).Elem()
  // 方法1. 遍历结构体所有成员字段
  for i := 0; i < typeOfCat.NumField(); i++ {
    // 获取每个成员的结构体字段类型
    fieldType := typeOfCat.Field(i)
    // 输出成员名,成员反射类型和tag
    fmt.Printf("name: %v,type: %v,tag: '%v'\n", fieldType.Name,fieldType.Type, fieldType.Tag)
  }
  // 方法2. 通过字段名, 找到字段类型信息
  catType, ok := typeOfCat.FieldByName("Type")
  if ok {
    // 从tag中取出需要的tag
    fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
  }
}

代码输出如下:

name: Name,type: string,tag: ''
name: Type,type: int,tag: 'json:"type" id:"100"'
type 100

1.4 通过反射类型对象创建实例

当已知 reflect.Type 时,可以动态地创建这个类型的实例,实例的类型为指针。

例如 reflect.Type 的类型为 int 时,创建 int 的指针,即*int,代码如下:

func main() {
    var a int
    // 取变量a的反射类型对象
    typeOfA := reflect.TypeOf(a)
    // 根据反射类型对象创建类型实例
    // 使用 reflect.New() 函数传入变量 a 的反射类型对象,创建这个类型的实例值,值以 reflect.Value 类型返回。这步操作等效于:new(int),因此返回的是 *int 类型的实例。
    aIns := reflect.New(typeOfA)
    // 输出Value的类型和种类
    fmt.Println(aIns.Type(), aIns.Kind())
}

运行结果:

*int ptr

reflect-反射值对象-获取值信息

相关文章
|
3天前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
8天前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片
|
11天前
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
26 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
|
3天前
|
存储 缓存 监控
企业监控软件中 Go 语言哈希表算法的应用研究与分析
在数字化时代,企业监控软件对企业的稳定运营至关重要。哈希表(散列表)作为高效的数据结构,广泛应用于企业监控中,如设备状态管理、数据分类和缓存机制。Go 语言中的 map 实现了哈希表,能快速处理海量监控数据,确保实时准确反映设备状态,提升系统性能,助力企业实现智能化管理。
22 3
|
2天前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
7天前
|
开发框架 前端开发 Go
eino — 基于go语言的大模型应用开发框架(二)
本文介绍了如何使用Eino框架实现一个基本的LLM(大语言模型)应用。Eino中的`ChatModel`接口提供了与不同大模型服务(如OpenAI、Ollama等)交互的统一方式,支持生成完整响应、流式响应和绑定工具等功能。`Generate`方法用于生成完整的模型响应,`Stream`方法以流式方式返回结果,`BindTools`方法为模型绑定工具。此外,还介绍了通过`Option`模式配置模型参数及模板功能,支持基于前端和用户自定义的角色及Prompt。目前主要聚焦于`ChatModel`的`Generate`方法,后续将继续深入学习。
98 6
|
3天前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
8天前
|
存储 开发框架 Devops
eino — 基于go语言的大模型应用开发框架(一)
Eino 是一个受开源社区优秀LLM应用开发框架(如LangChain和LlamaIndex)启发的Go语言框架,强调简洁性、可扩展性和可靠性。它提供了易于复用的组件、强大的编排框架、简洁明了的API、最佳实践集合及实用的DevOps工具,支持快速构建和部署LLM应用。Eino不仅兼容多种模型库(如OpenAI、Ollama、Ark),还提供详细的官方文档和活跃的社区支持,便于开发者上手使用。
72 8
|
18天前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
68 20
|
8天前
|
存储 算法 Go
Go语言实战:错误处理和panic_recover之自定义错误类型
本文深入探讨了Go语言中的错误处理和panic/recover机制,涵盖错误处理的基本概念、自定义错误类型的定义、panic和recover的工作原理及应用场景。通过具体代码示例介绍了如何定义自定义错误类型、检查和处理错误值,并使用panic和recover处理运行时错误。文章还讨论了错误处理在实际开发中的应用,如网络编程、文件操作和并发编程,并推荐了一些学习资源。最后展望了未来Go语言在错误处理方面的优化方向。

热门文章

最新文章