golang 类型断言自定义类型 可以断言吗

golang: 类型转换和类型断言
golang: 类型转换和类型断言
golang: 类型转换和类型断言
发表于12天前( 11:17)&&
阅读(564)&|&评论()
17人收藏此文章,
类型转换在程序设计中都是不可避免的问题。当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关注这方面的问题。但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动转换,编译器不会代你去做这个事。我之所以说通常需要手动转换,是因为interface类型作为一个特例,会有不同的处理方式。
类型转换在程序设计中都是不可避免的问题。当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关注这方面的问题。但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动转换,编译器不会代你去做这个事。我之所以说通常需要手动转换,是因为interface类型作为一个特例,会有不同的处理方式。
golang中的所有类型都有自己的默认值,对此我做了个测试。
$GOPATH/src
----typeassert_test
--------main.go
main.go的代码如下:
01package&main02&03import&(04&&&&"fmt"05)06&07type&myStruct&struct&{08&&&&name&&&bool09&&&&userid&int6410}11&12var&structZero&myStruct13var&intZero&int14var&int32Zero&int3215var&int64Zero&int6416var&uintZero&uint17var&uint8Zero&uint818var&uint32Zero&uint3219var&uint64Zero&uint6420var&byteZero&byte21var&boolZero&bool22var&float32Zero&float3223var&float64Zero&float6424var&stringZero&string25var&funcZero&func(int)&int26var&byteArrayZero&[5]byte27var&boolArrayZero&[5]bool28var&byteSliceZero&[]byte29var&boolSliceZero&[]bool30var&mapZero&map[string]bool31var&interfaceZero&interface{}32var&chanZero&chan&int33var&pointerZero&*int34&35func&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_test2$&go&build3$&./typeassert_test
您可以清楚的了解到各种类型的默认值。如bool的默认值是false,string的默认值是空串,byte的默认值是0,数组的默认就是这个数组成员类型的默认值所组成的数组等等。然而您或许会发现在上面的例子中:map、interface、pointer、slice、func、chan的默认值和nil是相等的。关于nil可以和什么样的类型做相等比较,您只需要知道nil可以赋值给哪些类型变量,那么就可以和哪些类型变量做相等比较。官方对此有明确的说明:,也可以看我的另一篇文章:。所以现在您应该知道nil只能赋值给指针、channel、func、interface、map或slice类型的变量。如果您用int类型的变量跟nil做相等比较,panic会找上您。
对于字面量的值,编译器会有一个隐式转换。看下面的例子:
01package&main02&03import&(04&&&&"fmt"05)06&07func&main()&{08&&&&var&myInt&int32&&&&&=&509&&&&var&myFloat&float64&=&010&&&&fmt.Println(myInt)11&&&&fmt.Println(myFloat)12}
对于myInt变量,它存储的就是int32类型的5;对于myFloat变量,它存储的是int64类型的0。或许您可能会写出这样的代码,但确实不是必须这么做的:
01package&main02&03import&(04&&&&"fmt"05)06&07func&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&3int&main(int&argc,&char&**argv)4{5&&&&&&&&int&uid&&=&12345;6&&&&&&&&long&gid&=&7&&&&&&&&printf("uid=%d,&gid=%d\n",&uid,&gid);8&&&&&&&&return&0;9}
但是在golang中,您不能这么做。有个类似的例子:
01package&main02&03import&(04&&&&"fmt"05)06&07func&main()&{08&&&&var&uid&int32&=&1234509&&&&var&gid&int64&=&int64(uid)10&&&&fmt.Printf("uid=%d,&gid=%d\n",&uid,&gid)11}
很显然,将uid赋值给gid之前,需要将uid强制转换成int64类型,否则会panic。golang中的类型区分静态类型和底层类型。您可以用type关键字定义自己的类型,这样做的好处是可以语义化自己的代码,方便理解和阅读。
01package&main02&03import&(04&&&&"fmt"05)06&07type&MyInt32&int3208&09func&main()&{10&&&&var&uid&int32&&&=&1234511&&&&var&gid&MyInt32&=&MyInt32(uid)12&&&&fmt.Printf("uid=%d,&gid=%d\n",&uid,&gid)13}
在上面的代码中,定义了一个新的类型MyInt32。对于类型MyInt32来说,MyInt32是它的静态类型,int32是它的底层类型。即使两个类型的底层类型相同,在相互赋值时还是需要强制类型转换的。可以用reflect包中的Kind方法来获取相应类型的底层类型。
对于类型转换的截断问题,为了问题的简单化,这里只考虑具有相同底层类型之间的类型转换。小类型(这里指存储空间)向大类型转换时,通常都是安全的。下面是一个大类型向小类型转换的示例:
01package&main02&03import&(04&&&&"fmt"05)06&07func&main()&{08&&&&var&gid&int32&=&0x09&&&&var&uid&int8&&=&int8(gid)10&&&&fmt.Printf("uid=%#x,&gid=%#x\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_test2$&go&build3$&./typeassert_test4uid=0x78,&gid=0x
如果您的输出结果是:
1uid=0x12,&gid=0x
那么请不要惊讶,因为您的机器是属于大端序存储。
其实很容易根据上面所说的知识来判断是属于大端序或小端序:
01package&main02&03import&(04&&&&"fmt"05)06&07func&IsBigEndian()&bool&{08&&&&var&i&int32&=&0x09&&&&var&b&byte&&=&byte(i)10&&&&if&b&==&0x12&{11&&&&&&&&return&true12&&&&}13&14&&&&return&false15}16&17func&main()&{18&&&&if&IsBigEndian()&{19&&&&&&&&fmt.Println("大端序")20&&&&}&else&{21&&&&&&&&fmt.Println("小端序")22&&&&}23}
1$&cd&$GOPATH/src/typeassert_test2$&go&build3$&./typeassert_test4小端序
接口的转换遵循以下规则:
普通类型向接口类型的转换是隐式的。接口类型向普通类型转换需要类型断言。
普通类型向接口类型转换的例子随处可见,例如:
01package&main02&03import&(04&&&&"fmt"05)06&07func&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上的方法。我们来看一个例子:
01package&main02&03import&(04&&&&"fmt"05)06&07//&演讲者接口08type&Speaker&interface&{09&&&&//&说10&&&&Say(string)11&&&&//&听12&&&&Listen(string)&string13&&&&//&打断、插嘴14&&&&Interrupt(string)15}16&17//&王兰讲师18type&WangLan&struct&{19&&&&msg&string20}21&22func&(this&*WangLan)&Say(msg&string)&{23&&&&fmt.Printf("王兰说:%s\n",&msg)24}25&26func&(this&*WangLan)&Listen(msg&string)&string&{27&&&&this.msg&=&msg28&&&&return&msg29}30&31func&(this&*WangLan)&Interrupt(msg&string)&{32&&&&this.Say(msg)33}34&35//&江娄讲师36type&JiangLou&struct&{37&&&&msg&string38}39&40func&(this&*JiangLou)&Say(msg&string)&{41&&&&fmt.Printf("江娄说:%s\n",&msg)42}43&44func&(this&*JiangLou)&Listen(msg&string)&string&{45&&&&this.msg&=&msg46&&&&return&msg47}48&49func&(this&*JiangLou)&Interrupt(msg&string)&{50&&&&this.Say(msg)51}52&53func&main()&{54&&&&wl&:=&&WangLan{}55&&&&jl&:=&&JiangLou{}56&57&&&&var&person&Speaker58&&&&person&=&wl59&&&&person.Say("Hello&World!")60&&&&person&=&jl61&&&&person.Say("Good&Luck!")62}
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为变量的值。来看个例子:
01package&main02&03import&(04&&&&"fmt"05)06&07type&Html&[]interface{}08&09func&main()&{10&&&&html&:=&make(Html,&5)11&&&&html[0]&=&"div"12&&&&html[1]&=&"span"13&&&&html[2]&=&[]byte("script")14&&&&html[3]&=&"style"15&&&&html[4]&=&"head"16&&&&for&index,&element&:=&range&html&{17&&&&&&&&if&value,&ok&:=&element.(string);&ok&{18&&&&&&&&&&&&fmt.Printf("html[%d]&is&a&string&and&its&value&is&%s\n",&index,&value)19&&&&&&&&}&else&if&value,&ok&:=&element.([]byte);&ok&{20&&&&&&&&&&&&fmt.Printf("html[%d]&is&a&[]byte&and&its&value&is&%s\n",&index,&string(value))21&&&&&&&&}22&&&&}23}
其实Comma-ok断言还支持另一种简化使用的方式:value := element.(T)。但这种方式不建议使用,因为一旦element.(T)断言失败,则会产生运行时错误。如:
01package&main02&03import&(04&&&&"fmt"05)06&07func&main()&{08&&&&var&val&interface{}&=&"good"09&&&&fmt.Println(val.(string))10&&&&//&fmt.Println(val.(int))11}
以上的代码中被注释的那一行会运行时错误。这是因为val实际存储的是string类型,因此断言失败。
还有一种转换方式是switch测试。既然称之为switch测试,也就是说这种转换方式只能出现在switch语句中。可以很轻松的将刚才用Comma-ok断言的例子换成由switch测试来实现:
01package&main02&03import&(04&&&&"fmt"05)06&07type&Html&[]interface{}08&09func&main()&{10&&&&html&:=&make(Html,&5)11&&&&html[0]&=&"div"12&&&&html[1]&=&"span"13&&&&html[2]&=&[]byte("script")14&&&&html[3]&=&"style"15&&&&html[4]&=&"head"16&&&&for&index,&element&:=&range&html&{17&&&&&&&&switch&value&:=&element.(type)&{18&&&&&&&&case&string:19&&&&&&&&&&&&fmt.Printf("html[%d]&is&a&string&and&its&value&is&%s\n",&index,&value)20&&&&&&&&case&[]byte:21&&&&&&&&&&&&fmt.Printf("html[%d]&is&a&[]byte&and&its&value&is&%s\n",&index,&string(value))22&&&&&&&&case&int:23&&&&&&&&&&&&fmt.Printf("invalid&type\n")24&&&&&&&&default:25&&&&&&&&&&&&fmt.Printf("unknown&type\n")26&&&&&&&&}27&&&&}28}
1$&cd&$GOPATH/src/typeassert_test2$&go&build3$&./typeassert_test
发表评论:
馆藏&21221
TA的最新馆藏微信号:callme_hr
扫码加一览职业生涯导师微信好友
深圳市一览网络股份有限公司(股票代码:833680)
版权所有 &更多公众号:GolangwebGo 语言社区专业的Go语言开发社区,除此之外我们还研发了Go 社区APP,让用户随时随地可以了解Go语言的行业动态。最新文章相关作者文章搜狗:感谢您阅读golang:interface{}类型测试,本文由网友投稿产生,如果侵犯了您的相关权益,请联系管理员。

我要回帖

更多关于 go 类型断言 的文章

 

随机推荐