Go 的基本类型包括数字、布尔值和字符串。每个变量都恰好有一个在编译期确定的类型,Go 从不会替你在类型之间自动转换。%T 可以打印任何值的类型:
注意 'G' 打印出来是 71,类型是 int32:单引号括起来的字符是 rune,而 rune 是一个数字(Unicode 码点)。rune 是 int32 的别名(同一个类型的第二个名字),所以 %T 报告的是 int32。同样,byte 报告的是 uint8。
整数
| 类型 | 大小 | 范围 |
|---|---|---|
int8 | 8 位 | -128 至 127 |
int16 | 16 位 | -32,768 至 32,767 |
int32 | 32 位 | 约 -21亿 至 21亿 |
int64 | 64 位 | 约 -9.2×10¹⁸ 至 9.2×10¹⁸ |
uint8(byte) | 8 位 | 0 至 255 |
uint16 | 16 位 | 0 至 65,535 |
uint32 | 32 位 | 0 至约 43亿 |
uint64 | 64 位 | 0 至约 1.8×10¹⁹ |
int、uint | 32 或 64 位 | 平台字长 |
uintptr | 与平台相关 | 保存指针值,用于底层代码 |
默认使用 int。 len() 返回 int,切片索引是 int,循环计数器也是 int。在所有 64 位平台上,int 都是 64 位。当大小是约定的一部分时,再使用固定大小的类型:二进制文件格式中的 int32,原始字节用的 uint8,以纳秒为单位的 Unix 时间戳用的 int64。
对于“不可能为负”的数量(比如计数),不要使用无符号类型。减到零以下时,它会回绕成一个巨大的数字,而不是产生一个你能发现的负数。
上限与溢出
math 包为每个上限都提供了常量。在运行时,溢出的整数运算会悄无声息地回绕:
没有 panic,也没有错误:127 + 1 变成了 -128。只有常量表达式会在编译期检查(var b int8 = 128 无法通过编译)。如果你的数据可能溢出,请在运算之前检查,或者使用 math/big。
整数字面量
million := 1_000_000 // underscores for readability (Go 1.13+)
mask := 0xFF // hexadecimal
perm := 0o755 // octal (also the older form 0755)
flags := 0b1010 // binary
浮点数
| 类型 | 大小 | 精度 |
|---|---|---|
float32 | 32 位 | 约 6 到 9 位有效数字 |
float64 | 64 位 | 约 15 到 17 位有效数字 |
带小数点或指数的字面量(2.5、1e6)默认是 float64,math 包接受和返回的也是 float64。除非大型数组的内存很重要,否则就用它。
浮点数是二进制的,所以大多数十进制小数都只是近似值:
由此得出两条规则:运算之后绝不要用 == 比较浮点数;也绝不要用浮点数存储金额,请用以“分”为单位的整数,或者使用十进制数库。
浮点数除以零得到 +Inf、-Inf 或 NaN,而不会 panic。整数除以零会在运行时 panic(runtime error: integer divide by zero),除数是常量零时则是编译错误。
布尔值
bool 保存 true 或 false,零值是 false。Go 不会把数字、空字符串或 nil 当作布尔值。if count {} 是编译错误,要写成 if count > 0 {}。运算符是 &&、|| 和 !,其中 && 和 || 会短路求值。
字符串
string 是一个不可变的字节序列,通常是 UTF-8 文本。len(s) 计算的是字节数,而不是字符数:
é 在 UTF-8 中占两个字节,所以五个字符的 len 是 6。双引号字符串会处理 \n 这样的转义序列;反引号括起来的原始字符串不会,并且可以跨越多行。strings 包在字符串页面中讲解,UTF-8 的细节在 rune 和字节页面中深入介绍。
byte 和 rune
| 别名 | 等同于 | 保存的内容 |
|---|---|---|
byte | uint8 | 一个字节的原始数据 |
rune | int32 | 一个 Unicode 码点 |
它们是别名,而不是新类型:byte 和 uint8 无需转换即可互换。名字只是说明这个数字代表什么。
复数
Go 内置了复数类型 complex64 和 complex128,配合 real、imag 以及 math/cmplx 包使用:
很少有程序需要它们,但信号处理和一些数值计算代码会用到。
复合类型与引用类型
基本类型可以组合成复合类型。每种类型都有自己的页面:
| 类型 | 示例 | 零值 |
|---|---|---|
| 数组 | [3]int | 三个零 |
| 切片 | []int | nil |
| map | map[string]int | nil |
| 结构体 | struct{ X, Y int } | 每个字段都是零值 |
| 指针 | *int | nil |
| 函数 | func(int) int | nil |
| 通道 | chan int | nil |
| 接口 | error、any | nil |
any 是 interface{} 的别名(从 Go 1.18 开始),可以保存任何类型的值。
命名类型
你可以基于已有类型定义一个新类型。它的底层表示相同,但是一个不同的类型,并且可以拥有方法:
type Celsius float64
type UserID int64
var t Celsius = 21.5
var f float64 = t // compile error: cannot use t (variable of float64 type Celsius) as float64 value in variable declaration
这种严格正是目的所在:UserID 不会被意外传到需要 OrderID 的地方。确实需要时再显式转换:float64(t)。转换规则见类型转换页面,数字与字符串之间的转换见 strconv 包的函数。
零值
每种类型都有零值,也就是变量在你赋值之前保存的值:数字是 0,bool 是 false,字符串是 "",指针、切片、map、通道、函数和接口是 nil。Go 中不存在未初始化的变量。
常见问题
Go 中的 int 有多大?
在 64 位平台(amd64、arm64)上,int 和 uint 是 64 位;在 32 位平台上是 32 位。如果需要固定大小,比如用于文件格式或网络协议,请使用 int32、int64、uint8 等类型。普通的计数器、索引和长度用 int 即可。
Go 中如何打印变量的类型?
使用 %T 动词:fmt.Printf("%T\n", x) 会打印 int、float64、[]string、main.User 等。在代码中,reflect.TypeOf(x) 以一个可供检查的值的形式返回同样的信息。
Go 中 byte 和 rune 有什么区别?
byte 是 uint8 的别名,保存一个字节的原始数据。rune 是 int32 的别名,保存一个 Unicode 码点。字符串是字节序列;用 for range 遍历字符串时,会把它解码成一个个 rune。
Go 中应该用 float32 还是 float64?
除非有特别的理由,否则用 float64。它是浮点数字面量的默认类型,也是 math 包使用的类型,大约有 15 到 17 位有效数字,而 float32 只有大约 6 到 9 位。只有在需要让大型数组的内存减半,或者要匹配外部格式时,才选择 float32。
Go 中 int 的最大值是多少?
使用 math 包中的常量:math.MaxInt(在 64 位平台上是 9223372036854775807)、math.MinInt、math.MaxInt64、math.MaxUint32 等。运行时超出上限的运算会悄无声息地回绕,不会 panic。