Golang中四种类型转换详解

Go类型转换

Go 存在 4 种类型转换分别为:断言、强制、显式、隐式。

在日常开发中常说的类型转换是指断言,显示是基本的类型转换、隐式使用到但是不会注意到。

断言、强制、显式三类在 Go 语法描述中均有说明,隐式是在日常使用过程中总结出来。

断言类型转换

断言通过判断变量是否可以转换成某一个类型

类型断言

一个简单的断言表达式:s, ok := x.(T), 表达式是 Go 提供的一种带返回是否成立的断言语法.

  • 如果 x 不是 nil,且 x 可以转换成 T 类型,就会断言成功,返回 T 类型的变量 s
  • 如果 T 不是接口类型,则要求 x 的类型就是 T,如果 T 是一个接口,要求 x 实现了 T 接口。

返回值就是 T 类型的 xok 就表示是否是成功了。

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 的朋友就很熟悉这样的指针类型转换,利用内存对齐才能保证转换可靠,例如 intuint 存在符号位差别,利用 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 类型,但是 Handlerfunc () 是两种实际类型,类型不会相等,使用反射和断言均会出现两种类型不同

两者类型不同验证代码:

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()
关于我
loading