Golang 中的数值类型

GolangBeginner
立即练习

介绍

欢迎各位 Gopher 进入新的篇章。在本节中,我们将学习数值类型。内容包括常用的整数类型、浮点数类型、布尔类型,以及复数和 1.13 版本中引入的字面值语法。

知识点:

  • 整数类型
  • 浮点数类型
  • 布尔类型
  • 复数
  • 字面值语法

整数

整数可以大致分为两类:无符号整数和有符号整数。有符号整数是使用最广泛的。

无符号意味着它只能表示非负数(0 和正数),而有符号数可以表示负数和非负数。

无符号整数可以分为四种大小:8 位、16 位、32 位和 64 位,分别由 uint8uint16uint32uint64 表示。对应的有符号整数是 int8int16int32int64。下表显示了每种类型表示的不同范围:

类型 描述 范围
uint8 8 位无符号整数 0 到 255
int8 8 位有符号整数 -128 到 127
uint16 16 位无符号整数 0 到 65535
int16 16 位有符号整数 -32768 到 32767
uint32 32 位无符号整数 0 到 4294967295
int32 32 位有符号整数 -2147483648 到 2147483647
uint64 64 位无符号整数 0 到 18446744073709551615
int64 64 位有符号整数 -9223372036854775808 到 9223372036854775807

让我们以 uint8int8 为例。它们都是 8 位的整数,可以表示 256 个值。在无符号整数类型 uint8 中,它可以表示的范围是从 0 到 255,而在有符号整数类型 int8 中,它可以表示的范围是从 -128 到 127。

除了上述 8 种类型之外,还有其他三种特殊的整数类型 uintintuintptr,其中 uintint 在不同的平台上可能代表不同的范围,而 uintptr 用于存储指针地址。

类型 范围
uint 在 32 位系统上是 uint32,在 64 位系统上是 uint64
int 在 32 位系统上是 int32,在 64 位系统上是 int64
uintptr 用于存储指针地址的无符号整数类型,主要用于底层编程,例如 unsafe 操作

现在,让我们创建一个名为 integer.go 的文件来演示整数的使用:

cd ~/project
touch integer.go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // 查看当前环境中 int 的类型
    // 声明 a 为 int 类型
    var a int
    // 使用 unsafe.Sizeof() 输出该类型占用的内存大小
    fmt.Printf("The type int in the current environment is %d bits\n", unsafe.Sizeof(a)*8)

    var b int8 = 125
    // 在 fmt.Printf 中使用 %d 占位符输出整数的值
    // 在 fmt.Printf 中使用 %T 占位符输出变量的类型
    fmt.Printf("The value of b is %d, and the type is %T\n", b, b)

    // 整数运算
    // 声明整数 c 和 d,并计算它们的和
    c, d := 2, 3
    fmt.Printf("c + d = %d\n", c+d)
    // 10 - 5
    fmt.Printf("10 - 5 = %d\n", 10-5)
    // 8 * 10
    fmt.Printf("8 * 10 = %d\n", 8*10)

    // uintptr 用法示例 - 存储地址
    var ptr uintptr
    x := 42
    // 将指向 x 的指针转换为 uintptr
    ptr = uintptr(unsafe.Pointer(&x))
    fmt.Printf("The address of x stored in ptr is: %v\n", ptr)
}

执行程序后,我们得到以下输出:

go run integer.go
The type int in the current environment is 64 bits
The value of b is 125, and the type is int8
c + d = 5
10 - 5 = 5
8 * 10 = 80
The address of x stored in ptr is: 824634818784

输出解释:

  • The type int in the current environment is 64 bits:此行显示运行代码的系统上的 int 类型是 64 位(或 8 个字节),表明它是 int64unsafe.Sizeof(a) 返回变量 a 的大小(以字节为单位),乘以 8 将其转换为位。这意味着 int 类型可以容纳更大的整数值。
  • The value of b is 125, and the type is int8:在这里,我们声明了一个 int8 类型的变量 b 并为其赋值 125。输出通过显示值和数据类型来确认这一点。
  • c + d = 510 - 5 = 58 * 10 = 80:这些行展示了基本的整数算术运算:加法、减法和乘法。输出确认了这些计算的正确结果。
  • The address of x stored in ptr is: 824634818784:这演示了如何使用 uintptr 存储内存地址。我们将指向整数变量 x 的指针转换为 uintptr 类型。每次程序运行时,实际地址值都会有所不同。这是一种高级用例,通常在 unsafe 操作和系统编程中找到。

在此文件中,unsafe.Sizeof() 函数可用于获取当前变量类型占用的字节数。1 字节 (byte) 等于 8 位,因此 unsafe.Sizeof()*8 可以获取该类型占用的位数。从输出中,我们可以看到在线环境是 64 位的。在线环境中 int 的实际类型是 int64

我们可以在终端中使用以下命令来确定当前的系统架构:

dpkg --print-architecture
amd64

浮点数

浮点数在 Go 中使用 float32float64 两种类型表示带有小数部分的实数。默认的浮点数类型是 float64

float32float64 表示不同的精度。 float64 的默认精度高于 float32

IEEE 754 标准是计算机中最广泛使用的浮点数计算标准,现代 CPU 都支持此标准。与其他编程语言一样,Go 也使用 IEEE 754 来存储浮点数。

我们知道计算机中的一个字节可以存储 8 位。 float32 是单精度浮点数,占用 4 个字节,即 32 位。 float64 是双精度浮点数,占用 8 个字节,即 64 位。

float32 中,符号位占用 1 位,指数占用 8 位,其余 23 位用于表示尾数。

float64 中,符号位也占用 1 位,指数占用 11 位,其余 52 位用于表示尾数。

float32 可以表示的最大值约为科学计数法中的 3.4e+38,最小值是 1.4e-45。 float64 可以表示的最大值约为 1.8e+308,最小值是 4.9e-324。我们可以看到浮点数值的范围可以从非常小到非常大。

我们可以使用常量 math.MaxFloat32 来表示 float32 中的最大值。使用常量 math.MaxFloat64 来表示 float64 中的最大值。

浮点数的表示

输出浮点数时,我们可以使用 fmt 包的 Printf 函数的 %f 占位符。这是一个例子:

cd ~/project
touch float.go
package main

import (
    "fmt"
    "math"
)

func main() {
    // 不使用指数形式输出
    fmt.Printf("2.333 without exponential form: %f\n", 2.333)
    fmt.Printf("Pi without exponential form: %f\n", math.Pi)
    // 使用 %.2f 保留 Pi 的两位小数
    fmt.Printf("Pi with two decimal places: %.2f\n", math.Pi)
    fmt.Printf("The maximum value of float32: %f\n", math.MaxFloat32)
    // 指数形式
    fmt.Printf("2.333 in exponential form: %e", 2.333)
}

运行命令,你将看到以下输出。

go run float.go
2.333 without exponential form: 2.333000
Pi without exponential form: 3.141593
Pi with two decimal places: 3.14
The maximum value of float32: 340282346638528859811704183484516925440.000000
2.333 in exponential form: 2.333000e+00

输出解释:

  • 2.333 without exponential form: 2.333000:这显示了使用 %f 占位符打印的数字 2.333。默认情况下,%f 占位符显示小数点后 6 位数字,因此 2.333 变为 2.333000
  • Pi without exponential form: 3.141593:此行打印常量 math.Pi 的值,它是数学常数 π 的近似值。 %f 占位符以完整精度显示它。
  • Pi with two decimal places: 3.14:通过使用 %.2f,我们告诉 fmt.Printf 将数字格式化为小数点后两位,结果为 3.14。当你只需要输出的特定精度时,这非常有用。
  • The maximum value of float32: 340282346638528859811704183484516925440.000000:这显示了 float32 可以表示的最大值。请注意,这是一个非常大的数字,并且在使用 %f 打印时,它将用许多数字表示。
  • 2.333 in exponential form: 2.333000e+00:此行显示如何使用 %e 占位符以指数(科学)计数法表示浮点数。末尾的 e+00 表示我们将 2.333000 乘以 10 的 0 次方,即 2.333。如果指数是 e+02,则该数字将乘以 10^2 (100),结果为 233.3

布尔类型

bool 类型只有两个可能的值:truefalse,默认值为 false。它具有以下特点:

  • 它不能转换为其他类型,例如将整数转换为布尔值或将布尔值转换为整数。
  • 它不能参与算术运算。

布尔类型通常与关系运算符结合使用,例如 =><。让我们创建一个名为 bool.go 的文件来看一个演示:

cd ~/project
touch bool.go
package main

import (
    "fmt"
)

func main() {
    // 在 fmt.Printf 中使用 %t 占位符来表示布尔值
    fmt.Printf("Is 3 equal to 2? %t\n", 3 == 2)
    fmt.Printf("Is 2 equal to 2? %t\n", 2 == 2)

    // 判断 a 和 b 是否相等
    a, b := 1, 2
    fmt.Printf("a is %d, b is %d\n", a, b)
    if a > b {
        fmt.Println("a is greater than b")
    } else if a == b {
        fmt.Println("a is equal to b")
    } else {
        fmt.Println("b is greater than a")
    }
}

运行程序后,我们得到以下输出:

go run bool.go
Is 3 equal to 2? false
Is 2 equal to 2? true
a is 1, b is 2
b is greater than a

输出解释:

  • Is 3 equal to 2? false:表达式 3 == 2 的计算结果为 false,然后使用 %t 占位符打印出来。
  • Is 2 equal to 2? true:表达式 2 == 2 的计算结果为 true,然后使用 %t 占位符打印出来。
  • a is 1, b is 2:此行打印整数变量 ab 的赋值。
  • b is greater than aif-else if-else 条件语句的计算结果为 a < b,即 1 < 2 (true),因此执行 else 语句,导致程序打印“b is greater than a”。

在这个程序中,我们首先在 fmt.Printf 中使用 %t 来表示布尔值,然后使用 if 语句来确定两个值之间的关系。在 fmt.Printf 中,我们可以使用 %d 占位符来表示整数,使用 %f 占位符来表示浮点数,使用 %t 占位符来表示布尔值。

复数

Go 也有内置的复数类型,可以分为 complex64complex128 类型。在 complex64 中,实部和虚部都是 32 位,而在 complex128 中,实部和虚部都是 64 位。我们可以很容易地在 Go 中执行复数运算。

创建一个名为 complex.go 的文件,并输入以下代码:

cd ~/project
touch complex.go
package main

import (
    "fmt"
)

func main() {
    // 以不同的方式初始化复数
    c1 := complex(3, 1)
    c2 := 4 + 5i

    // 复数运算
    c3 := c1 + c2
    c4 := c1 * c2
    // 使用 real() 函数获取复数的实部,使用 imag() 函数获取复数的虚部
    fmt.Printf("The real part of c1 is %v, the imaginary part is %v\n", real(c1), imag(c1))
    // fmt.Printf 中的 %v 可以用来表示复数
    fmt.Printf("c1 + c2 is %v\n", c3)
    fmt.Printf("c1 * c2 is %v\n", c4)
}

运行程序后,我们得到以下输出:

go run complex.go
The real part of c1 is 3, the imaginary part is 1
c1 + c2 is (7+6i)
c1 * c2 is (7+19i)

输出解释:

  • The real part of c1 is 3, the imaginary part is 1:函数 real(c1) 提取复数 c1 的实部,即 3,而 imag(c1) 提取 c1 的虚部,即 1。然后使用 %v 占位符打印这些值。
  • c1 + c2 is (7+6i):此行显示了复数 c1c2 相加的结果。 c1 定义为 3 + 1ic2 定义为 4 + 5i。分别添加实部和虚部得到 (3 + 4) + (1 + 5)i,即 7 + 6i
  • c1 * c2 is (7+19i):此行显示了复数 c1c2 相乘的结果。 (3 + 1i) * (4 + 5i) 的计算方式为 (3*4 - 1*5) + (3*5 + 1*4)i,简化为 (12-5) + (15+4)i,最后为 7+19i

在这个程序中,我们演示了如何使用 complex 函数以及 +* 运算符来初始化复数并对其执行运算。 real()imag() 分别用于提取复数的实部和虚部。 fmt.Printf 中的 %v 动词用作通用占位符来显示复数。

字面值语法

在 1.13 版本中,Go 引入了 Numeric Literal Syntax(数字字面值语法)。它定义了数字在不同进制中的表示方式。让我们来看看它的规则。

  • 二进制:在整数前添加 0b。例如,0b101 等于十进制的 5
  • 八进制:在整数前添加 0o0O。例如,0o11 等于十进制的 9
  • 十六进制:在整数前添加 0x0X。例如,0x1b 等于十进制的 27
  • 使用 _ 分隔整数中的数字,例如,0b1000_0100_0010_0001 等于 0b1000010000100001。这可以提高可读性。

让我们详细演示一下:

cd ~/project
touch literals.go
package main

import "fmt"

func main() {
    // 二进制,开头添加 0b
    var a int = 0b101
    fmt.Printf("Binary a is %b, decimal is %d\n", a, a)

    // 八进制,开头添加 0o 或 0O
    var b int = 0o11
    fmt.Printf("Octal b is %o, decimal is %d\n", b, b)

    // 十六进制,开头添加 0x 或 0X
    var c int = 0x1b
    fmt.Printf("Hexadecimal c is %x, decimal is %d\n", c, c)

    // 使用分隔符
    d := 0b1000_0100_0010_0001
    e := 0b1000010000100001
    if d == e {
        fmt.Println("d is equal to e")
    }
}

运行程序后,我们得到以下输出:

go run literals.go
Binary a is 101, decimal is 5
Octal b is 11, decimal is 9
Hexadecimal c is 1b, decimal is 27
d is equal to e

输出解释:

  • Binary a is 101, decimal is 5:此行显示了 0b101 的二进制表示形式,即 101,其十进制等效值为 5%b 占位符显示 a 的二进制值,而 %d 显示十进制表示形式。
  • Octal b is 11, decimal is 9:八进制数 0o11 等于十进制的 1*8^1 + 1*8^0 = 8 + 1 = 9%o 占位符显示 b 的八进制表示形式,而 %d 显示其十进制值。
  • Hexadecimal c is 1b, decimal is 27:十六进制数 0x1b 等于十进制的 1*16^1 + 11*16^0 = 16 + 11 = 27%x 占位符显示 c 的十六进制表示形式,而 %d 显示其十进制值。
  • d is equal to e:这表明二进制字面值 0b1000_0100_0010_0001 在值上等于 0b1000010000100001。下划线仅用于提高可读性,不会更改数字的值。 if 条件正确地评估为 true

在这个程序中,我们演示了不同进制的声明和输出,以及分隔符的使用。在 fmt.Printf 中,我们使用了不同的占位符来表示不同的进制。例如,%b 表示二进制,%x 表示十六进制。掌握它们将提高编程效率。

总结

让我们回顾一下在本节中学到的内容:

  • 整数可以分为有符号整数和无符号整数
  • 默认整数类型 intuint 的范围取决于平台
  • 浮点数的表示
  • 如何使用复数
  • 字面值语法的介绍
  • 如何使用不同的占位符

在本节中,我们解释并演示了常见的整数类型、浮点类型和布尔类型。我们讨论了它们的大小、范围和用法。我们还介绍了复数和字面常量。数值类型是 Go 程序的基础,学习者务必好好学习。