From 75dbc8a39330fd97b3a14244e03af124393eb963 Mon Sep 17 00:00:00 2001 From: leisore Date: Mon, 7 Sep 2015 15:50:41 +0800 Subject: [PATCH] CH 11.9 OK --- eBook/11.9.md | 253 +++++++++++++++++++++++++++++++++++++++++++++ eBook/directory.md | 1 + 2 files changed, 254 insertions(+) diff --git a/eBook/11.9.md b/eBook/11.9.md index e69de29..f5d40ef 100644 --- a/eBook/11.9.md +++ b/eBook/11.9.md @@ -0,0 +1,253 @@ +# 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:拆箱)需要用到类型断言。TODO:The 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 doesn’t +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 第二个例子:读和写](11.8.md) +- 下一节:[11.10 反射包](11.10.md) \ No newline at end of file diff --git a/eBook/directory.md b/eBook/directory.md index 3f42721..575b50e 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -102,6 +102,7 @@ - 11.6 [浣跨敤鏂规硶闆嗕笌鎺ュ彛](11.6.md) - 11.7 [绗竴涓緥瀛愶細浣跨敤Sorter鎺ュ彛鎺掑簭](11.7.md) - 11.8 [绗簩涓緥瀛愶細璇诲拰鍐橾(11.8.md) + - 11.9 [绌烘帴鍙(11.9.md) ## 绗笁閮ㄥ垎锛欸o 楂樼骇缂栫▼