隐型转换
转换是T(x)形式的表达式,其中T是类型,x是可以转换为类型T的表达式。
1
|
Conversion = Type "(" Expression [ "," ] ")" .
|
如果类型以operator *或<- 开头,或者类型以关键字func开头并且没有结果列表,则必须在必要时将其括起来以避免歧义:
1
2
3
4
5
6
7
8
|
*Point(p) // same as *(Point(p))
(*Point)(p) // p is converted to *Point
<-chan int(c) // same as <-(chan int(c))
(<-chan int)(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)
|
如果x可由T表示,则常数值x可以转换为类型T.作为特殊情况,可以使用与非常数x相同的规则将整数常量x转换为字符串类型。
转换常量会产生类型常量作为结果。
1
2
3
4
5
6
7
8
9
10
11
12
|
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可赋予T.
- 忽略struct标签(见下文),x的类型和T具有相同的底层类型。
- 忽略struct标签(见下文),x类型和T都是未命名的指针类型,它们的指针指向的对象类型一致
- x的类型和T都是整数或浮点类型。
- x的类型和T都是复杂类型。
- x是整数或slice of byte、slice of rune,T是字符串类型。
- x是字符串,T是slice of byte、slice of rune
为了转换的目的,在比较结构类型的标识时,会忽略结构标记:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
type Person struct {
Name string
Address *struct {
Street string
City string
}
}
var data *struct {
Name string `json:"name"`
Address *struct {
Street string `json:"street"`
City string `json:"city"`
} `json:"address"`
}
var person = (*Person)(data) // ignoring tags, the underlying types are identical
|
特定规则适用于数字类型之间或字符串类型之间的(非常量)转换。这些转换可能会更改x的表示形式并产生运行时成本。所有其他转换仅更改类型,但不更改x的表示。
没有语言机制来在指针和整数之间进行转换。软件包unsafe在受限情况下实现此功能。
数值类型之间的转换
非常量的数值之间的转换遵循下面三条原则:
-
整数之间的转换时,如果值是有符号的整数,它的符号位会扩展无限大,否则零扩展,然后它会被删减以适合结果类型。对于无符号数v: v := uint16(0x10F0),如果进行转换uint32(int8(v)),可以看到它的结果是0xFFFFFFF0,不会有溢出指示或者错误。
1
2
3
4
5
6
7
8
|
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看看输出是什么?此时转换不会符号位为负数的情况。
1
2
3
4
|
补码(two's complement) 指的是正数=原码,负数=反码加一
反码(ones' complement) 指的就是通常所指的反码。
对一个整数的补码再求补码,等于该整数自身。
补码的正零与负零表示方法相同。
|
-
浮点数转换成整数时,小数部分被丢弃,也就是朝0方向舍入。
1
2
3
4
|
var v1 float32 = 0.999999
fmt.Println(int(v1))
v1 = -0.999999
fmt.Println(int(v1))
|
-
转换整数或者浮点数到浮点数的时候,或者一个复数到另一个复数, 结果值会被舍入到目标类型的精度。例如类型为float32的变量x可以通过附加的精度超过标准的IEEE-754 32-bit数, 但是float32(x)代表x的值舍入到 IEEE-754 32 bit的精度。类似地, x + 0.1 可以使用超过32 bit的精度,但是float32(x + 0.1) 肯定是32 bit的精度。
关于浮点数格式IEEE-754, 它由三个域组成,float32中分别占1位、8位、和 23位.

在涉及浮点值或复数值的所有非常量转换中,如果结果类型无法表示转换成功的值,但结果值与实现有关。
字符串变量的转换
字符串代表一串字节流,所以很容易的和slice of byte, slice of rune进行转换。
- 将有符号或无符号整数值转换为字符串类型会生成包含整数的UTF-8表示形式的字符串。超出有效Unicode代码点范围的值将转换为“\uFFFD”。
字符’�’的 Unicode 代码点是U+FFFD。它是 Unicode 标准中定义的 Replacement Character,专用于替换那些未知的、不被认可的以及无法展示的字符。
```go
string('a') // "a"
string(-1) // "\ufffd" == "\xef\xbf\xbd"
string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8"
type MyString string
MyString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
```
-
将一个byte切片转换为字符串类型会产生一个字符串,根据UTF-8编码产生字符串,其连续字节是切片的元素。
1
2
3
4
5
6
|
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"
string([]byte{}) // ""
string([]byte(nil)) // ""
type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø"
|
-
将一个rune切片转换为字符串类型会产生一个字符串,该字符串是转换为字符串的各个rune值的串联。
1
2
3
4
5
6
|
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{}) // ""
string([]rune(nil)) // ""
type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
|
-
将字符串类型的值转换为byte切片类型会生成一个切片,其连续元素是字符串的字节。
1
2
3
4
|
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("") // []byte{}
MyBytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
|
-
将字符串类型的值转换为rune类型切片会生成包含字符串的各个Unicode代码点的切片。
1
2
3
4
|
[]rune(MyString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("") // []rune{}
MyRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
|
stirng与int,float,bool的转换
int与string
int转string
等价于
1
|
s := strconv.FormatInt(int64(i), 10)
|
string转int
1
|
i, err := strconv.Atoi(s)
|
int64与string
int64转string
1
2
|
i := int64(123)
s := strconv.FormatInt(i, 10)
|
第二个参数为基数,可选2~36
注:对于无符号整形,可以使用FormatUint(i uint64, base int)
string转int64
1
|
err := strconv.ParseInt(s, 10, 64)
|
第二个参数为基数(2~36),第三个参数位大小表示期望转换的结果类型,其值可以为0, 8, 16, 32和64,分别对应 int, int8, int16, int32和int64
float与string
float转string
sprintf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Desired output:
// "1.9"
// "10.9"
// "100.9"
fmt.Println("2g:")
fmt.Println(fmt.Sprintf("%.2g", 1.900)) // outputs "1.9"
fmt.Println(fmt.Sprintf("%.2g", 10.900)) // outputs "11"
fmt.Println(fmt.Sprintf("%.2g", 100.900)) // outputs "1e+02"
fmt.Println("\n2f:")
fmt.Println(fmt.Sprintf("%.2f", 1.900)) // outputs "1.90"
fmt.Println(fmt.Sprintf("%.2f", 10.900)) // outputs "10.90"
fmt.Println(fmt.Sprintf("%.2f", 100.900)) // outputs "100.90"
|
使用2g格式化的问题是,当整数增加数量级时,它会开始舍入.此外,它有时会显示带有e的数字.
使用2f格式化会出现显示尾随零的问题.
如何将将float格式化为n个小数位并且没有尾随零呢?我们需要FormatFloat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// FormatFloat 将浮点数 f 转换为字符串形式
// f:要转换的浮点数
// fmt:格式标记(b、e、E、f、g、G)
// prec:精度(数字部分的长度,不包括指数部分)
// bitSize:指定浮点类型(32:float32、64:float64),结果会据此进行舍入。
//
// 格式标记:
// 'b' (-ddddp±ddd,二进制指数)
// 'e' (-d.dddde±dd,十进制指数)
// 'E' (-d.ddddE±dd,十进制指数)
// 'f' (-ddd.dddd,没有指数)
// 'g' ('e':大指数,'f':其它情况)
// 'G' ('E':大指数,'f':其它情况)
//
// 如果格式标记为 'e','E'和'f',则 prec 表示小数点后的数字位数
// 如果格式标记为 'g','G',则 prec 表示总的数字位数(整数部分+小数部分)
// -1 代表输出的精度小数点后的位数,如果是<0的值,则返回最少的位数来表示该数,如果是大于0的则返回对应位数的值
// 参考格式化输入输出中的旗标和精度说明
func FormatFloat(f float64, fmt byte, prec, bitSize int) string
|
1
|
strconv.FormatFloat(10.900,’f’, – 1,64)
|
这将导致10.9.
-1作为第三个参数告诉函数打印准确表示浮点数所需的最少数字.
转化效率对比
需求
浮点数取2位精度输出
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package main
import (
"time"
"log"
"strconv"
"fmt"
)
func main() {
threadCount := 100000
fa := 3.233667
time1 := TestFn(threadCount,fa,func(fa float64) string{
return strconv.FormatFloat(fa,'f',2,64)
})
log.Printf("FormatFloat 耗时:%.4f ms",time1)
time2 := TestFn(threadCount,fa,func(fa float64) string{
return fmt.Sprintf("%.2f",fa)
})
log.Printf("Sprintf 耗时:%.4f ms",time2)
}
func TestFn(count int,fa float64,fn func(fa float64) string) float64{
t1 := time.Now()
for i := 0; i < count; i++ {
fn(fa)
}
t2 := time.Now()
return t2.Sub(t1).Seconds()*1000
}
|
效率对比
测试 100次 (即threadCount赋值100,下面同理)
2017/06/15 18:50:17 FormatFloat 耗时:0.0452 ms
2017/06/15 18:50:17 Sprintf 耗时:0.0512 ms
测试 1000次
2017/06/15 18:50:43 FormatFloat 耗时:0.3861 ms
2017/06/15 18:50:43 Sprintf 耗时:0.4903 ms
测试 10000次
2017/06/15 18:50:58 FormatFloat 耗时:3.9688 ms
2017/06/15 18:50:58 Sprintf 耗时:5.2045 ms
测试 100000次
2017/06/15 18:51:20 FormatFloat 耗时:41.9253 ms
2017/06/15 18:51:20 Sprintf 耗时:51.8639 ms
测试 10000000次
2017/06/15 18:51:49 FormatFloat 耗时:3917.7585 ms
2017/06/15 18:51:54 Sprintf 耗时:5131.5497 ms
结论
strconv 包的函数不需要格式化,比 fmt 包的函数更加高效。
strconv下的FormatFloat明显快一些。fmt.Sprintf用到反射,效率不高,建议少用。
string转float
1
|
func ParseFloat(s string, bitSize int) (f float64, err error)
|
解析一个表示浮点数的字符串并返回其值。
如果s合乎语法规则,函数会返回最为接近s表示值的一个浮点数(使用IEEE754规范舍入)。bitSize指定了期望的接收类型,32是float32(返回值可以不改变精确值的赋值给float32),64是float64;返回值err是*NumErr类型的,语法有误的,err.Error=ErrSyntax;结果超出表示范围的,返回值f为±Inf,err.Error= ErrRange。
string转float64 这里有两种方法,都支持指定精度。 注意:所有数字要在表现层显示最好转换为字符串传送给表现层,如果用于后端计算则转换为数字即可。比如:数字2.10 如果用保持5位数字精度显示: 那么 数字2.10 显示为:2.1, 而将2.10转换为字符串同时保持5位精度,则显示为: 2.10000。但是它们都是转换为了5位精度的,只是显示的时候,数字2.10000 直接显示为2.1了, 所以要显示精度则转换为字符串,要用于计算则转换为数字。
方法1: 只支持指定精度
1
2
3
4
5
6
7
|
func strToFloat64(str string, len int) float64 {
lenstr := "%." + strconv.Itoa(len) + "f"
value,_ := strconv.ParseFloat(str,64)
nstr := fmt.Sprintf(lenstr,value)
val,_ := strconv.ParseFloat(nstr,64)
return val
}
|
方法2:支持指定精度,支持是否四舍五入
1
2
3
4
5
6
7
8
9
10
11
12
|
func strToFloat64Round(str string, prec int, round bool) float64 {
f,_ := strconv.ParseFloat(str,64)
return Precision(f,prec,round)
}
func Precision(f float64, prec int, round bool) float64 {
pow10_n := math.Pow10(prec)
if round {
return math.Trunc(f + 0.5/pow10_n)*pow10_n) / pow10_n
}
return math.Trunc((f)*pow10_n) / pow10_n
}
|
bool与string
bool转string
1
2
3
4
5
6
|
func FormatBool(b bool) string
// 将字符串转换为布尔值
// 它接受真值:1, t, T, TRUE, true, True
// 它接受假值:0, f, F, FALSE, false, False
// 其它任何值都返回一个错误。
|
string转bool
1
|
func ParseBool(str string) (bool, error)
|
bytes与float64
bytes转float64
1
2
3
4
|
func bytesToFloat64(bytes []byte) float64 {
bits := binary.LittleEndian.Uint64(bytes)
return math.Float64frombits(bits)
}
|
float64转bytes
1
2
3
4
5
6
|
func float64ToBytes(input float64) []byte {
bits := math.Float64bits(input)
bytes := make([]byte,8) //这里表示[]uint8, 所以用了 8
binary.LittleEndian.PutUint64(bytes,bits)
return bytes
}
|
结构体互相转换
基本上只有两个完全一样的struct(就是结构体的名字不同,成员定义、顺序、包括tag都得一样)才可以直接转换,转换直接t2 := Test2(t1)这样就可以(t1是Test1类型)
前提是Test1, Test2符合转换条件,哪怕tag定义不一样都不行的,比如下面这样都不能转换的,编译会报错。
1
2
3
4
5
6
7
8
|
type Test1 struct {
Age int `json:"age"`
Name string
}
type Test1 struct {
Age int
Name string
}
|
数组与切片的转换
数组转切片
利用索引运算
1
2
|
a[low:high]
a[low:high:max]
|
切片转数组
slice的底层实现是数组,所以有一个"hack"方法,将slice的底层数组返回:
1
2
3
4
5
6
7
8
9
10
11
12
|
import (
"reflect"
"unsafe"
)
const SIZEOF_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:
1
2
3
4
5
6
7
|
import "encoding/binary"
const SIZEOF_INT32 = 4 // bytes
data := make([]int32, len(raw)/SIZEOF_INT32)
for i := range data {
// assuming little endian
data[i] = int32(binary.LittleEndian.Uint32(raw[i*SIZEOF_INT32:(i+1)*SIZEOF_INT32]))
}
|
struct和字符串之间的转换
struct类型的值和字符串之间的转换我们称之为marshal和unmarshal。
有非常多的库可以做这个事情,比如gob, encoding/json等。
Java字符串和Go字符串之间的转换
Java字符串在内部是以UTF-16编码方式存在的,每个字符包含两个字节。而Go字符串在内部是以UTF-8格式存在的,每个字符串占用的字节数可能不同。
可以使用unicode包进行转换,或者使用unicode/utf16
字节slice和整数之间的转换
包 encoding/binary实现了数值和字节序列之间的转换,包含变长int的各种编解码。
Go中的数值类型都是固定长度的位数(int8, uint8, int16, float32, complex64),所以组成这些数组的bit可以转换成各种字节slice。
变长int (varint)经常用于节省空间,比如一个, Go实现的varint规范可以参考proto-buff的实现。很多编解码库中都使用了变长的int,这样对于大量的小数字我们可以用更少的字节来表示,对于网络传输来说很有好处。
这个包经常用在网络传输的序列化和反序列中。
另外一个值得注意的是数值是由多个字节组成的,这就涉及到字节序的问题,你必须指定使用小端序或大端序。
首先看一下定长的数值的转换,主要是Read和Write两个方法,底层还是通过移位操作实现的。
1
2
|
func Read(r io.Reader, order ByteOrder, data interface{}) error
func Write(w io.Writer, order ByteOrder, data interface{}) error
|
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
func main() {
b := write()
read(b)
}
func write() []byte {
buf := new(bytes.Buffer)
var data = []interface{}{
uint16(61374), //efbe
int8(-54), //-36
uint8(254), //fe
}
for _, v := range data {
err := binary.Write(buf, binary.BigEndian, v)
if err != nil {
fmt.Println("binary.Write failed:", err)
}
}
fmt.Printf("%x\n", buf.Bytes()) //efbecafe
return buf.Bytes()
}
func read(b []byte) {
var i1 uint16
var i2 int8
var i3 uint8
buf := bytes.NewReader(b)
err := binary.Read(buf, binary.BigEndian, &i1)
if err != nil {
fmt.Println("binary.Read failed:", err)
}
err = binary.Read(buf, binary.BigEndian, &i2)
if err != nil {
fmt.Println("binary.Read failed:", err)
}
err = binary.Read(buf, binary.BigEndian, &i3)
if err != nil {
fmt.Println("binary.Read failed:", err)
}
fmt.Println(i1, i2, i3) //61374 -54 254
}
|
一种不通用的适合特定类型的转换也可以使用下面的方法:
1
2
3
4
5
6
7
8
|
func readInt32(b []byte) int32 {
// equivalnt of return int32(binary.LittleEndian.Uint32(b))
return int32(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
}
//或者
func ReadInt32Unsafe(b []byte) int32 {
return *(*int32)(unsafe.Pointer(&b[0]))
}
|
变长int的操作函数:
1
2
3
4
5
6
|
func PutUvarint(buf []byte, x uint64) int
func PutVarint(buf []byte, x int64) int
func Uvarint(buf []byte) (uint64, int)
func Varint(buf []byte) (int64, int)
func ReadUvarint(r io.ByteReader) (uint64, error)
func ReadVarint(r io.ByteReader) (int64, error)
|
以及一个对象被转换成多少字节的方法:
1
|
func Size(v interface{}) int
|
参考:https://colobu.com/2016/06/21/dive-into-go-6/#%E6%95%B0%E5%80%BC%E7%B1%BB%E5%9E%8B%E4%B9%8B%E9%97%B4%E7%9A%84%E8%BD%AC%E6%8D%A2