类型转换在编程语言中是很常见的操作,在 Go
语言中其类型转换有下面一些注意点。
1. 整数类型之间的转换
对于整数类型转换,原则上目标类型的取值范围要包含被转换值,也就是说要转换类型的值取值范围要小于目标类型的取值范围。
如果相反,即目标类型小,而要转换的源值类型大,比如把值的类型从 int16
转换为 int8
,会出现截断现象,如下代码:
func main() {
src := int16(-255)
dst := int8(src)
fmt.Println("dst is ", dst) // dst is 1
}
变量 src
的值是 int16
类型的 -255
,而变量 dst
的值是由前者转换而来的,类型是 int8
。int16
类型的可表示范围可比 int8
类型大。
整数在计算机中都是以补码的形式存储的。其中:
- 正数补码和原码相同;
- 负数补码是原码各位求反再加 1;
比如,int16
类型的值 -255
的补码是 1111111100000001
。
该值在转换为 int8
类型的值,那么就会把在较高位置(或者说最左边位置)上的 8 位二进制数直接截掉,从而得到 00000001
。又由于其最左边一位是 0
,表示它是个正整数,以及正整数的补码就等于其原码,所以 dst
的值就是 1 。
规则:
- 当整数值的类型的有效范围由宽变窄时,只需在补码形式下截掉一定数量的高位二进制数即可;
- 当浮点数类型的值转换为整数类型值时,会把浮点数值的小数部分全部截掉;
2. 整数转换字符串
把整数转换为字符串,有两种方法可以使用,分别是:
- 使用
string
直接转换; - 使用
strconv
库方法进行转换;
两者的区别,可以看下面代码:
func main() {
a := 101
s1 := string(a)
s2 := strconv.Itoa(a)
fmt.Printf("s1 is %#v, type is %T\n", s1, s1)
fmt.Printf("s2 is %#v, type is %T\n", s2, s2)
}
输出结果:
s1 is "e", type is string
s2 is "101", type is string
我们可以看到区别,
string
方法是将整数转换为对应的ASCII/Unicode
码值;strconv
库的Itoa
方法是将整数转换成整数字面量对应的字符串;
如下代码也是同样的
func main() {
a := 0x597D
s1 := string(a)
s2 := strconv.Itoa(a)
fmt.Printf("s1 is %#v, type is %T\n", s1, s1)
fmt.Printf("s2 is %#v, type is %T\n", s2, s2)
}
输出结果:
s1 is "好", type is string
s2 is "22909", type is string
16 进制数 0x597D
的十进制数字为 22909
,且其 Unicode
值为汉字的 好
。
但需要关注的是,被转换的整数值应该可以代表一个有效的 Unicode
代码点,否则转换的结果将会是 �
(仅由高亮的问号组成的字符串值)。
func main() {
a := -2
s1 := string(a)
s2 := strconv.Itoa(a)
fmt.Printf("s1 is %#v, type is %T\n", s1, s1)
fmt.Printf("s2 is %#v, type is %T\n", s2, s2)
}
输出结果为
s1 is "�", type is string
s2 is "-2", type is string
由于 -2 无法代表一个有效的 Unicode 值,所以得到的总会是 �
。
3. string类型与各切片类型之间转换
一个值在从 string
类型向 []byte
类型转换时,除了与 ASCII
编码兼容的那部分字符集可以用单个字节表示之外,以 UTF-8
编码的字符串会被拆分成零散、独立的字节,单一字节是无法代表一个字符的。
func main() {
a := "你好"
s1 := string(a)
s2 := []byte(a)
s3 := []rune(a)
fmt.Printf("s1 is %#v, type is %T\n", s1, s1)
fmt.Printf("s2 is %#v, type is %T\n", s2, s2)
fmt.Printf("s3 is %#v, type is %T\n", s3, s3)
}
输出结果为:
s1 is "你好", type is string
s2 is []byte{0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd}, type is []uint8
s3 is []int32{20320, 22909}, type is []int32
0xe4
, 0xbd
, 0xa0
合在一起才能代表字符 你
,而 0xe5
, 0xa5
, 0xbd
合在一起才能代表符 好
。
一个值在从 string
类型向 []rune
类型转换时代表着字符串会被拆分成一个个 Unicode
字符。
[]int32{20320, 22909} // 你好
4. 类型转换三方库
第三方包 github.com/spf13/cast
专门解决类型转换的问。这个包使用很简单,主要有两套函数:
- To_ 形式函数
这些函数始终返回所需的类型。如果无法正确转换为对应的类型,则返回目标类型的零值。
支持的类型包括所有的基本数据类型,还支持 time.Time
、time.Duration
、slice
、map
等常用类型。
比如:
cast.ToString("mayonegg") // "mayonegg"
cast.ToString(8) // "8"
cast.ToString(8.31) // "8.31"
cast.ToString([]byte("one time")) // "one time"
cast.ToString(nil) // ""
cast.ToTime("2021-08-10 22:00:00") // 2021-08-10 22:00:00 +0000 UTC
注意,转换为 time.Time
时,需要注意时区问题。ToTime
默认使用 UTC
,如果想用其他时区,得类似这么做:
secondsEastOfUTC := int((8 * time.Hour).Seconds())
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)
fmt.Println(cast.ToTimeInDefaultLocation("2021-08-10 22:00:00", beijing))
当然,你也可以这样:
fmt.Println(cast.ToTimeInDefaultLocation("2021-08-10 22:00:00", time.Local))
不过,Local
表示本地时区,要明确这个本地是不是你想要的。
- To_E 形式函数
E
表示 error
,也就是说,这一系列函数会返回 error
。在无法进行类型转换时,会将错误原因返回。To_
形式内部调用的是 To_E
形似,只是它忽略了错误。
这种形式就不举例了。一般地,除非你需要区分零值是因为出错导致的还是本身就是零值,否则应该使用 To_
系列函数,毕竟更省事。