标量类型(Scalar Types)
在Rust中,一共有4中标量类型, 也就是基本数据类型,分别是:
- 整型(Integers)
- 浮点型(Floats)
- 布尔型(Boolean)
- 字符型(Characters)
整型
无符号整型(Unsigned) | 有符号整型(Signed) |
---|---|
u8 | i8 |
u16 | i16 |
u32 | i32 |
u64 | i64 |
u128 | i128 |
usize | isize |
其中分为无符号整型(以u
开头),和有符号整型(以i
开头),后面的数据代表该类型在内存中占多少bits
. 其中usize
类型是个例外,usize
类型的位数取决于目标平台的架构:
- 在 32 位架构上,
usize
是 32 位的。 - 在 64 位架构上,
usize
是 64 位的。
usize
类型通常用于索引集合和与内存大小相关的操作,其大小与平台的内存寻址能力相匹配,确保在任何平台上都能高效地进行这些操作。例如可以用于array
或vector
的索引的类型。isize
与usize
类似,只不过它是有符号的而已。
如果在声明一个整型变量时没有显式的标准类型的话,整型变量默认会被Rust识别为i32
类型,因为这个类型的性能通常是最好的,即使是在64位的系统上也是如此。
需要注意的是,虽然Rust有这些整型,但根据你的系统位数不同,有的整型类型可能不被支持,比如在一个16-bit
的系统中,就只支持16位及以下的整型类型(u8
,i8
, u16
,i16
)和usize
, isize
.
整型与进制
进制 | 写法示例 | 说明 |
---|---|---|
十进制(Decimal) | 100000 | 直接以数字开头 |
十六进制(Hex) | 0xdeadbeef | 以0x 开头的数字会被识别为16进制 |
八进制(Octal) | 0o77543211 | 以0o 开头的数字会被识别为8进制 |
二进制(Binary) | 0b11110011 | 以0b 开头的数字会被识别为二进制 |
字节(byte) | b'A' | 用b开头,单引号内为一个ASCII码范围内的字符,因此byte类型在Rust中跟u8类型是等价的,因为他们都是0-255之间的数字 |
Rust会自动忽略在数字中出现的_
, 因此可以在数字中间任意位置加上_
以方便我们阅读代码,例如:
进制 | 写法示例 | 加下划线写法 |
---|---|---|
十进制(Decimal) | 100000 | 100_000 |
十六进制(Hex) | 0xdeadbeef | 0xde_ad_be_ef |
八进制(Octal) | 0o77543211 | 0o7754_3211 |
二进制(Binary) | 0b11110011 | 0b1_1_1_1_0_0_1_1 |
浮点型
浮点类型比整型简单许多,只有两种类型:
- f32
- f64
他们之间的区别就是精度上的不同,默认情况下浮点数的类型是f64
, 但是如果你的操作系统位数是低于64位的,f64
的性能就会很差,这是需要注意的一点。
浮点类型的格式遵循IEEE-754标准规范,由整数位,小数点和小数位组成,如:
rust
3.1415926 // legal
.1 // illegal, 必须有整数位
0.1 // legal
数字类型的声明方法
在定义一个数字类型的变量时, 有两种标注类型的方法。 第一种与我们之前介绍变量声明的章节中一样:
rust
let x: u16 = 5;
let y: f32 = 3.14;
第二种方法,也可以将类型作为后缀,放在值的后面,如下:
rust
let x = 5u16;
let y = 3.14f32;
为了方便阅读,也可以像在上一节中所说的一样,在数字中加上_
, 如下:
rust
let x = 5_u16;
let y = 3.14_f32;
这种方法在我们需要给一些泛型函数传递指定类型的参数时很有帮助, 比如一个函数有一个参数x
, 这个参数x
的类型可以是u16
, 也可以是u32
, 根据参数的类型不同,函数的行为会发生变化, 此时如果我需要传入的参数值为5, 那么我就需要为这个5指定类型,那么便可以写为5u16
或者 5u32
。
布尔型
布尔型只有两个可能的值:
- true
- false
注意都是小写。 可以如下定义一个布尔型变量:
rust
let b: bool = true;
let c = false;
布尔型不是数字,因此不可以用于数学计算,除非用as
关键词把它转换,例如:
rust
fn main(){
let x = 10 - true;
println!("{}", x);
}
这样是会报错的, 但是改成下面这样就不会了:
rust
fn main(){
let x = 10 - true as u8;
println!("{}", x);
}
上面的代码中true as u8
会把布尔型转换成整型(1u8),因此最终程序输出的结果是9
。
字符型
char
这个类型的命名并不准确,每个char
类型的变量应该都是一个标准的unicode
值, 例如下面的值都是合法的:
rust
let my_letter = 'a';
let i_kratkoa = 'й' ;
let ideograph = '你';
let my_fire = '🔥'
如上, 一个char
变量的值可能是一个英文字母,也可能是一个其他语言中的字母,也可能是一个汉字, 甚至可以是一个表情符号,还可能是一些不可打印的控制符号,只要它是在unicode
可以表示的范围内,一个char
类型的变量占4 bytes的空间。
其他语言中, string
一般都是由char
组成的, 但是在Rust中却不是。Rust中的String
中的元素是UTF-8格式的,而char
类型不是, 因此String
并没有用到char
类型。
我们的代码编辑器显示的源代码也是UTF-8格式的,因此,我们在代码中看到的一个字符,其实并不等同于Rust中的一个char
。
复合类型(Compound Types)
顾名思义, 复合类型就是把多个不同数据类型的值组合在一起,成为一个自定义的数据类型。
元组(Tuple)
我们要介绍的第一种复合类型是元组(tuple), 元组中可以存储多个不同类型的值,如下可以简单的声明并初始化一个元组:
rust
let info = (1, 3.3, 999);
上面的元组中包含了3个数字,由于没有显式的标注变量类型, 根据我们前面的章节内容, 1
和 999
会被自动识别为 i32
型, 3.3
会被自动识别为f64
型。如果要显式的标注元组类型,那么我们可以自然而然的想到应该像这样做:
rust
let info: (u8, f64, i32) = (1, 3.3, 999);
元组操作
在Rust中想要访问元组中的元素有两种方式, 第一种是用.
号, 通常也被叫做字段访问操作符, 例如:
rust
let info: (u8, f64, i32) = (1, 3.3, 999);
let jets = info.0; // 1
let fuel = info.1; // 3.3
let ammo = info.2; // 999
我猜想Rust用.
而不用[ ]
来访问元组元素的原因,可能是为了暗示这样一个事实:元组成员的数据类型不一定是全部一样的。元组的字段是没有名称的,所以我们使用下标来访问元组成员, 和其他大多数编程语言一样, 元组的下标从0
开始。
第二种访问元组成员的方法类似前面的章节中, 一次性为多个变量赋值,我们可以用一组与元组个数相同的变量直接解构一个元组,并将元组中的每个成员赋值给这些变量,如下:
rust
let info: (u8, f64, i32) = (1, 3.3, 999);
// let jets = info.0; // 1
// let fuel = info.1; // 3.3
// let ammo = info.2; // 999
let (jets, fuel, ammo) = info;
元组长度限制
在Rust中,并没有严格规定元组的长度限制。然而,值得注意的是,Rust 标准库为长度最多为 12 的元组实现了某些特质,比如 Debug 和 Clone。这意味着如果你有一个长度超过 12 的元组,你可能需要为这个元组手动实现这些特质,或者使用不同的数据结构。
举个例子,对于一个有 13 个元素的元组 (T1, T2, T3, ..., T13),你可能无法直接使用 println!("{:?}", tuple) 来打印这个元组,因为 Debug 特质默认只为长度最多为 12 的元组实现。这并不是说你不能创建长度超过 12 的元组,只是意味着某些元组操作可能不如较短元组那样开箱即用。
数组(Array)
与元组不同的是, 数组中存储的是一组相同类型的值。下面是一个声明并初始化数组的简单示例:
rust
let buf = [1, 2, 3];
如果想要初始化一个所有元素都相同的数组,也可以通过这种形式来书写: [元素值; 元素个数]
, 例如:
rust
let buf = [0; 3] // 等价于 let buf = [0, 0, 0];
标注数组的数据类型的方式跟元组不同,只能用[元素类型; 数组长度]
这样的格式, 下面给出了正确和错误的示例:
rust
let buf: [u8; 3] = [1, 2, 3]; // 正确
let buf: [u8, u8, u8] = [1, 2, 3]; // 错误
对于数组元素的索引,我们就跟其他语言一样,使用[下标]
的格式了,例如:
rust
buf[0]
数组长度限制
在 Rust 中,数组长度最好不要超过 32。 跟元组的长度限制类似,这主要是因为 Rust 标准库中对数组实现了某些特质(traits)的自动派生(derive)仅限于长度最多为 32 的数组。这些特质包括常用的 Debug、Clone、Hash、PartialEq、Eq、PartialOrd、Ord 等。
对于长度超过 32 的数组,这些特质就不会自动实现。这意味着如果你有一个长度超过 32 的数组,例如 [i32; 33],你将无法使用 println!("{:?}", array) 来打印这个数组,因为 Debug 特质没有为这样的数组实现。同样,你也不能自动获得数组的克隆、比较等能力。
这个限制源于 Rust 标准库的设计决策,目的是平衡编译时间和可用性。由于 Rust 在编译时需要为每种数组长度和每个特质生成特定的实现代码,过长的数组会显著增加编译器的负担,尤其是在编译大型项目时。
因此,当你需要长度超过 32 的数组时,通常推荐使用向量(Vec),向量是一个动态数组,可以容纳任意数量的元素,并且不受上述特质实现的限制。向量提供了灵活性和扩展性,适合于需要动态大小或长度未知的情况。在性能敏感的场合,或者当数组大小在编译时已知且不会变化时,固定长度的数组(如 [T; N])才是更好的选择。
小结
本章主要介绍了Rust的四种标量类型的使用方法与注意事项,以及元组与数组的基本知识,下一章将介绍Rust中的流控制语句。