Go反射修改变量必须满足三个条件:变量需可寻址(用&取地址后Elem)、类型严格匹配(如int用SetInt、int64用SetInt64)、结构体字段必须导出(首字母大写)。

反射修改变量前必须用 addr 获取地址
Go 的 reflect.Value 默认是只读副本,直接调用 Set 会 panic:「reflect.Value.Set using unaddressable value」。这是因为反射无法修改栈上原始变量的值,必须通过指针间接操作。
正确做法是先用 reflect.ValueOf(&v) 获取指向变量的 reflect.Value,再用 .Elem() 解引用得到可寻址的值:
package main
import (
"fmt"
"reflect"
)
func main() {
x := 42
v := reflect.ValueOf(&x).Elem() // ✅ 关键:取地址后再 .Elem()
v.SetInt(100)
fmt.Println(x) // 输出 100
}
- 如果传入的是非指针(如
reflect.ValueOf(x)),v.CanAddr()和v.CanSet()都返回false -
.Addr()方法仅对可寻址值有效(如结构体字段、切片元素),不能用于普通局部变量本身 - 函数参数默认按值传递,若想在函数内用反射修改,调用方必须传指针
Set 系列方法严格要求类型匹配
反射设置值不是“类型擦除”操作:v.SetInt(42) 要求 v.Kind() == reflect.Int 且底层类型为 int;若 v 是 int64,必须用 SetInt64,否则 panic。
常见错误场景:
- 对
interface{}变量反射后,其Value的类型是具体底层类型(如int),不是interface{} - 结构体字段是
int32,误用SetInt→ 应该用SetInt32 - 想用
SetString设置[]byte字段 → 不行,[]byte是切片,不是字符串
安全做法:先检查 v.Kind() 和 v.Type(),再选对应 SetXxx 方法。例如:
if v.Kind() == reflect.Int {
v.SetInt(123)
} else if v.Kind() == reflect.Int64 {
v.SetInt64(123)
}
修改结构体字段需确保字段可导出
反射只能修改首字母大写的导出字段。即使你用 reflect.ValueOf(&s).Elem().FieldByName("name") 拿到字段,若 name 是小写(如 name string),v.CanSet() 返回 false,调用 Set 会 panic。
示例对比:
type User struct {
Name string // ✅ 可修改
age int // ❌ 不可修改:未导出
}
u := User{}
v := reflect.ValueOf(&u).Elem()
v.FieldByName("Name").SetString("Alice") // OK
v.FieldByName("age").SetInt(25) // panic: cannot set unexported field
- 嵌套结构体同理:所有中间层级字段都必须导出,才能链式访问并修改最内层字段
- 使用
FieldByName前建议先用CanInterface()或CanSet()判断是否可写
切片/映射/通道的 Set 行为容易误解
对切片、映射或通道调用 Set,本质是替换整个底层数据结构,而不是修改其中元素。比如:
-
v.Set(reflect.ValueOf([]int{1,2,3}))→ 替换整个切片,原底层数组被丢弃 - 想修改切片第 0 个元素?要用
v.Index(0).SetInt(99),前提是v本身可寻址且长度足够 - 映射不支持
SetMapIndex以外的“整体赋值”,且SetMapIndex要求 key/value 类型完全匹配
特别注意:向空切片 []int(nil) 调用 v.Len() 或 v.Index(0) 会 panic,必须先用 reflect.MakeSlice 创建新切片再赋值。










