非零基础自学Golang
文章目录
- 非零基础自学Golang
- 第12章 接口与类型
- 12.5 类型断言
- 12.5.1 ok-pattern
- 12.5.2 switch-type
第12章 接口与类型
12.5 类型断言
类型断言是使用在接口变量上的操作。
简单来说,接口类型向普通类型的转换就是类型断言。
类型断言的语法是:
t, ok := X.(T)
这里X表示一个接口变量,T表示一个类型(也可为接口类型),这句代码的含义是判断X的类型是否是T。
如果断言成功,则ok为true,t的值为接口变量X的动态值;如果断言失败,则t的值为类型T的初始值,t的类型始终为T。
举个例子:
[ 动手写12.5.1]
package main
import "fmt"
func checkType(t interface{}, ok bool) {
if ok {
fmt.Println("断言成功!")
} else {
fmt.Println("断言失败!")
}
fmt.Printf("变量t 的类型 = %T , 值 = %v\n", t, t)
}
func main() {
var X interface{} = 1
fmt.Println("第一次断言: ")
t0, ok := X.(string)
checkType(t0, ok)
fmt.Println("第二次断言:")
t1, ok := X.(float64)
checkType(t1, ok)
fmt.Println("第三次断言:")
t2, ok := X.(int)
checkType(t2, ok)
}
运行结果
动手写12.5.1中,我们定义了一个接口变量X,接口变量的动态类型为int,动态值为1。
第一次我们断言X的动态类型为string,断言失败,t0的类型为string,值为空字符串,所以这里没有打印出任何东西;断言类型为float64时,t1的值为0,也就是float64的初始值。
当我们断言为int时,断言成功,t2的类型变为int,值为接口变量X的动态值1。
类型断言分两种情况:
-
第一种,如果断言的类型T是一个具体类型,类型断言X.(T)就检查X的动态类型是否和T的类型相同。
如果这个检查成功了,类型断言的结果是一个类型为T的对象,该对象的值为接口变量X的动态值。换句话说,具体类型的类型断言从它的操作对象中获得具体的值。
如果检查失败,接下来这个操作会抛出panic,除非用两个变量来接收检查结果,如:f, ok := w.(*os.File)。
-
第二种,如果断言的类型T是一个接口类型,类型断言X.(T)检查X的动态类型是否满足T接口。
如果这个检查成功,则检查结果的接口值的动态类型和动态值不变,但是该接口值的类型被转换为接口类型T。换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。
如果检查失败,接下来这个操作会抛出panic,除非用两个变量来接收检查结果,如:f, ok := w.(io.ReadWriter)。
我们再来看第二种情况。
[ 动手写 12.5.2]
package main
import "fmt"
type Person interface {
Speak()
}
type Student struct {
Name string
}
func (s Student) Speak() {
fmt.Println("My name is ", s.Name)
}
func checkType(t interface{}, ok bool) {
if ok {
fmt.Println("断言成功!")
} else {
fmt.Println("断言失败!")
}
fmt.Printf("变量t 的类型 = %T , 值 = %v \n", t, t)
}
func main() {
var student interface{} = Student{"小明"}
fmt.Println("第一次断言: ")
t0, ok := student.(string)
checkType(t0, ok)
fmt.Println("第二次断言: ")
t1, ok := student.(Person)
checkType(t1, ok)
}
运行结果
动手写12.5.2创建了一个Person接口和实现该接口的Student结构体。使用Student结构体实例化了一个student对象,第一次断言student为string类型,断言失败,第二次断言为Person接口,断言成功,并打印输出了student的值。
12.5.1 ok-pattern
接口类型断言有两种方式,一种是ok-pattern,一种是switch-type。
一般来说,当要断言的接口类型种类较少时,可以使用ok-pattern这种方式,结构如下:
if value, ok := 接口变量.(类型); ok == true {
//接口变量是该类型时的处理
}
举个例子:
[ 动手写 12.5.3]
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
s := make([]interface{}, 3)
s[0] = 1
s[1] = "str"
s[2] = Person{"张三", 20}
for index, data := range s {
if value, ok := data.(int); ok == true {
fmt.Printf("s[%d] Type = int, Value = %d\n", index, value)
}
if value, ok := data.(string); ok == true {
fmt.Printf("s[%d] Type = string , Value = %s\n", index, value)
}
if value, ok := data.(Person); ok == true {
fmt.Printf("s[%d] Type = Person , Person.Name = %v, Person.Age = %d\n", index, value.Name, value.Age)
}
}
}
运行结果
动手写12.5.3创建了一个长度为3的接口数组,分别赋值不同的变量类型,使用ok-pattern模式进行判断。【懂了!!!】
12.5.2 switch-type
当要断言的接口类型种类较多时,使用ok-pattern方式就显得很累赘,代码非常冗余,这时我们可以使用switch-type来进行批量判断,switch-type的结构如下:
switch value := 接口变量.(type) {
case类型1:
// 接口变量是类型1时的处理
case类型2:
// 接口变量是类型2时的处理
case类型3:
// 接口变量是类型3时的处理
...
default:
// 接口变量不是所有case中所列举的类型时的处理
}
同样拿上面的数据来举个例子:
[ 动手写 12.5.4]
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
s := make([]interface{}, 3)
s[0] = 1
s[1] = "str"
s[2] = Person{"张三", 20}
for index, data := range s {
switch value := data.(type) {
case int:
fmt.Printf("s[%d] Type = int, Value = %d\n", index, value)
case string:
fmt.Printf("s[%d] Type = string , Value = %s\n", index, value)
case Person:
fmt.Printf("s[%d] Type = Person , Person.Name = %v, Person.Age = %d\n", index, value.Name, value.Age)
}
}
}
运行结果
动手写12.5.4使用switch-type模式进行类型断言,代码结构更加清晰,利于维护。