反射
反射是用程序检查代码中所拥有的结构尤其是类型的一种能力,这是元编程的一种形式。反射可以在运行时检查类型和变量。但是存在着一定的隐患,除非真的有必要,否则应当避免使用或者小心使用。
方法和类型的反射
两个简单的函数:reflect.TypeOf
和reflect.ValueOf
,返回被检查对象的类型和值。
两个函数的签名:
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
其中,Type和Value都有Kind方法返回一个常量来表示类型:Uint,Float64,Slice
等,同样Value有叫作Int,Float
的方法可以获取存储在内部的值。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
v := reflect.ValueOf(x)
fmt.Println("value: ", v)
fmt.Println("type: ", v.Type())
fmt.Println("kind: ", v.Kind())
fmt.Println("value: ", v.Float())
fmt.Println(v.Interface())
fmt.Printf("value is %5.2e\n", v.Interface())
y := v.Interface().(float64)
fmt.Println(y)
}
/** The result is:
type: float64
value: 3.4
type: float64
kind: float64
value: 3.4
3.4
value is 3.40e+00
3.4
*/
从上述代码中可以很好的看出每一个函数的功能。
通过反射修改设置值
Value中是有方法可以对x的值进行修改的,但是一定要谨慎使用v.SetFloat(3.1415)
函数,因为可能会产生错误:
/** reflect.Value.SetFloat using unaddressable value */
其实我们是可以通过v.CanSet()
来进行测试的,测试是否可以设置。如果想要解决上述问题,我们需要间接使用v=v.Elem()
才能实现我们想要的效果
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("v: ", v.CanSet())
v = reflect.ValueOf(&x)
fmt.Println("v的类型: ", v.Type())
fmt.Println("v: ", v.CanSet())
v = v.Elem()
fmt.Println("v的Elem是: ", v)
fmt.Println("v: ", v.CanSet())
v.SetFloat(3.1415)
fmt.Println(v.Interface())
fmt.Println(v)
}
/** The result is:
v: false
v的类型: *float64
v: false
v的Elem是: 3.4
v: true
3.1415
3.1415
*/
反射结构
有些时候需要反射一个结构类型:NumField()
方法返回结构内的字段数量,通过一个for循环索引取得每个字段的值Field(i)
。
package main
import (
"fmt"
"reflect"
)
type NotknownType struct {
s1, s2, s3 string
}
func (n NotknownType) String() string {
return n.s1 + "-" + n.s2 + "-" + n.s3
}
var secret interface{} = NotknownType{"Google", "Go", "Code"}
func main() {
value := reflect.ValueOf(secret)
typ := reflect.TypeOf(secret)
fmt.Println(typ)
knd := value.Kind()
fmt.Println(knd)
for i := 0; i < value.NumField(); i++ {
// 返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
fmt.Printf("Field %d: %v\n", i, value.Field(i))
}
// 返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
// 返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。
// 如果i出界,或者v的持有值是接口类型的零值(nil),会panic。
results := value.Method(0).Call(nil)
fmt.Println(results)
}
/** The result is:
main.NotknownType
struct
Field 0: Google
Field 1: Go
Field 2: Code
[Google-Go-Code]
*/
同样,在尝试修改一个值的时候,会产生错误:
panic:reflect.Value.SetString using value obtained using unexported field
这是因为结构中只有被导出字段(首字母大写)才是可设置的
Printf和反射
fmt包中的Printf
函数是使用反射机制来分析它的...参数的,Printf
函数声明:
func Printf(format string, args ...interface{}) (n int, err error)
下面实现一个简单的通用输出函数,其中使用了type-switch来推导参数类型,并根据类型来输出每个参数的值:
package main
import (
"os"
"strconv"
)
type Stringer interface {
String() string
}
type Celsius float64
func (c Celsius) String() string {
return strconv.FormatFloat(float64(c), 'f', 1, 64) + "C"
/**
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
函数将浮点数表示为字符串并返回。
bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。
fmt表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)。
prec控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
*/
}
type Day int
var dayName = []string{"Monday", "Tudesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
func (day Day) String() string {
return dayName[day]
}
func printf(args ...interface{}) {
for i, arg := range args {
if i > 0 {os.Stdout.WriteString(" ")}
switch a := arg.(type) { // 类型转换
case Stringer: os.Stdout.WriteString(a.String())
case int: os.Stdout.WriteString(strconv.Itoa(a))
case string: os.Stdout.WriteString(a)
// more type
default: os.Stdout.WriteString("???")
}
}
}
func main() {
print(Day(1), "温度是", Celsius(18.36))
}
网友评论