Golang如何通过反射区分指针与非指针类型

Kind() == reflect.Ptr 是判断指针类型的唯一可靠方式,它对 nil 指针安全、不 panic,且不受层级嵌套或类型别名影响;Name() 和字符串匹配均不可靠。

Kind() 判断是否为指针类型最直接

Go 没有语法层面的 isPointer 运算符,但 reflect.Kind() 是唯一可靠、轻量、不 panic 的判断依据。它返回的是底层类型“种类”,不是类型名,所以不会被 *int**string 的表层写法干扰。

  • reflect.ValueOf(x).Kind() == reflect.Ptr → 是指针(包括 *T**T 等任意层级)
  • reflect.TypeOf(x).Kind() == reflect.Ptr → 同样有效,适合只关心类型定义的场景(比如参数校验)
  • 注意:Kind()nil 指针也安全,返回 reflect.Ptr,不会 panic
var p *int = nil
v := reflect.ValueOf(p)
fmt.Println(v.Kind() == reflect.Ptr) // true —— 安全

别用 Name() 或字符串匹配来判断指针

Name() 返回的是类型名(如 "int"),对指针类型永远返回空字符串;而把 Type.String() 结果拿去字符串匹配 "*",既脆弱又不可靠——结构体嵌套字段、泛型类型参数、别名类型都可能破坏匹配逻辑。

  • reflect.TypeOf(&x).Name()""(空),因为 *int 没有名字
  • reflect.TypeOf(&x).String()"*int",但 **map[string][]byte 这类就难解析
  • 一旦遇到自定义类型别名(如 type PtrInt *int),字符串匹配彻底失效

处理嵌套指针时,Elem() 要配合 IsNil() 一起用

想拿到指针指向的“真实类型”或“真实值”,必须调用 Elem(),但它在 nil 指针上会 panic。所以每次解引用前,必须确认非 nil 且可解引用。

  • 先检查 v.Kind() == reflect.Ptr
  • 再检查 !v.IsNil()IsNil() 只对 PtrSliceMap 等合法)
  • 满足后才调用 v.Elem(),否则 panic
func deref(v reflect.Value) reflect.Value {
	for v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return v // 或按需返回零值/错误
		}
		v = v.Elem()
	}
	return v
}

修改指针指向的值,必须确保原始变量可寻址

反射能改值的前提是:传入 reflect.ValueOf() 的是变量地址(&x),而不是值本身(x)。否则 SetXXX() 会 panic:“using unaddressable value”。

立即学习“go语言免费学习笔记(深入)”;

  • 错: v := reflect.ValueOf(x); v.Elem().SetInt(42) → panic
  • 对: v := reflect.ValueOf(&x); v.Elem().SetInt(42) → 成功
  • 对嵌套指针(如 ***int),只要最外层是可寻址的(即你传了 &p),就能一路 Elem().Elem().Elem() 改到底层
判断指针与否这件事本身很简单,真正容易出问题的是后续操作:解引用时忘了判 nil,改值时忘了传地址,或者误以为 Name() 能反映指针特征。记住,Kind() == reflect.Ptr 是唯一值得信赖的起点,其余全是围绕它做的防御性操作。