go语法专题:空接口,反射,空结构体,断言,泛型

参考:

https://blog.csdn.net/weixin_44014995/article/details/114596529(go语言的空接口,反射,泛型)

https://www.jianshu.com/p/6a46fc7b6e5b(go语言的类型断言)

前言

go语言就是通过接口interface{}和结构体struct{}组织起来的,interface{}是方法的集合,struct{}是数据结构的集合+接口的实现。

反射可以动态地获取任意对象的类型及其结构信息。

空接口的引入

Go语言打破了传统面向对象编程中类与类之间继承的概念,而是通过组合实现方法和属性的复用,所以不存在类似的继承关系数,也就没有所谓的祖宗类,而且类与接口之间也不再通过implements 关键字强制绑定实现关系,所以 Go 语言的面向对象编程非常灵活。

在Go语言中,类与接口的实现关系是通过类所实现的方法在编译期推断出来的,如果我们定义一个空接口的话,那么显然所有的类都实现了这个接口,反过来,我们也可以通过空接口来指向任意类型,从而实现类似Java中Object类所承担的功能,而且显然Go的空接口实现更加简洁,通过一个简单的字面量即可完成:

interface{}

空接口的基本使用

指向任意类型变量

#基本类型
var v1 interface{} = 1 // 将 int 类型赋值给 interface{} 
var v2 interface{} = "学院君" // 将 string 类型赋值给 interface{} 
var v3 interface{} = true  // 将 bool 类型赋值给 interface{}

#复合类型
var v4 interface{} = &v2 // 将指针类型赋值给 interface{} 
var v5 interface{} = []int{1, 2, 3}  // 将切片类型赋值给 interface{} 
var v6 interface{} = struct{   // 将结构体类型赋值给 interface{}
    id int
    name string
}{1, "学院君"} 

声明任意类型的参数

func Printf(fmt string, args ...interface{}) 
func Println(args ...interface{}) ...
func (p *pp) printArg(arg interface{}, verb rune)

反射

反射三大定律:

1,反射可以将interface变量转换成反射对象

2,反射可以将反射对象还原成interface变量

3,反射对象可修改,value值必须是可设置的

#第一定律
#TypeOf()和ValueOf()接受的参数都是interface{}类型的,也即x值是被转成了interface传入的。
var x float64 = 3.4
t := reflect.TypeOf(x)  //t is reflext.Type
fmt.Println("type:", t)

v := reflect.ValueOf(x) //v is reflext.Value
fmt.Println("value:", v)

#第二定律
#对象x转换成反射对象v,v又通过Interface()接口转换成接口对象,interface对象通过.(float64)类型断言获取float64类型的值。
var y float64 = v.Interface().(float64)
fmt.Println("value:", y)
#第三定律 var x float64 = 3.4 v := reflect.ValueOf(x) #不可修改 v.SetFloat(7.1) // Error: will panic. var x float64 = 3.4 v := reflect.ValueOf(&x) #可修改 v.Elem().SetFloat(7.1) fmt.Println("x :", v.Elem().Interface())

空结构体

另外,有的时候你可能会看到空的结构体类型定义:

struct{}

表示没有任何属性和成员方法的空结构体,该类型的实例值只有一个,那就是 struct{}{},这个值在 Go 程序中永远只会存一份,并且占据的内存空间是 0,当我们在并发编程中,将通道(channel)作为传递简单信号的介质时,使用 struct{} 类型来声明最好不过。

断言

golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。

1,x.(T)检查x的动态类型是否是T,其中x必须是接口值。

2,如果接口是具体类型T,对接口值x断言其动态类型是具体类型T,若成功则提取出x的具体值(返回2个值的时候为具体值,true);如果检查失败则panic(返回2个值的时候为零值,false不会返回panic)。

3,无论T是什么类型,如果x是nil接口值,则类型断言失败。

4,如果x是空接口类型,则T没有要求;如果x是非空接口类型,则T必须是实现了x;

5,如果x是接口类型T,类型断言检查x的动态类型是否满足T,如果检查成功,x的动态值不会被提取,返回值是一个类型为T的接口值

返回两个值

如果我们想知道类型断言是否失败,而不是失败时触发panic,可以使用返回两个值的版本:

y, ok := x.(T)
#举例
var w io.Writer = os.Stdout
f, ok := w.(*os.File) //成功:f为os.Stdout,ok为true
b, ok := w.(*bytes.Buffer) //失败:b为零值,这里是nil, ok为false,no panic

惯用:

if f, ok := w.(*os.File); ok {
    // ... use f ...
}

使用switch

func classifier(items ...interface{}) {
    for i, x := range items {
        switch x.(type) {
        case bool:
            fmt.Printf("Param #%d is a bool\n", i)
        case float64:
            fmt.Printf("Param #%d is a float64\n", i)
        case int, int64:
            fmt.Printf("Param #%d is a int\n", i)
        case nil:
            fmt.Printf("Param #%d is a nil\n", i)
        case string:
            fmt.Printf("Param #%d is a string\n", i)
        default:
            fmt.Printf("Param #%d is unknown\n", i)
        }
    }
}