怎么做公司的网站,深圳互联网企业有哪些,wordpress文件夹权限,没有域名可以先做网站吗为什么需要反射
下面的例子中编写一个 Sprint 函数#xff0c;只有1个参数#xff08;类型不定#xff09;#xff0c;返回和 fmt.Fprintf 类似的格式化后的字符串。实现方法大致为#xff1a;如果参数类型本身实现了 String() 方法#xff0c;那调用 String() 方法即可…为什么需要反射
下面的例子中编写一个 Sprint 函数只有1个参数类型不定返回和 fmt.Fprintf 类似的格式化后的字符串。实现方法大致为如果参数类型本身实现了 String() 方法那调用 String() 方法即可否则用 switch 分支类型断言测试各种类型并进行相应处理。
func Sprint(x interface{}) string {type stringer interface {String() string}switch x : x.(type) {case stringer:return x.String()case string:return xcase int:return strconv.Itoa(x)// ...similar cases for int16, uint32, and so on...case bool:if x {return true}return falsedefault:// array, chan, func, map, pointer, slice, structreturn ???}
}
以上做法有点“简洁暴力”。问题是一旦遇上数组、Slice、Map、结构体那样的复合类型用户构造类型这种类型本质上是无穷尽的用上面的做法是徒劳的。这就是反射存在的意义。
reflect.Type 和 reflect.Value
反射是 reflect 包提供的里面最重要的是 reflect.Type 和 reflect.Value 这两个类型。Type 表示一个 Go类型。 Type 其实是接口看 src/reflect/type.goType接口里有很多方法如String()、Kind()、Comparable()、Elem()等有许多方法来区分类型以及检查它们的组成部分唯一能反映 reflect.Type 实现的是接口的类型描述信息类型描述信息标识了接口值的动态类型参见接口中接口值一节。
函数 reflect.TypeOf 接受 interface{} 类型的参数即任意类型的参数并以 reflect.Type 形式返回具体类型。
t : reflect.TypeOf(3) // a reflect.Type
fmt.Println(t.String()) // int
fmt.Println(t) // int
在上面的代码中值 3 传递给 interface{} 参数其实会产生隐式的接口转换操作其实就类似装箱操作接口变量用指针指向底层数据3它会创建一个包含两个信息的接口值操作数的动态类型这里是int和它的动态的值这里是3。
reflect.TypeOf 返回的是一个动态类型的接口值它总是返回具体的类型即便变量被声明为接口类型TypeOf方法根据接口变量中的具体类型信息创建Type类型的对象。下面的代码中打印输出 *os.File 而非 io.Writer。
var w io.Writer os.Stdout
fmt.Println(reflect.TypeOf(w)) // *os.File
reflect.Type 接口是满足 fmt.Stringer 接口的src/fmt/print.go即实现了 String() 方法。打印出接口的动态类型对于调试和日志是有帮助的所以 fmt.Printf 提供了 %T 参数在内部使用 reflect.TypeOf 来输出 即 %T 是靠反射来识别类型的
fmt.Printf(%T\n, 3) // int
另一个重要类型是Value。 reflect.Value 类型可以装载任意类型的值 这一点和 空接口 interface{} 类似。函数 reflect.ValueOf 接受任意的 interface{}类型并返回一个装载了其动态值的 reflect.Value。reflect.ValueOf 返回的结果也是具体的类型或者一个接口值。
v : reflect.ValueOf(3) // a reflect.Value
fmt.Println(v) // 3
fmt.Printf(%v\n, v) // 3
fmt.Println(v.String()) // NOTE: int Value
上面的代码中3 先会隐式接口转换“装箱”然后根据转换后接口变量中的值部分生成Value类型的对象 v。和 reflect.Type 那样reflect.Value 也满足 fmt.Stringer 接口但除非 Value类型的对象内部持有的是字符串否则 String 方法返回类型信息而非所持有的值本身这也是可以理解的内部持有的是字符串可以简单输出“字符串表达的值”如果持有一个整数3或者浮点数3.14就涉及复杂的转换才能得到文本3或者3.14而这种转换对反射是没有意义的。和 %T 类似%v 标志会对 reflect.Value 特殊处理即 %T 取类型 %v 取值。
reflect.Value 可以通过 Type方法和 reflect.Type联系起来调用 Value类型对象的 Type 方法将返回具体类型所对应的 reflect.Type
t : v.Type() // a reflect.Type
fmt.Println(t.String()) // int
reflect.ValueOf(...) 是 interface {} --- Value 转换其逆操作是 reflect.Value.Interface(...) 即Value --- interface{} 转换
v : reflect.ValueOf(3) // a reflect.Value
x : v.Interface() // an interface{}
i : x.(int) // an int
fmt.Printf(%d\n, i) // 3
reflect.Value 和 空接口 interface{} 都能装载任意的值不同的是空接口类型本身没有提供方法或字段来说明那个值的内部表示只有明确知道了具体的动态类型才能使用类型断言来访问内部的值。相比之下Value提供了很多方法来检查其容纳的内容。简单理解就是 reflect.Value 类型的变量是“自我清醒的知道自己是谁有什么能耐几斤几两”空接口 interface{} 类型的变量是“自己糊涂的”。
有了反射就可以考虑处理文首的格式化打印任何类型的问题了。我们用 reflect.Value 的 Kind 方法代替 switch 分支判断因为 reflect.Value.Kind()方法返回 Value对象底层数据的数据类别数据类别kinds类型是有限的如 Bool、String 和 Int8……Complex128等数字类型的基础类型Array、Struct对应的复合类型Chan、Func、Pointer即Ptr、Slice、Map对应的引用类型Interface 接口类型还有表示空值的 Invalid类型。reflect.Value结构体类型源码定义中有成员 flagflag的最低5位表示所含值的 Kind类型接下来5位是标志位如果所含值表示的是方法那么剩余22位定义了方法编号
package formatimport (reflectstrconv
)func Any(value interface{}) string {return formatAtom(reflect.ValueOf(value))
}func formatAtom(v reflect.Value) string {switch v.Kind() {case reflect.Invalid:return invalidcase reflect.Int, reflect.Int8, reflect.Int16,reflect.Int32, reflect.Int64:return strconv.FormatInt(v.Int(), 10)case reflect.Uint, reflect.Uint8, reflect.Uint16,reflect.Uint32, reflect.Uint64, reflect.Uintptr:return strconv.FormatUint(v.Uint(), 10)// ...floating-point and complex cases omitted for brevity...case reflect.Bool:return strconv.FormatBool(v.Bool())case reflect.String:return strconv.Quote(v.String())case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:return v.Type().String() 0x strconv.FormatUint(uint64(v.Pointer()), 16)default: // reflect.Array, reflect.Struct, reflect.Interfacereturn v.Type().String() value}
}使用示例代码
package mainimport (fmtformattime
)type Post struct {Title stringcontent string
}func main() {var x int64 1var d time.Duration 1 * time.Nanosecondpost : Post{标题1, 帖子1的内容}fmt.Println(format.Any(x)) // 1fmt.Println(format.Any(d)) // 1fmt.Println(format.Any([]int64{x})) // []int64 0x8202b87b0fmt.Println(format.Any([]time.Duration{d})) // []time.Duration 0x8202b87e0fmt.Println(format.Any(post))
}显示的结果类似如下
D:\GoPath\src\examplego run main.go 1 是基础类型 int64 1 time.Duration 本质上是基础类型 int64 []int64 0xc00000a0e8 对slice打印类型和16进制形式的引用地址 []time.Duration 0xc00000a110 对slice打印类型和16进制形式的引用地址
main.Post value 对复合类型这里是结构体只打印类型
Display 递归打印
前面的代码对复合类型的显示不太完善我们希望构建一个适合于调试用的能详细显示复杂类型完整结构的 Display函数。
我们在 format包添加一个导出的 Display函数它将调用不导出的 display函数而 display将实现递归打印
func Display(name string, x interface{}) {fmt.Printf(Display %s (%T):\n, name, x)display(name, reflect.ValueOf(x))
}func display(path string, v reflect.Value) {switch v.Kind() {case reflect.Invalid:fmt.Printf(%s invalid\n, path)case reflect.Slice, reflect.Array: // 切片或数组for i : 0; i v.Len(); i {display(fmt.Sprintf(%s[%d], path, i), v.Index(i))}case reflect.Struct: // 结构体for i : 0; i v.NumField(); i {fieldPath : fmt.Sprintf(%s.%s, path, v.Type().Field(i).Name)display(fieldPath, v.Field(i))}case reflect.Map: // Mapfor _, key : range v.MapKeys() {display(fmt.Sprintf(%s[%s], path,formatAtom(key)), v.MapIndex(key))}case reflect.Ptr: // 指针if v.IsNil() {fmt.Printf(%s nil\n, path)} else {display(fmt.Sprintf((*%s), path), v.Elem())}case reflect.Interface: // 接口if v.IsNil() {fmt.Printf(%s nil\n, path)} else {fmt.Printf(%s.type %s\n, path, v.Elem().Type())display(path.value, v.Elem())}default: // basic types, channels, funcsfmt.Printf(%s %s\n, path, formatAtom(v))}
}对于切片和数组打印每一个元素此时 Value类型的 Len 和 Index 方法可以帮我们获取元素个数和每个元素对于结构体打印它的成员Value类型的 NumField方法和Field方法可以获取成员个数和每个成员的值而成员名称可以 从Value类型生成对应Type对象根据Type对象的Field方法以及StructField的Name字段值获得名称对于Map则是靠 MapKeys 和 MapIndex 两个方法来遍历键值对对于指针Value的Elem方法可以返回包装了指向变量的指针的reflect.Value类型可以递归调用对空指针调用Elem方法也是安全的不过可以用Value的IsNil方法来更精细处理对于接口处理方式和指针类似一般指空接口即golang的void*指针即使用Value的Elem方法。
package main// ……type Movie struct {Title, Subtitle stringYear intColor boolActor map[string]stringOscars []stringSequel *string
}func main() {// ……strangelove : Movie{Title: Dr. Strangelove,Subtitle: How I Learned to Stop Worrying and Love the Bomb,Year: 1964,Color: false,Actor: map[string]string{Dr. Strangelove: Peter Sellers,Grp. Capt. Lionel Mandrake: Peter Sellers,Pres. Merkin Muffley: Peter Sellers,Gen. Buck Turgidson: George C. Scott,Brig. Gen. Jack D. Ripper: Sterling Hayden,Maj. T.J. King Kong: Slim Pickens,},Oscars: []string{Best Actor (Nomin.),Best Adapted Screenplay (Nomin.),Best Director (Nomin.),Best Picture (Nomin.),},}format.Display(strangelove, strangelove)
}输出结果如下
Display strangelove (main.Movie):
strangelove.Title Dr. Strangelove
strangelove.Subtitle How I Learned to Stop Worrying and Love the Bomb
strangelove.Year 1964
strangelove.Color false
strangelove.Actor[Grp. Capt. Lionel Mandrake] Peter Sellers
strangelove.Actor[Pres. Merkin Muffley] Peter Sellers
strangelove.Actor[Gen. Buck Turgidson] George C. Scott
strangelove.Actor[Brig. Gen. Jack D. Ripper] Sterling Hayden
strangelove.Actor[Maj. T.J. \King\ Kong] Slim Pickens
strangelove.Actor[Dr. Strangelove] Peter Sellers
strangelove.Oscars[0] Best Actor (Nomin.)
strangelove.Oscars[1] Best Adapted Screenplay (Nomin.)
strangelove.Oscars[2] Best Director (Nomin.)
strangelove.Oscars[3] Best Picture (Nomin.)
strangelove.Sequel nil
这个 Display 函数不仅可以打印用户定义的类型也可以打印标准库中的类型。反射可以访问到结构体中未导出的成员即可能破坏访问控制不过好在非导出的成员是只读的见后。
观察以下代码的输出 var i interface{} 3format.Display(i, i)format.Display(i, i)
输出
Display i (int):
i 3
Display i (*interface {}):
(*i).type int
(*i).value 3
要理解以上输出先来理解下图 当我们调用 Display(i, i) 时因为 x 的参数类型是 interface{}所以 3 会被打包成 int, 3然后 函数内reflect.ValueOf 根据 int, 3 生成 reflect.Value对象对象内部指针指向 3int,3中的3因为是基本类型display就调用了 formatAtom最终用 strconv.FormatInt(v.Int(), 10) 得到结果 3 。
当我们调用 Display(i, i) 时因为被打包的类型是指针int*, 0x....display会按指针去打印最终执行 fmt.Printf(%s.type %s\n, path, v.Elem().Type()) 和 display(path.value, v.Elem())后一句 v.Elem() 才对应数据 3 。
目前实现的 Display 在遇到对象图中有环的情形时会陷入死循环。要处理环需要额外记录已经访问的路径此处暂略。
编码为S表达式
S表达式是 Lisp语言语法的数据格式Go标准库支持JSON、XML等数据格式但不支持S表达式因为没有公认的规范。下面的例子支持以下结构整数如 42字符串如 hello标识符如 foo列表如 (1 2 3)对布尔型 nil 表示falset 表示 true
在 sexpr 包 的 sexpr.go 中类似前面 Display函数定义 encode函数它接受 reflect.Value 型参数将编码结果写入 *bytes.Buffer型缓冲区然后在文件中再仿照json等包定义一个导出的 Marshal 函数其接受 interface{} 类型任何类型。
package sexprimport (bytesfmtreflect
)func Marshal(v interface{}) ([]byte, error) {var buf bytes.Bufferif err : encode(buf, reflect.ValueOf(v)); err ! nil {return nil, err}return buf.Bytes(), nil
}func encode(buf *bytes.Buffer, v reflect.Value) error {switch v.Kind() {case reflect.Invalid:buf.WriteString(nil)case reflect.Int, reflect.Int8, reflect.Int16,reflect.Int32, reflect.Int64:fmt.Fprintf(buf, %d, v.Int())case reflect.Uint, reflect.Uint8, reflect.Uint16,reflect.Uint32, reflect.Uint64, reflect.Uintptr:fmt.Fprintf(buf, %d, v.Uint())case reflect.String:fmt.Fprintf(buf, %q, v.String())case reflect.Ptr:return encode(buf, v.Elem())case reflect.Array, reflect.Slice: // (value ...)buf.WriteByte(()for i : 0; i v.Len(); i {if i 0 {buf.WriteByte( )}if err : encode(buf, v.Index(i)); err ! nil {return err}}buf.WriteByte())case reflect.Struct: // ((name value) ...)buf.WriteByte(()for i : 0; i v.NumField(); i {if i 0 {buf.WriteByte( )}fmt.Fprintf(buf, (%s , v.Type().Field(i).Name)if err : encode(buf, v.Field(i)); err ! nil {return err}buf.WriteByte())}buf.WriteByte())case reflect.Map: // ((key value) ...)buf.WriteByte(()for i, key : range v.MapKeys() {if i 0 {buf.WriteByte( )}buf.WriteByte(()if err : encode(buf, key); err ! nil {return err}buf.WriteByte( )if err : encode(buf, v.MapIndex(key)); err ! nil {return err}buf.WriteByte())}buf.WriteByte())case reflect.Bool:if v.Bool() {fmt.Fprintf(buf, %s, t)} else {fmt.Fprintf(buf, %s, nil)}default: // float, complex, chan, func, interfacereturn fmt.Errorf(unsupported type: %s, v.Type())}return nil
}调用 Marshal的例子
// ……
type Movie struct {Title, Subtitle stringYear intColor boolActor map[string]stringOscars []stringSequel *string
}func main() {// ……strangelove : Movie{Title: Dr. Strangelove,Subtitle: How I Learned to Stop Worrying and Love the Bomb,Year: 1964,Color: false,Actor: map[string]string{Dr. Strangelove: Peter Sellers,Grp. Capt. Lionel Mandrake: Peter Sellers,Pres. Merkin Muffley: Peter Sellers,Gen. Buck Turgidson: George C. Scott,Brig. Gen. Jack D. Ripper: Sterling Hayden,Maj. T.J. King Kong: Slim Pickens,},Oscars: []string{Best Actor (Nomin.),Best Adapted Screenplay (Nomin.),Best Director (Nomin.),Best Picture (Nomin.),},}// ……var buf []bytevar err errorif buf, err sexpr.Marshal(strangelove); err ! nil {fmt.Printf(%v, err)}fmt.Println(string(buf))
}输出结果如下
((Title Dr. Strangelove) (Subtitle How I Learned to Stop Worrying and Love the Bomb) (Year 1964) (Color nil) (Actor ((Maj. T.J. \King\ Kong Slim Pickens) (Dr. Strangelove Peter Sellers) (Grp. Capt. Lionel Mandrake Peter Sellers) (Pres. Merkin Muffley Peter Sellers) (Gen. Buck Turgidson George C. Scott) (Brig. Gen. Jack D. Ripper Sterling Hayden))) (Oscars (Best Actor (Nomin.) Best Adapted Screenplay (Nomin.) Best Director (Nomin.) Best Picture (Nomin.))) (Sequel nil))
上面的输出结果都在一行上要美化需要仔细调整代码不同层打印不同的缩进这里略过。
通过 reflect.Value 修改值
先来看下面的代码及其输出 x : 2 // value type variable?a : reflect.ValueOf(2) // 2 int nob : reflect.ValueOf(x) // 2 int noc : reflect.ValueOf(x) // x *int nod : c.Elem() // 2 int yes (x)fmt.Println(a.CanAddr()) // falsefmt.Println(b.CanAddr()) // falsefmt.Println(c.CanAddr()) // falsefmt.Println(d.CanAddr()) // truefmt.Printf(x %p, d %p, x%d,d%d\n, x, d, x, d)
输出
false
false
false
true
x 0xc00000a130, d 0xc000008048, x2,d2
本质上 reflect.ValueOf(x) 返回的 reflect.Value 都是不可取地址的参见前面图中 data T 到 reflect.Value的拷贝转换它只是x的拷贝副本不能调用 Value 的 Addr()方法。上面代码中的d它是c的解引用方式生成的指向另一个变量因此d可以取地址可以 d.Addr()。Elem方法只能用于接口或者指针返回的Value对象对应接口包含的元素或者指针指向的元素c是x的拷贝c.Elem()返回的Value对象内部指针指向了x从而d它可以取地址。注意CanAddr()方法对应的可不可以取地址是这个value有没有对应指向数据的指针的意思不是value本身能不能取地址。例如对 c 来说可以 c但不能 c.Addr()对于 d 来说可以 d也可以 d.Addr()只是后者 d.Addr() 返回的Value对象它包含的地址值是 x从而它可以作为指针去取对应的数据。我们可以通过调用 reflect.ValueOf(x).Elem() 来获取任意变量x对应的可取地址的 Value。
每当我们通过指针间接获取的 reflect.Value 都是可取地址的即使开始的是一个不可取地址的Value。在反射机制中所有关于是否支持取地址的规则都是类似的。例如 slice 的索引表达式 e[i] 将隐式地包含一个指针它就是可取地址的即使开始的 e 表达式不支持也没有关系。同样 reflect.ValueOf(e).Index(i) 对应的值也是可取地址的即使原始的 reflect.ValueOf(e) 不支持也没有关系。理解这一点可以通过 Value 本身的定义
type Value struct {typ *rtypeptr unsafe.Pointerflag
}
Value 类型就是 “一个指向数据的指针其他一些字段” 的形式而从数组索引切片得到的 slice 类型就是指向 data的指针 len cap。这里的意思就是在反射机制中所有类似的类型都可以 Addr() 方法取得数据对应的地址想想也是总要让我们可以挖出数据来而取得地址是必需的。
要从变量对应的可取地址的 reflect.Value 来访问变量需要三步1、调用 Addr() 方法返回的是 Value里面保存了指向变量的指针 就像前面的 Value类型对象d它调用Addr()对应的值是 x 2、 在 Value 上调用 Interface() 方法返回一个 interface{}里面包含指向变量的指针可以参考前面那个图中从 Value 到 interface{} 的逆转换 3、如果我们知道变量的类型可以用类型断言将得到的 interface{}类型的接口强制转为普通的类型指针最终我们可以通过这个普通指针来更新变量了。这一步相当于前面的图中从 interface{} 到类型T。例子如下 x : 2d : reflect.ValueOf(x).Elem()px : d.Addr().Interface().(*int)*px 3fmt.Println(x) // now x is 3
不用类型断言也可以通过使用 Value.Set()方法来实现用一个 Value 来设置另一个 Value d.Set(reflect.ValueOf(5))fmt.Println(x) // now x is 5
Value.Set() 方法会在运行时执行可赋值性约束检查比类型断言更常用。如果用一个类型 A 的Value 去设置另一个类型B的 Value将 panic。
d.Set(reflect.ValueOf(int64(5))) // panic: int64 不能赋值给 intint8 也不行
对一个不可取地址的 reflect.Value 调用 Set方法也会 panic也就是前面说的“可取地址的Value”这一步是必需的。本质上 reflect.Value.Set(...) 是内部指针的替换两个reflect.Value应该都有内部指针。
x : 2
b : reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic
使用 d.Set(reflect.ValueOf(5)) 这样的写法显得很累赘而此类操作又常见所以对于基本数据类型有很多 SetXxx 方法来简化 SetInt、SetUint、SetString、SetFloat…… 这样d.SetInt(5) 看上去清爽多了。 然而对于引用 interface{} 类型的 reflect.Value两种写法并不等价调用 SetInt会panic即使“看上去没有问题”
x : 1
rx : reflect.ValueOf(x).Elem()
rx.SetInt(2) // OK, x 2
rx.Set(reflect.ValueOf(3)) // OK, x 3
rx.SetString(hello) // panic: string is not assignable to int
rx.Set(reflect.ValueOf(hello)) // panic: string is not assignable to intvar y interface{}
ry : reflect.ValueOf(y).Elem()
ry.SetInt(2) // panic: SetInt called on interface Value
ry.Set(reflect.ValueOf(3)) // OK, y int(3)
ry.SetString(hello) // panic: SetString called on interface Value
ry.Set(reflect.ValueOf(hello)) // OK, y hello
通过反射机制我们可以突破Go语言关于导出的规则读取到结构体中未导出的成员但访问是只读的因为一个可取地址的 reflect.Value 会记录一个结构体成员是否是未导出成员如果是的话就拒绝修改操作。Value 的 CanAddr() 方法只反映是否可以取到对应的值CanSet() 才是用于检查对应的 reflect.Value是否可取地址并可被修改。
示例解码S表达式
标准库中包 text/scanner 可以将字节输入流解析为一个个类似注释、标识符、字符串字面值和数字字面值之类的标记token。输入扫描器 scanner 中的 Scan() 方法将提前扫描和返回下一个记号。对于大多数token比如 (对应 token值是单一 rune 可表示的 Unicode值 40 0x28但 text/scanner 也用小的负数来标记标识符、字符串、整数等由多个字符组成的 token例如 EOF-1Ident-2Int-3Float-4Char-5String-6……。调用 scanner.Scan() 将返回这些 token 的类型接着调用 TokenText() 方法将返回 token 对应的文本形式内容。
下面示例中用类型 lexer 包装了 scanner.Scanner 类型scan字段字段 token 跟踪最近返回的 token。方法 next()、text() 是对 Scanner类型 Scan()、和 TokenText()方法的简单包装方法 consume(want) 是检查当前 token 是否满足预期不满足就 panic满足就继续读入下一个 token。
package sexprimport (bytesfmtreflectstrconvtext/scanner
)type lexer struct {scan scanner.Scannertoken rune
}func (lex *lexer) next() {lex.token lex.scan.Scan()
}func (lex *lexer) text() string {return lex.scan.TokenText()
}func (lex *lexer) consume(want rune) {if lex.token ! want {panic(fmt.Sprintf(got %q, want %q, lex.text(), want))}lex.next()
}func read(lex *lexer, v reflect.Value) {switch lex.token {case scanner.Ident:if lex.text() nil {v.Set(reflect.Zero(v.Type()))lex.next()return}case scanner.String:s, _ : strconv.Unquote(lex.text())v.SetString(s)lex.next()returncase scanner.Int:i, _ : strconv.Atoi(lex.text())v.SetInt(int64(i))lex.next()returncase (:lex.next()readList(lex, v)lex.next()return}panic(fmt.Sprintf(unexpected token %q, lex.text()))
}func readList(lex *lexer, v reflect.Value) {switch v.Kind() {case reflect.Array:for i : 0; !endList(lex); i {read(lex, v.Index(i))}case reflect.Slice:for !endList(lex) {item : reflect.New(v.Type().Elem()).Elem()read(lex, item)v.Set(reflect.Append(v, item))}case reflect.Struct:for !endList(lex) {lex.consume(()if lex.token ! scanner.Ident {panic(fmt.Sprintf(got token %q, want field name, lex.text()))}name : lex.text()lex.next()read(lex, v.FieldByName(name))lex.consume())}case reflect.Map:v.Set(reflect.MakeMap(v.Type()))for !endList(lex) {lex.consume(()key : reflect.New(v.Type().Key()).Elem()read(lex, key)value : reflect.New(v.Type().Elem()).Elem()read(lex, value)v.SetMapIndex(key, value)lex.consume())}default:panic(fmt.Sprintf(cannot decode list into %v, v.Type()))}
}func endList(lex *lexer) bool {switch lex.token {case scanner.EOF:panic(end of file)case ):return true}return false
}func Unmarshal(data []byte, out interface{}) (err error) {lex : lexer{scan: scanner.Scanner{Mode: scanner.GoTokens}}lex.scan.Init(bytes.NewReader(data))lex.next()defer func() {if x : recover(); x ! nil {err fmt.Errorf(error at %s: %v, lex.scan.Position, x)}}()read(lex, reflect.ValueOf(out).Elem())return nil
}func Marshal(v interface{}) ([]byte, error) {
// ......... see above .....
}func encode(buf *bytes.Buffer, v reflect.Value) error {
// ......... see above ....
}上面的代码中 read函数读取 lex 中当前 token根据不同token类型更新 reflect.Value型变量v对应的值注意v必须是可取地址的这里更新v对应的值相当于读入token对应内容处理完继续读下一个token。如果当前读到的 token 是左括号 (这意味着一个列表的起始调用 readList函数读入列表。因为列表内每个元素又涉及读取 token所以 read和readList是构成递归调用的。
readList 根据不同列表类型数组、切片、结构体、Map进行处理这部分代码还是比较复杂的涉及反射包中很多函数这里先略过。
使用 sexpr包的代码例子如下注意 Unmarshal中read函数调用是 reflect.ValueOf(out).Elem()所以out参数应该传入地址
package mainimport (fmtgpl.12.6/sexpr
)type Movie struct {Title, Subtitle stringYear intColor boolActor map[string]stringOscars []stringSequel *string
}func main() {// ... see above ...var outMovie Movieif err sexpr.Unmarshal(buf, outMovie); err ! nil {fmt.Printf(%v, err)return}fmt.Println()fmt.Printf(%v, outMovie)
}输出类似如下
((Title Dr. Strangelove) (Subtitle How I Learned to Stop Worrying and Love the Bomb) (Year 1964) (Color nil) (Actor ((Dr. Strangelove Peter Sellers) (Grp. Capt. Lionel Mandrake Peter Sellers) (Pres. Merkin Muffley Peter Sellers) (Gen. Buck Turgidson George C. Scott) (Brig. Gen. Jack D. Ripper Sterling Hayden) (Maj. T.J. \King\ Kong Slim Pickens))) (Oscars (Best Actor (Nomin.) Best Adapted Screenplay (Nomin.) Best Director (Nomin.) Best Picture (Nomin.))) (Sequel nil)){Dr. Strangelove How I Learned to Stop Worrying and Love the Bomb 1964 false map[Brig. Gen. Jack D. Ripper:Sterling Hayden Dr. Strangelove:Peter Sellers Gen. Buck Turgidson:George C. Scott Grp. Capt. Lionel Mandrake:Peter Sellers Maj. T.J. King Kong:Slim Pickens Pres. Merkin Muffley:Peter Sellers] [Best Actor (Nomin.) Best Adapted Screenplay (Nomin.) Best Director (Nomin.) Best Picture (Nomin.)] nil}
实际生产代码中不会简单panic来处理错误。即便像上面那样简化处理解码反射的使用依然是golang中比较复杂的部分。
不好意思我以为这章完了其实没完。
获取结构体字段标签
下面的例子由两个文件组成 package main 对应 search.gopackage params 对应 params.go。前者有一个 http handler函数search在 main函数中让它和路由路径 /search 对应并启动web服务启动web服务可以参考 golang web学习随便记1-快速入门-CSDN博客handler的概念也可以参考这部分。
在 search 这个 handler func 中我们定义了一个匿名结构体变量 data用来“容纳”解析后的URL请求参数为了代码容易读结构体的成员名称总是定义得长一点为了节约网络流量URL请求参数的名称总是比较短含义不好猜。因此两者之间存在映射。结构体成员标签就是干这个事情的而我们的程序中必须有能力获取结构体成员标签从而知道把参数对应值放到结构体哪里。下述代码中params.Unpack(...) 完成请求参数到结构体变量的“容纳”过程。
package mainimport (fmtnet/httpgpl.12.7/params
)func search(resp http.ResponseWriter, req *http.Request) {var data struct {Labels []string http:lMaxResult int http:maxExact bool http:x}data.MaxResult 10 // 默认值if err : params.Unpack(req, data); err ! nil {http.Error(resp, err.Error(), http.StatusBadRequest) // 400return}// ... handler 其余部分 ...fmt.Fprintf(resp, Search: %v\n, data)
}func main() {http.HandleFunc(/search, search)http.ListenAndServe(:8088, nil) // 使用 80 端口需要高权限这里不用
}测试流程和输出类似如下
sjgsjg-PC:~/go/src/gpl.12.7$ curl -X GET http://localhost:8088/search?xtruelgolanglprogramming
Search: {Labels:[golang programming] MaxResult:10 Exact:true}
sjgsjg-PC:~/go/src/gpl.12.7$ curl -X GET http://localhost:8088/search?qhellox123
x: strconv.ParseBool: parsing 123: invalid syntax
理解下面的 Unpack 和 populate 函数除了需要回顾前面的反射有关知识还需要了解如何从 URL 获得参数。后者可以参考 golang web学习随便记2-请求有关-CSDN博客
package paramsimport (fmtnet/httpreflectstrconvstrings
)// 参数 ptr 是空接口并且后面用反射这样才使得 Unpack 能够用于各种场合
// 而不仅仅是某种固定参数格式
func Unpack(req *http.Request, ptr interface{}) error {if err : req.ParseForm(); err ! nil {return err}fields : make(map[string]reflect.Value) // fields 存放 URL参数v : reflect.ValueOf(ptr).Elem() // 获得 ptr所指数据对应的 Value型变量vfor i : 0; i v.NumField(); i { // 对 v 迭代其实是获取 匿名struct的信息fieldInfo : v.Type().Field(i)tag : fieldInfo.Tagname : tag.Get(http)if name {name strings.ToLower(fieldInfo.Name)}fields[name] v.Field(i) // 确保结构体的默认值存放到 fields}for name, values : range req.Form { // ParseForm解析后参数都在 Form中f : fields[name] // 获得 键name 对应的参数值的“Value值” fif !f.IsValid() { // 参数值无效如类型不符合预期continue}for _, value : range values { // 键name 对应的参数值可能是列表if f.Kind() reflect.Slice { // 本身预期类型是列表elem : reflect.New(f.Type().Elem()).Elem() // 空的代表列表元素的“Value值”if err : populate(elem, value); err ! nil {return fmt.Errorf(%s: %v, name, err)}f.Set(reflect.Append(f, elem)) // value填充空的elem再加到f后} else {if err : populate(f, value); err ! nil {return fmt.Errorf(%s: %v, name, err)}}}}return nil
}func populate(v reflect.Value, value string) error { // 用 value “填充” vswitch v.Kind() {case reflect.String:v.SetString(value)case reflect.Int:i, err : strconv.ParseInt(value, 10, 64)if err ! nil {return err}v.SetInt(i)case reflect.Bool:b, err : strconv.ParseBool(value)if err ! nil {return err}v.SetBool(b)default:return fmt.Errorf(unsupported kind %s, v.Type())}return nil
}理解上述代码的要点有Unpack是通用的它并不清楚要“反序列化”到怎样的结构体因此先要根据传入的指针 ptr 去解析其背后的结构体的结构是怎样的例如结构体字段名和标签信息。传入的参数对于同一个键名可能是单值的也可能是多值的。单值的比较简单populate函数会把参数值装入代表值的Value型变量f中。多值的会稍微绕一点需要先生成空的代表Value型变量f此时f是数组切片的元素的Value型变量elempopulate函数是把参数值装入elem然后elem追加到f后面。无论哪一种最后都依赖“通过reflect.Value修改值”即修改了 ptr背后的结构体的各个字段的值。
显示一个类型的方法集合
这最后一小节的例子比较简单。它会打印给定的类型的所有方法。
package methodsimport (fmtreflectstrings
)func Print(x interface{}) {v : reflect.ValueOf(x)t : v.Type()fmt.Printf(type %s\n, t)for i : 0; i v.NumMethod(); i {methType : v.Method(i).Type() // 第 i 个方法的类型即参数类型列表fmt.Printf(func (%s) %s%s\n, t, t.Method(i).Name, // 第 i 个方法的名字strings.TrimPrefix(methType.String(), func))}
}//-----------------以下为 main() 主要代码-------------------------------methods.Print(time.Hour)methods.Print(new(strings.Replacer))
输出
type time.Duration
func (time.Duration) Abs() time.Duration
func (time.Duration) Hours() float64
func (time.Duration) Microseconds() int64
func (time.Duration) Milliseconds() int64
func (time.Duration) Minutes() float64
func (time.Duration) Nanoseconds() int64
func (time.Duration) Round(time.Duration) time.Duration
func (time.Duration) Seconds() float64
func (time.Duration) String() string
func (time.Duration) Truncate(time.Duration) time.Duration
type *strings.Replacer
func (*strings.Replacer) Replace(string) string
func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
使用反射处理问题时我们总是会定义有 interface{}空接口类型参数的导出函数或方法这样任意类型的参数都可以往里装了。在内部往往是 interface{} 向 reflect.Value 转换必要时也从 reflect.Value获取reflect.Type。内部用反射干活往往需要先处理类型再处理值。尽管反射类型 reflect.Value对自己很清楚但因为处理起来总是盲人摸象式的间接处理所以既显得复杂性能又低这就是反射灵活性的代价。