Golang中四种类型转换详解
Go类型转换
Go 存在 4 种类型转换分别为:断言、强制、显式、隐式。
在日常开发中常说的类型转换是指断言,显示是基本的类型转换、隐式使用到但是不会注意到。
断言、强制、显式三类在 Go 语法描述中均有说明,隐式是在日常使用过程中总结出来。
断言类型转换
断言通过判断变量是否可以转换成某一个类型
类型断言
一个简单的断言表达式:s, ok := x.(T)
, 表达式是 Go
提供的一种带返回是否成立的断言语法.
- 如果
x
不是nil
,且x
可以转换成T
类型,就会断言成功,返回T
类型的变量s
。 - 如果
T
不是接口类型,则要求x
的类型就是T
,如果T
是一个接口,要求x
实现了T
接口。
返回值就是 T
类型的 x
,ok
就表示是否是成功了。
switch
Go
语法种还提供了另外一种类型 switch
的断言方法 x.(type)
x
断言成了 type
类型,type
类型具体值就是 switch case
的值,如果 x
成功断言成了某个 case
类型,就可以执行那个 case
,此时 i := x.(type)
返回的 i
就是那个类型的变量了,可以直接当作 case
类型使用。
switch i:= x.(type) {
case nil:
// xxxx
default:
// xxxx
}
强制类型转换
强制类型转换通过修改变量类型,该方法不常见,主要用于 unsafe
包和接口类型检测,需要懂得 go
变量的知识。
unsafe
unsafe.Pointer
其实就是一个*int
,一个通用型的指针。 下面是关于unsafe.Pointer
的4个规则。
- 任何指针都可以转换为
unsafe.Pointer
unsafe.Pointer
可以转换为任何指针uintptr
可以转换为unsafe.Pointer
unsafe.Pointer
可以转换为uintptr
unsafe
强制转换是指针的底层操作了,用 c
的朋友就很熟悉这样的指针类型转换,利用内存对齐才能保证转换可靠,例如 int
和 uint
存在符号位差别,利用 unsafe
转换后值可能不同,但是在内存存储二进制一模一样。
接口类型检测
例如下列代码:var _ Context = (*ContextBase)(nil)
nil
的类型是 nil
地址值为 0
,利用强制类型转换成了 * ContextBase
,返回的变量就是类型为 * ContextBase
地址值为 0
,然后 Context=xx
赋值如果 xx
实现了 Context
接口就没事,如果没有实现在编译时期就会报错,实现编译期间检测接口是否实现。
显示类型转换
一个显式转换的表达式 T (x)
,其中 T
是一种类型并且 x
是可转换为类型的表达式 T
,例如:uint(666)
。
在以下任何一种情况下,变量 x
都可以转换成 T
类型(逻辑来自 reflect.Value.Convert
方法逻辑):
x
可以分配成 T
类型。
忽略 struct
标签 x
的类型和 T
具有相同的基础类型。
忽略 struct
标记 x
的类型和 T
是未定义类型的指针类型,并且它们的指针基类型具有相同的基础类型。
x
的类型和 T
都是整数或浮点类型。
x
的类型和 T
都是复数类型。
x
的类型是整数或 [] byte
或 [] rune
,并且 T
是字符串类型。
x
的类型是字符串,T
类型是 [] byte
或 [] rune
。
例如下列代码利用了规则进行转换,规则实现可以参考
int64(222)
[]byte("ssss")
type A int
A(2)
隐式类型转换
隐式类型转换日常使用并不会感觉到,但是运行中确实出现了类型转换,以下列出了两种。
组合间的重新断言类型
type Reader interface {
Read(p []byte) (n int, err error)
}
type ReadClose interface {
Reader
Close() error
}
var rc ReadClose
r := rc
ReaderClose
接口组合了 Reader
接口,但是 r=rc
的赋值时还是类型转换了,Go
使用系统内置的函数执行了类型转换。以前遇到过类似接口组合类型的变量赋值,然后使用 pprof 和 bench 测试发现了这一细节,在接口类型转移时浪费了一些性能。
相同类型间赋值
type Handler func()
func NewHandler() Handler {
return func() {}
}
尽管 type
定义了 Handler
类型,但是 Handler
和 func ()
是两种实际类型,类型不会相等,使用反射和断言均会出现两种类型不同。
两者类型不同验证代码:
package main
import (
"fmt"
"reflect"
)
type Handler func()
func main() {
var i interface{} = main
_, ok := i.(func())
fmt.Println(ok) // true
_, ok = i.(Handler)
fmt.Println(ok) // false
fmt.Println(reflect.TypeOf(main) == reflect.TypeOf((*Handler)(nil)).Elem()) // false
}
Go语言程序中对指针获取反射对象时,可以通过 reflect.Elem()
方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*
操作。
(*Handler)(nil)
: 表示一个指向Handler
类型的空指针。reflect.TypeOf((*Handler)(nil)))
: 表示获得指针的类型,*main.Handler
reflect.TypeOf((*Handler)(nil))).Elem()
: 表示指针指向的值。结果为main.Hadler
func (v Value) Elem() Value
: 函数用于获取接口v
包含的值或指针v
指向的值。reflect.TypeOf(main)
: 结果为func()