测试一个值是否实现了某个接口 下面是类型断言那一part里的一个特例:假定 v
是一个值,然后我们想测试它是否实现了 Stringer
接口,可以用:
1 2 3 4 5 6 type Stringer interface { String() string }if sv, ok := v.(Stringer); ok { fmt.Printf("v implements String(): %s\n" , sv.String()) }
Print()
函数就是如此检测类型是否可以打印自身的。
接口是一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同的接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。
编写参数是接口变量的函数,这使得它们更具有一般性。
使用接口使代码更具有普适性
而标准库中都是使用了这个原则(所以需要看标准库的源码)
接下来会用两个例子去深入理解并掌握它们
使用方法集和接口 在前文第 10.6.3 节 及例子methodset1.go 中我们看到,作用于变量上的方法实际上是不区分变量到底是指针还是值的。当碰到接口类型的值时,这会变得有点复杂,原因是接口变量中存储的具体值时不可寻址的,当然如果使用不当编译器会给出错误提示。
示例methodset2.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package mainimport "fmt" type List []int func (l List) Len() int { return len (l) }func (l *List) Append(val int ) { *l = append (*l, val) }type Appender interface { Append(int ) }func CountInto (a Appender, start, end int ) { for i := start; i <= end; i++ { a.Append(i) } }type Lener interface { Len() int }func LongEnough (l Lener) bool { return l.Len()*10 > 42 }func main () { var lst List if LongEnough(lst) { fmt.Printf("- lst is long enough\n" ) } plst := new (List) CountInto(plst, 1 , 10 ) if LongEnough(plst) { fmt.Printf("- plst is long enough\n" ) } }
输出结果:
在 lst
上调用 CountInto
时会导致一个编译器错误,因为 CountInto
需要一个 Appender
,而它的方法 Append
只定义在指针上。在 lst
上调用 LongEnough
是可以的,因为 Len
定义在值上。
在 plst
上调用 CountInto
是可以的,因为 CountInto
是需要一个 Appender
,并且它的方法 Append
定义在指针上。在 plst
上调用 LongEnough
也是可以的,因为指针会被自动解引用。
总结:
在接口上调用方法,必须有和方法定义时相同的接收者类型或者是可以根据具体类型 p
直接辨识的:
指针方法可以通过指针调用
值方法可以通过值调用
接收者是值的方法可以通过指针调用,因为指针会首先被解引用
接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址
将一个值赋值给一个接口,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。
PS:
Go中规范定义了接口方法集的调用规则:
类型 *T
的可调用方法集包含接受者为 *T
或 T
的所有方法集
类型 T
的可调用方法集包含接受者为 T
的所有方法
类型 T
的可调用方法集不包含 接受者为 *T
的方法
例子1:使用sorter接口排序 这里将以 Sort
包为例子,对一组数字或字符串进行排序,只需要实现三个方法:反应元素个数的 Len()
方法、比较第 i
和 j
个元素的 Less(i, j)
方法以及交换第 i
和 j
个元素的 Swap(i, j)
方法。
排序函数的算法只会用到这三个方法(和Python不一样诶,这里使用的是冒泡排序)
1 2 3 4 5 6 7 8 9 func Sort (data Sorter) { for pass := 1 ; pass < data.Len(); pass++ { for i := 0 ; i < data.Len()-pass; i++ { if data.Less(i+1 , i) { data.Swap(i, i+1 ) } } } }
两层循环的解释:
外层循环:控制轮数,轮数最多为长度减1,因为每层至少有一个元素会被放在正确的位置上。
内层循环:遍历未排序的元素,data.Len() - pass
保证经过一轮后,已排序的最大元素不会被再次比较。
Sort
函数接收一个接口类型的参数: Sorter
,它声明了如下方法:
1 2 3 4 5 type Sorter interface { Len() int Less(i, j int ) bool Swap(i, j int ) }
参数中的 int
是待排序序列长度的类型,而不是要排序的对象一定要是一组 int
。i
and j
表示元素的整型索引,长度也是整型的。
如果现在相对一个 int
数组进行排序,必须要做的事情就是:为数组顶一个类型并且在它上面实现 Sorter
接口的方法:
1 2 3 4 type IntArray []int func (p IntArray) Len() int {return len (p)}func (p IntArray) Less(i, j int ) bool {return p[i] < p[j]}func (p IntArray) Swap(i, j int ) {p[i], p[j] = p[j], p[i]}
下面是一个调用排序函数的例子:
1 2 3 data := []int {74 , 59 , 238 , -784 , 9845 , 959 , 905 , 0 , 0 , 42 , 7586 , -5467984 , 7586 } a := sort.IntArray(data) sort.sort(a)
接下来是完整的代码sort.go and sortmain.go
示例 sort.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package sorttype Interface interface { Len() int Less(i, j int ) bool Swap(i, j int ) }func Sort (data Interface) { for i := 1 ; i < data.Len(); i++ { for j := i; j > 0 && data.Less(j, j-1 ); j-- { data.Swap(j, j-1 ) } } }func IsSorted (data Interface) bool { n := data.Len() for i := n - 1 ; i > 0 ; i-- { if data.Less(i, i-1 ) { return false } } return true }type IntArray []int func (p IntArray) Len() int { return len (p) }func (p IntArray) Less(i, j int ) bool { return p[i] < p[j] }func (p IntArray) Swap(i, j int ) { p[i], p[j] = p[j], p[i] }type Float64Array []float64 func (p Float64Array) Len() int { return len (p) }func (p Float64Array) Less(i, j int ) bool { return p[i] < p[j] }func (p Float64Array) Swap(i, j int ) { p[i], p[j] = p[j], p[i] }type StringArray []string func (p StringArray) Len() int { return len (p) }func (p StringArray) Less(i, j int ) bool { return p[i] < p[j] }func (p StringArray) Swap(i, j int ) { p[i], p[j] = p[j], p[i] }func SortInts (a []int ) { Sort(IntArray(a)) }func SortFloat64s (a []float64 ) { Sort(Float64Array(a)) }func SortStrings (a []string ) { Sort(StringArray(a)) }func IntsAreSorted (a []int ) bool { return IsSorted(IntArray(a)) }func Float64sAreSorted (a []float64 ) bool { return IsSorted(Float64Array(a)) }func StringsAreSorted (a []string ) bool { return IsSorted(StringArray(a)) }
这里面要注意的就是 Sort
函数是j--
和j-1
,众所周知由于编写习惯都会写++。我最开始写完就是报错,查了之后才发现这里写错了。然后Sort记得放在Sort包下,别和sortmain
在一个包里
示例sortmain.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package mainimport ( "fmt" "myproject/chapter11/sort" )func ints () { data := []int {74 , 59 , 238 , -784 , 9845 , 959 , 905 , 0 , 0 , 42 , 7586 , -5467984 , 7586 } a := sort.IntArray(data) sort.Sort(a) if !sort.IsSorted(a) { panic ("fail" ) } fmt.Printf("The sorted array is: %v\n" , a) }func strings () { data := []string {"monday" , "friday" , "tuesday" , "wednesday" , "sunday" , "thursday" , "" , "saturday" } a := sort.StringArray(data) sort.Sort(a) if !sort.IsSorted(a) { panic ("fail" ) } fmt.Printf("The sorted array is: %v\n" , a) }type day struct { num int shortName string longName string }type dayArray struct { data []*day }func (p *dayArray) Len() int { return len (p.data) }func (p *dayArray) Less(i, j int ) bool { return p.data[i].num < p.data[j].num }func (p *dayArray) Swap(i, j int ) { p.data[i], p.data[j] = p.data[j], p.data[i] }func days () { Sunday := day{0 , "SUM" , "Sunday" } Monday := day{1 , "MON" , "Monday" } Tuesday := day{2 , "TUE" , "Tuesday" } Wednesday := day{3 , "WED" , "Wednesday" } Thursday := day{4 , "THU" , "Thursday" } Friday := day{5 , "FRI" , "Friday" } Saturday := day{6 , "SAT" , "Saturday" } data := []*day{&Sunday, &Monday, &Tuesday, &Wednesday, &Thursday, &Friday, &Saturday} a := dayArray{data} sort.Sort(&a) if !sort.IsSorted(&a) { panic ("fail" ) } for _, d := range data { fmt.Printf("%s" , d.longName) } fmt.Printf("\n" ) }func main () { ints() strings() days() }
输出结果
1 2 3 The sorted array is: [-5467984 -784 0 0 42 59 74 238 905 959 7586 7586 9845] The sorted array is: [ friday monday saturday sunday thursday tuesday wednesday] SundayMondayTuesdayWednesdayThursdayFridaySaturday
PS:
panic('fail')
用于停止处在非正常状态下的程序(详细参考第13章 ),当然也可以先打印一条信息,然后调用os.Exit(1)
来停止程序。
当然,众所周知Sort
都是标准库,所以正常调用直接用就行,不用自己写。对于一般性的排序,sort
包定义了一个接口:
1 2 3 4 5 type Interface interface { Len() int Less(i, j int ) bool Swap(i, j int ) }
这个接口主要是总结了用于排序的抽象方法,函数Sort(data Interface)
用来对此类对象进行排序,可以用它们来实现对其他类型的数据(非基本类型)进行排序。在上面的例子中,我们也是这么做的,不仅可以对 int
和 string
序列进行排序,也可以对用户自定义类型 dayArray
进行排序。
例子2:读和写 读和写作为程序中常见的操作,一提起就会想到读写文件、缓存、标准输入输出(万恶的ACM模式)、标准错误以及网络连接、管道等,或者读写自定义类型。为了让代码尽可能通用,Go采用的是一致的方式来读写。
io
包提供了读和写的接口 io.Reader
和 io,Writer
:
1 2 3 4 5 6 7 type Reader interface { Read(p []byte ) (n int , err error ) }type Writer interface { Write(p []byte ) (n int , err error ) }
只要类型实现了读写接口,提供 Read
和 Write
方法,就可以从它读取数据,或者向它写入数据。一个对象要是可读的,它必须实现 io.Reader
接口,这个接口只有一个签名是 Read(p []byte) (n int, err error)
的方法,它从调用它的对象上读取数据,并把读取到的数据放入参数中的字节切片中,然后返回读取的字节数和一个 error
对象。如果没有错误发生返回 nil
,如果已经到达输入数据的尾端,会返回 io.EOF("EOF")
,如果读取的过程中发生了错误,就会返回具体的错误信息。类似的写操作也是一样的。
io
包中的 Reader
和 Writers
都是不带缓冲的,bufio
包里提供了对应的带缓冲操作,在读写 UTF-8
编码的文本时尤其有用。
所以在实际编程中,尽可能使用这些接口,能够使程序更加通用,可以在任何实现了接口的类型上使用读写方法。
空接口 概念 一个概念一出我就瞬间想到了那几步XXX的历史,XXX的由来,XXX的定义,XXX的现状,XXX的未来,XXX的应用
空接口或者最小接口 不包含任何方法,它对实现不做任何要求:
任何其他类型都实现了空接口(它不仅像 Java/C#
中 Object
引用类型),any
或 Any
是空接口一个很好的别名或缩写。
空接口类似 Java/C#
中所有类的基类:Object
类,二者的目标也很相近。
示例empty_interface.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport "fmt" var i = 5 var str = "ABC" type Person struct { name string age int }type Any interface {}func main () { var val Any val = 5 fmt.Printf("val has the value: %v\n" , val) val = str fmt.Printf("val has the value: %v\n" , val) pers1 := new (Person) pers1.name = "Rob Pike" pers1.age = 42 val = pers1 fmt.Printf("Person 1: %v\n" , val) switch t := val.(type ) { case int : fmt.Printf("Type int %T\n" , t) case string : fmt.Printf("Type string %T\n" , t) case bool : fmt.Printf("Type bool %T\n" , t) case *Person: fmt.Printf("Type *Person %T\n" , t) default : fmt.Printf("Type unknown %T" , t) } }
输出结果
1 2 3 4 val has the value: 5 val has the value: ABC Person 1: &{Rob Pike 42} Type *Person *main.Person
在上述例子中,接口变量 val
被依次赋予一个 int
, string
和 Person
实例的值,然后使用 type-switch
来测试它的实际类型。每个 interface()
变量在内存中占据两个字长:一个用来存储它所包含的类型,一个用来存储它所包含的数据或指向数据的指针。
示例emptyint_switch.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport "fmt" type specialString string var whatlsThis 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 float64 : fmt.Printf("any %v is a float64 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.Printf("unknown type!" ) } } testFunc(whatlsThis) }func main () { TypeSwitch() }
输出结果
1 any hello is a special String!
该示例中说明了空接口在 type-switch
中联合 lambda
函数的用法
构建通用类型或包含不同类型变量的数组 在 7.6.6 中我们看到了能被搜索和排序的 int
数组、float
数组以及string
数组,那么对于其他类型的数组呢?(莫名想到leetcode)
具体实现就是通过一个空接口,让我们给空接口定一个别名类型 Element
: type Elemet interface{}
然后定义一个容器类型的结构体 Verctor
,它包含一个 Element
类型元素的切片:
1 2 3 type Vector struct { a []Element }
Vector
里能够放任何类型的元素,因为任何类型都是先了空接口,实际上 Vector
里放的每个元素可以是不同类型的变量。我们为它定义一个 At()
方法用于返回 第 i
个元素:
1 2 3 func (p *Vector) At(i int ) Element { return p.a[i] }
再定一个 Set()
方法用于设置第 i
个元素的值:
1 2 3 func (p *Vertor) Set(i int , e Element) { p.a[i] = e }
Vector
中存储的元素都是 Element
类型,要得到它们的原始类型(unboxing:拆箱)需要用到类型断言。TODO: The compiler rejects assertions guaranteed to fail
,类型断言总是在运行时才执行,因此它会产生运行时错误。
复制数据到切片至空切片接口 假设有一个 myType
类型的数据切片,你想将切片中的数据复制到一个空接口切片中,类似:
1 2 var dataSlice []myType = FuncReturnSlice()var interfaceSlice []interface {} = dataSlice
可惜的是这样会直接报错 cannot use dataSlice (type []myType) as type []interface { } in assignment
。
原因是他俩在内存中的布局是不一样的(参考go wiki )
必须使用 for-range
语句来一个一个显式地赋值:
1 2 3 4 5 var dataSlice []myType = FuncReturnSlice()var interfaceSilce []interface {} = make ([]interface {}, len (dataSlice))for i, d := range dataSlice { interfaceSlice[i] = d }