Press "Enter" to skip to content

Go 数据类型篇(六):基本数据类型之间的转化

通过前面几篇教程,学院君已经陆续介绍完了 Go 语言中的基本数据类型,分别是布尔类型整型浮点型复数类型字符串字符类型,此外,Go 语言还支持这些基本数据类型之间的转化,不过由于 Go 是强类型语言,所以不支持动态语言那种自动转化,而是要对变量进行强制类型转化。

下面,我们来看看在 Go 语言中如何实现不同数据类型之间的强制转化。

数值类型之间的转化

整型之间的转化

关于数值类型之间的转化,我们前面在介绍运算符的时候已经提到过,在进行类型转化时只需要调用要转化的数据类型对应的函数即可:

v1 := uint(16)   // 初始化 v1 类型为 unit
v2 := int8(v1)   // 将 v1 转化为 int8 类型并赋值给 v2
v3 := uint16(v2) // 将 v2 转化为 uint16 类型并赋值给 v3

看起来很简单,不过需要注意,在有符号与无符号以及高位数字向低位数字转化时,需要注意数字的溢出和截断。

比如我们看这个例子:

v1 := uint(-255)

由于 uint 是无符号整型,无符号数字不包含负数,所以上述转化编译时会报溢出错误:

constant -255 overflows uint

我们将上述代码改造如下,将无符号数字转化为有符号数字:

v1 := uint(255)
v2 := int8(v1)  // v2 = -1

由于 int8 能够表示的范围是 -128~127,255 超出其表示范围,所以,会截取后8位,v1 是一个无符号整型,后八位都是 1,int8 是一个有符号的整型,所以最高位作为符号位,因此转化后的数字 v2 是负数,取 1111 1111 的补码,所以转化结果是 -1。

有人可能困惑为啥是 -1,而不是 -127,这就要了解计算机底层是如何表示数字的了。

原码、反码和补码

计算机底层是通过二进制表示数字的,我们把这种二进制形式的数字称之为机器数,数字是有正负之分的,这个正负是通过机器数的第一位作为标识的(俗称符号位):0 表示正数,1 表示负数。为区别有符号数真实值与形式值的不同,又将带符号位的机器数对应的真正数值称为机器数的真值(无符号数的真值就是自身)。

除了基本的二进制数字外,计算机还提供了三种数字编码方式:原码、反码和补码。

原码就是符号位+真值,比如:

[+1]原 = 0000 0001
[-1]原 = 1000 0001

以 8 位二进制数表示范围为例,使用原码的话对应的区间范围是 [11111111, 01111111],即 [-127, 127]

我们接着来看反码,正数的反码是自身,负数的反码是在其原码基础上,符号位不变,其余各位按位取反。比如:

[+1]反 = 0000 0001
[-1]反 = 1111 1110

以 8 位二进制数表示范围为例,使用反码的话对应的区间范围是 [11111111, 01111111],还是 [-127, 127]

上面两种编码都存在一个问题,那就是数字 0 存在 +0-0 两种编码:

[+0]原 = 0000 0000
[-0]原 = 1000 0000

[+0]反 = 0000 0000
[-0]反 = 1111 1111

这就导致数字 0 在计算机中的编码不唯一,对于凡事要求确定性的计算机来说,这是绝对不行的,为了解决这个问题,计算机科学家们又提出补码的概念。

正数的补码和反码一样,都是其自身,而负数的补码是在其原码基础上,符号位不变,其余各位按位取反,最后+1(即反码+1)。比如:

[+1]补 = 0000 0001
[-1]补 = 1111 1111

就是这个简单的 +1,却非常巧妙地解决了数字 0 双重编码的问题,现在,+0 的补码是 0000 0000,但是 -0 的补码变成了 0000 0000,所以只有一个表示 0 的编码了。

不仅如此,所有的负数都整体做了 +1 操作,之前的 1111 1111 由于进位溢出,变成了 1000 0000,我们将这个数字用于表示 -128,所以对于 8 位机器数,通过补码表示的话,现在的情况是:

  • 正数区间依然是 [0, 127] 不变;
  • 负数区间变成了 [-128, -1](之前 [-127, -0] 每个数字 +1 演化而来)。

这也是目前计算机系统底层 8 位整型数字的区间范围,所以计算机底层是通过补码来表示数字的,也只能通过补码来表示。

了解到这里,我们再来看为什么 uint 类型的 255 转化为 int8 类型的值后是 -1。

255 是无符号正数,补码和原码都是 255,即 16 个 1 组成的机器数,转化为 int8 类型后,由于 int8 只能存放 8 位机器数,所以会截取 255 后 8 位数字,也就是 1111 1111int8 是有符号数字,第一位是符号位,所以真值是后 7 位,计算机底层通过补码表示数字,需要将其转化为补码,而这个数字又是负数,所以需要将后 7 位按位取反再 +1,也就是 1000 0001,即 -1

整型与浮点型之间的转化

然后,我们再来看下整型和浮点型之间的转化,浮点型转化为整型时,小数位被丢弃:

v1 := 99.99
v2 := int(v1)  // v2 = 99

将整型转化为浮点型时,比较简单,直接调用对应的函数即可:

v1 := 99
v2 := float64(v2). // v2 = 99

数值和布尔类型之间的转化

目前 Go 语言不支持将数值类型转化为布尔型,你需要自己根据需求去实现类似的转化。

字符串和其他基本类型之间的转化

将整型转化为字符串

整型数据可以通过 Unicode 字符集转化为对应的 UTF-8 编码的字符串:

v1 := 65
v2 := string(v1)  // v2 = A

v3 := 30028
v4 := string(v3)  // v4 = 界

Unicode 兼容 ASCII 字符集,所以 65 被转化为 A。

此外,还可以将 byte 数组或者 rune 数组转化为字符串,因为字符串底层就是通过这两个基本字符类型构建的:

v1 := []byte{'h', 'e', 'l', 'l', 'o'}
v2 := string(v1)  // v2 = hello

v3 := []rune{0x5b66, 0x9662, 0x541b}
v4 := string(v3)  // v4 = 学院君

当然了,byteuint8 的别名,runeint32 的别名,所以也可以看做是整型数组和字符串之间的转化。

strconv 包

Go 语言默认不支持将字符串类型强制转化为数值类型,即使字符串中包含数字也不行。

如果要实现更强大的基本数据类型与字符串之间的转化,可以使用 Go 官方 strconv 包提供的函数:

v1 := "100"
v2, _ := strconv.Atoi(v1)  // 将字符串转化为整型,v2 = 100

v3 := 100
v4 := strconv.Itoa(v3)   // 将整型转化为字符串, v4 = "100"

v5 := "true"
v6, _ := strconv.ParseBool(v5)  // 将字符串转化为布尔型
v5 = strconv.FormatBool(v6)  // 将布尔值转化为字符串

v7 := "100"
v8, _ := strconv.ParseInt(v7, 10, 64)   // 将字符串转化为整型,第二个参数表示进制,第三个参数表示最大位数
v7 = strconv.FormatInt(v8, 10)   // 将整型转化为字符串,第二个参数表示进制

v9, _ := strconv.ParseUint(v7, 10, 64)   // 将字符串转化为无符号整型,参数含义同 ParseInt
v7 = strconv.FormatUint(v9, 10)  // 将无符号整数型转化为字符串,参数含义同 FormatInt

v10 := "99.99"
v11, _ := strconv.ParseFloat(v10, 64)   // 将字符串转化为浮点型,第二个参数表示精度
v10 = strconv.FormatFloat(v11, 'E', -1, 64)

q := strconv.Quote("Hello, 世界")    // 为字符串加引号
q = strconv.QuoteToASCII("Hello, 世界")  // 将字符串转化为 ASCII 编码

关于 strconv 包的更多功能,请查看对应的包 API

8 Comments

  1. ccxx
    ccxx 2021年9月4日

    《将整型转化为字符串》 那里
    rune 应该是 int32 的别名 而文章那里 却显示 “rune 是 uint32 的别名”

  2. caiyaonan
    caiyaonan 2021年10月19日

    strconv 包里code里面的注释
    v7 = strconv.FormatUint(v9, 10) // 将字符串转化为无符号整型,参数含义同 FormatInt
    应该是将无符号整数型转化为字符串

  3. tiesheng
    tiesheng 2023年3月15日

    -0 的补码不应该是 0000 0000 么 ? 文中是不是写错了 -0 的补码写成了 1000 0001

  4. tiesheng
    tiesheng 2023年3月15日

    uint 所占空间由操作系统决定 如果是64位系统应该是占8个字节 与 int64 一样,那正数255 应该是后八位为 1111 1111 其余位为0 的,而不是文中所说的16个1的数值码,int8 为8位 所以会截取后八位 就变成了 1111 1111 取其补码为 1000 0001 即-1

    文章写的很有逻辑性,就是错误太多了,望谨慎

    • 学院君
      学院君 2023年3月15日

      至于下面这个64位/8位 是当初写的时候比较口语化偷懒了 但从专业角度看确实不够严谨

发表回复