隐型转换

转换是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在受限情况下实现此功能。

数值类型之间的转换

非常量的数值之间的转换遵循下面三条原则:

  1. 整数之间的转换时,如果值是有符号的整数,它的符号位会扩展无限大,否则零扩展,然后它会被删减以适合结果类型。对于无符号数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位:

    1
    
    1111 0000
    

    不幸的是,这个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) 指的就是通常所指的反码。
    对一个整数的补码再求补码,等于该整数自身。
    补码的正零与负零表示方法相同。
    
  2. 浮点数转换成整数时,小数部分被丢弃,也就是朝0方向舍入。

    1
    2
    3
    4
    
    var v1 float32 = 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的精度。

    关于浮点数格式IEEE-754, 它由三个域组成,float32中分别占1位、8位、和 23位.

在涉及浮点值或复数值的所有非常量转换中,如果结果类型无法表示转换成功的值,但结果值与实现有关。

字符串变量的转换

字符串代表一串字节流,所以很容易的和slice of byte, slice of rune进行转换。

  1. 将有符号或无符号整数值转换为字符串类型会生成包含整数的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"
```
  1. 将一个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ø"
    
  2. 将一个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" == "白鵬翔"
    
  3. 将字符串类型的值转换为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'}
    
  4. 将字符串类型的值转换为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.Itoa(i)

等价于

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

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