golang: 类型转换和类型断言

类型转换在程序设计中都是不可避免的问题。当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关注这方面的问题。但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动转换,编译器不会代你去做这个事。我之所以说通常需要手动转换,是因为interface类型作为一个特例,会有不同的处理方式。

类型转换在程序设计中都是不可避免的问题。当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关注这方面的问题。但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动转换,编译器不会代你去做这个事。我之所以说通常需要手动转换,是因为interface类型作为一个特例,会有不同的处理方式。

golang中的所有类型都有自己的默认值,对此我做了个测试。

$GOPATH/src

----typeassert_test

--------main.go

main.go的代码如下:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 type myStruct struct {
08     name   bool
09     userid int64
10 }
11  
12 var structZero myStruct
13 var intZero int
14 var int32Zero int32
15 var int64Zero int64
16 var uintZero uint
17 var uint8Zero uint8
18 var uint32Zero uint32
19 var uint64Zero uint64
20 var byteZero byte
21 var boolZero bool
22 var float32Zero float32
23 var float64Zero float64
24 var stringZero string
25 var funcZero func(intint
26 var byteArrayZero [5]byte
27 var boolArrayZero [5]bool
28 var byteSliceZero []byte
29 var boolSliceZero []bool
30 var mapZero map[string]bool
31 var interfaceZero interface{}
32 var chanZero chan int
33 var pointerZero *int
34  
35 func main() {
36     fmt.Println("structZero: ", structZero)
37     fmt.Println("intZero: ", intZero)
38     fmt.Println("int32Zero: ", int32Zero)
39     fmt.Println("int64Zero: ", int64Zero)
40     fmt.Println("uintZero: ", uintZero)
41     fmt.Println("uint8Zero: ", uint8Zero)
42     fmt.Println("uint32Zero: ", uint32Zero)
43     fmt.Println("uint64Zero: ", uint64Zero)
44     fmt.Println("byteZero: ", byteZero)
45     fmt.Println("boolZero: ", boolZero)
46     fmt.Println("float32Zero: ", float32Zero)
47     fmt.Println("float64Zero: ", float64Zero)
48     fmt.Println("stringZero: ", stringZero)
49     fmt.Println("funcZero: ", funcZero)
50     fmt.Println("funcZero == nil?", funcZero == nil)
51     fmt.Println("byteArrayZero: ", byteArrayZero)
52     fmt.Println("boolArrayZero: ", boolArrayZero)
53     fmt.Println("byteSliceZero: ", byteSliceZero)
54     fmt.Println("byteSliceZero's len?", len(byteSliceZero))
55     fmt.Println("byteSliceZero's cap?", cap(byteSliceZero))
56     fmt.Println("byteSliceZero == nil?", byteSliceZero == nil)
57     fmt.Println("boolSliceZero: ", boolSliceZero)
58     fmt.Println("mapZero: ", mapZero)
59     fmt.Println("mapZero's len?", len(mapZero))
60     fmt.Println("mapZero == nil?", mapZero == nil)
61     fmt.Println("interfaceZero: ", interfaceZero)
62     fmt.Println("interfaceZero == nil?", interfaceZero == nil)
63     fmt.Println("chanZero: ", chanZero)
64     fmt.Println("chanZero == nil?", chanZero == nil)
65     fmt.Println("pointerZero: ", pointerZero)
66     fmt.Println("pointerZero == nil?", pointerZero == nil)
67 }

1 cd $GOPATH/src/typeassert_test
2 $ go build
3 $ ./typeassert_test

您可以清楚的了解到各种类型的默认值。如bool的默认值是false,string的默认值是空串,byte的默认值是0,数组的默认就是这个数组成员类型的默认值所组成的数组等等。然而您或许会发现在上面的例子中:map、interface、pointer、slice、func、chan的默认值和nil是相等的。关于nil可以和什么样的类型做相等比较,您只需要知道nil可以赋值给哪些类型变量,那么就可以和哪些类型变量做相等比较。官方对此有明确的说明:http://pkg.golang.org/pkg/builtin/#Type,也可以看我的另一篇文章:golang: 详解interface和nil。所以现在您应该知道nil只能赋值给指针、channel、func、interface、map或slice类型的变量。如果您用int类型的变量跟nil做相等比较,panic会找上您。

对于字面量的值,编译器会有一个隐式转换。看下面的例子:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 func main() {
08     var myInt int32     = 5
09     var myFloat float64 = 0
10     fmt.Println(myInt)
11     fmt.Println(myFloat)
12 }

对于myInt变量,它存储的就是int32类型的5;对于myFloat变量,它存储的是int64类型的0。或许您可能会写出这样的代码,但确实不是必须这么做的:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 func main() {
08     var myInt int32     = int32(5)
09     var myFloat float64 = float64(0)
10     fmt.Println(myInt)
11     fmt.Println(myFloat)
12 }

在C中,大多数类型转换都是可以隐式进行的,比如:

1 #include <stdio.h>
2  
3 int main(int argc, char **argv)
4 {
5         int uid  = 12345;
6         long gid = uid;
7         printf("uid=%d, gid=%d\n", uid, gid);
8         return 0;
9 }

但是在golang中,您不能这么做。有个类似的例子:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 func main() {
08     var uid int32 = 12345
09     var gid int64 = int64(uid)
10     fmt.Printf("uid=%d, gid=%d\n", uid, gid)
11 }

很显然,将uid赋值给gid之前,需要将uid强制转换成int64类型,否则会panic。golang中的类型区分静态类型和底层类型。您可以用type关键字定义自己的类型,这样做的好处是可以语义化自己的代码,方便理解和阅读。

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 type MyInt32 int32
08  
09 func main() {
10     var uid int32   = 12345
11     var gid MyInt32 = MyInt32(uid)
12     fmt.Printf("uid=%d, gid=%d\n", uid, gid)
13 }

在上面的代码中,定义了一个新的类型MyInt32。对于类型MyInt32来说,MyInt32是它的静态类型,int32是它的底层类型。即使两个类型的底层类型相同,在相互赋值时还是需要强制类型转换的。可以用reflect包中的Kind方法来获取相应类型的底层类型。

对于类型转换的截断问题,为了问题的简单化,这里只考虑具有相同底层类型之间的类型转换。小类型(这里指存储空间)向大类型转换时,通常都是安全的。下面是一个大类型向小类型转换的示例:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 func main() {
08     var gid int32 = 0x12345678
09     var uid int8  = int8(gid)
10     fmt.Printf("uid=0x%02x, gid=0x%02x\n", uid, gid)
11 }

在上面的代码中,gid为int32类型,也即占4个字节空间(在内存中占有4个存储单元),因此这4个存储单元的值分别是:0x12, 0x34, 0x56, 0x78。但事实不总是如此,这跟cpu架构有关。在内存中的存储方式分为两种:大端序和小端序。大端序的存储方式是高位字节存储在低地址上;小端序的存储方式是高位字节存储在高地址上。本人的机器是按小端序来存储的,所以gid在我的内存上的存储序列是这样的:0x78, 0x56, 0x34, 0x12。如果您的机器是按大端序来存储,则gid的存储序列刚好反过来:0x12, 0x34, 0x56, 0x78。对于强制转换后的uid,肯定是产生了截断行为。因为uid只占1个字节,转换后的结果必然会丢弃掉多余的3个字节。截断的规则是:保留低地址上的数据,丢弃多余的高地址上的数据。来看下测试结果:

1 cd $GOPATH/src/typeassert_test
2 $ go build
3 $ ./typeassert_test
4 uid=0x78, gid=0x12345678

如果您的输出结果是:

1 uid=0x12, gid=0x12345678

那么请不要惊讶,因为您的机器是属于大端序存储。

其实很容易根据上面所说的知识来判断是属于大端序或小端序:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 func IsBigEndian() bool {
08     var i int32 = 0x12345678
09     var b byte  = byte(i)
10     if b == 0x12 {
11         return true
12     }
13  
14     return false
15 }
16  
17 func main() {
18     if IsBigEndian() {
19         fmt.Println("大端序")
20     else {
21         fmt.Println("小端序")
22     }
23 }

1 cd $GOPATH/src/typeassert_test
2 $ go build
3 $ ./typeassert_test
4 小端序

接口的转换遵循以下规则:

  1. 普通类型向接口类型的转换是隐式的。

  2. 接口类型向普通类型转换需要类型断言。

普通类型向接口类型转换的例子随处可见,例如:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 func main() {
08     var val interface{} = "hello"
09     fmt.Println(val)
10     val = []byte{'a''b''c'}
11     fmt.Println(val)
12 }

正如您所预料的,"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上的方法。我们来看一个例子:

01 package main
02  
03 import (
04     "fmt"
05 )
06  
07 // 演讲者接口
08 type Speaker interface {
09     // 说
10     Say(string)
11     // 听
12     Listen(string) string
13     // 打断、插嘴
14     Interrupt(string)
15 }
16  
17 // 王兰讲师
18 type WangLan struct {
19     msg string
20 }
21  
22 func (this *WangLan) Say(msg string) {
23     fmt.Printf("王兰说:%s\n", msg)
24 }
25  
26 func (this *WangLan) Listen(msg string) string {
27     this.msg = msg
28     return msg
29 }
30  
31 func (this *WangLan) Interrupt(msg string) {
32     this.Say(msg)
33 }
34  
35 // 江娄讲师
36 type JiangLou struct {
37     msg string
38 }
39  
40 func (this *JiangLou) Say(msg string) {