目录
小记
make与new的区别
map操作
类型断言
反射
1. 变量的内在机制
2. 反射与空接口
3. 怎么分析?
4. 常用类型的变量类型分析
5. 通过反射获取结构体的字段值信息和类型信息
6. 通过反射给结构体字段赋值
7. 获取结构体方法的类型信息
8. 通过反射调用结构体的方法
9. 通过反射获取结构体字段的tag信息
小记
make与new的区别
make()用来分配引用类型的内存,比如map、slice以及channel
new()用来分配除了引用类型以外的所有其他类型的内存,比如int、数组等; new返回类型的指针;
package main
import `fmt`
func main() {
var a *int = new(int)
fmt.Println(a) // a是一个int指针类型 0xc00000a0c8
// a = 100 // 此时不能用a直接赋值,因为a是int指针类型,而100是int类型,两者类型不同,所以不同赋值
*a = 100 // 必须要在a前面加一个星号*,*a 表示内存地址指向的那块内存空间,*a=100表示将数字100存储到a(内存地址)指向的那块内存空间中;
fmt.Println(*a)
var b *[]int = new([]int)
// (*b)[0] = 1 // 此时b是一个指针类型,它的值是一个内存地址
fmt.Println(b, &b) // b的值是一个内存地址,而这个内存地址也需要一块内存空间去存储,所以 &b是取b的内存地址
fmt.Printf("%p\n", b) // 打印:0xc0000044c0, 这是b的值
fmt.Println(*b) // 是一个空切片,打印:[]
*b = make([]int, 5, 100) // 给b(内存地址)分配一块内存空间并初始化切片长度为5,容量为100,这里才可以进行索引操作,如下
(*b)[0] = 10
(*b)[1] = 20
(*b)[2] = 30
fmt.Println(*b) // 打印:[10 20 30 0 0]
var c = make([]int, 5, 20)
c[0] = 100
c[1] = 200
fmt.Println(c) // 打印:[100 200 0 0 0]
// 通过上面的例子可以看出,用new()初始化引用类型的变量是多余的
}
map操作
package main
import `fmt`
func main() {
var m map[string]int // map声明后未初化是nil
fmt.Println(m == nil) // true
// 初始化 方式一:
m = map[string]int{}
// 对未初始化的map直接赋值会抛出异常 panic: assignment to entry in nil map
m["cai"] = 22000
m["kung"] = 20000
fmt.Println(m, len(m))
// 初始化 方式二:
n := make(map[string]int)
n["guang"] = 12000
n["wang"] = 10000
n["guo"] = 8000
n["liang"] = 9000
fmt.Println(n, len(n))
value, ok := n["wangg"]
if ok {
fmt.Println("value:", value)
} else {
fmt.Println("value is not exists.")
}
}
类型断言
空接口与类型断言
空接口类型的变量可以存入任何类型的值
package main
import (
`fmt`
)
func main() {
var a = 10
// testInterface(a)
var b = "hello world"
// testInterface(b)
var c = 3.1415926
// 构造一个万能类型的数组list,什么都有存
var list []interface{}
list = append(list, a, b, c)
findType(list)
}
func testInterface(a interface{}) {
b, ok := a.(int)
if ok {
fmt.Printf("a is int: %d, type: %T\n", b, b) // a is int: 10, type: int
}
c, ok := a.(string)
if ok {
fmt.Printf("a is string: %s, type: %T\n", c, c) // a is string: hello world, type: string
}
}
func findType(a interface{}) {
list, ok := a.([]interface{})
if ok {
for _, value := range list {
switch v := value.(type) {
case int:
fmt.Printf("value: %d, type: %T\n", v, v)
case string:
fmt.Printf("value: %s, type: %T\n", v, v)
case float64:
fmt.Printf("value: %f, type: %T\n", v, v)
case []interface{}:
fmt.Println("type: []interface{}")
default:
fmt.Println("unknown type.")
}
}
} else {
fmt.Println("a is not []interface{} type.")
}
}
b, ok := a.(int)
断言a为int类型,如果断言错误则ok为false,断言正确,ok为true且将值赋值给b;value.(type)
type是关键字,这种用法只能在switch
语句中使用;可以这样直接赋值v := value.(type)
,在case中就可以直接使用v
了,避免多次类型断言;
反射
1. 变量的内在机制
参考文档: Golang反射
- 类型信息,这部分是元信息,是预先定义好的;
- 值信息,这部分是程序运行过程中,动态改变的;
2. 反射与空接口
- A. 空接口可以存储任何类型的变量;
- B.那么给你一个空接口,怎么知道里面存储的是什么类型的变量?
- C. 在运行时动态的获取一个变量的类型信息和值信息,就叫反射;
3. 怎么分析?
- A.内置包
reflect
; - B. 获取类型信息:
reflect.TypeOf
- C. 获取值信息:
reflect.ValueOf
获取一个空接口的变量类型
package main
import (
`fmt`
`reflect`
)
func reflectExample(a interface{}) {
t := reflect.TypeOf(a)
fmt.Println("a的类型是:", t) // 打印:a的类型是: float64
}
func main() {
var x float64 = 3.1415926
reflectExample(x)
}
4. 常用类型的变量类型分析
reflect.ValueOf(a).Type()
和 reflect.TypeOf(a)
功能相同
package main
import (
`fmt`
`reflect`
)
func reflectType(a interface{}) {
t := reflect.TypeOf(a)
k := t.Kind()
fmt.Println("a的类型是:", t.String(), k.String()) // 打印:a的类型是: float64
}
func reflectValue(a interface{}) {
v := reflect.ValueOf(a)
t := v.Type() // reflect.ValueOf(a).Type() 和 reflect.TypeOf(a) 功能相同
// k := v.Kind()
t1 := reflect.TypeOf(a)
t1.Kind()
fmt.Println("a的类型是:", t.String())
switch t.Kind() {
case reflect.Int:
fmt.Println("a is a int. ")
case reflect.Float32, reflect.Float64:
fmt.Println("a is a Float.")
case reflect.String:
fmt.Println("a is a String.")
default:
fmt.Println("other type.")
}
}
func main() {
var x = 3.1415926
reflectType(x)
reflectValue(x)
}
5. 通过反射获取结构体的字段值信息和类型信息
package main
import (
`fmt`
`reflect`
)
type Student struct {
Name string
Age int
Sex string
}
func main() {
var stu = Student{
Name: "caigy",
Age: 18,
Sex: "male",
}
// 类型信息是静态的,在编译时就确定了;
// 值信息是动态的,在运行时才能确定
v := reflect.ValueOf(stu)
t := v.Type()
fmt.Println("stu的类型:", t.Kind().String()) // 变量类型为struct
switch t.Kind() {
case reflect.String:
fmt.Println("is string.")
case reflect.Struct:
fmt.Println("is struct.")
fmt.Println("结构体字段个数:", v.NumField()) // t.NumField()获取结构体字段数量,输出3
for i := 0; i < v.NumField(); i++ {
fmt.Println("type: ", t.Field(i),t.Field(i).Type) // t.Field(i) 通过下标获取字段的类型信息,
fmt.Println("value: ", v.Field(i), v.Field(i).Type()) // v.Field(i) 通过下标获取字段的值信息,
}
fmt.Println("name: ", v.FieldByName("Name")) // v.FieldByName("Name")根据字段名称获取字段的值
fmt.Printf("struct value: %+v, type: %T\n", v.Interface(), v.Interface()) // {Name:caigy Age:18 Sex:male}, type: main.Student
name := v.FieldByName("Name").Interface()
fmt.Printf("name: %T, %+v\n", name, name) // name: string, caigy
}
}
v.Type()
返回变量stu
的类型信息;v.Kind()
返回变量stu
的类型,为struct;v.Field(i)
通过下标获取一个字段的值信息,返回的类型是reflect.Value
;v.Field(i).Type()
返回字段的值类型;v.FieldByName("Name")
通过字段名称获取字段的值信息;v.FieldByName("Name").Interface()
获取指定字段的值,如Name
,获取到的值为caigy
,值类型为string;v.Interface()
返回结构所有字段;t.Field(i)
通过下标获取字段的类型信息,返回的类型是reflect.StructField
;t.Field(i).Type
返回结构体字段类型对象,通过t.Field(i).Type.Kind()
获取字段具体类型;
6. 通过反射给结构体字段赋值
注意:通过反射给结构体字段赋值,reflect.ValueOf(&stu)
必须传入指针类型&stu
;
package main
import (
`fmt`
`reflect`
)
type Student struct {
Name string
Age int
Sex string
}
func main() {
var stu Student
v := reflect.ValueOf(&stu)
v.Elem().FieldByName("Name").SetString("caigy")
v.Elem().FieldByName("Age").SetInt(18)
v.Elem().FieldByName("Sex").SetString("male")
fmt.Printf("Student: %+v", stu) // Student: {Name:caigy Age:18 Sex:male}
}
v.Elem()
返回指针v指向的值(内存),所以要想给结构体字段赋值必需这样使用:v.Elem().FieldByName("Name").SetString("caigy")
7. 获取结构体方法的类型信息
方法的类型信息是静态信息,编译时已经确定好了,所以应该用reflect.TypeOf(&stu)
或者reflect.ValueOf(&stu).Type()
获取得方法的类型信息
package main
import (
`fmt`
`reflect`
)
type Student struct {
Name string
Age int
Sex string
}
func (stu *Student) SetName(name string) {
stu.Name = name
}
func (stu *Student) PrintVal() {
fmt.Println(stu.Name, stu.Age, stu.Sex)
}
func (stu *Student) InitVal() {
stu.Name = "CaiGY"
stu.Age = 18
stu.Sex = "Male"
}
func main() {
var stu Student
v := reflect.ValueOf(&stu)
t := v.Type()
fmt.Println(t.NumMethod()) // 返回Student的方法个数
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
fmt.Printf("num: %d, methodName: %+v, methodSign: %+v\n", i, method.Name, method.Type)
// 打印结果如下:
// num: 0, methodName: InitVal, methodSign: func(*main.Student)
// num: 1, methodName: PrintVal, methodSign: func(*main.Student)
// num: 2, methodName: SetName, methodSign: func(*main.Student, string)
}
printVal, ok := t.MethodByName("PrintVal")
// initVal, ok := t.MethodByName("InitVal")
if !ok {
fmt.Println("not ok.")
return
}
fmt.Println("printVal.Type: ", printVal.Type) // 返回方法类型:func(*main.Student)
}
t.NumMethod()
返回方法的个数;t.Method(i)
通过下标获取一个方法对象(reflect.Method
),printVal, ok := t.MethodByName("PrintVal")
通过名称获取方法对象(reflect.Method
);t.Method(i).Name
返回访求的名称;t.Method(i).Type
返回访求的签名,如SetName()方法的签名:func(*main.Student, string)
;
8. 通过反射调用结构体的方法
调用结构体的方法, 是运行时确定的,所以要通过值类型来获取调用: reflect.ValueOf(&stu)
;
package main
import (
`fmt`
`reflect`
)
type Student struct {
Name string
Age int
Sex string
}
func (stu *Student) SetName(name string) {
stu.Name = name
}
func (stu *Student) PrintVal() {
fmt.Println(stu.Name, stu.Age, stu.Sex)
}
func (stu *Student) InitVal() {
stu.Name = "CaiGY"
stu.Age = 18
stu.Sex = "Male"
}
func main() {
var stu Student
v := reflect.ValueOf(&stu)
printVal := v.MethodByName("PrintVal")
initVal := v.MethodByName("InitVal")
// 无参方法的调用
var arg []reflect.Value
initVal.Call(arg) // 需要传入一个空数组:[]reflect.Value
printVal.Call(arg) // CaiGY 18 Male
// 有参方法的调用:
setName := v.MethodByName("SetName")
// 1. 先构造方法的参数
var args []reflect.Value
name := reflect.ValueOf("CaiGuangyin")
args = append(args, name)
// 2. 通过Call()传入第1步构造的参数来调用SetName方法
setName.Call(args)
fmt.Printf("student: %+v\n", stu) // student: {Name:CaiGuangyin Age:18 Sex:Male}
}
9. 通过反射获取结构体字段的tag信息
tag属于静态信息,保存在字段的类型信息里面,所以要用 reflect.ValueOf(stu).Type()
或者reflect.TypeOf(stu)
来取得变量的类型信息
package main
import (
`fmt`
`reflect`
)
type Student struct {
Name string `json:"name" db:"meicai"`
Age int `json:"age" db:"hehe"`
}
func main() {
var stu Student
v := reflect.ValueOf(stu)
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag
fmt.Println("json: ", tag.Get("json")) // 获取tag名为json的值
fmt.Println("db: ", tag.Get("db")) // 获取tag名为db的值
}
// 通过 tag.Get(key)获取tag的值,如果key不存在时,则默认返回空字符串
// 通过 tag.Lookup(key)获取tag的值时会返回值本身和一个bool值,可通过bool值来判断key是否存在
name, ok := t.FieldByName("Name")
if !ok {
fmt.Println("field name is not exists.")
return
}
jsonVal, ok := name.Tag.Lookup("json")
if ! ok {
fmt.Println("tag json is not exists.")
return
}
fmt.Println("json tag value: ", jsonVal) //json tag value: name
}
t.Field(i).Tag
返回结构体字段的tag信息
tag.Get(key)
获取指定key的tag值,key不存在时,返回空字符串;tag.Lookup(key)
获取指定key的tag值,会返回两个值,key不存在时,返回的第二个值为false.
有疑问加站长微信联系(非本文作者)