menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right the-way-to-go_ZH_CN chevron_right eBook chevron_right 11.9.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    11.9.md
    8.12 KB / 2024-07-16 23:14:27
        # 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](examples/chapter_11/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-switch` 来测试它的实际类型。每个 `interface {}` 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。
    
    示例 [emptyint_switch.go](examples/chapter_11/emptyint_switch.go) 说明了空接口在 `type-switch` 中联合 `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](exercises/chapter_11/simple_interface3.go):
    
    继续练习 11.2,在它中添加一个 `gI()` 函数,它不再接受 `Simpler` 类型的参数,而是接受一个空接口参数。然后通过类型断言判断参数是否是 `Simpler` 类型。最后在 `main` 使用 `gI()` 取代 `fI()` 函数并调用它。确保你的代码足够安全。
    
    ## 11.9.2 构建通用类型或包含不同类型变量的数组
    
    在 [7.6.6](07.6.md) 中我们看到了能被搜索和排序的 `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](exercises/chapter_11/min_interface.go) / [minmain.go](exercises/chapter_11/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`。
    
    原因是它们俩在内存中的布局是不一样的(参考 [Go wiki](https://github.com/golang/go/wiki/InterfaceSlice))。
    
    必须使用 `for-range` 语句来一个一个显式地赋值:
    
    ```go
    var dataSlice []myType = FuncReturnSlice()
    var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
    for i, d := range dataSlice {
        interfaceSlice[i] = d
    }
    ```
    
    ## 11.9.4 通用类型的节点数据结构
    
    在 [10.1](10.1.md) 中我们遇到了诸如列表和树这样的数据结构,在它们的定义中使用了一种叫节点的递归结构体类型,节点包含一个某种类型的数据字段。现在可以使用空接口作为数据字段的类型,这样我们就能写出通用的代码。下面是实现一个二叉树的部分代码:通用定义、用于创建空节点的 `NewNode` 方法,及设置数据的 `SetData` 方法。
    
    示例 11.10 [node_structures.go](examples/chapter_11/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` 方法这个调用就可以正常运行(译注:若 `x` 的底层类型未定义 `print` 方法,此处类型断言会导致 `panic`,最佳实践应该为 `if mpi, ok := x.(myPrintInterface); ok { mpi.print() }`,参考 [11.3](11.3.md) 章节)。
    
    ## 链接
    
    - [目录](directory.md)
    - 上一节:[第二个例子:读和写](11.8.md)
    - 下一节:[反射包](11.10.md)
    
    
    links
    file_download