在Golang
世界中,有一种叫interface
的东西,非常神奇。算法
若是你事前并不知道变量是哪一种数据类型,不知道它是整数仍是字符串,可是你仍是想要使用它。编程
Golang
就产生了名为interface{}
的数据类型,表示并不知道它是什么类型。举例子:segmentfault
package main import ( "fmt" "reflect" ) func print(i interface{}) { fmt.Println(i) } func main() { // 声明一个未知类型的 a,代表不知道是什么类型 var a interface{} a = 2 fmt.Printf("%T,%v\n", a, a) // 传入函数 print(a) print(3) print("i love you") // 使用断言,判断是不是 int 数据类型 v, ok := a.(int) if ok { fmt.Printf("a is int type,value is %d\n", v) } // 使用断言,判断变量类型 switch a.(type) { case int: fmt.Println("a is type int") case string: fmt.Println("a is type string") default: fmt.Println("a not type found type") } // 使用反射找出变量类型 t := reflect.TypeOf(a) fmt.Printf("a is type: %s", t.Name()) }
输出:数组
int,2 2 3 i love you a is int type,value is 2 a is type int a is type: int
咱们使用interface{}
,能够声明一个未知类型的变量a
:安全
// 声明一个未知类型的 a,代表不知道是什么类型 var a interface{} a = 2 fmt.Printf("%T,%v\n", a, a)
而后给变量赋值一个整数:a=2
,这时a
仍然是未知类型,使用占位符%T
能够打印变量的真实类型,占位符%v
打印值,这时fmt.Printf
在内部会进行类型判断。数据结构
咱们也能够将函数的参数也定为interface
,和变量的定义同样:并发
func print(i interface{}) { fmt.Println(i) }
使用时:数据结构和算法
// 传入函数 print(a) print(3) print("i love you")
咱们传入print
函数的参数能够是任何类型,如整数3
或字符串i love you
等。进入函数后,函数内变量i
丢失了类型,是一个未知类型,这种特征使得咱们若是想处理不一样类型的数据,不须要写多个函数。函数式编程
固然,结构体里面的字段也能够是interface{}
:函数
type H struct { A interface{} B interface{} }
咱们定义了interface{}
,可是实际使用时,咱们有判断类型的需求。有两种方法能够进行判断。
使用断言:
// 使用断言,判断是不是 int 数据类型 v, ok := a.(int) if ok { fmt.Printf("a is int type,value is %d\n", v) }
直接在变量后面使用.(int)
,有两个返回值v, ok
会返回。ok
若是是true
代表确实是整数类型,这个整数会被赋予v
,而后咱们能够拿v
愉快地玩耍了。不然,ok
为false
,v
为空值,也就是默认值 0。
若是咱们每次都这样使用,会很难受,由于一个interface{}
类型的变量,数据类型多是.(int)
,多是.(string)
,可使用switch
来简化:
// 使用断言,判断变量类型 switch a.(type) { case int: fmt.Println("a is type int") case string: fmt.Println("a is type string") default: fmt.Println("a not type found type") }
在swicth
中,断言再也不使用.(具体类型)
,而是a.(type)
。
最后,还有一种方式,使用的是反射包reflect
来肯定数据类型:
// 使用反射找出变量类型 t := reflect.TypeOf(a) fmt.Printf("a is type: %s", t.Name())
这个包会直接使用非安全指针来获取真实的数据类型:
func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) }
通常平常开发,不多使用反射包。
咱们如今都是函数式编程,或者是结构体方法式的编程,难道没有其余语言那种面向对象,对象继承的特征吗?有,Golang
语言叫作面向接口编程。
package main import ( "fmt" "reflect" ) // 定义一个接口,有一个方法 type A interface { Println() } // 定义一个接口,有两个方法 type B interface { Println() Printf() int } // 定义一个结构体 type A1Instance struct { Data string } // 结构体实现了Println()方法,如今它是一个 A 接口 func (a1 *A1Instance) Println() { fmt.Println("a1:", a1.Data) } // 定义一个结构体 type A2Instance struct { Data string } // 结构体实现了Println()方法,如今它是一个 A 接口 func (a2 *A2Instance) Println() { fmt.Println("a2:", a2.Data) } // 结构体实现了Printf()方法,如今它是一个 B 接口,它既是 A 又是 B 接口 func (a2 *A2Instance) Printf() int { fmt.Println("a2:", a2.Data) return 0 } func main() { // 定义一个A接口类型的变量 var a A // 将具体的结构体赋予该变量 a = &A1Instance{Data: "i love you"} // 调用接口的方法 a.Println() // 断言类型 if v, ok := a.(*A1Instance); ok { fmt.Println(v) } else { fmt.Println("not a A1") } fmt.Println(reflect.TypeOf(a).String()) // 将具体的结构体赋予该变量 a = &A2Instance{Data: "i love you"} // 调用接口的方法 a.Println() // 断言类型 if v, ok := a.(*A1Instance); ok { fmt.Println(v) } else { fmt.Println("not a A1") } fmt.Println(reflect.TypeOf(a).String()) // 定义一个B接口类型的变量 var b B //b = &A1Instance{Data: "i love you"} // 不是 B 类型 b = &A2Instance{Data: "i love you"} fmt.Println(b.Printf()) }
输出:
a1: i love you &{i love you} *main.A1Instance a2: i love you not a A1 *main.A2Instance a2: i love you 0
咱们能够定义一个接口类型,使用type 接口名 interface
,这时候再也不是interface{}
:
// 定义一个接口,有一个方法 type A interface { Println() } // 定义一个接口,有两个方法 type B interface { Println() Printf() int }
能够看到接口A
和B
是一种抽象的结构,每一个接口都有一些方法在里面,只要结构体struct
实现了这些方法,那么这些结构体都是这种接口的类型。如:
// 定义一个结构体 type A1Instance struct { Data string } // 结构体实现了Println()方法,如今它是一个 A 接口 func (a1 *A1Instance) Println() { fmt.Println("a1:", a1.Data) } // 定义一个结构体 type A2Instance struct { Data string } // 结构体实现了Println()方法,如今它是一个 A 接口 func (a2 *A2Instance) Println() { fmt.Println("a2:", a2.Data) } // 结构体实现了Printf()方法,如今它是一个 B 接口,它既是 A 又是 B 接口 func (a2 *A2Instance) Printf() int { fmt.Println("a2:", a2.Data) return 0 }
咱们要求结构体必须实现某些方法,因此能够定义一个接口类型的变量,而后将结构体赋值给它:
// 定义一个A接口类型的变量 var a A // 将具体的结构体赋予该变量 a = &A1Instance{Data: "i love you"} // 调用接口的方法 a.Println()
若是结构体没有实现该方法,将编译不经过,没法编译二进制。
固然也可使用断言和反射来判断接口类型是属于哪一个实际的结构体struct
。
// 断言类型 if v, ok := a.(*A1Instance); ok { fmt.Println(v) } else { fmt.Println("not a A1") } fmt.Println(reflect.TypeOf(a).String())
Golang
很智能判断结构体是否实现了接口的方法,若是实现了,那么该结构体就是该接口类型。咱们灵活的运用接口结构的特征,使用组合的形式就能够开发出更灵活的程序了。
我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook。