引言
在写代码时,我们可能会有这样的需求:根据特定的对象类型进行定制操作。
针对这件事情,C++中提供了type_traits这一机制。说的高大上一些,它是一种萃取机。实际上我认为type_traits就是利用模板的特化和模板参数推导,从而在编译期就获得的类模板的一个实例。
而接下来介绍Go一种比较有意思的机制:反射。相当特别的是,这件事是在运行期完成的。
借用李文周的博客中对反射的介绍:
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
可以看到,反射在为程序员提供便利时也带来了一些运行期的开销,这一点可能比较像C++中的RTTI技术。同样的,过多的反射会降低可执行程序的运行效率,我们应该慎用它。
实现
这里只举了string和int的例子,具体讲解在注释中。
package main
import (
"fmt"
"reflect"
)
// TypeOf : 类型信息
// ValueOf : 值信息
func set_val(src interface{
}, dst interface{
}) bool {
src_val := reflect.ValueOf(src)
// 无法被设置通常代表其非指针
if !src_val.CanSet() && !(src_val.Kind() == reflect.Ptr) {
fmt.Printf("can't set %v\n", reflect.TypeOf(src).Kind())
return false
}
// 到此处应是一个有效的Ptr
src_elem := src_val.Elem()
switch src_elem.Kind() {
case reflect.String:
src_elem.SetString(reflect.ValueOf(dst).String())
fmt.Printf("string : %v in func set_val\n", src_elem.String())
case reflect.Int:
src_elem.SetInt(reflect.ValueOf(dst).Int())
fmt.Printf("int : %v in func set_val\n", src_elem.Int())
default:
fmt.Printf("unknow type : in func set_val\n")
}
return true
}
// 修改反射对象
func main() {
var (
s string = "s"
i int = 0
)
fmt.Println(i, s)
// 无效的修改
set_val(i, 666)
set_val(s, "alter s")
fmt.Println(i, s)
// 有效的修改
set_val(&s, "alter s")
set_val(&i, 666)
fmt.Println(i, s)
}
输出:
0 s
can't set int
can't set string
0 s
string : alter s in func set_val
int : 666 in func set_val
666 alter s
另外,当我尝试修改结构体成员时,我失败了,我找不到一个通用的方法使reflect.Value转换成实际类型。并且我发现传入一个reflect.Value类型时会出现不正确的结果(在例子中,对象被识别为struct),而在代码中调用Kind,显示又是正确的。这样的话可能就没法递归调用这个函数了。
代码如下:
package main
import (
"fmt"
"reflect"
)
type S struct {
s1 string
s2 string
}
// TypeOf : 类型信息
// ValueOf : 值信息
func set_val(src interface{
}, dst interface{
}) bool {
src_val := reflect.ValueOf(src)
dst_val := reflect.ValueOf(dst)
// 无法被设置通常代表其非指针
if !src_val.CanSet() && !(src_val.Kind() == reflect.Ptr) {
fmt.Printf("can't set %v\n", reflect.TypeOf(src).Kind())
return false
}
// 到此处应是一个有效的Ptr
src_elem := src_val.Elem()
// 获取结构体成员
// 对非Ptr调用Elem会造成panic
var dst_elem reflect.Value
if dst_val.Kind() == reflect.Ptr {
dst_elem = reflect.ValueOf(dst).Elem()
} else {
dst_elem = reflect.ValueOf(dst)
}
switch src_elem.Kind() {
case reflect.String:
src_elem.SetString(reflect.ValueOf(dst).String())
fmt.Printf("string : %v in func set_val\n", src_elem.String())
case reflect.Int:
src_elem.SetInt(reflect.ValueOf(dst).Int())
fmt.Printf("int : %v in func set_val\n", src_elem.Int())
case reflect.Struct:
for i := 0; i < src_elem.NumField(); i++ {
src_field := src_elem.Field(i)
dst_field := dst_elem.Field(i)
//fmt.Println("Kind of src_field is", src_field.Kind()) // Kind of src_field is string
// 递归
set_val(src_field, dst_field) // can't set struct
}
fmt.Printf("struct : %v in func set_val\n", src_elem)
default:
fmt.Printf("unknow type : in func set_val\n")
}
return true
}
// 修改反射对象
func main() {
var (
s string = "s"
i int = 0
s_struct S = S{
"s1", "s2"}
)
fmt.Println(i, s, s_struct)
// 无效的修改
set_val(i, 666)
set_val(s, "alter s")
fmt.Println(i, s, s_struct)
// 有效的修改
set_val(&s, "alter s")
set_val(&i, 666)
set_val(&s_struct, S{
"alter s1", "alter s2"})
fmt.Println(i, s, s_struct)
}
输出:
0 s {
s1 s2}
can't set int
can't set string
0 s {
s1 s2}
string : alter s in func set_val
int : 666 in func set_val
can't set struct
can't set struct
struct : {
s1 s2} in func set_val
666 alter s {
s1 s2}