golang 类型断言 VS 类型转换
golang 中类型断言和类型转换两个概念很容易困惑,它们看上去提供了相同的功能(把变量从一个类型转到类型)。但是 golang 为什么会有两个功能相似的概念呢?那么在本文中,我们将了解类型断言和类型转换本质的区别,并深入了解在 go 中使用它们会发生什么?
首先让我们来看一下在 go 中如何使用它们:
类型断言
var greeting interface{} = "hello world" greetingStr := greeting.(string)
类型转换
greeting := []byte("hello world") greetingStr := string(greeting)
最明显的不同是它们有着不同的语法: variable.(type) vs type(variable)。
类型断言
类型断言顾名思义,它是用来断言变量具有哪些类型的。类型断言只能作用在接口上。在上面类型断言的例子中 greeting 就是一个接口(interface{})类型,我们给他分配了字符串(string)类型的变量,那么我们现在可以说greeting具有了字符串类型,虽然greeting依然是接口类型,但是我们可以通过类型断言获取字符串类型:
注意:这里我们要考虑一个问题:在使用类型断言时是否一定要提前知道接口变量的原始类型呢?如果我们不知道原始类型,或者接口变量不具备我们想目标类型会有什么结果呢?
我们来关注一下类型断言的另外一种用法:
var greeting interface{} = "42" greetingStr, ok := greeting.(string)
这种用法,类型断言有两个返回值,第一个值ok时bool类型,当断言成功时返回true,否则返回false。如果我们采用第一中写法(只有一个返回值),在发生断言错误时会抛出panic。
注意:上面这些表明了类型断言是在程序运行时执行的。
类型断言之 type switch
当我们不确定一个接口的底层变量类型时,这时候 type switch 就是一个很好的语法结构
var greeting interface{} = 42 switch g := greeting.(type) { case string: fmt.Println("g is a string with length", len(g)) case int: fmt.Println("g is an integer, whose value is", g) default: fmt.Println("I don't know what g is") }
什么是断言
上面的例子中看上去我们是把变量greeing从interface{}类型转为了string或者int,但是实际上greeing的类型是固定的,依然是它初始化时的类型。将greeing分配给接口类型时,不会更改其基础类型。 同样的,当我们断言它的类型时,我们只是在使用整个原始类型的功能,而不是接口暴露的有限方法(函数)。
类型转换
在讲解类型转换前,首先让我们来理解一下什么是类型?golang 中类型定义了两个事情:
- 数据结构:变量在底层以什么形式存储
- 行为:变量具有哪些方法或者说是函数
golang 中变量类型可以有两类:基础类型和混合类型,基础类型有 string,int 等等;混合类型包含:struct,map,slice 等。
基于基础类型我们还可以声明一个新的类型:
// myInt 是一个新的变量类型,它的基础类型是 int type myInt int // 函数 AddOne 工作在 myInt 上,和 int 没有任何关系 func (i myInt) AddOne() myInt { return i + 1} func main() { var i myInt = 4 fmt.Println(i.AddOne()) }
我们基于基础类型int声明了一个新的类型myInt,它们底层的数据结构都是一样的,但是myInt 多了一个函数 AddOne。因为它们的底层类型是一样的,我们可以使用类型转换,把变量从一种类型转为另一种类型
var i myInt = 4 originalInt := int(i)
上面i的类型是 myint,originalInt 的类型是 int。
什么时候我们可以使用类型转换?
只有当两个类型的底层数据结构相同的时候,才可以使用类型转换,在做类型转换时不会检查类型的方法。让我们来看一个结构体的例子:
type person struct { name string age int } type child struct { name string age int } type pet { name string } func main() { bob := person{ name: "bob", age: 15, } babyBob := child(bob) // "babyBob := pet(bob)" would result in a compilation error fmt.Println(bob, babyBob) }
上面代码中person和child有着相同的底层数据结构:
struct { name string age int }
它们就可以相互进行类型转换
如果像上面这种声明多个具有相同的底层数据结构的结构体类型,可以有一个简洁的写法:
type person struct { name string age int } type child person
为什么叫做类型转换
正如上面提到的不同的类型,在其上的限制和方法是不同的,即使它们有着相同的底层数据结构。在我们使用 golang 的类型转换时我们改变的是这个变量的行为(因为不同的类型有不同的函数),而不是仅仅暴露这个变量的原始类型(后者是类型断言做的事)。另外,如果两个类型不能相互转换,golang 会在编译时报错,不像类型断言时在运行时报错。
总结
类型转换和类型断言不仅仅是语法上的不同,通过这两个概念进一步强调了 golang 中接口类型和非接口类型(具体类型)不同。接口类型没有底层数据结构,它仅仅是暴露了预先定义在具体类型中的方法。类型断言是获取隐藏在接口类型变量之下的具体类型,类型转换是改变我们操作变量底层数据的方法。