基础数据类型和复合类型

基础类型 标量(scalar)


标量的定义是:单独的对象、值。Rust提供了四种基本的标量:布尔型、字符型、整型、浮点型。

布尔型

Rust里的bool有两个字面值:truefalse,例:

1
2
3
4
5
6
let mut fg = false;
if !fg {
fg = true;
}
if true {
}

字符型

Rust的char字面值用两个'包围单个char表示,与其他语言不同的是,Rust的char大小为4Bytes,因为Rust里的char编码格式为Unicode编码,意味着单个char能存储单个字母、中文、日文、emoji等,例:

1
2
3
4
let ch = 'a';
let ch = '😻';
let ch = '中';
let ch = 'あ';

但是Rust的strString不以char为单位存储字符,而是按照UTF-8格式存储字符串。

整型(Integer)

Rust中整型分有符号和无符号两类,下面表格中列出了Rust原生的整型:

长度 有符号 无符号
1Byte i8 u8
2Bytes i16 u16
4Bytes i32 u32
8Bytes i64 u64
16Bytes i128 u128
基于计算机架构 isize uszie

可能有人会问为什么原生的只有i(2^(2+n)),而没有i19i24之类的。

我从网上查到了较详细的解释,可参照Beginning_Rust:使用原始类型(第六章)(完+1)

这里重点说明一下isizeuszie,当编译目标为x32架构时,其长度为32bit,当编译目标是x64架构时,其长度为64bit因为size类型主要用于内存地址、位置、索引、记录长度(或相对偏移量)或大小。

运算符类型 运算符
算术运算符 +,-,*,/,%
关系运算符 ==,!=,<,>,<=,>=
位运算符 &,|,^,!(按位非/按位取反),<<,>>
赋值运算符 =,{+,-,*,/,%}=,{&,|,^,<<,>>}=

同许多语言一样,Rust的整型支持许多运算符操作,但与C/C++不同的是,Rust 没 有 自增(++)自减(--)运算符!另外,也不能对整型使用逻辑运算符(逻辑运算符只能用于bool,要想使用就必须先将Integer转成bool,类似的,关系运算符两边的类型也必须相同)。

1
2
3
4
5
6
7
8
fn main() {
let mut a = 0;

// a++; 这一句无法编译,因为整型没有自增运算符

if a && true {
}
}

尝试编译上述代码,编译器会报错:

1
2
|     if a && true {
| ^ expected `bool`, found integer

至于为什么没有自增自减运算符,Rust FAQ(|)对此有较合理的答案,可自行参阅。

当然这在某些场景下会带来很大的麻烦(比如刷题),但请放轻松,除了需要将赋值分开写而不能连着写外,又有多大的损失呢?(如果你是压行狂魔的话当我没说)

附加内容:整型溢出

Rust的整型溢出比较特殊:在debug编译运行时,如发生整型溢出,就会panic(Rust术语,指程序抛出错误信息并停止运行),报错发生了整型溢出;在release编译运行时,则能正常运行而不panic,此时,溢出的部分将会被消除,例如一个u8255时,加上1就会变成0

直接使用整型溢出的特性在Rust中被认为是一种错误用法,需要的话,请使用std::num::Wrapping

浮点型

Rust目前只有两个采用IEEE-754标准存储运算的原生浮点型:f32f64目前Rust并没有设计f16f128(可能将来会有,消息不明),不过已经有第三方库(实验中)f128实现了f128

显式声明字面值类型


在某些情况下,可能会需要确定字面值的类型(因为默认的整型字面值类型为i32,浮点字面值类型为f64),这时可以在字面值两边添加一些字符以确定其类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 将123类型确定为`u8`,此时a的类型也会变成u8
// 当然你也可以直接写成let a: u8 = 123;
let a = 123u8;
// 将45类型确定为f32
let b = 45f32;

// 默认的浮点字面值常量为`f64`,字面值后加上f32可确定为`f32`
let c = 67.8f32;

// 另外,rust允许有如下写法,此时d为`f64`
let d = 67.;
// 但是不允许写let d = 67.f32;
// 不过可以写成let d = 67f32;

// 对于`char`,有时我们要将其转成ASCII码(`u8`),就可以在字面值前添加字符`b`
// 因为是转成ASCII码,所以此时b''里的字符只能为ASCII支持的字符。
let asci = b'a'; // asci为`u8`

普通类型强转

Rust提供隐式的普通类型强转,大致格式为:数据 as 目标类型,例如:

1
let i = 4.5 as i32; // i == 4

复合类型


数组

同其它语言一样,Rust也有数组,数组里的每个元素的类型必须相同

声明一个数组,可使用多种方法:

1
2
3
4
5
6
7
8
9
10
11
// 格式let xx: [元素类型; 数量] = ,数组类型可不写,让编译器推断。
let arr: [i32; 3] = [1, 2, 3];
let arr = [1, 2, 3];
let arr = [1u8, 2, 3]; // 注意这样其他的数也会确定为`u8`,arr类型为[u8; 3]
let arr = ['a', 'b', 'c'];

// let xxx = [元素值; 数量]
let arr = [1; 3]; //声明一个数组[1, 1, 1]
// 注意这种方法有限制
// 1. 数量必须为常量(Rust"所有类型"必须在编译前确定大小)
// 2. 元素类型必须实现了`copy`(超前知识,先不解释)

访问数组中的元素可以直接使用下标访问,例如arr[0]

还有,Rust数组的元素数量必须是常数,因为编译器需要在编译时就能确定数组的大小,这样就能分配栈内存给数组以加快效率。如果你想使用动态数组,你可以使用Vec(Vector),相对的,因为Vec大小无法在编译时确定,Rust只能为其分配堆内存,其效率也会比数组更低。

元组(tuple)

数组虽然很灵活,但当我们需要将一堆类型不同的数据放到一个类型中时数组就无能为力了。

为了解决这个问题,我们可以直接声明一个结构体,但更方便的方法是使用元组。

元组类型形如(T1, T2, T3,..., Tn),文字不好描述,下面我结合代码解释:

1
2
3
4
// 声明一个(i32, f64)
let tu: (i32, f64) = (12, 3.4);
// 声明一个(bool, char, i32, f64)
let tu = (true, '例', 12, 3.4);

而访问一个元组可以通过使用以下方式访问:

1
2
3
4
5
let tu: (true, '例', 12, 3.4);
let b = tu.0; // b == true;
let c = tu.1; // c == '例';
let i = tu.2; // i == 12;
let f = tu.3; // f == 3.4;

元组支持所有用Rust定义的类型,意味着所有Rust原生类型和自己定义的类型都能放入元组中。

回顾一下,1.3中我提到

...main()函数无返回值(返回值为())...

其中的()元组是特殊的元组,()元组又被称为单元类型(unit type),字面值()又被称为单元值(unit value)(()既可以视为类型,也可以视为字面值)。如果一个函数没有写返回值,编译器会自动补上返回(),并为函数注明返回类型为()元组,但是我们在写无返回值函数时不需要也不建议写明返回()和返回类型为()元组。