# 反射

golang的反射主要包含两个类型：`reflect.Type`和`reflect.Value`，他们主要由函数`reflect.TypeOf(变量名)`以及`reflect.ValueOf(变量名)`创建而来。其中`Type`用于表示目标变量的类型的元数据，而通过`Value`则是代表变量的值（可以对值进行修改、函数值进行函数调用等）。

1. 在运行时动态获取变量的各种信息，比如变量的类型(type)、类别(kind)
2. 如果是结构体变量，还可以获取结构体本身的 的信息(字段、方法)
3. 通过反射可以修改变量的值，也可以调用关联方法
4. 使用反射需要引入包`import "reflect"`

## 反射的基本函数

```go
package main

import (
	"fmt"
	"reflect"
)

func main() {

	var n int64 = 10

	// 获取变量类型信息 reflect.Type
	rTyp := reflect.TypeOf(n)
	fmt.Printf("rType: type=%T, toString=%s\n", rTyp, rTyp.String())

	// 获取变量值信息 reflect.Value
	rVal := reflect.ValueOf(n)
	fmt.Printf("rVal: type=%T, toString=%s, originValue=%d\n", rVal, rVal.String(), rVal.Int()) // 转换为原始类型，如果调用的方法与实际类型不匹配，会发生panic异常

	// 获取变量对应的kind，type代表变量的具体类型，而kind代表变量的分类类型
	// 如果是基本类型，kind==type，比如 kind=int64 type=int64
	// 如果不是基本类型，比如struct，type与kind一般不同，比如 kind=struct type=main.Student
	kind1 := rTyp.Kind()
	kind2 := rVal.Kind()
	fmt.Printf("kind=%s, %s", kind1, kind2)


	// 根据 rVal 获取变量值，因为不知道是什么类型，所以以interface的方式返回
	iV := rVal.Interface()
	fmt.Printf("iV: type=%T, originValue=%v\n ", iV, iV)

	// 将interface{} 通过断言转换成需要的类型
	n2 := iV.(int64)
	fmt.Println(n2)
}
```

返回结果：

```
rType: type=*reflect.rtype, toString=int64
rVal: type=reflect.Value, toString=<int64 Value>, originValue=10
kind=int64, int64iV: type=int64, originValue=10
10
```

## 通过反射修改值

在 Golang 中，通过 反射 的方法`reflect.ValueOf`传入的不管是变量的值还是地址，通过反射都不可以直接修改变量的值，如果尝试修改，将会引发一个`panic`：

```go
package main

import (
	"fmt"
	"reflect"
)

func main() {
	i := 10
	rVal := reflect.ValueOf(i) // panic: reflect: reflect.Value.SetInt using unaddressable value
	rVal.SetInt(11)
	fmt.Println(i)
}
```

上面的异常是说，`SetInt`方法，也就是修改变量值的反射方法，不可以用在一个不可取地址的`reflect.Value`上，通过`reflect.ValueOf`传入变量获取的`reflect.Value`都是不可取地址值。这是因为无论什么类型的变量，`relect.Value`都是存储变量的指针(地址)。

1. 值类型值传入

   ```go
   var x float64 = 3.4
   v := reflect.ValueOf(x) // x 值传入，ValueOf接收到的x已经不是原来的x
   v.SetFloat(7.1) // 这句话及时执行成功，也无法修改变量x的值
   ```
2. 值类型取地址传入

   ```go
   var x float64 = 3.4
   p := reflect.ValueOf(&x) // 注意：获取 x 的地址
   fmt.Println("type of p:", p.Type())
   fmt.Println("settability of p:", p.CanSet()) // CanSet用来判断是否是可取地址的， 此处返回false
   // 此处仍然是不可取地址的，因为p内部存储的是变量的地址，需要通过类似 *x 的方式真正访问变量空间
   ```
3. 使用`Elem()`方法访问变量内存

   ```go
   v := p.Elem()
   fmt.Println("settability of v:", v.CanSet()) // true
   v.SetFloat(7.1)
   fmt.Println(v.Interface()) // 7.1
   fmt.Println(x) // 7.1
   ```
4. 结构体值访问

   ```go
   type T struct {
       A int
       B string
   }
   t := T{23, "skidoo"}
   s := reflect.ValueOf(&t).Elem()
   typeOfT := s.Type()
   for i := 0; i < s.NumField(); i++ {
       f := s.Field(i)
       fmt.Printf("%d: %s %s = %v\n", i,
           typeOfT.Field(i).Name, f.Type(), f.Interface())
   }
   ```
5. 结构体字段值修改

   ```go
   s.Field(0).SetInt(77)
   s.Field(1).SetString("Sunset Strip")
   fmt.Println("t is now", t)
   ```

## 反射操作方法与函数

```go
// 获取add函数Type
vType:=reflect.TypeOf(add)

// 返回func类型的参数个数，如果不是函数，将会panic 
numIn:=vType.NumIn()

addIn:=make([]reflect.Type，numIn) 
for i:=0;i<numIn;i++{ addIn[i]=vType.In(i) 
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/golang/fan-she.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
