博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接
博客内容主要围绕:
5G/6G协议讲解
高级C语言讲解
Rust语言讲解
文章目录
- Rust 基础数据类型介绍——数组、向量和切片
- 一、数组、向量和切片
- 1.1 数组
- 1.1.1 数组的声明方法1
- 1.1.2 数组的声明方法2
- 1.2 向量
- 1.2.1 向量分配方法1
- 1.2.2 向量分配方法2
- 1.2.3 向量分配方法3
- 1.2.4 向量分配方法4
- 1.2.5 向量内部实现介绍
- 1.2.6 向量的基本操作
- 1.3 切片
Rust 基础数据类型介绍——数组、向量和切片
Rust中的数据类型如下所示,我会分多篇博客来介绍,下面先看一个总览:
类型 | 简要说明 |
---|---|
i8、i16、i32、i64、i128、u8、u16、u32、u64、u128 | 给定位宽的有符号整数和无符号整数 |
isize、usize | 与机器字(32bit、64bit)一样大的有符号整数和无符号整数 |
f32、f64 | 单精度IEEE浮点数和双精度IEEE浮点数 |
bool | 布尔值 |
char | Unicode字符,32位宽(4字节) |
() | 单元元组(空元组) |
(char,u8,i32) | 元组(允许混合类型) |
Box<Attend> | 指向堆中值的拥有型指针 |
&i32、&mut i32 | 共享引用和可变引用,非拥有型指针,其生命周期不能超出引用目标 |
String | UTF-8字符串,动态分配大小 |
&str | 对str的引用,指向UTF-8文本的非拥有型指针 |
[f64;4]、[u8;256] | 数组,固定长度,其元素类型都相同 |
Vec[f64] | 向量,可变长度,其元素类型都相同 |
&[u8]、*mut [u8] | 对切片(数组或向量某一部分)的引用,包含指针和长度 |
Option<&str> | 可选值,或为None(无值),或者为Some(v)(有值,其值为v) |
Result<u64, Error> | 可能失败的操作结果,或者为成功值OK(v),或者为错误值Err(e) |
struct S { x: f32, y: f32 } | 具名字段型结构体 |
struct T(i32, char); | 元组型结构体 |
struct E; | 单元型结构体,无字段 |
enum Attend { OnTime, Late(u32)} | 枚举,或代数数据类型 |
&dyn Any、&mut dyn Read | 特型(trait)对象,是对任何实现了一组给定方法的值的引用 |
fn(&str)->bool | 函数指针 |
(闭包类型没有显式书写形式) | 闭包 |
一、数组、向量和切片
Rust使用三种类型来表示内存中的值序列:
- 类型
[T; N]
表示N
个值的数组,其类型为T
。数据的大小是在编译期确定的,并且是类型的一部分,不能追加新元素或缩小数组; - 类型
Vec<T>
可称为 T的向量,它是一个动态分配且可增长的T类型的值序列。向量元素存在于堆内存中,因此可以随意调整向量的大小; - 类型
&[T]
和&mut [T]
可称为T的共享切片和T的可变切片,它们是对一系列元素的引用,这些元素可以是数据或向量的一部分。共享切片&[T] 允许在多个读者之间共享访问权限,但不允许修改元素;可变切片&mut [T] 允许读取和修改元素,但不能共享;
给定这3种类型中的任意一种类型的值v,表达式v.len()
会给出 v 中的元素数,而v[i]
引用的是 v 中的第 i 个元素(i 的类型必须是 usize)。v的第一个元素是v[0],最后一个元素是v[v.len()-1]。Rust会检查 i 是否在正确的范围内,如果没有则会出现panic。
1.1 数组
1.1.1 数组的声明方法1
可以在声明变量的同时,通过 方括号 来初始化一个数组。
fn main() {
let array:[i32;3] = [2, 3, 5];
println!("{:?}", array);
assert_eq!(array.len(), 3);
}
1.1.2 数组的声明方法2
对于一些较长的数组,需要填充一些值时,可以使用[V; N]
方法,其中V是每个元素的值,N是长度。下面的代码声明了一个长度为300的数组,其中每个元素初始值为100。
fn main() {
let mut array = [100; 300];
for i in 1..array.len() {
array[0] += array[i];
}
println!("array[0]={}", array[0]);
assert_eq!(array.len(), 300);
}
Rust中没有任何能定义未初始化数组的写法。且数组的长度是其类型的一部分,并会在编译器固定下来。如果n是变量,则不能写成 [100; n] 以期望得到一个包含 n 个元素的数组。当你需要一个长度在运行期可变的数组时,请使用向量。
数组上看到的那些方法(遍历、查找、排序、填充、过滤等)都是作为切片而非数组的方法提供的。但Rust在搜索各种方法时会隐式地将对数组的引用转换为切片,因此可以直接在数组上调用任何切片的方法:
let mut chaos = [3, 5, 4, 1, 2];
chaos.sort();
assert_eq!(chaos, [1, 2, 3, 4, 5]);
上面的代码中sort()函数是定义在切片上的,但是由于它是通过引用获取的操作目标,因此Rust会隐式地生成一个整数组数的 &mut [i32] 切片,并将其传给 sort 来进行操作。
1.2 向量
向量Vec<T>
是一个可调整大小的T类型元素的数组,它是在堆上分配的。我们这里先介绍几种简单的分配向量的方法。
1.2.1 向量分配方法1
使用vec!
宏来分配向量,它使用起来感觉特别像数组:
fn main() {
let vector = vec![1,2,30];
println!("{}", vector.iter().product::<i32>());
}
与数组不同的是,我们可以动态地向它添加元素:
fn main() {
let mut vector = vec![1,2,30];
println!("{}", vector.iter().product::<i32>());
vector.push(40);
println!("{}", vector.iter().product::<i32>());
}
1.2.2 向量分配方法2
还可以通过给定值重复一定次数来构建向量,可以再次使用模仿数组字面量的语法:
fn main() {
let mut vector = vec![2;3];
println!("{}", vector.iter().product::<i32>());
}
1.2.3 向量分配方法3
vec!
宏相当于调用 Vec::new
来创建一个新的空向量,然后将元素压入其中,例如下面的代码:
fn main() {
let mut vector = Vec::new();
vector.push(3);
vector.push(7);
vector.push(9);
println!("{}", vector.iter().product::<i32>());
}
1.2.4 向量分配方法4
还有一种方法是从迭代器
生成的值构建一个向量:
fn main() {
let vector:Vec<i32> = (1..5).collect();
println!("{}", vector.iter().product::<i32>());
}
使用collect
时,通常需要指定类型,因为它可以构建出不同种类的集合,而不仅仅是向量。通过指定vector的类型,我们明确表达了自己想要哪种集合。
与数组类型一样,可以对向量使用切片的方法:
fn main() {
let mut palindrome = vec!["a man", "a plan", "a canal", "panama"];
palindrome.reverse();
println!("{:?}", palindrome);
}
上面的代码中,reverse
方法实际上是在切片上定义的,但是此调用会隐式地从此向量中借用一个 &mut [&str] 切片并在其上调用reverse。
1.2.5 向量内部实现介绍
Vec<T>
是由3个值组成的:
- 指向元素在堆中分配的缓冲区的指针;
- 缓冲区能够存储的元素数量,即向量容量;
- 目前实际包含的元素数量,也就是它的长度;
当缓冲区达到其最大容量时,往向量中添加另一个元素需要分配一个更大的缓冲区,将当前内容复制到其中,更新向量的指针和向量容量以指向新缓冲区,最后释放旧缓冲区。
如果事先知道向量元素数量,就可以调用Vec::with_capacity
而不是Vec::new
来创建一个向量,它的缓冲区足够的大,可以从一开始就容纳所有的元素,然后将所有元素逐个添加到向量中,而不会导致任何重新分配。当然,如果实际的元素数量超出了预估的数量,还是会重新分配缓冲区的。
vec!
宏就使用了上面的技巧,因为它知道最终向量将包含多少个元素。
向量的len
方法会返回它现在包含的元素数,而capacity
方法则会返回在不重新分配的情况下可以容纳的元素数:
fn main() {
let mut vector:Vec<i32> = Vec::with_capacity(2);
println!("len is {}", vector.len());
println!("capacity is {}", vector.capacity());
vector.push(2);
vector.push(3);
println!("len is {}", vector.len());
println!("capacity is {}", vector.capacity());
vector.push(4);
println!("len is {}", vector.len());
println!("capacity is {}", vector.capacity());
}
上面代码的运行结果如下图,最后打印出的容量大小不能保证恰好为4,但至少大于等于3,因为此向量包含3个元素。
1.2.6 向量的基本操作
可以在向量中任意位置插入元素和移除元素,不过这些操作会将受影响位置之后的所有元素向前或向后移动,因此如果向量很长就可能很慢。
下面的代码展示在恰当位置插入一个元素的insert
方法和删除一个恰当位置元素的remove
方法:
fn main() {
let mut v = vec![10, 20, 30, 40, 50];
println!("{:?}",v);
// 在索引为3的元素处插入35
v.insert(3, 35);
println!("{:?}",v);
// 移除索引为1的元素
v.remove(1);
println!("{:?}",v);
}
下面的代码展示使用push
方法在向量末尾添加一个元素,和pop
方法移除向量中的最后一个元素并返回其值:
fn main() {
let mut v = vec!["Snow Puff", "Glass Gem"];
println!("remove {:?}, current value is {:?}",v.pop(),v);
println!("remove {:?}, current value is {:?}",v.pop(),v);
println!("remove {:?}, current value is {:?}",v.pop(),v);
}
上面代码运行结果为:
由上面的执行结果可以看到,pop
方法返回的值是一个Option<T>
类型,如果:
- 向量为空,则返回
None
; - 如果其最后一个值为 v ,则返回
Some(v)
。
1.3 切片
切片是数组和向量中的一个区域,写作[T]
。由于切片可以是任意长度的,因此它不能直接存储在变量中或作为函数参数传递。切片总是通过引用传递。对切片的引用是一个胖指针
:
- 双字值;
- 第一个字是指向切片中第一个元素的指针;
- 第二个字是切片中元素的数量;
执行下面代码后的内存布局如下图所示。
fn main() {
let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707];
let a: [f64; 4] = [0.0, -0.707, -1.0, -0.707];
let sv: &[f64] = &v;
let sa: &[f64] = &a;
}
上面最后两行代码,将&Vec<f64>
和&[f64; 4]
转换为直接指向数据的切片引用。
普通引用是指向单个值的非拥有型指针,而切片的引用是指向内存中一系列连续值的非拥有型指针。如果要写一个对数组和向量进行操作的函数,那么切片引用就是不错的选择。例如下面的代码:
fn main() {
let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707];
let a: [f64; 4] = [0.0, -0.707, -1.0, -0.707];
fn print(n: &[f64]) {
for elt in n {
println!("{}", elt);
}
}
print(&a); // 打印数组
print(&v); // 打印向量
}
还可以使用范围值对数组或向量进行索引,以获取一个切片的引用,该引用既可以指向数组或向量,也可以指向一个既有切片:
print(&v[0..2]) //打印v的前两个元素
print(&a[2..]) //打印从a[2]开始的元素
print(&sv[1..3]) //打印v[1]和v[2]
与普通数组访问一样,Rust会检查索引是否有效。尝试访问超出数组末尾的切片会导致panic
由于切片几乎总是出现在引用符号之后,因此通常只将 &[T] 或 &str 之类的类型称为切片。