本文介绍 类型转换(Conversion)、类型断言(type assertion) 和类型切换(type switch)。
这三个概念类似但是又完全不同。
将一个值x转换成特定类型T,格式为 T(x)
,非常的简单,类型加小括号即可。
如果类型T以 *、<-、func(不带结果列表),未避免造成歧义,需要将类型括号包裹起来: (T)(x)
:
*Point(p) // same as *(Point(p)) (*Point)(p) // p is converted to *Point <-chanint(c)// same as <-(chan int(c)) (<-chanint)(c)// c is converted to <-chan int func()(x)// function signature func() x (func())(x)// x is converted to func() (func()int)(x)// x is converted to func() int func()int(x)// x is converted to func() int (unambiguous)
并不是任意的值都可以转换成类型T, 它需要遵循一定的规则,下面一一道来。
对于一个常量值x, 如果能转换成T类型的值,它需要满足下面的条件之一:
uint(iota)// iota value of type uint float32(2.718281828)// 2.718281828 of type float32 complex128(1)// 1.0 + 0.0i of type complex128 float32(0.49999999)// 0.5 of type float32 float64(-1e-1000)// 0.0 of type float64 string('x')// "x" of type string string(0x266c)// "♬" of type string MyString("foo"+"bar")// "foobar" of type MyString string([]byte{'a'})// not a constant: []byte{'a'} is not a constant (*int)(nil)// not a constant: nil is not a constant, *int is not a boolean, numeric, or string type int(1.2)// illegal: 1.2 cannot be represented as an int string(65.0)// illegal: 65.0 is not an integer constant
对于一个常量值x, 如果能转换成T类型的值,它需要满足下面的条件之一:
数值类型和字符串之间的转换可能会改变x的呈现并且会带来运行时的花费。其它的转换只是改变x的类型,不会改变x的呈现。
并没有直接整数和指针之间的转换。但是在前面的章节中也举例了,指针可以通过曲折的方式转换成整数,
它是通过包 unsafe
实现的, 甚至于你通过这种方式还可以访问struct未导出的字段。
类型不一致的两个变量不能赋值, 并且也没有什么强制类型转换的概念:
vari1int8=10 vari2uint8= i1//错误 vari3uint8= (uint8)i1//错误
比如在类型那一章讲的例子,也是通过这种类型转换实现的:
x := [...]int{1,2,3,4,5} p := &x[0] //p = p + 1 index2Pointer := unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(x[0])) p = (*int)(index2Pointer)//x[1] fmt.Printf("%d/n", *p)//2
这一节介绍常见类型一致的转换。
非常量的数值之间的转换遵循下面三条原则:
1、整数之间的转换时,如果值是有符号的整数,它的符号位会扩展无限大,否则零扩展,然后它会被删减以适合结果类型。怎么理解,看例子。
对于无符号数v: v := uint16(0x10F0)
,如果进行转换 uint32(int8(v))
,可以看到它的结果是 0xFFFFFFF0
,不会有溢出指示或者错误。
v1 := uint16(0x10F0) fmt.Printf("%d=%b/n", v1, v1)//4336=1000011110000 v2 := int8(v1) fmt.Printf("%d=%b/n", v2, v2)//-16=-10000 v3 := uint16(v2) fmt.Printf("%d=%b/n", v3, v3)//65520=1111111111110000 v4 := int16(v2) fmt.Printf("%d=%b/n", v4, v4)//-16=-10000
介绍一下。 对于v1,它是一个无符号的整数, 要把它转为有符号的int8,那么我们只看v1的后8位:
不幸的是,这个8位的最高位是1,我们会把它作为符号位,所以v2是个负数,那么 11110000
就是这个负数的补码,
那么它的原码是多少呢,计算补码的补码就是负数的原码: 1001 0000
,所以它是-16。如果最高位是0,简单了,本身就是它的原码。
再看v2转v3, 也就是有符号整数转无符号整数。v2是负数,内部表示为 11110000
,因为要扩展为16位,将符号位1扩展到最高位 1111 1111 1111 0000
,因为它是无符号整数,所以这个值整数的值65520。
你可以把v1值的值改为0xff60看看输出是什么?此时转换不会符号位为负数的情况。
补码(two's complement) 指的是正数=原码,负数=反码加一
反码(ones' complement) 指的就是通常所指的反码。
对一个整数的补码再求补码,等于该整数自身。
补码的正零与负零表示方法相同。
2、浮点数转换成整数时,小数部分被丢弃,也就是朝0方向舍入。
varv1float32=0.999999 fmt.Println(int(v1)) v1 =-0.999999 fmt.Println(int(v1))
3、当转换整数或者浮点数到浮点数的时候,或者一个复数到另一个复数, 结果值会被舍入到目标类型的精度。例如类型为float32的变量x可以通过附加的精度超过标准的IEEE-754 32-bit数, 但是float32(x)代表x的值舍入到 IEEE-754 32 bit的精度。类似地, x + 0.1 可以使用超过32 bit的精度,但是float32(x + 0.1) 肯定是32 bit的精度。
the value of a variable x of type float32 may be stored using additional precision beyond that of an IEEE-754 32-bit number, but float32(x) represents the result of rounding x's value to 32-bit precision. Similarly, x + 0.1 may use more than 32 bits of precision, but float32(x + 0.1) does not.
关于浮点数格式IEEE-754, 随便一本计算机原理的教材中都会介绍,网上也有无数的文章介绍,它由三个域组成,float32中分别占1位、8位、和 23位,本文中就不详细介绍了。
虽然有人提议实现快速的整数和bool之间的转换,但是目前看起来还没有实现,所以下面的语句是不对的:
i1 :=1 i2 :=0 fmt.Printf("%t %t/n",bool(i1),bool(i2))
但是你完全可以通过其它方式实现, 比如判断语句 n > 0
, 或者利用一个定义好的表(map,数组等)进行查表转换。
参考
字符串代表一串字节流,所以很容易的和slice of byte, slice of rune进行转换。
1、无符号整数或者有符号整数通过它对应的UTF-8编码转换成字符串。合法的Unicode code之外的值都被转换成 /uFFFD
。这里的整数也包含rune.
string('a')// "a" string(-1)// "/ufffd" == "/xef/xbf/xbd" string(0xf8)// "/u00f8" == "ø" == "/xc3/xb8" typeMyStringstring MyString(0x65e5)// "/u65e5" == "日" == "/xe6/x97/xa5"
2、字节slice根据UTF-8编码产生字符串
string([]byte{'h','e','l','l','/xc3','/xb8'})// "hellø" string([]byte{})// "" string([]byte(nil))// ""
3、将rune slice转换成字符串相当于将rune连接起来
string([]rune{0x9E1F,0x7A9D})// "/u9e1f/u7a9d" == "鸟窝" string([]rune{})// "" string([]rune(nil))// ""
4、将字符串转为byte slice会将字符串的字节流复制到一个byte slice
5、将一个字符串转为rune slice会将产生一个新的rune slice,包含字符串中每个rune
包strconv提供了字符串和基本数据类型的转换。上面我们提到了字符串和整数之间的转换,但是有时候我们需要的是将 12转换成字符串 "12",或者从字符串中解析处一个整数,这个时候就可以使用这个包。
首先它提供了一组往byte slice增加基本类型元素的方法:
funcAppendBool(dst []byte, bbool) []byte funcAppendFloat(dst []byte, ffloat64, fmtbyte, prec, bitSizeint) []byte funcAppendInt(dst []byte, iint64, baseint) []byte funcAppendQuote(dst []byte, sstring) []byte funcAppendQuoteRune(dst []byte, rrune) []byte funcAppendQuoteRuneToASCII(dst []byte, rrune) []byte funcAppendQuoteRuneToGraphic(dst []byte, rrune) []byte funcAppendQuoteToASCII(dst []byte, sstring) []byte funcAppendQuoteToGraphic(dst []byte, sstring) []byte funcAppendUint(dst []byte, iuint64, baseint) []byte
一组从字符串中解析出基本类型的方法:
funcParseBool(strstring) (valuebool, err error) funcParseFloat(sstring, bitSizeint) (ffloat64, err error) funcParseInt(sstring, baseint, bitSizeint) (iint64, err error) funcParseUint(sstring, baseint, bitSizeint) (nuint64, err error)
一组为字符串或者rune加引号和剥离引号的方法:
funcQuote(sstring)string funcQuoteRune(rrune)string funcQuoteRuneToASCII(rrune)string funcQuoteRuneToGraphic(rrune)string funcQuoteToASCII(sstring)string funcQuoteToGraphic(sstring)string funcUnquote(sstring) (tstring, err error) funcUnquoteChar(sstring, quotebyte) (valuerune, multibytebool, tailstring, err error)
一组检查字符串或者rune为特定类型的方法:
funcCanBackquote(sstring)bool funcIsGraphic(rrune)bool funcIsPrint(rrune)bool
一组格式化基本类型为字符串的方法:
funcFormatBool(bbool)string funcFormatFloat(ffloat64, fmtbyte, prec, bitSizeint)string funcFormatInt(iint64, baseint)string funcFormatUint(iuint64, baseint)string
重要的放在最后说,我们在编程中更多的用到的两个方法, 整数字面值和字符串之间的转换:
funcAtoi(sstring) (iint, err error) funcItoa(iint)string
参考
包 encoding/binary实现了数值和字节序列之间的转换,包含变长int的各种编解码。
Go中的数值类型都是固定长度的位数(int8, uint8, int16, float32, complex64),所以组成这些数组的bit可以转换成各种字节slice。
变长int (varint)经常用于节省空间,比如一个, Go实现的varint规范可以参考 proto-buff的实现 。很多编解码库中都使用了变长的int,这样对于大量的小数字我们可以用更少的字节来表示,对于网络传输来说很有好处。
这个包经常用在网络传输的序列化和反序列中。
另外一个值得注意的是数值是由多个字节组成的,这就涉及到字节序的问题,你必须指定使用小端序或大端序。
首先看一下定长的数值的转换,主要是 Read
和 Write
两个方法,底层还是通过移位操作实现的。
funcRead(r io.Reader, order ByteOrder, datainterface{}) error funcWrite(w io.Writer, order ByteOrder, datainterface{}) error
例子:
packagemain import( "bytes" "encoding/binary" "fmt" ) funcmain() { b := write() read(b) } funcwrite() []byte{ buf := new(bytes.Buffer) vardata = []interface{}{ uint16(61374),//efbe int8(-54),//-36 uint8(254),//fe } for_, v :=rangedata { err := binary.Write(buf, binary.BigEndian, v) iferr !=nil{ fmt.Println("binary.Write failed:", err) } } fmt.Printf("%x/n", buf.Bytes())//efbecafe returnbuf.Bytes() } funcread(b []byte) { vari1uint16 vari2int8 vari3uint8 buf := bytes.NewReader(b) err := binary.Read(buf, binary.BigEndian, &i1) iferr !=nil{ fmt.Println("binary.Read failed:", err) } err = binary.Read(buf, binary.BigEndian, &i2) iferr !=nil{ fmt.Println("binary.Read failed:", err) } err = binary.Read(buf, binary.BigEndian, &i3) iferr !=nil{ fmt.Println("binary.Read failed:", err) } fmt.Println(i1, i2, i3) //61374 -54 254 }
一种不通用的适合特定类型的转换也可以使用下面的方法:
funcreadInt32(b []byte)int32{ // equivalnt of return int32(binary.LittleEndian.Uint32(b)) returnint32(uint32(b[0]) |uint32(b[1])<<8|uint32(b[2])<<16|uint32(b[3])<<24) } //或者 funcReadInt32Unsafe(b []byte)int32{ return*(*int32)(unsafe.Pointer(&b[0])) }
变长int的操作函数:
funcPutUvarint(buf []byte, xuint64)int funcPutVarint(buf []byte, xint64)int funcUvarint(buf []byte) (uint64,int) funcVarint(buf []byte) (int64,int) funcReadUvarint(r io.ByteReader) (uint64, error) funcReadVarint(r io.ByteReader) (int64, error)
以及一个对象被转换成多少字节的方法:
funcSize(vinterface{})int
参考
数组转换成slice很简单,前面讲到了,利用索引运算:
a[low:high] a[low:high:max]
而slice转数组,我们可以好好分析一下。
slice的底层实现是数组,所以有一个"hack"方法,将slice的底层数组返回:
import( "reflect" "unsafe" ) constSIZEOF_INT32 =4// bytes // Get the slice header header := *(*reflect.SliceHeader)(unsafe.Pointer(&raw)) // The length and capacity of the slice are different. header.Len /= SIZEOF_INT32 header.Cap /= SIZEOF_INT32 // Convert slice header to an []int32 data := *(*[]int32)(unsafe.Pointer(&header))
安全的方式是生成数组然后依次赋值,注意copy是不行的,因为copy的参数必须都是slice:
import"encoding/binary" constSIZEOF_INT32 =4// bytes data := make([]int32,len(raw)/SIZEOF_INT32) fori :=rangedata { // assuming little endian data[i] = int32(binary.LittleEndian.Uint32(raw[i*SIZEOF_INT32:(i+1)*SIZEOF_INT32])) }
参考
struct类型的值和字符串之间的转换我们称之为marshal和unmarshal。有非常多的库可以做这个事情,比如gob, encoding/json等。
Go序列化框架的性能比较可以参照我的一个开源项目: gosercomp 。
和上节的类型转换不同,类型断言是将接口类型的值x,转换成类型T。
格式为:
x.(T) v := x.(T) v, ok := x.(T)
类型断言的必要条件是x是接口类型,非接口类型的x不能做类型断言:
variint=10 v := i.(int)//错误
T可以是非接口类型,如果想断言合法,则T应该实现x的接口。
T也可以是接口,则x的动态类型也应该实现接口T。
varxinterface{} =7// x 的动态类型为int, 值为 7 i := x.(int)// i 的类型为 int, 值为 7 typeIinterface{ m() } vary I s := y.(string)// 非法: string 没有实现接口 I (missing method m) r := y.(io.Reader) // y如果实现了接口io.Reader和I的情况下, r的类型则为io.Reader
类型断言如果非法,运行时时候就会出现 impossible type assertion panic,为了避免这种情况,可以使用下面的语法:
v, ok = x.(T) v, ok := x.(T) varv, ok = x.(T)
ok代表类型断言是否合法,如果非法ok =false,v为T的零值,这样就不会出现运行时panic了。
希望你能记住,类型转换和类型断言完全是两个概念。
类型切换(暂且这么翻译吧,英语更准确)用来比较类型而不是对值进行比较。
switch语句虽然在下一章中去讲,但是对于读者来说,多少会一种或者几种常用的编程语言,switch是一个条件语句,它可以判断某个值是否匹配某个case clause。但是对于type switch,它检查的是值x的类型T是否匹配某个类型。
格式如下,类型类型断言,但是括号内的不是某个具体的类型,而是单词 type
:
switchx.(type) { // cases }
type switch语句中可以有一个简写的变量声明,这种情况下,等价于这个变量声明在每个case clause隐式代码块的开始位置。如果case clause只列出了一个类型,则变量的类型就是这个类型,否则就是原始值的类型。
假设下面的例子中x的类型为x interface{}
switchi := x.(type) { casenil: printString("x is nil")// i的类型是 x的类型 (interface{}) caseint: printInt(i) // i的类型 int casefloat64: printFloat64(i) // i的类型是 float64 casefunc(int)float64: printFunction(i) // i的类型是 func(int) float64 casebool,string: printString("type is bool or string")// i的类型是 x (interface{}) default: printString("don't know the type")// i的类型是 x的类型 (interface{}) }
也许你已经看到上面的例子中有一个case clause中的类型是nil,它用来匹配x为nil的interface{}的情况。