Files
the-way-to-go_ZH_CN/eBook/11.9.md
2015-09-08 06:11:37 -04:00

255 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 11.9 空接口
## 11.9.1 概念
**空接口或者最小接口**不包含任何方法,它对实现不做任何要求:
```go
type Any interface {}
```
任何其他类型都实现了空接口(它不仅仅像 `Java/C#``Object` 引用类型),`any``Any` 是空接口一个很好的别名或缩写。
空接口类似 `Java/C#` 中所有类的基类: `Object` 类,二者的目标也很相近。
可以给一个空接口类型的变量 `var val interface {}` 赋任何类型的值。
示例 11.8 empty_interface.go
```go
package main
import "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 = 55
val = pers1
fmt.Printf("val has the value: %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 boolean %T\n", t)
case *Person:
fmt.Printf("Type pointer to Person %T\n", t)
default:
fmt.Printf("Unexpected type %T", t)
}
}
```
输出:
val has the value: 5
val has the value: ABC
val has the value: &{Rob Pike 55}
Type pointer to Person *main.Person
在上面的例子中,接口变量 `val` 被依次赋予一个 `int``string``Person` 实例的值,然后使用 `type-swtich` 来测试它的实际类型。每个 `interface {}` 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。
例子 emptyint_switch.go 说明了空接口在 `type-swtich` 中联合 `lambda` 函数的用法:
```go
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()
}
```
输出:
any hello is a special String!
**练习 11.9** simple_interface3.go
继续 练习11.2,在它中添加一个 `gI` 函数,它不再接受 `Simpler` 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 `Simpler` 类型。最后在 `main` 使用 `gI` 取代 `fI` 函数并调用它。确保你的代码足够安全。
## 11.9.2 构建通用类型或包含不同类型变量的数组
在 7.6.6 中我们看到了能被搜索和排序的 `int` 数组、`float` 数组以及 `string` 数组,那么对于其他类型的数组呢,是不是我们必须得自己编程实现它们?
现在我们知道该怎么做了,就是通过使用空接口。让我们给空接口定一个别名类型 `Element``type Element interface{}`
然后定义一个容器类型的结构体 `Vector`,它包含一个 `Element` 类型元素的切片:
```go
type Vector struct {
a []Element
}
```
`Vector` 里能放任何类型的变量,因为任何类型都实现了空接口,实际上 `Vector` 里放的每个元素可以是不同类型的变量。我们为它定义一个 `At()` 方法用于返回第 `i` 个元素:
```go
func (p *Vector) At(i int) Element {
return p.a[i]
}
```
再定一个 `Set()` 方法用于设置第 `i` 个元素的值:
```go
func (p *Vector) Set(i int, e Element) {
p.a[i] = e
}
```
`Vector` 中存储的所有元素都是 `Element` 类型要得到它们的原始类型unboxing拆箱需要用到类型断言。TODOThe compiler rejects assertions guaranteed to fail类型断言总是在运行时才执行因此它会产生运行时错误。
**练习 11.10** min_interface.go / minmain.go
仿照11.7中开发的 `Sorter` 接口,创建一个 `Miner` 接口并实现一些必要的操作。函数 `Min` 接受一个 `Miner` 类型变量的集合,然后计算并返回集合中最小的元素。
## 11.9.3 复制数据切片至空接口切片
假设你有一个 `myType` 类型的数据切片,你想将切片中的数据复制到一个空接口切片中,类似:
```go
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = dataSlice
```
可惜不能这么做,编译时会出错:`cannot use dataSlice (type []myType) as type []interface { } in assignment`
原因是它们俩在内存中的布局是不一样的(参考[http://golang.org/doc/go_spec.html](http://golang.org/doc/go_spec.html))。
必须使用 `for-range` 语句来一个一个显式地复制:
```go
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for ix, d := range dataSlice {
interfaceSlice[i] = d
}
```
## 11.9.4 通用类型的节点数据结构
在10.1中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 `NewNode` 方法,及设置数据的 `SetData` 方法.
示例 11.10 node_structures.go:
```go
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}
}
```
## 11.9.5 接口到接口
一个接口的值可以赋值给另一个接口变量,只要底层类型实现了必要的方法。这个转换是在运行时进行检查的,转换失败会导致一个运行时错误:这是 'Go' 语言动态的一面,可以那它和 `Ruby``Python` 这些动态语言相比较。
假定:
```go
var ai AbsInterface // declares method Abs()
type SqrInterface interface {
Sqr() float
}
var si SqrInterface
pp := new(Point) // say *Point implements Abs, Sqr
var empty interface{}
```
那么下面的语句和类型断言是合法的:
```go
empty = pp // everything satisfies empty
ai = empty.(AbsInterface) // underlying value pp implements Abs()
// (runtime failure otherwise)
si = ai.(SqrInterface) // *Point has Sqr() even though AbsInterface doesnt
empty = si // *Point implements empty set
// Note: statically checkable so type assertion not necessary.
```
下面是函数调用的一个例子:
```go
type myPrintInterface interface {
print()
}
func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
```
`x` 转换为 `myPrintInterface` 类型是完全动态的:只要 `x` 的底层类型(动态类型)定义了 `print` 方法这个调用就可以正常运行。
## 链接
- [目录](directory.md)
- 上一节:[第二个例子:读和写](11.8.md)
- 下一节:[反射包](11.10.md)