在Go语言中,类型断言(Type assertions)和类型转换(Type conversions)似乎是一个令人困惑的主题,因为它们看起来都做同样的事情。
在这篇文章中,我们将看到断言和转换实际上是很不同的,还会深入了解在Go中使用它们时发生了什么。
首先,让我们看看,它们是什么样子的……
这是一个 Go 中的类型断言:
var greeting interface{} = "hello world"
greetingStr := greeting.(string)
这是一个类型转换:
greeting := []byte("hello world")
greetingStr := string(greeting)
最明显的区别是它们具有不同的语法(variable.(type)
vs type(variable)
)。让我们详细地看看每种情况。
类型断言
正如其名称所示,类型断言用于断言某个变量是某种类型。类型断言只能在接口上进行。
在我们的类型断言示例中,greeting
是一个 interface{}
类型,我们赋了一个字符串。现在,我们可以说 greeting
实际上是一个字符串,但对于我们暴露的接口是 interface{}
。
如果我们想要获取 greeting
的原始类型,我们可以断言它是一个字符串,这个断言返回它的原始字符串类型。
这意味着在进行类型断言时,我们应该知道任何变量的底层类型,但并不总是如此。这就是为什么类型断言表达式实际上会返回第二个可选值的原因:
var greeting interface{} = "42"
greetingStr, ok := greeting.(string)
第二个值 ok
是一个布尔值,如果我们的断言正确,则为 true
;否则为 false
。
这也意味着类型断言是在运行时执行的。
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")
}
为什么它是一个断言?
在上面的示例中,似乎你正在将 greeting
的类型从 interface{}
转换为 int
或 string
。但是,greeting
的类型是固定的,并且与初始化期间声明的类型相同。
当您将 greeting
分配给接口类型时,您不会更改其底层类型。同样,当您断言其类型时,您只是使用整个原始类型的功能,而不是接口所公开的方法。
类型转换
首先,让我们花一点时间了解“类型”实际上是什么。Go中的每种类型都定义了两个东西:
- 变量如何存储(底层数据结构)
- 可以对变量执行什么操作(可以用于其中的方法和函数)
有一些基本类型,其中包括字符串和int。还有复合类型,它包括结构体、map、数组和切片。
您可以从基本类型或通过创建复合类型来声明新类型:
// 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())
}
当我们声明 myInt
类型时,我们基于基本的 int
类型定义了变量数据结构,但是更改了我们可以使用 myInt
类型变量的方式(通过在其上声明一个新方法)。
由于 int
和 myInt
的底层数据结构相似,因此这些类型的变量可以相互转换:
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)" 会导致编译错误
fmt.Println(bob, babyBob)
}
在这里,person
和 child
具有相同的数据结构,即:
struct {
name string
age int
}
因此它们可以相互转换。
具有不同底层数据结构的类型无法相互转换。
可以使用缩写来声明具有相同数据结构的多个类型:
type pet person
这意味着 child
基于与 person
相同的数据结构(类似于我们之前的整数示例)。
总结
类型断言和类型转换之间的差别不仅仅是语法上的差异。这还强调了Go中接口类型和非接口(或具体)类型之间的差异。
接口类型没有任何底层数据结构,而是公开现有具体类型的一些方法(具有底层数据结构)。
类型断言会将底层具体类型暴露出来,而类型转换则会更改您可以在具有相同数据结构的两个具体类型之间使用变量的方式。同时,类型转换还会在尝试将其转换为错误的类型时给出编译错误,而不是像类型断言一样在运行时出现错误和可选的 ok 返回值。