下面定义了具有一个字段和两个方法的结构类型S
代码6.1
type S struct { i int }
func (p *S) Get() int { return p.i }
func (p *S) Put(v int) { p.i = v }
也可以定义接口类型,仅仅是方法的集合。这里定义了一个有两个方法的接口I:
type I interface {
Get() int
Put(int)
}
对于接口I,S是合法的实现,因为它定义了I所需的两个方法。注意,即便是没有明确定义S实现了I,这也是正确的。
接口值:
例:
func f(p I) { //定义一个函数接受一个接口类型作为参数
fmt.Println(p.Get()) //p实现了接口I,必须有Get()方法
p.Put(1) //Put()方法是类似的
}
这里的变量p保存了接口类型的值。因为S实现了I,可以调用f向其传递S类型的值的指针:
var s S; f(&s)
获取s的地址,而不是S的值的原因,是因为在s的指针上定义了方法,参阅上面的代码6.1。这并不是必须的——可以定义让方法接受值——但是这样的话Put方法就不会像期望的那样工作了。
实际上,无须明确一个类型是否实现了一个接口意味着Go实现了叫做duck typing的模式。这不是纯粹的duck typing,因为如果可能的话Go编译器将对类型是否实现了接口进行实现静态检查。
假设需要在函数f中知道实际的类型。在Go中可以使用type switch得到。
func f(p I) {
switch t := p.(type) { //类型判断。在switch语句中使用(type)。保存类型到变量t;case *S: //p的实际类型是S的指针;case *R: //p的实际类型是R的指针;case S: //p的实际类型是S;case R: //p的实际类型是R;default: //实现了I的其他类型。}
}
注意: element.(type)语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用 comma-ok。
类型判断不是唯一的运行时得到类型的方法。为了在运行时得到类型,同样可以使用“comma, ok”来判断一个接口类型是否实现了某个特定接口:
if t, ok := something.(I); ok {
// 对于某些实现了接口I 的
// t 是其所拥有的类型
}
Comma-ok断言
Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。
如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。
例:
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List [] Element
type Person struct {
name string
age int
}
//定义了String方法,实现了fmt.Stringer
func (p Person) String() string {
return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
} else {
fmt.Println("list[%d] is of a different type", index)
}
}
}
输出结果:
list[0] is an int and its value is 1
list[1] is a string and its value is Hello
list[2] is a Person and its value is (name: Dennis - age: 70 years)
确定一个变量实现了某个接口,可以使用:
t := something.(I)
空接口
由于每个类型都能匹配到空接口:interface{}。我们可以创建一个接受空接口作为参数的普通函数:
Listing 6.2.用空接口作为参数的函数
func g(something interface{}) int {
return something.(I).Get()
}
在这个函数中的return something.(I).Get()是有一点窍门的。值something具有类型interface{},这意味着方法没有任何约束:它能包含任何类型。.(I)是类型断言,用于转换something到I类型的接口。如果有这个类型,则可以调用Get()函数。因此,如果创建一个*S类型的新变量,也可以调用g(),因为*S同样实现了空接口。
s = new(S)
fmt.Println(g(s));
例:
package main
import (
"fmt"
)
func main(){
s := new(S)
ss := "Hello world"
fmt.Printf("%d\n",g(s))
fmt.Printf("%s\n",demo(ss))
}
func g(something interface{}) int {
return something.(I).Get() //?为什么不是something.(S).Get()
}
func demo(something interface{}) string {
return something.(string)
}
type I interface {
Get() int
Put(int)
}
type S struct { i int }
func (p *S) Get() int {
return p.i
}
func (p *S) Put(v int) {
p.i = v
}
输出结果:
0
Hello world
例2:
package main
import (
"fmt"
)
func main(){
s := S{1}
fmt.Printf("%d\n",g(s))
}
func g(something S) int {
return something.Get()
}
type S struct { i int }
func (p *S) Get() int {
return p.i
}
func (p *S) Put(v int) {
p.i = v
}
输出结果:
1
接口名字
根据规则,单方法接口命名为方法名加上-er后缀:Reader,Writer,Formatter等。
有一堆这样的命名,高效的反映了它们职责和包含的函数名。Read,Write,Close,Flush,String等等有着规范的声明和含义。为了避免混淆,除非有类似的声明和含义,否则不要让方法与这些重名。相反的,如果类型实现了与众所周知的类型相同的方法,那么就用相同的名字和声明;将字符串转换方法命名为String而不是ToString。
有疑问加站长微信联系(非本文作者)