数据类型又称类型,是数据的一种属性,它决定了值的分类,如何解释保存值的内存,以及可以对值执行的操作(包括值具有的方法)。编程语言通常包含三类类型:内建的原始类型,如各种数值类型、布尔类型、字符串类型;基于原始类型构建的复合类型,如数组、切片、结构体等;与各种数据结构模型对应的抽象类型,如列表、堆栈、树、映射等。请注意这些划分并不是绝对的,如我们也可以认为字符串是切片,属于复合类型,同时认为映射也属于复合类型。语言的类型系统将类型与计算值相关联,通过检查这些值的流,保证不会发生类型错误。在编程时,必须充分了解语言的类型和类型系统。
Go 和 Rust 都是静态类型语言,从而在程序编译时需要知道所有变量的类型。本文将介绍除了字符串类型之外的其他原始类型,这些类型在类型系统中最为简单。同时由于他们都表示单个值,因此也可称为标量类型。
标量类型列表
Go 和 Rust 中有如下表所示的标量类型:
Go 类型 | Rust 类型 | 说明 |
---|---|---|
uint8, byte | u8 | 8 位无符号整数 |
uint16 | u16 | 16 位无符号整数 |
uint32 | u32 | 32 位无符号整数 |
uint64 | u64 | 64 位无符号整数 |
u128 | 128 位无符号整数 | |
int8 | i8 | 8 位有符号整数 |
int16 | i16 | 16 位有符号整数 |
int32, rune | i32 | 32 位有符号整数 |
int64 | i64 | 64 位有符号整数 |
i128 | 128 位有符号整数 | |
float32 | f32 | 32 位浮点数 |
float64 | f64 | 64 位浮点数 |
complex64 | 64 位复数 | |
complex128 | 128 位复数 | |
uint | usize | 无符号整数,长度视架构而定 |
int | isize | 有符号整数,长度视架构而定 |
uintptr | ||
char | Unicode 字符 | |
bool | bool | 布尔类型 |
说明:
- 每个有符号整数类型的大小范围是 -(2n - 1 ) ~ 2n - 1 - 1,无符号整数类型的大小范围是 0 ~ 2n - 1,其中 n 是该定义形式的位长度。
- 单精度 32 位浮点数至少有 6 位有效数字,数值范围为 -3.4×1038 ~3.4×1038 ;双精度 64 位浮点数至少有 15 位有效数字,数值范围为 -1.8×10308 ~1.8×10308 。
- 一个
complex64
复数值的实部和虚部都是float32
类型的值;一个complex128
复数值的实部和虚部都是float64
类型的值。 - Go 中的
int
和uint
类型分别对应 Rust 中的isize
和usize
,它们的长度依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。在 Rust 中,isize
和usize
类型一般用作某些集合的索引。 - 在 Go 中,
uintptr
类型类似uint
类型,它总是能保存任意的内存地址。
Go 和 Rust 的以上数据类型存在以下方面的重要区别:
- 如果较难确定究竟该使用何种整数类型,在 Go 中一般使用
int
类型,而在 Rust 中则一般使用i32
类型,这也分别是两种语言中整型的默认类型。 - Rust 内建有 128 位的整型
i128
和u128
,Go 没有。 - Go 内建有复数类型
complex64
和complex128
,Rust 没有。
字面量表示
两种语言都用 true
和 false
表示布尔类型字面量,这里就不再多做介绍了。两种数字在表示数值字面量时,都可以用下划线 _
作为可视分隔符,如 10_000
、3.141_59
。
两种语言的一个重要区别是:Go 中的标量字面量是无类型的,而 Rust 中的标量字面量具有默认类型,这在前面变量与常量一文已经讨论过。在 Rust 中,除了字节字面量外,其他数字字面量都可以通过添加类型后缀显式指定其类型,如 10_000i32
、3.141_59_f32
,Go 不支持(也没有必要支持)这种方法。
整数字面量
字面量指值的字面形式。整数类型的字面量可以是十进制、二进制、八进制或十六进制的形式。
在 Go 中,各种进制字面量的表示方法为:
- 十进制表示时,除了数字 0,其他整数不能以 0 开头,如 0、13、267;
- 二进制表示时,以
0b
或0B
开头,如 0b0、0B1101、0B100001011; - 八进制表示时,以
0
、0o
或0O
开头,如 0o0、015、0O413; - 十六进制表示时,以
0x
或0X
开头,如0x0、0Xd、0x10b。
在 Go 中,以下三行将全部输出 true
:
println(13 == 0b1101) // true
println(13 == 015) // true
println(13 == 0Xd) // true
在 Rust 中,各种进制字面量的表示方法为:
- 十进制表示时,可以以 0 开头,如 0、013、267;
- 二进制表示时,以
0b
开头,如 0b0、0b1101、0b100001011; - 八进制表示时,以
0o
开头,如 0o0、0o15、0o413; - 十六进制表示时,以
0x
开头,如0x0、0xd、0x10b。
在 Rust 中,以下三行将不会打印出错信息:
assert_eq!(13, 0b1101);
assert_eq!(13, 0o15);
assert_eq!(013, 0xd);
可以看出,Go 中可以以 0B
、0
或 0O
、0X
开头表示二进制、八进制及十六进制整数字面量,Rust 不支持这些。另外,在 Rust 中,以 0
开头的整数(如 013)不是 Go 中的八进制数,而是十进制数。
浮点数字面量
根据 Go 语言规范,Go 支持以下十进制形式的浮点数字面量:
0.
72.40
072.40 // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5. // == 15.0
0.15e+0_2 // == 15.0
在 Rust 中,这些浮点字面量并没有获得全部的支持。其中 Rust 不支持 .25
形式的浮点字面量,要求必须有整数部分,即应写作 0.25
,同样,.12345E+5
也应该写作 0.12345E+5
;另外,1.e+0
中,e
前面不能紧临小数点,应写成 1.0e+0
或 1e+0
。
Go 还支持十六进制形式的浮点数表示。十六进制浮点字面量从左到右分别是:0x
或 0X
、十六进制的整数部分、小数点、十六进制的小数部分、指数部分(p
或 P
紧跟一个可选的正负号及十进制数字)。整数部分和小数部分可以省略其一,小数点也可以省略,而指数部分不可省略。该浮点数的值由 p
或 P
前面的有效数字乘以以 2 为底的指数函数 2exp
值,其中 exp 为指数部分的值。如下示例:
0x1p-2 // == 0.25
0x2.p10 // == 2048.0
0x1.Fp+0 // == 1.9375
0X.8p-0 // == 0.5
0X_1FFFP-16 // == 0.1249847412109375
0x15e-2 // == 0x15e - 2 (整数减法)
Rust 中还没有内建对十六进制浮点数的支持。
在 Go 中,以下浮点数字面量都是无效的(但有些在 Rust 中却是有效的):
0x.p1 // 无效:有效数字部分没有数字
1p-2 // 无效:p 指数前面应有十六进制的有效数字
0x1.5e-2 // 无效:十六进制的有效数字后应有 p 指数
1_.5 // 无效:_ 前后必须为数字
1._5 // 无效:_ 前后必须为数字
1.5_e1 // 无效:_ 前后必须为数字
1.5e_1 // 无效:_ 前后必须为数字
1.5e1_ // 无效:_ 前后必须为数字
字符字面量
Go 中的 rune
类型表示一个 Unicode 码点(或称码位),而 Rust 中的 char
类型表示一个 Unicode 标量值,因此两者是有些微区别的。Unicode 码点是在码空间中的任意值,其取值范围使 0x0000
~0x10FFFF
。而 Unicode 标量值是除了高代理码点和低代理码点之外的码点,范围是 0x0000
~0xD7FF
及 0xE000
~0x10FFFF
。在 Rust 中,如果一个 char
值超过此范围,它将立刻变成未定义状态。而在 Go 中,如果一个 rune
值超过此范围,该值仍然有效,但作为 Unicode 字符,它实际上并不表示任何字符。
在 Go 中,表示 rune
类型的字面量时,最常见的方式是用一对单引号直接包括该字符,如 'a'
、'我'
等。也可以用如下转义方式:
\x
后紧跟 2 个十六进制数字;\u
后紧跟 4 个十六进制数字;\U
后紧跟 8 个十六进制数字;\
后紧跟 3 个八进制数字,可表示的值范围为 [0, 255]。
根据以上规则,在 Go 中,'a'
、97
、'\x61'
、'\u0061'
、'\U00000061'
和 '\141'
都表示相同的字符,而 '我'
、25105
、'\u6211'
和 '\U00006211'
也表示相同的字符。如果通过以上 4 种转义方式表示字符,当值超出 Unicode 标量值范围时,将无法编译。如以下语句:
a := '\uD800'
将会导致编译错误:“escape is invalid Unicode code point U+D800”。不过以下语句却是有效的:
var a rune = 0xD800
在 Rust 中,同样可以用单引号包括字符来表示 char
类型的字面量,如 'a'
和 '我'
。也可以用转义方式,方法为:
\x
后紧跟 2 个十六进制数字;\u
后紧跟用花括号对{}
包括的 1~6 个十六进制数字。
根据以上规则,在 Rust 中,'a'
、'\x61'
、'\u{61}'
、'\u{061}'
、'\u{0061}'
、'\u{00061}'
和 '\u{000061}'
都表示相同的字符,而 '我'
、'\u{6211}'
、'\u{06211}'
和 '\u{006211}'
也表示相同的字符。同样,如下语句:
let a = '\u{D800}';
将会导致编译错误:“error: invalid unicode character escape”。
总的来说,Go 中的 rune
就是一个 32 位有符号整数 int32
,可以很方便地参加一些数值运算,而 Rust 对 char
类型做了较多的封装,能始终确保其有效是有效的 Unicode 标量值。
在 Go 和 Rust 中,还有另外一些特殊值能用反斜杠转义表示:
\a U+0007 警报或响铃
\b U+0008 退格符
\f U+000C 换页符
\n U+000A 换行符
\r U+000D 回车符
\t U+0009 水平制表符
\v U+000B 垂直制表符
\\ U+005C 反斜杠
\' U+0027 单引号 (Go 中仅在 rune 字面中有效;Rust 中始终有效)
\" U+0022 双引号 (Go 中仅在字符串字面中有效;Rust 中始终有效)
在 Rust 中,u8
类型的字节字面量也可以以 b
字符开头,后面紧跟一对单引号包含的单个合法的 ASCII 字符,如 b'a'
,该值与 97u8
是等价的。在 Go 中,不必要这样,'a'
本身就是无类型的整数,可以直接将其赋值给任何类型的整型变量,当然也包括 byte
类型:
var ch byte = 'a'