要理解类型转换(type conversion),那么首先要知道有哪些类型,请先阅读这篇文章 [go 数据系统]()。
知道了 go 中数据类型有哪些,我们来大概了解一下数据类型的转换有些什么不同,我们知道 go 是静态的强类型语言,也就是说声明变量的时候需要字母数据类型,虽然我们有 :=
这种语法糖,但实际上编译器自动确定了类型。还有不同变量间需要手动进行转换。
虽然 go 是静态语言,但 go 中也有 interface 这样的动态类型,因此设计它的转换比较特殊,需要单独讲解。
基本数据类型转换
数字类型间的转换
数字类型包括 int 系和 float 系,它们间可以直接转换,只需要注意的是,高精度转换为低精度会丢失精度。
语法:varOfTypeB = typeB(varOfTypeA)
,即 B 类型变量 = 类型B( A 类型变量)
如
int 转 int32, int64
var a int = 10
// int 型
fmt.Printf("var a int = 10, a :%v\n", reflect.TypeOf(a))
// int 型转 int32
fmt.Printf("int32(a), a :%v\n", reflect.TypeOf(int32(a)))
// int 型转 int64
fmt.Printf("int64(a), a :%v\n", reflect.TypeOf(int64(a)))
int32, int64 转 int
var a int32 = 10
// int32 型
fmt.Printf("var a int32 = 10, a :%v\n", reflect.TypeOf(a))
var b int64 = 20
// int64 型
fmt.Printf("var b int64 = 20, b :%v\n", reflect.TypeOf(b))
// int32 转 int, 容易精度丢失
fmt.Printf("int(a), a: %v\n", reflect.TypeOf(int(a)))
// int64 转 int, 容易精度丢失
fmt.Printf("int(b), b: %v\n", reflect.TypeOf(int(b)))
float 转 int
var a float32 = 10
// float32 型
fmt.Printf("var a float32 = 10, a : %v\n", reflect.TypeOf(a))
// float32 转 int
fmt.Printf("int(a), a : %v\n", reflect.TypeOf(int(a)))
数字类型和 string 间的相互转换
- int, int32, int64 -> string
数字类型和 string 的底层类型不同,因此不能直接使用 T(X) 格式强制转换,需要借助 go 内置的一些函数
strconv 包就是用于 string 转换相关的,里面有两个函数比较常用 Atoi 和 Itoa,我们知道,计算机中一般使用 char array 存储 string, 所以 Atoi 就是 array to int 的缩写,也就是 string 转 int, 同理 Itoa 即 int to array ,即 int 转 string
我们来看 Itoa 的源码
func Itoa(i int) string {
return FormatInt(int64(i), 10)
}
我们发现 Itoa 函数传入一个 int 型,返回 string 型,就是把 int 型转为 string 型,我们刚刚已经知道,int 型间可以直接转换的,所以如果数值不是很大的话,我们可以把 int32,int64 均转为 int,再用 Itoa 函数转换,自然 int8, in16 比 int 精度还小,没什么问题,
我们来把低精度的 int, in32, int64 转为 string
var a int = 10
fmt.Printf("var a int = 10, a := %v\n", reflect.TypeOf(a))
fmt.Printf("strconv.Itoa(a), a : %v\n", reflect.TypeOf(strconv.Itoa(a)))
var b int32 = 10
fmt.Printf("var b int32 = 20, b := %v\n", reflect.TypeOf(b))
fmt.Printf("strconv.Itoa(int(b)), b : %v\n", reflect.TypeOf(strconv.Itoa(int(b))))
var c int64 = 10
fmt.Printf("var c int64 = 30, c := %v\n", reflect.TypeOf(c))
fmt.Printf("strconv.Itoa(int(c)), c : %v\n", reflect.TypeOf(strconv.Itoa(int(c))))
当然,这种方法只适用于精度不大的情况,如果精度大的话,会有精度损失,我们如果细心一点,会发现 Itoa 的源码中,直接返回了FormatInt(int64(i), 10),我们发现它传入了一个 int64 型,显然低精度到高精度不会有精度损失,又是我们自然想到可能可以用这个函数,我们来看 FormatInt 的源码
func FormatInt(i int64, base int) string {
if fastSmalls && 0 <= i && i < nSmalls && base == 10 {
return small(int(i))
}
_, s := formatBits(nil, uint64(i), base, i < 0, false)
return s
}
我们发现此函数传入了一个 int64 和 int,返回一个 stirng,也就是把 int 进制的一个 int64 数转为 string 型,显然这个函数更加精缺,刚刚的 Itoa 函数只是封装了一下,默认是 10 进制而已。
我们来看实际代码,把高精度的 int32, int64 转为 stirng
var b int32 = 123456789
fmt.Printf("var b int32 = 123456789, b := %v\n", reflect.TypeOf(b))
fmt.Printf("reflect.TypeOf(strconv.FormatInt(int64(b), 10), b : %v\n",reflect.TypeOf(strconv.FormatInt(int64(b), 10)))
var c int64 = 123456789
fmt.Printf("var c int64 = 123456789, c := %v\n", reflect.TypeOf(c))
fmt.Printf("reflect.TypeOf(strconv.FormatInt(int64(c), 10), c : %v\n",reflect.TypeOf((strconv.FormatInt(int64(c), 10))))
当然还有其他的,如无符号型转 string,float 转 string, 等,这些可以继续看 strconv.FormatUint() 和 strconv.FormatFloat() 等函数,由又不常用,这里就不展开了。
- string -> int, int32, int64
我们来继续看 Atoi 的源码
func Atoi(s string) (int, error) {
//
i64, err := ParseInt(s, 10, 0)
if nerr, ok := err.(*NumError); ok {
nerr.Func = fnAtoi
}
return int(i64), err
}
我们发现 Atoi 传入一个 stirng, 返回一个 int 和 error,同 Itoa 函数,这里我们同样发现精度问题,如果精度比较小,那么转为 int 型显然没什么问题,那这就变成 string 转为 int,之后再把 int 转为 int32 或 int64, 我们实际运用一下
str := "1234"
i, _ := strconv.Atoi(str)
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(int32(i)))
fmt.Println(reflect.TypeOf(int64(i)))
同样的,如果我们遇到精度高的情况,这种情况是不行的,我们来继续观察 Atoi 的源码,我们发现最后 return 时,返回的是 int(i64), 这显然是把 int64 转为了 int 型,这里就降低了精度,而 i64 又是由 ParseInt 函数而来,显然 Atoi 又是一个被封装好的函数,为了保持精度,我们来看 ParseInt 的源码
func ParseInt(s string, base int, bitSize int) (i int64, err error) {
//
return n, nil
}
我们发现传入了一个 string, 两个 int, 返回一个 int64 和 eror, 仔细观察,显然两个 int ,一个是 进制,一个是比特数,那么我们要把 string 转为 int32 或 int64 又不损失精度,就比较方便了,我们来看实例
// string 转换成int64
strInt64, _ := strconv.ParseInt(str, 10, 64)
// string转换成int32
strInt32, _ := int32(strconv.ParseInt(str, 10, 32))
- []byte 和 string
上面我们讲解了 string 和 int,int32,int64 间的相互转换,实际上还有一种常见的转换,我们知道 byte 是 uint8 的别名, uint8
就是无符号 8 比特,也就是 一字节,所以 go 设置了个别名 byte, 很多地方都会用到字节流,在 go 中也就是 []byte, 所以 []byte 和 string 间的相互转换也很常见。
string 转 字节切片([]byte)
str := "hahah"
bs := []byte(str)
字节切片([]byte) 转 string
bs := []byte("asf")
str := string(bs)
自定义类型
我们刚讲了 go 自带的底层类型,现在我们来讲一下是 Type 关键字声明的自定义类型,我们知道 type 声明的类型最终都是由一些底层类型组合而来,那么这些类型间的相互转换又有什么关系呢,我们来看代码
type myInt int
var a myInt = 10
b := int(a)
fmt.Println(reflect.TypeOf(b))
我们声明了一个以 int 为底层类型的 myInt 类型,然后实例一个 a, 再把 a 转换为 int 类型,发现并没有报错,我们继续
type myInts myInt
type myInt int
var a myInt = 10
b := int(a)
fmt.println(reflect.TypeOf(b))
我们再自定义类型上又嵌套了一个自定义类型,仍然没有报错,这似乎说明底层类型相同就是转换,那我们来弄个底层类型不同的试试
type dog struct {
age int
name string
}
type cat struct {
name string
}
d := dog{}
fmt.Println(reflect.TypeOf(cat(d)))
会报 error cannot convert d (type dog) to type cat
,那我们给 cat 加上 age filed 试试
type dog struct {
age int
name string
}
type cat struct {
age int
name string
}
d := dog{}
fmt.Println(reflect.TypeOf(cat(d)))
结果成功了,那我们可以得出结论了,对于自定义类型,只要底层类型相同,就可以使用varOfTypeB = typeB(varOfTypeA)
语法强制转换。
接口转换
我们知道接口 interface 动态类型的,那么它的类型就是不固定的,要转换它的类型,就需要判断它的类型,所以这里需要用到断言,断言也就是一段逻辑表达式,在这里就是类型断言。如果对接口不了解,请先阅读这篇文章 [接口](),如果对断言不了解,请先阅读这篇文章 [断言]()
接口转换为其他类型
类型断言涉及到接口,我们先给出类型断言的语法
varOfTypeA = varOfInterfaceB.(TypeA)
即 A类型变量 = 接口变量 B.(类型 A)
例子
// 建一个空接口 A
type A interface{}
// 实例一个 A 变量 a,并赋值为 int 型
var a A = 123
// 判断 a 是否为 string,并输出结果 Type
fmt.Println(reflect.TypeOf(a.(string)))
我们得到了一个 pannic interface conversion: main.A is int, not string
,显然失败了,我们再改为 a.(int) 试试,得到结果 int, 显然成功了。这就是最基本的类型断言,把接口变量转换为其他类型的变量。但是刚刚的例子我们也发现我们难以判断是否类型断言成功了,不够好在 go 还给我们提供了一种语法,请看
varOfTypeA,ok = varOfInterfaceB.(TypeA)
我们发现多了一个返回值,是的,这个返回值是一个 bool 类型,用于判断是否断言成功,我们一般用 ok 表示,我们看例子
// 建一个空接口 A
type A interface{}
// 实例一个 A 变量 a,并赋值为 int 型
var a A = 123
// 判断 a 是否为 string,并输出结果 Type
//fmt.Println(reflect.TypeOf(a.(string)))
// 这里自行把 string 改为 int,看是否改变了分支
_, ok := a.(string)
if !ok {
fmt.Println(ok)
fmt.Println("断言失败!")
} else {
fmt.Println(ok)
fmt.Println("断言成功!")
}
从上面我们这个 ok 断言我们发现非常方便,其他还有类型的用法,如判断 map 中 key 是否存在的断言,判断 channel 中是否有值的断言等,具体的请查看 [断言]() 这篇文章。
其他类型转换为接口
其他类型转换为接口 interface ,这是我们日常中比较常见的用法,我们知道 interface 是方法的集合,其他类型只要方法集是接口方法集的超集,那么这个类型就自动的实现了这个接口,那么这个类型就可以直接转换为接口。具体请查看 [接口]()这篇文章
看例子
// Animal 接口
type Animal interface {
Speak(msg string)
}
type dog struct {
}
// dog 实现了 Animal 接口
func (d dog) Speak(msg string) {
fmt.Println("dog say:" + msg)
}
type cat struct {
}
// cat 实现了 Animal 接口
func (c cat) Speak(msg string) {
fmt.Println("cat say:" + msg)
}
func main() {
var animal Animal
// animal 接口转为 dog struct
animal = dog{}
animal.Speak("i'm dog")
// animal 接口转为 cat struct
animal = cat{}
animal.Speak("i'm cat")
}