3.3 数据类型:复合类型
3.3.0. 写在正文之前
欢迎来到Rust自学的第三章,一共有6个小节,分别是:
- 变量与可变性
- 数据类型:标量类型
- 数据类型:复合类型(本文)
- 函数和注释
- 控制流:
if else - 控制流:循环
通过第二章的小游戏(没看的初学者强烈建议看一下),相信你已经学会了基本的Rust语法,而在第三章我们将更深一层,了解Rust中的通用的编程概念。
3.3.1. 复合类型的简介
- 复合类型可以将多个值组合成一个类型。
- Rust提供了两种基础的复合类型:元组和数组。
3.3.1. 元组(Tuple)
元组的特点:
- 元组可以将多个不同类型的值组合成一个类型。
- 元组的长度是固定的:一旦声明就无法改变。
创建元组:
- 在小括号里,将值用逗号分开。
- 元组中的每个位置都对应一个类型,元组中各元素的类型不必相同。
fn main(){
let tup: (u32, f32, i64) = (6657, 0.0721, 114514);
println!("{},{},{}", tup.0, tup.1, tup.2);
// Output: 6657,0.0721,114514
}
获取元组元素值:
- 可以使用模式匹配来解构(destructure)一个元组,从而获取元素值。
fn main(){
let tup: (u32, f32, i64) = (6657, 0.0721, 114514);
let (x, y, z) = tup;
println!("{},{},{}", x, y, z);
// Output: 6657,0.0721,114514
}
访问元组的元素:
- 在元组变量后使用点标记法,后接元素的索引号。
#![allow(unused)]
fn main() {
println!("{},{},{}", tup.0, tup.1, tup.2);
}
3.3.2. 数组
数组的特点:
- 数组中的每个元素的类型必须相同。
- 数组也可以将多个值放入一个类型。
- 数组的长度是固定的。
声明数组:
- 在中括号里,各值用逗号分开。
#![allow(unused)]
fn main() {
let a = [1, 1, 4, 5, 1, 4];
}
数组的用处:
- 如果想把数据放在栈(Stack)上而不是堆(Heap)上,或者想保证有固定数量的元素,这时使用数组更有好处。
- 数组没有向量(vector,以后会讲)灵活。
- 向量由标准库提供,而数组内置于语言中,并通过prelude模块提供(prelude也是标准库的一部分)。
- 向量的长度可以改变。
- 不确定应该使用数组还是向量时,大概率应该使用向量。
数组的类型语法:
- 数组的类型以
[类型; 长度]的形式表示。
#![allow(unused)]
fn main() {
let machine: [u32; 4] = [6, 6, 5, 7];
}
声明数组的其他方法:
- 如果数组的每个元素值都相同,那么可以:
- 在中括号里指定初始值
- 然后跟着一个
; - 最后加上数组的长度
#![allow(unused)]
fn main() {
let a = [3; 3];
let b = [3, 3, 3];
}
这个例子中,a和b是等价的。
访问数组的元素:
- 数组是栈上分配的一块连续内存。
- 可以使用索引来访问数组的元素。
#![allow(unused)]
fn main() {
let machine = [6, 6, 5, 7];
let wjq = machine[0];
}
- 如果索引越界:
- 在编译器能够证明错误的情况下,Rust可能在编译时发现它
- 否则会在运行时panic,因为Rust不允许程序继续读取该地址的内存
数组背后是一块连续内存。假设数组的第一个元素在内存位置x,那么第二个元素位于x + 第一个元素的大小,之后以此类推。
如果索引大于数组的实际长度,程序就会读取数组之外的内存,而那里的内容可能是任意值。在C中完全没有边界检查。在C++中普通数组也没有,只有std::array有;在Rust里强制进行边界检查。
| Feature | C | C++ | Rust |
|---|---|---|---|
| Memory model | Contiguous | Contiguous | Contiguous |
| Safety | No bounds checking | std::array has bounds checking; ordinary arrays do not | Bounds checking is enforced |
| Dynamic arrays | Manual memory management required | std::vector | Vec |
| Multidimensional arrays | Yes | Yes | Yes |
| Special abilities | Simple and efficient | Rich STL containers | Ownership and borrow checking |
如果编译器能够证明索引越界,就会在编译期拒绝该程序:
#![allow(unused)]
fn main() {
let a = 5;
let machine = [6, 6, 5, 7];
let wjq = machine[a]; // Error: this operation will panic at runtime
}
如果索引要到运行时才能确定,程序可以编译通过,但越界访问会在运行时panic:
fn get_index() -> usize {
5 // 设想这个值来自输入或其他地方
}
fn main() {
let machine = [6, 6, 5, 7];
let wjq = machine[get_index()];
}
Output:
thread 'main' panicked at src/main.rs:7:15:
index out of bounds: the len is 4 but the index is 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace