060-类型断言(Type Assertion)

接口相关的知识中,最重要的不仅仅是了解接口如何实现,接口的构成(类型和值)。另一个非常非常重要的知识点就是类型断言。

正好上一篇文章所介绍的,根据接口判断 error 的类型,相当重要,这可以帮助我们根据不同的错误来制定相应的策略。

1. 类型断言

首先,类型断言,当然只能在接口上进行操作啦。普通对象你都已经知道它的类型了,还有断言的必要吗?

对于接口 x,语法形式上像 x.(T) 这样的,就称之为类型断言。x.(T) 会返回一个值。断言成功情况下,返回的值有两种可能:

  • 如果 T 是普通的具体类型,x.(T) 就会返回 x 的值部分,即类型为 T 的值。
  • 如果 T 仍然是接口类型,x.(T) 会返回 T 类型的接口值

如果断言失败呢?根据你的书写形式,你可以选择让程序 panic 或者返回一个 bool 值。说这么多,不如举几个例子来实在。

var w io.Writer
w = os.Stdout
f := w.(*os.File)      // 断言成功,返回 w 中的值部分。f == os.Stdout
c := w.(*bytes.Buffer) // Panic! 断言失败

如果你不希望你的程序在断言的过程上发生 panic,你可以写成下面这样:

var w io.Writer
w = os.Stdout
f, ok := w.(*os.File)      // 断言成功,f == os.Stdout, ok == true
c, ok := w.(*bytes.Buffer) // 断言失败, c == nil, ok == false

什么时候用单返回值形式,什么时候用双返回值形式的断言?

当你明确要求某个接口值的类型部分是约定的类型的时候,你就使用单返回值形式。比如你和同事合作开发一个项目,你要求你同事返回的接口值的类型部分是 int,那你就直接使用单返回值的类型断言。如果你的程序 panic 了,你就可以去 diss 你的同事了 : )

2. 断言接口类型

上面的例子是断言某个接口值是某个具体类型,你也可以断言接口值可以是另一种接口类型。比如:

var w io.Writer
w = os.Stdout
rw := w.(io.ReadWriter) // 断言成功,(*os.File) 的确有 Read 和 Write 方法

_, pw := io.Pipe() // func Pipe() (*PipeReader, *PipeWriter)
w = pw
rw = pw.(io.ReadWriter) // panic, 断言失败。(*PipeWriter) 并没有实现 Read 方法

上面比较奇怪的是,我们断言 w 接口值是 io.ReadWriter 接口值,尽管 w 是 io.Writer 接口类型,但是这也没什么关系。断言一个接口是不是另一种类型,看的是这个接口内部 type 那个部分是否真的符合你断言的类型。

3. 对 nil 接口值断言

无论被断言成什么类型都会失败。

var w io.Writer
rw := w.(*os.File)     // 断言失败,panic
var w io.Writer
rw, ok := w.(*os.File) // 断言失败,rw == nil, ok == false

4. 判断 error 类型

还记得上一篇文章遗留的一个问题吗?在程序的最后,有这样一段:

if e, ok := err.(syscall.Errno); ok && e == 2 {
    fmt.Printf("Error: %v\n", e)
}

现在你应该能看到这是什么意思了吧。其实非常简单,我们断言 err 接口值的类型是 syscall.Errno,如果断言成员,那 e 就是 syscall.Errno 类型的值,同时 ok 也为 true,最后再判断 e 的值是否为 2,如果是,就打印输出 e.

5. 总结

  • 掌握类型断言
  • 掌握类型言返回单值和双值的区别

你可能感兴趣的:(Go,语言学习笔记(更新中...),Go,语言修炼指南,golang,类型断言)