Go 类型转换和类型断言


类型转换

静态类型的语言都有类型转换 分为 显示类型转换 和 隐式类型转换
强制类型转换
f := 11.22
i := int(f)
float32 变量被赋值为11.22 现在想去掉小数部分 最简单的方法就是将 float32 转换为 int32
强制类型转换 从一个类型强制转换到另一个类型 适用于基本类型 如int float 类等
Go类型匹配相当严格 编译器不会帮你
比如 其它静态类型语言中 无问题的代码片段 在Go中编译期 会报错 Go会强制让做类型转换
var i int = 1
var f float64 = i
隐式类型转换 Go隐式类型转换主要存在于运行时
var w io.Writer = os.Stdout
将 *File 类型赋值给 io.Writer 类型 在运行时会做一个隐式的类型转换
类型转换 有些语言将类型转换给模糊了 大多数时候开发者并不需要去关注这方面的问题
但Go类型匹配是很严格 不同的类型之间 需要手动转换 编译器不会代你去做

类型断言

断言 是对一种条件进行假设 如果是 干嘛 如果不是又干嘛
类型断言就是对类型进行一种假设
Typescript 在TS里 很多时候会用到一种类型叫联合类型 联合类型 A | B 可以理解为 可以是A类型或者是B类型
let sanmao:Student | null //表示sanmao是一个Student或者null类型
需要使用sanmao的时候 可以使用类型断言
if(sanmao) zs = <Student>sanmao //或者
if(sanmao) zs = sanmao as Student
Go 类型断言和Typescript中的第二种类型断言比较相像 在Go中形如 x.(T)
x是一种接口类型 T可以是一种具体的类型 也可以是一种接口类型
Go为什么需要类型断言
Go 对类型的要求十分严格 且Go没有Typescript中的联合类型 一切类型都是固定不变的 有了强制转换类型
为什么还需要类型断言呢
Go 接口类型是能够隐式转换的
var w io.Writer = os.Stdout
w 类型为 io.Writer  它被赋值了*File  是Go帮助做了一次类型转换
这次类型转换是在运行时的 编译时并不能确定下来
在运行时 这个接口值的类型被赋值为了*File 值也被赋值为了os.Stdout
说明了 接口值的类型是不固定的!
因为类型是要在运行时才能确定下来  需要看它的动态值的类型才能确定 这就是需要类型断言的原因
var w io.Writer = os.Stdout 这条语句执行过后
w 拥有write方法 但是原本的*File不止拥有write方法 应该还拥有read方法
它也是io.ReadWriter接口的一个实例 如果这时候想使用read方法怎么办 那就需要类型断言了
rw := w.(io.ReadWriter)这里将w断言为ReadWriter类型 断言类型为一个接口  暴露了*File的read和write方法

类型断言的检查机制

Go 类型断言的检查机制 Go到底是如何来判断断言是否成功的
x.(T) 首先明确的是x必须为一个接口类型 而T可以是一个具体的类型也可以是一个接口类型
当T为一个接口类型时
首先会判断x的动态值是否符合T这个接口 如果符合的话 断言成功 反之断言失败
断言失败时会抛出一个panic异常 但是如果类型断言出现在一个预期有两个结果的赋值操作中 那么断言失败不会抛出panic异常 而是用一个bool值标识是否断言成功
var w io.Writer = os.Stdout
b, ok := w.(*bytes.Buffer)
为了健壮性 应该对ok返回的结果进行处理 标识是否断言成功
var w io.Writer = os.Stdout
if b, ok := w.(*bytes.Buffer);!ok {
fmt.Fprintf(os.Stderr, "断言失败")
} else {//TODO
}
对一个接口类型的类型断言改变了类型的表述方式 改变了可以获取的方法集合(通常更大) 但是它保护了接口值内部的动态类型和值的部分
当T为一个具体类型时
会先检查x的动态值的类型是否为T 如果为T则断言成功 如果不为T 则断言失败
具体类型的类型断言从它的操作对象中获得具体的值
当x为nil 不论断言类型是任何类型 都会断言失败

Go 所有类型都有自己的默认值

default_value.go代码如下:
package main
import ("fmt")
type myStruct struct {
    name   bool
    userid int64
}
var structZero myStruct
var intZero int
var int32Zero int32
var int64Zero int64
var uintZero uint
var uint8Zero uint8
var uint32Zero uint32
var uint64Zero uint64
var byteZero byte
var boolZero bool
var float32Zero float32
var float64Zero float64
var stringZero string
var funcZero func(int) int
var byteArrayZero [5]byte
var boolArrayZero [5]bool
var byteSliceZero []byte
var boolSliceZero []bool
var mapZero map[string]bool
var interfaceZero interface{}
var chanZero chan int
var pointerZero *int
func main() {
fmt.Println("structZero: ", structZero)
fmt.Println("intZero: ", intZero)
fmt.Println("int32Zero: ", int32Zero)
fmt.Println("int64Zero: ", int64Zero)
fmt.Println("uintZero: ", uintZero)
fmt.Println("uint8Zero: ", uint8Zero)
fmt.Println("uint32Zero: ", uint32Zero)
fmt.Println("uint64Zero: ", uint64Zero)
fmt.Println("byteZero: ", byteZero)
fmt.Println("boolZero: ", boolZero)
fmt.Println("float32Zero: ", float32Zero)
fmt.Println("float64Zero: ", float64Zero)
fmt.Println("stringZero: ", stringZero)
fmt.Println("funcZero: ", funcZero)
fmt.Println("funcZero == nil?", funcZero == nil)
fmt.Println("byteArrayZero: ", byteArrayZero)
fmt.Println("boolArrayZero: ", boolArrayZero)
fmt.Println("byteSliceZero: ", byteSliceZero)
fmt.Println("byteSliceZero's len?", len(byteSliceZero))
fmt.Println("byteSliceZero's cap?", cap(byteSliceZero))
fmt.Println("byteSliceZero == nil?", byteSliceZero == nil)
fmt.Println("boolSliceZero: ", boolSliceZero)
fmt.Println("mapZero: ", mapZero)
fmt.Println("mapZero's len?", len(mapZero))
fmt.Println("mapZero == nil?", mapZero == nil)
fmt.Println("interfaceZero: ", interfaceZero)
fmt.Println("interfaceZero == nil?", interfaceZero == nil)
fmt.Println("chanZero: ", chanZero)
fmt.Println("chanZero == nil?", chanZero == nil)
fmt.Println("pointerZero: ", pointerZero)
fmt.Println("pointerZero == nil?", pointerZero == nil)
}//output
structZero:  {false 0}
intZero:  0
int32Zero:  0
int64Zero:  0
uintZero:  0
uint8Zero:  0
uint32Zero:  0
uint64Zero:  0
byteZero:  0
boolZero:  false
float32Zero:  0
float64Zero:  0
stringZero:  
funcZero:  <nil>
funcZero == nil? true
byteArrayZero:  [0 0 0 0 0]
boolArrayZero:  [false false false false false]
byteSliceZero:  []
byteSliceZero's len? 0
byteSliceZero's cap? 0
byteSliceZero == nil? true
boolSliceZero:  []
mapZero:  map[]
mapZero's len? 0
mapZero == nil? true
interfaceZero:  <nil>
interfaceZero == nil? true
chanZero:  <nil>
chanZero == nil? true
pointerZero:  <nil>
pointerZero == nil? true
//各种类型的默认值
map interface pointer slice func chan 默认值和nil 相等
nil可以和什么样的类型做相等比较 nil可以赋值给哪些类型变量  就可以和哪些类型变量做相等比较
官方说明  //pkg.golang.org/pkg/builtin/#Type
nil只能赋值给 指针 channel func interface map或slice类型的变量 字面量的值 编译器有隐式转换
package main
import (
"fmt"
)
func main() {
var myInt int32     = 5
var myFloat float64 = 0
fmt.Println(myInt)
fmt.Println(myFloat)
}
对于myInt变量 存储的就是int32类型的5 对于myFloat变量 存储的是int64类型的0
func main() {
var uid int32 = 12345
var gid int64 = int64(uid)
fmt.Printf("uid=%d, gid=%d\n", uid, gid)
}将uid赋值给gid之前 需要将uid强制转换成int64类型 否则会panic
golang中的类型区分静态类型和底层类型
即使两个类型的底层类型相同 在相互赋值时 也需要强制类型转换 可以用reflect包中的Kind方法来查看相应类型的底层类型

类型转换截断

这里只考虑具有相同底层类型之间的类型转换 小类型(这里指存储空间)向大类型转换时 安全的
是一个大类型向小类型转换
func main() {
var gid int32 = 0x12345678
var uid int8  = int8(gid)
fmt.Printf("uid=0x%02x, gid=0x%02x\n", uid, gid)
}// gid为int32类型 也即占4个字节空间(在内存中占有4个存储单元) 因此这4个存储单元的值分别是 0x12  0x34 0x56 0x78
但事实不总是如此 这跟cpu架构有关
内存中的存储方式分为两种 大端序和小端序
大端序的存储方式是 高位字节存储在低地址上0x12, 0x34, 0x56, 0x78
小端序的存储方式是高位字节存储在高地址上0x78, 0x56, 0x34, 0x12
对于强制转换后的uid 产生了截断行为
因为uid只占1个字节 转换后的结果必然会丢弃掉多余的3个字节
截断的规则是 保留低地址上的数据 丢弃多余的高地址上的数据
判断是属于大端序或小端序
package main
import ("fmt")
func IsBigEndian() bool {
var i int32 = 0x12345678
var b byte  = byte(i)
if b == 0x12 {
        return true
}
return false
}
func main() {
if IsBigEndian() {
        fmt.Println("大端序")
} else {
        fmt.Println("小端序")
}}

接口的转换遵循规则

普通类型向接口类型的转换是隐式的
接口类型向普通类型转换需要类型断言
普通类型向接口类型转换的例子随处可见
func main() {
var val interface{} = "hello"
fmt.Println(val)//hello
val = []byte{'a', 'b', 'c'}
fmt.Println(val)//[97 98 99]
}
//"hello"作为string类型存储在interface{}类型的变量val中
[]byte{'a', 'b', 'c'}作为slice存储在interface{}类型的变量val中
这个过程是隐式的 是编译期确定的
接口类型向普通类型转换有两种方式
Comma-ok断言和switch测试
任何实现了接口I的类型都可以赋值给这个接口类型变量
由于interface{}包含了0个方法 所以任何类型都实现了interface{} 接口
这就是为什么可以将任意类型值赋值给interface{}类型的变量 包括nil
接口的实现
*T包含了定义在T和*T上的所有方法 而T只包含定义在T上的方法

package main
import (
    "fmt"
)     
type Speaker interface {// 演讲者接口
    Say(string)
    Listen(string) string
    Interrupt(string)
}
type Wang struct {    // 王兰讲师
    msg string
}
func (this *Wang) Say(msg string) {
    fmt.Printf("王兰说:%s\n", msg)
}
func (this *Wang) Listen(msg string) string {
    this.msg = msg
    return msg
}
func (this *Wang) Interrupt(msg string) {
    this.Say(msg)
}
type JiangLou struct {// 江娄讲师
    msg string
}
func (this *Jiang) Say(msg string) {
    fmt.Printf("江娄说:%s\n", msg)
}
func (this *Jiang) Listen(msg string) string {
    this.msg = msg
    return msg
}
func (this *Jiang) Interrupt(msg string) {
    this.Say(msg)
}
func main() {
    wl := &Wang{}
    jl := &Jiang{}
    var person Speaker
    person = wl
    person.Say("Hello World!")
    person = jl
    person.Say("Good Luck!")
}//Speaker接口有两个实现Wang类型和Jiang类型
但是具体到实例来说 变量wl和变量jl只有是对应实例的指针类型 才真正能被Speaker接口变量所持有
因为Wang类型和Jiang类型所有对Speaker接口的实现 都是在*T上 这就是上例中person能够持有wl和jl的原因
golang能将不同的类型存入到接口类型的变量中 使得问题变得复杂
所以有时候不得不面临这样一个问题 究竟往接口存入的是什么样的类型
反向查询
Comma-ok断言的语法是 value, ok := element.(T)
element必须是接口类型的变量 T是普通类型 如果断言失败 ok为false 否则ok为true并且value为变量的值
package main
import ("fmt")
type Html []interface{}
func main() {
    html := make(Html, 5)
    html[0] = "div"
    html[1] = "span"
    html[2] = []byte("script")
    html[3] = "style"
    html[4] = "head"
    for index, element := range html {
            if value, ok := element.(string); ok {
                    fmt.Printf("html[%d] is a string and its value is %s\n", index, value)
            } else if value, ok := element.([]byte); ok {
                    fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value))
            }
    }
}其实Comma-ok断言还支持另一种简化使用的方式 value := element.(T)
但不建议使用 因为一旦 element.(T)断言失败 则会产生运行时错误
package main
import ("fmt")
func main() {
    var val interface{} = "good"
    fmt.Println(val.(string)) // fmt.Println(val.(int))
}代码中被注释的那一行会运行时错误
这是因为val实际存储的是string类型 因此断言失败
还有一种转换方式是switch测试 这种转换方式只能出现在switch语句中
将刚才用Comma-ok断言的例子换成由switch测试来实现
package main
import ("fmt")
type Html []interface{}
func main() {
    html := make(Html, 5)
    html[0] = "div"
    html[1] = "span"
    html[2] = []byte("script")
    html[3] = "style"
    html[4] = "head"
    for index, element := range html {
            switch value := element.(type) {
            case string:
                    fmt.Printf("html[%d] is a string and its value is %s\n", index, value)
            case []byte:
                    fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value))
            case int:
                    fmt.Printf("error type\n")
            default:
                    fmt.Printf("unknown type\n")
            }
    }
}