Golang——reflect(反射)

        反射是指在程序运行期间对程序本身进行访问和修改的能力。

        一. 变量的内在机制

  • 变量包含类型信息和值信息
  • 类型信息:是静态的元信息,是预先定义好的
  • 值信息:是程序运行过程中动态改变的

        二. 反射的使用

  • reflect包封装了反射相关的方法
  • 获取类型信息:reflect.TypeOf,是静态的
  • 获取值信息:reflect.ValueOf,是动态的

        三. 空接口和反射

  • 反射可以在运行时动态获取程序的各种详细信息
  • 反射获取interface类型信息

  • 反射获取interface值信息

  • 反射修改值信息

 四. 结构体与反射

  • 查看类型,字段和方法

        注意:结构体方法名和属性名必须大写,否则获取不到方法或报错。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func (u User) SayHello() {
	fmt.Println("hello")
}

func Poni(x interface{}) {
	//获取类型
	t := reflect.TypeOf(x)
	fmt.Println(t)
	fmt.Println("字符串类型:", t.Name())

	//获取值
	v := reflect.ValueOf(x)
	fmt.Println("值:", v)

	//获取所有属性
	//通过类型获取结构体字段个数
	for i := 0; i < t.NumField(); i++ {
		//通过类型取每一个字段,包括类型和字段变量名
		f := t.Field(i)
		fmt.Printf("%s, %v\n", f.Name, f.Type)

		//通过值获取字段的值信息
		//interface():获取字段对应的值
		val := v.Field(i).Interface()
		fmt.Println("val : ", val)
	}

	fmt.Println("---------方法------------")
	//通过类型获取方法数
	for i := 0; i < t.NumMethod(); i++ {
		m := t.Method(i)
		fmt.Println(m.Name)
		fmt.Println(m.Type)

	}
}

func main() {
	u := User{
		1,
		"wy",
		20,
	}
	Poni(u)
}

  •  查看匿名字段

  • 修改结构体的值

  • 调用结构体方法

  • 获取字段的tag

        标签保存在类型的reflect.StructField结构体里。

 五.反射练习

  • 任务:解析如下配置文件
    • 序列化:将结构体序列化为配置文件数据并保存到硬盘
    • 反序列化:将配置文件内容反序列化到程序的结构体
  • 配置文件有servermysql相关配置
#this is comment
;this a comment
;[]表示一个section
[server]
ip = 10.238.2.2
port = 8080

[mysql]
username = root
passwd = admin
database = test
host = 192.168.10.10
port = 8000
timeout = 1.2
  • 思路

        反序列化:将配置文件读上来,根据根据结构体的tag,赋值到对应结构体属性字段。利用反射(reflect)获取到tag和赋值

        序列化:利用反射(reflect)获取到对应字段tag和值,编写为对应形式,再写道文件中。

main.go:主函数

package main

import (
	"io/ioutil"
	"log"
)

// 反序列化
func parseFile(infilename string, res interface{}) {
	data, err := ioutil.ReadFile(infilename)
	if err != nil {
		log.Println("read file fail ", err)
		return
	}
	//fmt.Println(string(data))

	err = Unmarshal(data, res)
	if err != nil {
		log.Println("Unmarshal fail ", err)
		return
	}
}

// 序列化
func parseDataToFile(filename string, res interface{}) {
	data, err := Marshal(res)
	if err != nil {
		log.Println("marshal fail ", err)
		return
	}

	err = ioutil.WriteFile(filename, data, 0666)
	if err != nil {
		log.Println("write file fail  ", err)
		return
	}
}

func main() {
	var cfg Cofig
	parseFile("./config.ini", &cfg)
	log.Printf("反序列化成功%#v", cfg)

	parseDataToFile("./my.ini", &cfg)
}

model.go:结构体

package main

//定义结构体

type ServerConfig struct {
	IP   string `ini:"ip"`
	Port int    `ini:"port"`
}

type MysqlConfig struct {
	UserName string  `ini:"username"`
	Passwd   string  `ini:"passwd"`
	Database string  `ini:"database"`
	Host     string  `ini:"host"`
	Port     int     `ini:"port"`
	Timeout  float64 `ini:"timeout"`
}

type Cofig struct {
	ServerConf ServerConfig `ini:"server"`
	MysqlConf  MysqlConfig  `ini:"mysql"`
}

init_config.go:序列化和反序列化细节实现。

package main

import (
	"errors"
	"fmt"
	"log"
	"reflect"
	"strconv"
	"strings"
)

func myLable(str string, res_type reflect.Type) (fieldname string, err error) {
	//去掉前后[]
	lable_str := str[1 : len(str)-1]
	//循环查找tag
	for i := 0; i < res_type.NumField(); i++ {
		field := res_type.Field(i)
		lable_name := field.Tag.Get("ini")
		if lable_name == lable_str {
			//属性名,不是tag名
			fieldname = field.Name
			return
		}
	}
	return
}

func myField(fieldName string, line string, res interface{}) (err error) {
	//获取key和value
	key_str := strings.TrimSpace(line[0:strings.Index(line, "=")])
	val_str := strings.TrimSpace(line[strings.Index(line, "=")+1:])

	//找到res中属性名为fieldName的field
	res_val := reflect.ValueOf(res).Elem()
	//fmt.Println(fieldName)
	lable_name := res_val.FieldByName(fieldName)

	//找到tag为key_str的属性
	lable_type := lable_name.Type()
	if lable_type.Kind() != reflect.Struct {
		return errors.New(fieldName + " is not a struct")
	}
	var key_name string
	for i := 0; i < lable_type.NumField(); i++ {
		field := lable_type.Field(i)
		tag_str := field.Tag.Get("ini")
		if tag_str == key_str {
			key_name = field.Name
			break
		}
	}

	//设置值
	val_field := lable_name.FieldByName(key_name)
	switch val_field.Type().Kind() {
	case reflect.String:
		val_field.SetString(val_str)
	case reflect.Int:
		tmp, err := strconv.ParseInt(val_str, 10, 32)
		if err != nil {
			log.Println(err)
			return err
		}
		val_field.SetInt(tmp)
	case reflect.Float64:
		tmp, err := strconv.ParseFloat(val_str, 64)
		if err != nil {
			log.Println(err)
			return err
		}
		val_field.SetFloat(tmp)
	}

	return
}

// 解析配置,序列化和反序列化
func Unmarshal(data []byte, res interface{}) (err error) {
	//得到res类型
	res_type := reflect.TypeOf(res)
	//是输出型参数,需要是指针类型
	if res_type.Kind() != reflect.Ptr {
		return errors.New("args type error not pointer")
	}
	//获得实际类型
	real_res_type := res_type.Elem()
	if real_res_type.Kind() != reflect.Struct {
		return errors.New("args real type error not struct")
	}

	//以'\n'分隔每一行
	var myFieldName string
	datalines := strings.Split(string(data), "\n")
	for _, line := range datalines {
		//去掉空格
		line = strings.TrimSpace(line)

		//处理有注释的情况
		if len(line) == 0 || line[0] == '#' || line[0] == ';' {
			continue
		}

		//按方括号判断大标签的情况
		if line[0] == '[' {
			//赋值大标签
			myFieldName, err = myLable(line, real_res_type)
			if err != nil {
				log.Println("handle lable fail ", err)
				return
			}
			continue
		}

		err = myField(myFieldName, line, res)
		if err != nil {
			continue
		}
	}

	return
}

func Marshal(res interface{}) (data []byte, err error) {
	res_type := reflect.TypeOf(res).Elem()
	res_val := reflect.ValueOf(res).Elem()
	if res_type.Kind() != reflect.Struct {
		return nil, errors.New("type error")
	}

	var lines []string
	for i := 0; i < res_type.NumField(); i++ {
		//取类型字段
		type_field := res_type.Field(i)
		//取值
		val_field := res_val.Field(i)
		//获得标签
		tag_str := type_field.Tag.Get("ini")
		if len(tag_str) == 0 {
			continue
		}
		//标签
		line := fmt.Sprintf("[%s]\n", tag_str)
		fmt.Print(line)
		lines = append(lines, line)

		if type_field.Type.Kind() != reflect.Struct {
			continue
		}
		//遍历成员的属性
		for j := 0; j < type_field.Type.NumField(); j++ {
			sub_type_field := type_field.Type.Field(j)
			//取标签
			sub_tag := sub_type_field.Tag.Get("ini")
			if len(sub_tag) == 0 {
				continue
			}
			//取值
			sub_val_field := val_field.Field(j)
			//Interface为真正的值
			line = fmt.Sprintf("%s = %v\n", sub_tag, sub_val_field.Interface())
			fmt.Print(line)
			lines = append(lines, line)
		}
		lines = append(lines, "\n")
	}

	for _, str := range lines {
		d := []byte(str)
		data = append(data, d...) //...将切片打散
	}
	return
}

 写入到文件演示:

        问题:

        我直接编译main.go出现报错:

        原因:

  • go run和go build命令都会编译文件,go run不仅编译还会直接执行文件。由于上面只输入了main.go文件,在main.go文件中引用其它文件的函数,结构或变量会出现找不到的情况。

        解决:

  • 由于go run必须输入文件,此时需要输入所有引用文件

  • go build可以不输入文件,默认自动查找引用文件并打开包。或者go build加上所有引用文件。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值