多态
Go里的接口类似C++多态
相同接口的变量在不同的时刻表现出不同的行为
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
实现接口时: | 指针变量 | 值变量 |
---|---|---|
接收者是*T 类型的方法 | OK | ERROR 存储在接口里的值没有地址 |
接收者是T类型的方法 | OK 自动解指针 | OK |
将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
package main
import "fmt"
type SayHellor interface {
SayHello()
}
type Man struct {
name string
}
type Woman struct {
name string
}
// Man和Woman都必须实现SayHello方法
func (m *Man)SayHello() {
fmt.Println(m.name, "say hello")
}
func (w *Woman)SayHello() {
fmt.Println(w.name, "say hello")
}
func main() {
var m Man = Man{"Tom"}
var manSayHello SayHellor = &m // 必须传地址
Say(manSayHello)
w := &Woman{"Amy"}
var womanSayHello SayHellor = w
Say(womanSayHello)
}
// SayHellor对象可以变成Man或者Woman
func Say(people SayHellor) {
people.SayHello()
}
接口嵌套接口
->接口继承接口,还是接口
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
接口的继承
当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。
type tester interface {
show()
}
type implementTester struct {
str string
}
func (i *implementTester) show() {
fmt.Println("show...", i.str)
}
type Test struct {
str string
*implementTester
}
func main() {
t := Test{"1111", &implementTester{"aaa"}} // show... aaa
t.show()
}
类型断言:检测和转换接口变量的类型
ok: 测试接口变量是否为类型 T
v: 接口变量转换成类型为T的值
if v, ok := 接口变量.(T); ok { // checked type assertion
Process(v)
return
}
// 接口变量不是类型T
注意T是结构体指针类型
package main
import "fmt"
type SayHellor interface {
SayHello()
}
type Man struct {
name string
}
type Woman struct {
name string
}
func (m *Man)SayHello() {
fmt.Println(m.name, "say hello")
}
func (w *Woman)SayHello() {
fmt.Println(w.name, "say hello")
}
func main() {
var m Man = Man{"Tom"}
var hellor SayHellor = &m
if t, ok := hellor.(*Man); ok {
// 是*Man类型
fmt.Println("Man:", t) // Man: &{Tom}
fmt.Printf("The type of hellor is: %T\n", t) // The type of hellor is: *main.Man
}
w := new(Woman)
w.name = "Amy"
hellor = w
if t, ok := hellor.(*Woman); ok {
// 是*Woman类型
fmt.Println("Woman:", t) // Woman: &{Amy}
fmt.Printf("The type of hellor is: %T\n", t) // The type of hellor is: *main.Woman
}
}
类型判断:type-switch
var m Man = Man{"Tom"}
var hellor SayHellor = &m
switch hellor.(type) {
case *Man:
fmt.Println("type Man")
case *Woman:
fmt.Println("type Woman")
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}
类型分类函数
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)
}
}
}
func main() {
classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false)
}
测试类是否实现了某个接口
var _ SayHellor = new(Man)
var _ 接口类型 = new(类)
如果没实现,IDE会报错
Eg: 使用sort包,自定义排序
sort包里部分源码:
一个接口类型Interface和快排排序Sort方法都在库里实现了。
package sort
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
n := data.Len()
quickSort(data, 0, n, maxDepth(n))
}
那么使用时,只需要实现接口方法再调用Sort函数,实现排序.
leetcode 252
type timeArray [][]int
func (T timeArray) Len() int {
return len(T)
}
func (T timeArray) Less(i, j int) bool {
if T[i][0] < T[j][0] {
return true
} else if T[i][0] == T[j][0] {
if T[i][1] > T[j][1] {
return true
}
return false
}
return false
}
func (T timeArray) Swap(i, j int) {
T[i], T[j] = T[j], T[i]
}
func canAttendMeetings(intervals timeArray) bool {
size := len(intervals)
if size < 2 {
return true
}
sort.Sort(intervals)
for i := 0; i < size - 1; i++ {
if intervals[i][1] > intervals[i + 1][0] {
return false
}
}
return true
}
Eg :读和写
io 包提供了用于读和写的接口 io.Reader 和 io.Writer:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
“模板”
Go是动态语言,比C++的模板更灵活,
空接口
可以给一个空接口类型的变量 var val interface {} 赋任何类型的值。
var x interface{} // x为接口类型
s := "WD"
x = s // 绑定string类型
y,ok := x.(int)
z,ok1 := x.(string) // 判断类型,为string
fmt.Println(y,ok) // 0 false
fmt.Println(z,ok1) // WD true
空接口+type-switch+lambda
package main
import "fmt"
type specialString string
var whatIsThis specialString = "hello"
func TypeSwitch() {
testFunc := func(any interface{}) {
switch v := any.(type) {
case bool:
fmt.Printf("any %v is a bool type", v)
case int:
fmt.Printf("any %v is an int type", v)
case float32:
fmt.Printf("any %v is a float32 type", v)
case string:
fmt.Printf("any %v is a string type", v)
case specialString:
fmt.Printf("any %v is a special String!", v)
default:
fmt.Println("unknown type!")
}
}
testFunc(whatIsThis)
}
func main() {
TypeSwitch()
}
tuple实现
type Element interface{}
type Tuple []Element
func (p *Tuple) At(i int) Element {
return (*p)[i]
}
func (p *Tuple) Set(i int, e Element) {
(*p)[i] = e
}
func main() {
tuple := Tuple{"1", 2, "abc", 3.1}
fmt.Println(tuple) // [1 2 abc 3.1]
}
复制数据切片至空接口切片
它们俩在内存中的布局是不一样的,必须一个个复制
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
interfaceSlice[i] = d
}
Eg:二叉树
package main
import "fmt"
type Node struct {
le *Node
data interface{}
ri *Node
}
func NewNode(left, right *Node) *Node {
return &Node{left, nil, right}
}
func (n *Node) SetData(data interface{}) {
n.data = data
}
func main() {
root := NewNode(nil, nil)
root.SetData("root node")
// make child (leaf) nodes:
a := NewNode(nil, nil)
a.SetData("left node")
b := NewNode(nil, nil)
b.SetData("right node")
root.le = a
root.ri = b
fmt.Printf("%v\n", root) // Output: &{0x125275f0 root node 0x125275e0}
}
接口A《-》接口B
只有接口A的底层结构定义了接口B里的方法,那么就可以通过断言转换
package main
import "fmt"
type AInterface interface {
A()
}
type BInterface interface {
B()
}
type Object struct {
a, b int
}
func (o *Object) A() {
fmt.Println("A()")
}
func (o *Object) B() {
fmt.Println("B()")
}
func main() {
o := &Object{1, 2}
var ai AInterface = o
ai.A()
bi, ok := ai.(BInterface) // AInterface->BInterface
bi.B()
fmt.Println(ok) // true
}
反射
一、理解变量的内在机制
1.类型信息,元信息,是预先定义好的,静态的。
2.值信息,程序进行过程中,动态变化的。
二、反射和空接口
1.空接口相当于一个容器,能接受任何东西。
2.那怎么判断空接口变量存储的是什么类型呢?之前有使用过类型断言,这只是一个比较基础的方法
3.如果想获取存储变量的类型信息和值信息就要使用反射机制,所以反射是什么? 反射就是动态的获取变量类型信息和值信息的机制。
三、怎么利用反射分析空接口里面的信息呢?
①首先利用的是GO语言里面的Reflect包
②利用包里的TypeOf方法可以获取 反射类型reflect.Type对象
利用包里的ValueOf方法可以获取 反射类型reflect.Value对象
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
1) 从reflect.Value对象中获取值的方法
package main
import (
"fmt"
"reflect"
)
func main() {
var a int = 1024
// 获取变量 a 的反射值对象,类型为 reflect.Value
valueOfA := reflect.ValueOf(a)
// 将 valueOfA 反射值对象以 interface{} 类型取出,通过类型断言转换为 int 类型并赋值给 getA
var getA int = valueOfA.Interface().(int)
// 获取64位的值, 强制类型转换为int类型
var getA2 int = int(valueOfA.Int())
fmt.Println(valueOfA, getA, getA2) // 1024 1024 1024
}
2.从reflect.Type对象中获取各种信息
type Type interface {
Align() int // 此类型的变量对齐后所占用的字节数
FieldAlign() int // 如果是struct的字段,对齐后占用的字节数
Method(int) Method // 返回类型方法集里的第 `i` (传入的参数)个方法
MethodByName(string) (Method, bool) // 通过名称获取方法
NumMethod() int // 获取类型方法集里导出的方法个数
Name() string // 类型名称
PkgPath() string // 返回类型所在的路径,如:encoding/base64
Size() uintptr // 返回类型的大小,和 unsafe.Sizeof 功能类似
String() string // 返回类型的字符串表示形式
Kind() Kind // 返回类型的类型值
Implements(u Type) bool // 类型是否实现了接口 u
AssignableTo(u Type) bool // 是否可以赋值给 u
ConvertibleTo(u Type) bool // 是否可以类型转换成 u
Comparable() bool // 类型是否可以比较
// 下面这些函数只有特定类型可以调用
// 如:Key, Elem 两个方法就只能是 Map 类型才能调用
Bits() int // 类型所占据的位数
ChanDir() ChanDir // 返回通道的方向,只能是 chan 类型调用
// 返回类型是否是可变参数,只能是 func 类型调用
// 比如 t 是类型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool
Elem() Type // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
// 返回结构体类型的第 i 个字段,只能是结构体类型调用
// 如果 i 超过了总字段数,就会 panic
Field(i int) StructField
FieldByIndex(index []int) StructField // 返回嵌套的结构体的字段
FieldByName(name string) (StructField, bool) // 通过字段名称获取字段
// FieldByNameFunc returns the struct field with a name
// 返回名称符合 func 函数的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)
In(i int) Type // 获取函数类型的第 i 个参数的类型
Key() Type // 返回 map 的 key 类型,只能由类型 map 调用
Len() int // 返回 Array 的长度,只能由类型 Array 调用
NumField() int // 返回类型字段的数量,只能由类型 Struct 调用
NumIn() int // 返回函数类型的输入参数个数
NumOut() int // 返回函数类型的返回值个数
Out(i int) Type // 返回函数类型的第 i 个值的类型
common() *rtype // 返回类型结构体的相同部分
uncommon() *uncommonType // 返回类型结构体的不同部分
}
Kind() 获取类型
reflect.Type和reflect.Value类型都有Kind()方法,返回底层数据类型
Elem() 通过反射修改值
当 v := reflect.ValueOf(x)
函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。
要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x)
。
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
// 1. CanSet看一看能不能设置了
fmt.Println(v.CanSet()) // false
// 2. 不能是因为ValueOf方法创建的反射对象为传值,必须传地址才能设置
vp := reflect.ValueOf(&x)
fmt.Println(vp.CanSet()) // false
// 3. 使用Elem导出
vp = vp.Elem()
fmt.Println(vp.CanSet()) // true
// 4. 设置
vp.SetFloat(3.1415)
fmt.Println(vp)
}
NumField(), Field() 获取结构体字段
type Test struct {
A int
B string
}
func main() {
t := Test{22, "aaaa"}
elem := reflect.ValueOf(&t).Elem()
typeOfT := reflect.TypeOf(t)
for i := 0; i < typeOfT.NumField(); i++ {
fieldValue := elem.Field(i)
fieldType := typeOfT.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
fieldType.Name, fieldValue.Type(), fieldValue)
}
}
Printf 和反射
库中很多函数都使用反射来分析参数
func Printf(format string, args ... interface{}) (n int, err error)
总结:Go 中的面向对象
我们总结一下前面看到的:Go 没有类,而是松耦合的类型、方法对接口的实现。
OO 语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢?
封装(数据隐藏):
1)包范围内的:通过标识符首字母小写,对象 只在它所在的包内可见
2)可导出的:通过标识符首字母大写,对象 对所在包以外也可见
类型只拥有自己所在包中定义的方法。
继承:用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现
多态:用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。