摘要

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

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

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

$GOPATH/src

----typeassert_test

--------main.go

main.go的代码如下:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type myStruct struct {
  6. name   bool
  7. userid int64
  8. }
  9. var structZero myStruct
  10. var intZero int
  11. var int32Zero int32
  12. var int64Zero int64
  13. var uintZero uint
  14. var uint8Zero uint8
  15. var uint32Zero uint32
  16. var uint64Zero uint64
  17. var byteZero byte
  18. var boolZero bool
  19. var float32Zero float32
  20. var float64Zero float64
  21. var stringZero string
  22. var funcZero func(int) int
  23. var byteArrayZero [5]byte
  24. var boolArrayZero [5]bool
  25. var byteSliceZero []byte
  26. var boolSliceZero []bool
  27. var mapZero map[string]bool
  28. var interfaceZero interface{}
  29. var chanZero chan int
  30. var pointerZero *int
  31. func main() {
  32. fmt.Println("structZero: ", structZero)
  33. fmt.Println("intZero: ", intZero)
  34. fmt.Println("int32Zero: ", int32Zero)
  35. fmt.Println("int64Zero: ", int64Zero)
  36. fmt.Println("uintZero: ", uintZero)
  37. fmt.Println("uint8Zero: ", uint8Zero)
  38. fmt.Println("uint32Zero: ", uint32Zero)
  39. fmt.Println("uint64Zero: ", uint64Zero)
  40. fmt.Println("byteZero: ", byteZero)
  41. fmt.Println("boolZero: ", boolZero)
  42. fmt.Println("float32Zero: ", float32Zero)
  43. fmt.Println("float64Zero: ", float64Zero)
  44. fmt.Println("stringZero: ", stringZero)
  45. fmt.Println("funcZero: ", funcZero)
  46. fmt.Println("funcZero == nil?", funcZero == nil)
  47. fmt.Println("byteArrayZero: ", byteArrayZero)
  48. fmt.Println("boolArrayZero: ", boolArrayZero)
  49. fmt.Println("byteSliceZero: ", byteSliceZero)
  50. fmt.Println("byteSliceZero's len?", len(byteSliceZero))
  51. fmt.Println("byteSliceZero's cap?", cap(byteSliceZero))
  52. fmt.Println("byteSliceZero == nil?", byteSliceZero == nil)
  53. fmt.Println("boolSliceZero: ", boolSliceZero)
  54. fmt.Println("mapZero: ", mapZero)
  55. fmt.Println("mapZero's len?", len(mapZero))
  56. fmt.Println("mapZero == nil?", mapZero == nil)
  57. fmt.Println("interfaceZero: ", interfaceZero)
  58. fmt.Println("interfaceZero == nil?", interfaceZero == nil)
  59. fmt.Println("chanZero: ", chanZero)
  60. fmt.Println("chanZero == nil?", chanZero == nil)
  61. fmt.Println("pointerZero: ", pointerZero)
  62. fmt.Println("pointerZero == nil?", pointerZero == nil)
  63. }
  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会找上您。

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var myInt int32     = 5
  7. var myFloat float64 = 0
  8. fmt.Println(myInt)
  9. fmt.Println(myFloat)
  10. }

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var myInt int32     = int32(5)
  7. var myFloat float64 = float64(0)
  8. fmt.Println(myInt)
  9. fmt.Println(myFloat)
  10. }

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

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

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var uid int32 = 12345
  7. var gid int64 = int64(uid)
  8. fmt.Printf("uid=%d, gid=%d\n", uid, gid)
  9. }

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type MyInt32 int32
  6. func main() {
  7. var uid int32   = 12345
  8. var gid MyInt32 = MyInt32(uid)
  9. fmt.Printf("uid=%d, gid=%d\n", uid, gid)
  10. }

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

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var gid int32 = 0x12345678
  7. var uid int8  = int8(gid)
  8. fmt.Printf("uid=%#x, gid=%#x\n", uid, gid)
  9. }

在上面的代码中,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

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

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func IsBigEndian() bool {
  6. var i int32 = 0x12345678
  7. var b byte  = byte(i)
  8. if b == 0x12 {
  9. return true
  10. }
  11. return false
  12. }
  13. func main() {
  14. if IsBigEndian() {
  15. fmt.Println("大端序")
  16. } else {
  17. fmt.Println("小端序")
  18. }
  19. }
  1. $ cd $GOPATH/src/typeassert_test
  2. $ go build
  3. ./typeassert_test
  4. 小端序

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

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

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

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var val interface{} = "hello"
  7. fmt.Println(val)
  8. val = []byte{'a', 'b', 'c'}
  9. fmt.Println(val)
  10. }

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

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. // 演讲者接口
  6. type Speaker interface {
  7. // 说
  8. Say(string)
  9. // 听
  10. Listen(string) string
  11. // 打断、插嘴
  12. Interrupt(string)
  13. }
  14. // 王兰讲师
  15. type WangLan struct {
  16. msg string
  17. }
  18. func (this *WangLan) Say(msg string) {
  19. fmt.Printf("王兰说:%s\n", msg)
  20. }
  21. func (this *WangLan) Listen(msg string) string {
  22. this.msg = msg
  23. return msg
  24. }
  25. func (this *WangLan) Interrupt(msg string) {
  26. this.Say(msg)
  27. }
  28. // 江娄讲师
  29. type JiangLou struct {
  30. msg string
  31. }
  32. func (this *JiangLou) Say(msg string) {
  33. fmt.Printf("江娄说:%s\n", msg)
  34. }
  35. func (this *JiangLou) Listen(msg string) string {
  36. this.msg = msg
  37. return msg
  38. }
  39. func (this *JiangLou) Interrupt(msg string) {
  40. this.Say(msg)
  41. }
  42. func main() {
  43. wl := &WangLan{}
  44. jl := &JiangLou{}
  45. var person Speaker
  46. person = wl
  47. person.Say("Hello World!")
  48. person = jl
  49. person.Say("Good Luck!")
  50. }

Speaker接口有两个实现WangLan类型和JiangLou类型。但是具体到实例来说,变量wl和变量jl只有是对应实例的指针类型才真正 能被Speaker接口变量所持有。这是因为WangLan类型和JiangLou类型所有对Speaker接口的实现都是在*T上。这就是上例中 person能够持有wl和jl的原因。

想象一下java的泛型(很可惜golang不支持泛型),java在支持泛型之前需要手动装箱和拆箱。由于golang能将不同的类型存入到接口 类型的变量中,使得问题变得更加复杂。所以有时候我们不得不面临这样一个问题:我们究竟往接口存入的是什么样的类型?有没有办法反向查询?答案是肯定的。

Comma-ok断言的语法是:value, ok := element.(T)。element必须是接口类型的变量,T是普通类型。如果断言失败,ok为false,否则ok为true并且value为变量的值。来看个例子:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Html []interface{}
  6. func main() {
  7. html := make(Html, 5)
  8. html[0] = "div"
  9. html[1] = "span"
  10. html[2] = []byte("script")
  11. html[3] = "style"
  12. html[4] = "head"
  13. for index, element := range html {
  14. if value, ok := element.(string); ok {
  15. fmt.Printf("html[%d] is a string and its value is %s\n", index, value)
  16. } else if value, ok := element.([]byte); ok {
  17. fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value))
  18. }
  19. }
  20. }

其实Comma-ok断言还支持另一种简化使用的方式:value := element.(T)。但这种方式不建议使用,因为一旦element.(T)断言失败,则会产生运行时错误。如:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var val interface{} = "good"
  7. fmt.Println(val.(string))
  8. // fmt.Println(val.(int))
  9. }

以上的代码中被注释的那一行会运行时错误。这是因为val实际存储的是string类型,因此断言失败。

还有一种转换方式是switch测试。既然称之为switch测试,也就是说这种转换方式只能出现在switch语句中。可以很轻松的将刚才用Comma-ok断言的例子换成由switch测试来实现:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Html []interface{}
  6. func main() {
  7. html := make(Html, 5)
  8. html[0] = "div"
  9. html[1] = "span"
  10. html[2] = []byte("script")
  11. html[3] = "style"
  12. html[4] = "head"
  13. for index, element := range html {
  14. switch value := element.(type) {
  15. case string:
  16. fmt.Printf("html[%d] is a string and its value is %s\n", index, value)
  17. case []byte:
  18. fmt.Printf("html[%d] is a []byte and its value is %s\n", index, string(value))
  19. case int:
  20. fmt.Printf("invalid type\n")
  21. default:
  22. fmt.Printf("unknown type\n")
  23. }
  24. }
  25. }
  1. $ cd $GOPATH/src/typeassert_test
  2. $ go build
  3. ./typeassert_test

golang type 和断言 interface{}转换的更多相关文章

  1. Go -- type 和断言 interface{}转换

    摘要 类型转换在程序设计中都是不可避免的问题.当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关 注这方面的问题.但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动转换,编 ...

  2. Golang 类型转换,断言和显式强制转换

    1 前言 类型转换,可以用断言(只能使用在interface{}类型转换成其它类型)和显式类型强制转换(常规是用于基本类型) 2 代码 //graphql-go func(params graphql ...

  3. Golang面向API编程-interface(接口)

    Golang面向API编程-interface(接口) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Golang并不是一种典型的面向对象编程(Object Oriented Pr ...

  4. golang 学习笔记 -- struct interface的使用

    一个 interface 类型定义了一个方法集做接口. 区分goalng的方法和函数 func go() { fmt.Println('go to home') } 这是函数 type car str ...

  5. is not valid JSON: json: cannot unmarshal string into Go value of type map[string]interface | mongodb在windows和Linux导出出错

    执行mongoexport命令的时候 mongoexport --csv -f externalSeqNum,paymentId --host 127.0.0.1:27017 -d liveX -c ...

  6. 工作随笔——Golang interface 转换成其他类型

    新的公司,新的氛围.一年了,打算写点什么.so,那就写google的golang语言吧. 最最最基础的语法结构见go语言菜鸟教程 接下来写点菜鸟教程没有的. go语言的设计者认为:go语言必须让程序员 ...

  7. golang学习笔记:Interface类型断言详情

    原文链接:https://www.2cto.com/kf/201712/703563.html 1. 用于判断变量类型 demo如下: switch t := var.(type){ case str ...

  8. golang type

    参考链接 https://blog.csdn.net/tzs919/article/details/53571632 type是golang中非常重要的关键字,常见的就是定义结构体,但是其功能远不止是 ...

  9. Golang之接口(interface)

    Golang最重要的接口,,,, package main import ( "fmt" ) //interface类型默认是指针 /* 接口的实现 Golang中的接口,不需要显 ...

随机推荐

  1. linux修改mac地址

    先禁用网卡ifconfig eth0 down 再用ifconfig eth0 hw ether 新地址 这样就可以了 要想永久修改的话,在/etc/rc.d/rc.local里加上下面三句(/etc ...

  2. 20150916_001 vba 基础

    一.什么是“宏”.“宏”有什么用 关于“宏”的详细定义,可以参考百度百科的解释(点击查看).我给它一个简单的或许不太严谨的定义: 宏的通俗定义:宏是被某些软件所能识别.理解并执行的特定代码/脚本. 宏 ...

  3. 敌兵布阵(线段树HDU 1166)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submissi ...

  4. JAVA基础知识之多线程——线程池

    线程池概念 操作系统或者JVM创建一个线程以及销毁一个线程都需要消耗CPU资源,如果创建或者销毁线程的消耗源远远小于执行一个线程的消耗,则可以忽略不计,但是基本相等或者大于执行线程的消耗,而且需要创建 ...

  5. Android图形系统之Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之间的联系

    1.Surface Surface extends Objectimplements Parcelable java.lang.Object    ↳ android.view.Surface Cla ...

  6. shell脚本之间互相调用

    在Shell中要如何调用别的shell脚本,或别的脚本中的变量,函数呢? 方法一: . ./subscript.sh 方法二: source ./subscript.sh 注意: .两个点之间,有空格 ...

  7. PowerDesigner连接SqlServer数据库

  8. windows api线程

    一.1.定义入口函数static void  threadFunc(void);//在TestDlg.h里面声明 void CTestDlg::threadFunc(void) //在TestDlg. ...

  9. mysql DATE_ADD DATE_SUB

    一.DATE_ADD() 函数向日期添加指定的时间间隔. DATE_ADD(date,INTERVAL expr type)date 参数是合法的日期表达式.expr 参数是您希望添加的时间间隔. t ...

  10. BZOJ 2584: [Wc2012]memory(扫描线+线段树)

    题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2584 题意:给出平面n个线段,任意两个线段严格不相交,且每个线段不平行于坐标轴.移 ...