最后我强调,如果可重名变量的类型不同,那么就需要引起我们的特别关注了,它们之间可能会存在“屏蔽”的现象。
必要时,我们需要严格地检查它们的类型,但是怎样检查呢?咱们现在就说。
我今天的问题是:怎样判断一个变量的类型?我们依然以在上一篇文章中展示过的 demo11.go 为基础。
package main import "fmt" var container = []string{"zero", "one", "two"} func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} fmt.Printf("The element is %q.\n", container[1]) }那么,怎样在打印其中元素之前,正确判断变量container的类型?
典型回答答案是使用“类型断言”表达式。具体怎么写呢?
value, ok := interface{}(container).([]string)这里有一条赋值语句。在赋值符号的右边,是一个类型断言表达式。
它包括了用来把container变量的值转换为空接口值的interface{}(container)。
以及一个用于判断前者的类型是否为切片类型 []string 的 .([]string)。
这个表达式的结果可以被赋给两个变量,在这里由value和ok代表。变量ok是布尔(bool)类型的,它将代表类型判断的结果,true或false。
如果是true,那么被判断的值将会被自动转换为[]string类型的值,并赋给变量value,否则value将被赋予nil(即“空”)。
顺便提一下,这里的ok也可以没有。也就是说,类型断言表达式的结果,可以只被赋给一个变量,在这里是value。
但是这样的话,当判断为否时就会引发异常。
这种异常在 Go 语言中被叫做panic,我把它翻译为运行时恐慌。因为它是一种在 Go 程序运行期间才会被抛出的异常,而“恐慌”二字是英文 Panic 的中文直译。
除非显式地“恢复”这种“恐慌”,否则它会使 Go 程序崩溃并停止。所以,在一般情况下,我们还是应该使用带ok变量的写法。
问题解析正式说明一下,类型断言表达式的语法形式是x.(T)。其中的x代表要被判断类型的值。这个值当下的类型必须是接口类型的,不过具体是哪个接口类型其实是无所谓的。
所以,当这里的container变量类型不是任何的接口类型时,我们就需要先把它转成某个接口类型的值。
如果container是某个接口类型的,那么这个类型断言表达式就可以是container.([]string)。这样看是不是清晰一些了?
在 Go 语言中,interface{}代表空接口,任何类型都是它的实现类型。
这里的具体语法是interface{}(x),例如前面展示的interface{}(container)。
你可能会对这里的{}产生疑惑,为什么在关键字interface的右边还要加上这个东西?
请记住,一对不包裹任何东西的花括号,除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型)。
比如你今后肯定会遇到的struct{},它就代表了不包含任何字段和方法的、空的结构体类型。
而空接口interface{}则代表了不包含任何方法定义的、空的接口类型。
当然了,对于一些集合类的数据类型来说,{}还可以用来表示其值不包含任何元素,比如空的切片值[]string{},以及空的字典值map[int]string{}。
我们再向答案的最右边看。圆括号中[]string是一个类型字面量。所谓类型字面量,就是用来表示数据类型本身的若干个字符。
比如,string是表示字符串类型的字面量,uint8是表示 8 位无符号整数类型的字面量。
再复杂一些的就是我们刚才提到的[]string,用来表示元素类型为string的切片类型,以及map[int]string,用来表示键类型为int、值类型为string的字典类型。
还有更复杂的结构体类型字面量、接口类型字面量,等等。
针对当前的这个问题,我写了 demo12.go。它是 demo11.go 的修改版。我在其中分别使用了两种方式来实施类型断言,一种用的是我上面讲到的方式,另一种用的是我们还没讨论过的switch语句,先供你参考。
package main import ( "fmt" ) var container = []string{"zero", "one", "two"} func main() { container := map[int]string{0: "zero", 1: "one", 2: "two"} // 方式1。 _, ok1 := interface{}(container).([]string) _, ok2 := interface{}(container).(map[int]string) if !(ok1 || ok2) { fmt.Printf("Error: unsupported container type: %T\n", container) return } fmt.Printf("The element is %q. (container type: %T)\n", container[1], container) // 方式2。 elem, err := getElement(container) if err != nil { fmt.Printf("Error: %s\n", err) return } fmt.Printf("The element is %q. (container type: %T)\n", elem, container) } func getElement(containerI interface{}) (elem string, err error) { switch t := containerI.(type) { case []string: elem = t[1] case map[int]string: elem = t[1] default: err = fmt.Errorf("unsupported container type: %T", containerI) return } return }可以看到,当前问题的答案可以只有一行代码。你可能会想,这一行代码解释起来也太复杂了吧?