Rust 笔记:Rust 语言中使用 vector(向量)

简介: 本文介绍 Rust 语言中的向量,包括向量与数组的区别,向量的相关API的用法等等。

Rust 笔记:Rust 语言中使用 vector(向量)


作者李俊才 (jcLee95)https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343

邮箱 :291148484@163.com

本文地址https://blog.csdn.net/qq_28550263/article/details/130876619


【介绍】:本文介绍 Rust 语言中的向量,包括向量与数组的区别,向量的相关API的用法等等。

上一节:《 Rust 语言中的枚举 | 下一节:《 -


目 录



1. 为什么需要 Vector

1.1 Rust 数组的简单回顾

在 Rust 中,数组是一种固定大小的数据结构,需要在编译时指定长度。使用数组是很方便的,本节我们先回顾以下 Rust 语言中数组的一些基本特点。

1.1.1 数组在内存中是连续存储的

当需要在栈上分配一块连续的内存空间时,由于数组在内存中是连续存储的,这意味着元素的存储是相邻的,可以通过索引快速访问元素。这在需要快速、直接访问元素的场景中非常有用。

// 创建一个长度为5的整数数组
let array: [i32; 5] = [1, 2, 3, 4, 5];
// 访问数组元素
println!("第一个元素: {}", array[0]);
println!("第三个元素: {}", array[2]);

1.1.2 数组在编译时进行长度检查

当需要确切知道数组的长度,并希望在编译时进行检查时,由于 Rust 数组能够在编译时进行长度检查,从而确保不会越界访问。这在需要确保数组长度不变的情况下使用,以防止意外的错误。

// 创建一个长度为3的字符串数组letarray: [String; 3] = [
String::from("Hello"),
String::from("World"),
String::from("Rust"),
];
// 访问数组元素forelementin &array {
println!("{}", element);
}

需要注意的是,Rust 中的数组长度必须是一个 编译时常量,这意味着 在创建数组时需要确切地知道其长度,并且无法在运行时动态改变数组的大小。这种静态长度的限制使得数组在某些情况下不够灵活,无法适应动态的数据集合。

1.2 Vector 应运而生

1.2.1 Rust 数组的缺陷

虽然 Rust 数组在一些场景下非常有用,但它有时候使用起来却不那么方便。首先,由于 Rust 数组的长度在编译时确定,并且在运行时无法改变。这意味着 一旦数组被创建,它的大小就是固定的,无法根据实际需求进行动态调整。这限制了数组在需要动态增长或缩小的场景下的灵活性。例如:

// 创建一个长度为3的整数数组letarray: [i32; 3] = [1, 2, 3];
// 无法动态改变数组长度// array.push(4); // 错误!数组无法调用 `push` 方法

也正是由于数组的长度固定,当需要在数组已满时添加更多元素时,需要手动重新分配内存空间,并将现有数据复制到新的数组中。这个过程需要手动管理,增加了编码复杂性,并且可能导致性能损失。例如:

// 创建一个长度为3的整数数组let mutarray: [i32; 3] = [1, 2, 3];
// 当数组已满时,需要手动重新分配内存letnew_array: [i32; 6] = [array[0], array[1], array[2], 4, 5, 6];
array=new_array;
// 现在数组可以容纳更多元素array[3] =7;
array[4] =8;
array[5] =9;

在这个例子中,当需我们要向数组中添加更多元素时,需要手动重新分配内存,并复制现有数据到新数组的过程。这不仅繁琐,还容易引入错误和性能问题。

1.2.2 克服数组缺陷:这就是需要 Vector 的原因

为了克服上面提到的数组的各种缺陷, Rust 提供了 向量Vector)类型——这是一种可以在 运行时 动态改变大小,并且 自动处理内存分配 和 元素的移动 的数据结构。向量让我们在处理动态大小的数据集合时更加灵活和方便。

2. 对比数组:Rust 向量 初体验

2.1 动态大小

向量的长度可以在运行时动态改变,不像数组一样需要在编译时指定固定长度。这使得向量更适用于需要动态增长或缩小的场景,可以根据实际需求自动调整大小。

// 创建一个空的向量let mutvector: Vec<i32>=Vec::new();
// 向向量添加元素vector.push(1);
vector.push(2);
vector.push(3);

2.2 自动内存管理

向量自动处理内存分配和元素的移动。当向量的长度超过当前分配的内存空间时,它会自动重新分配更大的内存空间,并将现有的元素复制到新的内存空间中。这样可以避免手动管理内存和复制数据的繁琐工作,从而减少了出错的可能性。

// 创建一个空的向量let mutvector: Vec<i32>=Vec::new();
// 向向量添加元素vector.push(1);
vector.push(2);
vector.push(3);
vector.push(4); // 超过当前内存空间,向量会自动重新分配更大的空间// 自动处理内存分配和元素的移动// 无需手动重新分配内存和复制数据

2.3 灵活的元素类型

Rust 向量可以容纳不同类型的元素,而不限于特定的类型。这使得向量更加灵活,可以存储和处理各种类型的数据。

// 创建一个包含不同类型元素的向量let mutvector: Vec<dynstd::fmt::Debug>=Vec::new();
vector.push(1);
vector.push("hello");
vector.push(true);

2.4 丰富的方法和功能

Rust 向量提供了丰富的方法和功能,用于操作和管理向量的元素。例如,可以使用迭代器遍历向量、使用 len() 获取向量长度、使用 contains() 检查元素是否存在等。

letvector=vec![1, 2, 3, 4, 5];
// 使用迭代器遍历向量forelementin &vector {
println!("{}", element);
}
// 获取向量长度letlength=vector.len();
println!("向量长度: {}", length);
// 检查元素是否存在letcontains_two=vector.contains(&2);
println!("向量是否包含2? {}", contains_two);

3. Vec 结构体

Rust 语言中,Vec 是一个结构体类型,定义在标准库的 alloc::vec::Vec 模块中(Struct alloc::vec::Vec),表示一种连续的可增长数组类型,写为 Vec ,是“vector”的缩写。:

pub struct Vec<T, A: Allocator = Global> { /* private fields */ }

Vec 的源码请参考:https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#396-399 以下是部分摘抄:

impl<T>Vec<T> {
#[inline]#[rustc_const_stable(feature = "const_vec_new", since = "1.39.0")]#[stable(feature = "rust1", since = "1.0.0")]#[must_use]pubconstfnnew() ->Self {
Vec { buf: RawVec::NEW, len: 0 }
    }
// ... 省略其它方法}
impl<T, A: Allocator>Vec<T, A> {
#[inline]#[unstable(feature = "allocator_api", issue = "32838")]pubconstfnnew_in(alloc: A) ->Self {
Vec { buf: RawVec::new_in(alloc), len: 0 }
    }
// ... 省略其它方法}
impl<T: Clone, A: Allocator>Vec<T, A> {
#[cfg(not(no_global_oom_handling))]#[stable(feature = "vec_resize", since = "1.5.0")]pubfnresize(&mutself, new_len: usize, value: T) {
letlen=self.len();
ifnew_len>len {
self.extend_with(new_len-len, ExtendElement(value))
        } else {
self.truncate(new_len);
        }
    }
// ... 省略其它方法}
impl<T, A: Allocator, constN: usize>Vec<[T; N], A> {
#[unstable(feature = "slice_flatten", issue = "95629")]pubfninto_flattened(self) ->Vec<T, A> {
let (ptr, len, cap, alloc) =self.into_raw_parts_with_alloc();
let (new_len, new_cap) =ifT::IS_ZST {
            (len.checked_mul(N).expect("vec len overflow"), usize::MAX)
        } else {
// SAFETY:// - `cap * N` cannot overflow because the allocation is already in// the address space.// - Each `[T; N]` has `N` valid elements, so there are `len * N`// valid elements in the allocation.unsafe { (len.unchecked_mul(N), cap.unchecked_mul(N)) }
        };
unsafe { Vec::<T, A>::from_raw_parts_in(ptr.cast(), new_len, new_cap, alloc) }
    }
}
impl<T, A: Allocator>Vec<T, A> {
#[cfg(not(no_global_oom_handling))]fnextend_with<E: ExtendWith<T>>(&mutself, n: usize, mutvalue: E) {
self.reserve(n);
unsafe {
let mutptr=self.as_mut_ptr().add(self.len());
// Use SetLenOnDrop to work around bug where compiler// might not realize the store through `ptr` through self.set_len()// don't alias.let mutlocal_len=SetLenOnDrop::new(&mutself.len);
// Write all elements except the last onefor_in1..n {
ptr::write(ptr, value.next());
ptr=ptr.add(1);
// Increment the length in every step in case next() panicslocal_len.increment_len(1);
            }
ifn>0 {
// We can write the last element directly without cloning needlesslyptr::write(ptr, value.last());
local_len.increment_len(1);
            }
        }
    }
}
impl<T: PartialEq, A: Allocator>Vec<T, A> {
#[stable(feature = "rust1", since = "1.0.0")]#[inline]pubfndedup(&mutself) {
self.dedup_by(|a, b| a==b)
    }
}
// ... ...

如果你觉得这个 vector 的类型不能满足你的需要,其实也完全可以参考其代码自己实现一个。

4. 选择:何时使用数组,何时使用向量

4.1 向量 同样是有缺陷的

又有点就有缺点,就像一句不知出处名言说所,“当上帝关上一扇窗的同时,就为你打开了另外的一扇窗”。本节我们讨论的是 在Rust 语言中,相比于 数组,向量的一些劣势。

4.1.1 额外的内存开销

由于向量是动态大小的,它需要在堆上分配内存来存储元素,并且还需要一些额外的内存空间来管理和追踪向量的大小和容量。这导致向量相比于数组具有更高的内存开销。

4.1.2 运行时性能开销

由于向量需要在运行时动态分配内存并进行元素的移动,这会带来一定的性能开销。向量的操作(如添加、删除、调整大小等)可能需要重新分配内存和复制数据,这可能会影响程序的性能。

4.1.3 内存碎片化

向量在进行动态大小调整时,可能会导致堆内存中出现碎片化。当向量的容量不再满足需求,需要重新分配更大的内存空间时,如果没有连续的大块可用内存,就需要进行内存拷贝和重新分配,从而导致内存碎片化的问题。

4.1.4 其它劣势

堆分配带来的限制

向量的元素始终存储在 上,并且需要使用指针进行访问。这在某些情况下可能引入额外的间接访问开销,尤其是当元素数量较少且局部性较高时,相比于数组的栈分配,可能会有一些性能上的损失。

难以预测的运行时错误

由于向量在运行时动态调整大小,可能出现内存分配失败的情况。当向量无法分配足够的内存来存储元素时,可能会导致运行时错误,需要适当处理这些错误。

4.2 依据不同场景,选用数组和向量

在了解 Rust 向量的优缺点后,进一步需要的就是如何依据其优缺点进行选用。本节讨论的是,在不同的需求场景下,如何在使用 数组 还是 向量中进行选择。

4.2.1 选择的考据因素

在 Rust 编程语言中,选择使用数组还是向量取决于以下几个方面的考虑:

固定大小 还是 动态大小:

  • 数组适合在编译时就能确定长度的情况。当需要在栈上分配一块连续的内存空间,并且长度固定、不会改变时,数组是一个合适的选择。例如,存储像素数据的图像缓冲区或表示矩阵的二维数组等场景。
  • 向量适合在运行时动态改变大小的情况。当长度需要根据实际需求进行动态调整时,或者需要在堆上分配内存并且在运行时进行扩展和缩小时,向量是更为灵活的选择。例如,处理动态输入数据、动态生成数据集合等场景。

内存管理需求:

  • 数组在编译时确定长度,所需的内存空间也在编译时进行分配,不需要动态的内存管理。这对于需要在编译时就明确控制内存使用的场景非常有用。
  • 向量自动处理内存分配和元素的移动,可以根据实际需要动态地分配和释放内存。这对于需要在运行时根据数据集合的大小进行动态内存管理的场景非常有用。

元素类型的灵活性:

  • 数组要求所有元素的类型必须相同,无法容纳不同类型的数据。当需要确保所有元素具有相同类型时,数组是合适的选择。例如,存储同一类型的传感器数据等场景。
  • 向量可以容纳不同类型的元素,提供更大的灵活性。当需要存储和处理不同类型的数据集合时,向量是更合适的选择。例如,处理来自不同传感器的数据集合、处理不同类型的配置信息等场景。

4.2.2 具体的例子

一个选用数组的例子

// 创建一个长度为10的整数数组,存储像素数据letpixels: [u8; 10] = [255, 0, 128, 64, 200, 100, 0, 0, 255, 255];
// 创建一个长度为3的字符串数组,存储命令行参数letargs: [String; 3] = [
String::from("program"),
String::from("arg1"),
String::from("arg2"),
];

在这个例子中,,数组被用来存储特定大小的数据集合,如像素数据和命令行参数。这些数据集合在编译时就已经具有固定的大小,并且 不需要在运行时动态调整大小。因此,数组是合适的选择,因为它提供了固定大小的数据结构,并且在编译时就确定了内存分配。

一个选用向量的例子

// 创建一个空的整数向量,用于存储动态输入数据let mutnumbers: Vec<i32>=Vec::new();
// 向向量添加动态输入的数据letinput=42;
numbers.push(input);
// 创建一个动态大小的字符串向量,用于存储不同类型的数据let mutdata: Vec<dynstd::fmt::Debug>=Vec::new();
data.push(42);
data.push("hello");
data.push(true);

在这个例子中,数据集合的大小需要在运行时动态改变的,如动态输入数据和不同类型的数据集合。显然数组是不具备这样的能力的,因而我们选择使用了向量。

总而言之,要是能够使用数组的场景下,我们一般选用数组,从而获取更高的性能。如果数组实在无法满足要求时,我们考虑选择使用向量。

5. Rust Vec 的方法解析

注意:不需要死背,只需要有个印象,然后记住常用又简单的一些方法如pop、push等等,在有需要的时候再来此查阅即可。

5.1 方法总览

以下是逐个解析 Rust 语言中 Vec 结构体的方法:

方法名 描述
allocator 返回用于分配 Vec 内部缓冲区的分配器(allocator)。该方法返回 Option<&A>,其中 A 是用于分配内存的分配器类型。
append 将一个 Vec 的所有元素追加到另一个 Vec 中。这个方法将原始 Vec 中的元素转移所有权到目标 Vec 中。
as_mut_ptr 返回指向 Vec 缓冲区第一个元素的可变指针。
as_mut_slice 返回一个可变的切片,它引用整个 Vec 的内容。
as_ptr 返回指向 Vec 缓冲区第一个元素的不可变指针。
as_slice 返回一个不可变的切片,它引用整个 Vec 的内容。
capacity 返回 Vec 当前分配的内存容量(以元素为单位)。
clear 清空 Vec 中的所有元素,但不释放分配的内存。
dedup 去除 Vec 中相邻的重复元素,只保留其中一个。
dedup_by 使用指定的比较闭包(closure)去除 Vec 中相邻的重复元素。
dedup_by_key 使用指定的键提取函数去除 Vec 中相邻的重复元素。
drain 返回一个迭代器,它从 Vec 中移除并产生指定范围的元素。
drain_filter 返回一个迭代器,它从 Vec 中移除并产生满足指定条件的元素。
extend_from_slice 将一个切片的所有元素追加到 Vec 中。
extend_from_within 使用指定的闭包从 Vec 中的一部分元素创建并追加新的元素。
from_raw_parts 从给定的指针和长度创建一个 Vec,可以是未初始化的。
from_raw_parts_in 在指定的分配器上从给定的指针和长度创建一个 Vec
insert 在指定索引位置插入一个元素,并将后续元素依次后移。
into_boxed_slice Vec 转换为一个 Box<[T]>,将所有权转移到 Box 中。
into_flattened Vec<Vec<T>> 的嵌套结构展平为一个单独的 Vec<T>
into_raw_parts Vec 转换为元组 (ptr, len, cap),并保留所有权。
into_raw_parts_with_alloc Vec 转换为元组 (ptr, len, cap, alloc),并保留所有权。
is_empty 检查 Vec 是否为空。
leak Vec 转换为裸指针,并避免析构函数被调用。
len 返回 Vec 中当前存储的元素数量。
new 创建一个空的 Vec
new_in 使用指定的分配器创建一个空的 Vec
pop 移除并返回 Vec 中的最后一个元素。
push Vec 的末尾追加一个元素。
push_within_capacity Vec 的末尾追加一个元素,如果有足够的容量则不会分配新的内存。
remove 移除指定索引位置的元素,并将后续元素依次前移。
reserve 增加 Vec 的容量以容纳指定的附加元素数量。
reserve_exact 增加 Vec 的容量以容纳指定的附加元素数量,使容量完全匹配。
resize 更改 Vec 的长度,可能会插入默认值或删除元素。
resize_with 更改 Vec 的长度,使用指定的闭包生成新元素。
retain 保留满足指定条件的 Vec 中的元素,移除不满足条件的元素。
retain_mut 在指定条件下保留 Vec 中的可变元素,移除不满足条件的元素。
set_len 设置 Vec 的长度,可以超过当前容量,但是会产生未初始化的元素。
shrink_to Vec 的容量收缩到指定的大小。
shrink_to_fit Vec 的容量收缩到当前存储的元素数量。
spare_capacity_mut 返回一个可变引用,允许直接访问 Vec 的未使用的缓冲区。
splice 将另一个可迭代对象的元素替换为 Vec 中的一部分元素。
split_at_spare_mut Vec 拆分为两个可变切片,其中一个引用未使用的缓冲区。
split_off Vec 拆分为两个独立的 Vec,根据指定的索引位置。
swap_remove 移除指定索引位置的元素,并用最后一个元素替换它。
truncate Vec 截断为指定的长度。
try_reserve 尝试增加 Vec 的容量以容纳指定的附加元素数量。
try_reserve_exact 尝试增加 Vec 的容量以容纳指定的附加元素数量,使容量完全匹配。
with_capacity 使用指定的初始容量创建一个空的 Vec
with_capacity_in 使用指定的分配器和初始容量创建一个空的 Vec

5.2 容量和内存管理 相关方法

5.2.1 capacity 方法

该方法用于返回当前分配的内存容量(以元素为单位)。例如:

letvec=vec![1, 2, 3, 4, 5];
letcapacity=vec.capacity();
println!("Capacity: {}", capacity);

5.2.2reserve 方法

该方法用于增加 Vec 的容量以容纳指定的附加元素数量。例如:

let mutvec=vec![1, 2, 3];
vec.reserve(3); // 增加容量以容纳3个额外元素println!("Capacity after reserve: {}", vec.capacity());

5.2.3 reserve_exact 方法

该方法用于增加 Vec 的容量以容纳指定的附加元素数量,使容量完全匹配。例如:

let mutvec=vec![1, 2, 3];
vec.reserve_exact(4); // 增加容量以容纳4个额外元素println!("Capacity after reserve_exact: {}", vec.capacity());

5.2.4 shrink_to 方法

该方法用于将 Vec 的容量收缩到指定的大小。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.shrink_to(3); // 将容量收缩到3println!("Capacity after shrink_to: {}", vec.capacity());

5.2.5 shrink_to_fit 方法

该方法用于将 Vec 的容量收缩到当前存储的元素数量。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.pop(); // 移除一个元素vec.shrink_to_fit(); // 将容量收缩到当前存储的元素数量println!("Capacity after shrink_to_fit: {}", vec.capacity());

5.2.6 spare_capacity_mut 方法

该方法用于返回一个可变引用,允许直接访问 Vec 的未使用的缓冲区。例如:

let mutvec=Vec::with_capacity(10);
letspare_capacity=vec.spare_capacity_mut();
unsafe {
foriin0..spare_capacity {
// 直接修改未使用的缓冲区*spare_capacity.offset(iasisize) =i;
    }
vec.set_len(vec.len() +spare_capacity);
}
println!("Modified Vec: {:?}", vec);

5.2.7 with_capacity 方法:

该方法用于使用指定的初始容量创建一个空的 Vec。例如:

letvec: Vec<u32>=Vec::with_capacity(5);
println!("Capacity: {}", vec.capacity());

5.2.8 with_capacity_in 方法

该方法用于使用指定的分配器和初始容量创建一个空的 Vec。例如:

usestd::alloc::System;
letvec: Vec<u32, System>=Vec::with_capacity_in(5);
println!("Capacity: {}", vec.capacity());

这些方法提供了对 Vec 容量和内存管理的功能,可以根据需求调整容量、预留内存或收缩容量,以优化内存使用和性能。

5.3 元素访问和操作 相关方法

5.3.1 as_mut_ptr 方法

该方法返回一个可变指针,指向 Vec 的第一个元素。例如:

let mutvec=vec![1, 2, 3];
letptr=vec.as_mut_ptr();
unsafe {
*ptr=5; // 修改第一个元素的值}
println!("Modified Vec: {:?}", vec);

5.3.2 as_mut_slice 方法

该方法返回一个可变切片,包含整个 Vec 的元素。例如:

let mutvec=vec![1, 2, 3];
letslice=vec.as_mut_slice();
slice[0] =5; // 修改第一个元素的值println!("Modified Vec: {:?}", vec);

5.3.3 as_ptr 方法

该方法返回一个不可变指针,指向 Vec 的第一个元素。例如:

letvec=vec![1, 2, 3];
letptr=vec.as_ptr();
unsafe {
println!("First element: {}", *ptr);
}

5.3.4 as_slice 方法

该方法用于返回一个不可变切片,包含整个 Vec 的元素。例如:

letvec=vec![1, 2, 3];
letslice=vec.as_slice();
println!("Slice: {:?}", slice);

5.3.5 get 方法

该方法用于根据索引获取 Vec 中的元素的不可变引用。例如:

letvec=vec![1, 2, 3];
ifletSome(element) =vec.get(1) {
println!("Second element: {}", element);
} else {
println!("Element not found");
}

5.3.6 get_mut 方法

该方法用于根据索引获取 Vec 中的元素的可变引用。例如:

let mutvec=vec![1, 2, 3];
ifletSome(element) =vec.get_mut(1) {
*element=5; // 修改第二个元素的值} else {
println!("Element not found");
}
println!("Modified Vec: {:?}", vec);

5.3.7 insert 方法

该方法用于在指定索引位置插入一个元素,并将后续元素依次后移。例如:

let mutvec=vec![1, 2, 3];
vec.insert(1, 4); // 在索引1插入元素4println!("Modified Vec: {:?}", vec);

5.3.8 pop 方法

该方法用于移除并返回 Vec 中的最后一个元素。例如:

let mutvec=vec![1, 2, 3];
ifletSome(element) =vec.pop() {
println!("Popped element: {}", element);
} else {
println!("Vec is empty");
}

5.3.9 push 方法

该方法用于在 Vec 的末尾添加一个元素。例如:

let mutvec=vec![1, 2, 3];
vec.push(4); // 在末尾添加元素4println!("Modified Vec: {:?}", vec);

5.3.10 push_within_capacity 方法

该方法用于在 Vec 的末尾添加一个元素,仅当容量允许时才进行扩展。例如:

let mutvec=Vec::with_capacity(3);
vec.push_within_capacity(1); // 添加元素1,容量不变vec.push_within_capacity(2); // 添加元素2,容量不变vec.push_within_capacity(3); // 添加元素3,容量不变vec.push_within_capacity(4); // 添加元素4,容量扩展println!("Modified Vec: {:?}", vec);

5.3.11 remove 方法

该方法用于移除指定索引处的元素,并将后续元素依次前移。例如:

let mutvec=vec![1, 2, 3];
letremoved=vec.remove(1); // 移除索引1处的元素println!("Removed element: {}", removed);
println!("Modified Vec: {:?}", vec);

5.3.12 swap 方法

该方法用于交换 Vec 中两个索引位置的元素。例如:

let mutvec=vec![1, 2, 3];
vec.swap(0, 2); // 交换索引0和索引2处的元素println!("Modified Vec: {:?}", vec);

5.3.13 swap_remove 方法

该方法用于移除指定索引处的元素,并用最后一个元素替换,可以减少移动其他元素的成本。例如:

let mutvec=vec![1, 2, 3];
letremoved=vec.swap_remove(1); // 交换移除索引1处的元素println!("Removed element: {}", removed);
println!("Modified Vec: {:?}", vec);

这些方法提供了对 Vec 元素的访问和操作的功能,可以通过索引、指针或切片来访问、插入、移除和修改元素,以及交换元素位置。使用这些方法可以灵活地操作 Vec 中的元素。

5.4 元素迭代和遍历 相关方法

5.4.1 drain 方法

该方法创建一个迭代器,用于在迭代过程中逐个移除 Vec 的元素。例如:

let mutvec=vec![1, 2, 3, 4, 5];
letdrained: Vec<_>=vec.drain(1..4).collect(); // 移除索引1到3的元素println!("Drained elements: {:?}", drained);
println!("Remaining Vec: {:?}", vec);

5.4.2 drain_filter 方法

该方法创建一个迭代器,用于在迭代过程中逐个移除满足特定条件的 Vec 元素。例如:

let mutvec=vec![1, 2, 3, 4, 5];
letremoved: Vec<_>=vec.drain_filter(|&x| x % 2==0).collect(); // 移除偶数元素println!("Removed elements: {:?}", removed);
println!("Remaining Vec: {:?}", vec);

5.4.3 iter 方法

该方法返回一个不可变迭代器,用于按顺序访问 Vec 的元素。例如:

letvec=vec![1, 2, 3, 4, 5];
forelementinvec.iter() {
println!("Element: {}", element);
}

5.4.4 iter_mut 方法

该方法返回一个可变迭代器,用于按顺序访问 Vec 的元素的可变引用。例如:

let mutvec=vec![1, 2, 3, 4, 5];
forelementinvec.iter_mut() {
*element*=2; // 修改元素的值}
println!("Modified Vec: {:?}", vec);

这些方法提供了对 Vec 元素进行迭代和遍历的功能。drain 方法通过创建一个迭代器来移除元素,drain_filter 方法通过创建一个迭代器来移除满足特定条件的元素。iter 方法返回一个不可变迭代器,用于按顺序访问元素,而 iter_mut 方法返回一个可变迭代器,用于按顺序访问元素的可变引用。通过这些方法,可以方便地对 Vec 的元素进行迭代和操作。

5.5 元素修改和变换 相关方法

5.5.1 append 方法

该方法将另一个 Vec 的所有元素追加到当前 Vec 的末尾。例如:

let mutvec1=vec![1, 2, 3];
letvec2=vec![4, 5, 6];
vec1.append(&mutvec2); // 将 vec2 的元素追加到 vec1 的末尾println!("Modified Vec1: {:?}", vec1);
println!("Modified Vec2: {:?}", vec2);

5.5.2 clear 方法

该方法移除 Vec 的所有元素,将其长度设置为 0。例如:

let mutvec=vec![1, 2, 3];
vec.clear(); // 移除所有元素println!("Cleared Vec: {:?}", vec);

5.5.3 dedup 方法

该方法移除 Vec 中连续出现的重复元素,只保留一个副本。例如:

let mutvec=vec![1, 2, 2, 3, 3, 3, 4, 5];
vec.dedup(); // 移除连续出现的重复元素println!("Modified Vec: {:?}", vec);

5.5.4 dedup_by 方法

该方法根据自定义的比较函数,移除 Vec 中连续出现的满足特定条件的重复元素,只保留一个副本。例如:

let mutvec=vec![1, 2, 3, 4, 5, 6, 7];
vec.dedup_by(|a, b| a % 2==b % 2); // 移除连续出现的奇偶数重复元素println!("Modified Vec: {:?}", vec);

5.5.5 dedup_by_key 方法

该方法根据自定义的键提取函数,移除 Vec 中连续出现的相同键的元素,只保留一个副本。例如:

let mutvec=vec!["apple", "banana", "orange", "pear"];
vec.dedup_by_key(|s| s.chars().next().unwrap()); // 移除连续出现的首字母相同的元素println!("Modified Vec: {:?}", vec);

5.5.6 extend 方法

该方法将一个可迭代对象中的所有元素追加到当前 Vec 的末尾。例如:

let mutvec=vec![1, 2, 3];
letother_vec=vec![4, 5, 6];
vec.extend(other_vec); // 将 other_vec 中的元素追加到 vec 的末尾println!("Modified Vec: {:?}", vec);

5.5.7 extend_from_slice 方法

该方法将一个切片中的所有元素追加到当前 Vec 的末尾。例如:

let mutvec=vec![1, 2, 3];
letother_slice= &[4, 5, 6];
vec.extend_from_slice(other_slice); // 将 other_slice 中的元素追加到 vec 的末尾println!("Modified Vec: {:?}", vec);

5.5.8 extend_from_within 方法

该方法根据提供的索引,将 Vec 中的元素复制到指定位置。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.extend_from_within(1..4); // 复制索引1到3的元素到末尾println!("Modified Vec: {:?}", vec);

5.5.9 replace 方法

该方法将 Vec 中指定位置的元素替换为新的元素,并返回被替换的旧元素。例如:

let mutvec=vec![1, 2, 3, 4, 5];
letold_element=vec.replace(2, 6); // 将索引为2的元素替换为6println!("Old element: {:?}", old_element);
println!("Modified Vec: {:?}", vec);

5.5.10 resize 方法

该方法修改 Vec 的长度,将其扩展或收缩到指定的大小,并使用给定的值填充新元素。例如:

let mutvec=vec![1, 2, 3];
vec.resize(5, 0); // 扩展 Vec 的长度到5,并用0填充新元素println!("Modified Vec: {:?}", vec);

5.5.11 resize_with 方法

该方法修改 Vec 的长度,将其扩展或收缩到指定的大小,并使用提供的闭包生成新元素。例如:

let mutvec=vec![1, 2, 3];
vec.resize_with(5, Default::default); // 扩展 Vec 的长度到5,并使用默认值生成新元素println!("Modified Vec: {:?}", vec);

5.5.12 retain 方法

该方法根据指定的条件保留 Vec 中满足条件的元素,移除不满足条件的元素。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.retain(|&x| x % 2==0); // 保留偶数元素println!("Modified Vec: {:?}", vec);

5.5.13 retain_mut 方法

该方法根据指定的条件保留 Vec 中满足条件的元素的可变引用,移除不满足条件的元素的可变引用。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.retain_mut(|x| {
*x*=2; // 修改元素的值*x % 4==0// 保留能被4整除的元素});
println!("Modified Vec: {:?}", vec);

5.5.14 set_len 方法

该方法修改 Vec 的长度,将其设置为指定的长度,但不改变底层的内存分配。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.set_len(3); // 设置 Vec 的长度为3println!("Modified Vec: {:?}", vec);

5.5.15 splice 方法

该方法替换 Vec 中指定范围的元素,并返回被替换的元素作为迭代器。例如:

let mutvec=vec![1, 2, 3, 4, 5];
letspliced: Vec<_>=vec.splice(1..4, vec![6, 7, 8]).collect(); // 替换索引1到3的元素为新的元素println!("Spliced elements: {:?}", spliced);
println!("Modified Vec: {:?}", vec);

5.6 状态和属性查询 相关方法

5.6.1 allocator 方法

该方法返回用于分配和释放 Vec 内存的分配器。例如:

usestd::alloc::System;
usestd::alloc::Layout;
usestd::mem::MaybeUninit;
letallocator=System;
let mutvec: Vec<i32>=Vec::new_in(allocator);
letlayout=Layout::array::<i32>(10).unwrap();
vec.resize_with(10, || unsafe { MaybeUninit::uninit().assume_init() });
letallocated_size=vec.allocator().usable_size(&layout);
println!("Allocated size: {:?}", allocated_size);



5.6.2 is_empty 方法

该方法检查 Vec 是否为空,即是否包含任何元素。例如:

letvec: Vec<i32>=Vec::new();
println!("Is empty: {:?}", vec.is_empty()); // 输出: Is empty: trueletvec=vec![1, 2, 3];
println!("Is empty: {:?}", vec.is_empty()); // 输出: Is empty: false


5.6.3 len 方法

该方法返回 Vec 中元素的数量。例如:

letvec: Vec<i32>=Vec::new();
println!("Length: {:?}", vec.len()); // 输出: Length: 0letvec=vec![1, 2, 3];
println!("Length: {:?}", vec.len()); // 输出: Length: 3

这些方法允许你查询关于 Vec 的状态和属性。allocator 方法返回用于分配和释放 Vec 内存的分配器,is_empty 方法检查 Vec 是否为空,len 方法返回 Vec 中元素的数量。通过这些方法,你可以获得关于 Vec 的有关信息,并进行相应的处理。


5.7 其它方法

5.7.1 from_raw_parts 方法

该方法接受一个裸指针、元素数量和容量,返回一个 Vec,并拥有指定的内存区域。例如:

usestd::mem;
letptr=Box::into_raw(Box::new([1, 2, 3])) as*muti32;
letlen=3;
letcapacity=3;
letvec=unsafe { Vec::from_raw_parts(ptr, len, capacity) };
println!("Vec: {:?}", vec);

5.7.2 from_raw_parts_in 方法

该方法接受一个裸指针、元素数量、容量和分配器,返回一个使用指定分配器的 Vec,并拥有指定的内存区域。例如:

usestd::alloc::System;
usestd::mem;
letallocator=System;
letptr=allocator.alloc(Layout::array::<i32>(3).unwrap()) as*muti32;
letlen=3;
letcapacity=3;
letvec=unsafe { Vec::from_raw_parts_in(ptr, len, capacity, allocator) };
println!("Vec: {:?}", vec);

5.7.3 into_boxed_slice 方法

该方法将 Vec 转换为一个拥有其所有元素的 Box<[T]>。例如:

letvec=vec![1, 2, 3];
letboxed_slice: Box<[i32]>=vec.into_boxed_slice();
println!("Boxed slice: {:?}", boxed_slice);

5.7.4 into_flattened 方法

该方法将 Vec<Vec<T>> 转换为一个 Vec<T>,将内部的嵌套 Vec 展平。例如:

letvec=vec![vec![1, 2], vec![3, 4, 5], vec![6]];
letflattened: Vec<i32>=vec.into_flattened();
println!("Flattened Vec: {:?}", flattened);

5.7.5 into_raw_parts 方法

该方法将 Vec 分解为其原始数据、长度和容量,并返回它们的元组。例如:

letvec=vec![1, 2, 3];
let (ptr, len, capacity) =vec.into_raw_parts();
println!("Pointer: {:p}", ptr);
println!("Length: {:?}", len);
println!("Capacity: {:?}", capacity);

5.7.6 into_raw_parts_with_alloc 方法

该方法将 Vec 分解为其原始数据、长度、容量和分配器,并返回它们的元组。例如:

usestd::alloc::System;
letallocator=System;
letvec=vec![1, 2, 3];
let (ptr, len, capacity, alloc) =vec.into_raw_parts_with_alloc(allocator);
println!("Pointer: {:p}", ptr);
println!("Length: {:?}", len);
println!("Capacity: {:?}", capacity);
println!("Allocator: {:?}", alloc);

5.7.7 leak 方法

该方法将 Vec 转换为一个静态生命周期的引用,并且不会执行内存释放。例如:

letvec: Vec<i32>=vec![1, 2, 3];
letleaked: &'staticmut [i32] =Vec::leak(vec);
println!("Leaked slice: {:?}", leaked);

5.7.8 truncate 方法

该方法修改 Vec 的长度,截断到指定的长度,丢弃超过长度的元素。例如:

let mutvec=vec![1, 2, 3, 4, 5];
vec.truncate(3); // 截断 Vec 的长度为3println!("Truncated Vec: {:?}", vec);

5.7.9 try_reserve 方法

该方法尝试增加 Vec 的容量,以至少能够容纳指定的元素数量。例如:

let mutvec=vec![1, 2, 3];
letnew_len=vec.len() +5;
ifvec.try_reserve(new_len).is_ok() {
vec.extend(4..=8);
println!("Modified Vec: {:?}", vec);
} else {
println!("Failed to reserve capacity");
}

5.7.10 try_reserve_exact 方法

该方法尝试增加 Vec 的容量,使其能够容纳精确指定的元素数量。例如:

let mutvec=vec![1, 2, 3];
letnew_len=vec.len() +5;
ifvec.try_reserve_exact(new_len).is_ok() {
vec.extend(4..=8);
println!("Modified Vec: {:?}", vec);
} else {
println!("Failed to reserve exact capacity");
}

6. 关于 alloc::vec

Rust 语言中提供了 alloc::vec 宏,用于创建一个Vec包含参数的 Dust 向量 。

macro_rules!vec {
    () => { ... };
    ($elem:expr; $n:expr) => { ... };
    ($($x:expr),+ $(,)?) => { ... };
}

使用 alloc::vec 宏,你可以通过在方括号内提供初始值来初始化向量。宏会根据提供的初始值计算出向量所需的容量,并在堆上分配足够的内存来存储这些值。你可以如下使用 alloc::vec 宏 来创建和初始化 Rust 向量:

usealloc::vec;
letvec=vec![1, 2, 3, 4, 5];
println!("Vector: {:?}", vec);

本例中,我们使用 alloc::vec 宏创建了一个包含 1、2、3、4、5 这6个元素的向量。宏根据提供的初始值计算出向量的长度,并在堆上分配了适当大小的内存来存储这些值。最后,我们打印出了创建的向量。

alloc::vec 宏还支持在初始值中使用重复的元素。例如,你可以使用 [0; n] 的形式来创建一个包含 n 个重复元素的向量:

usealloc::vec;
letvec=vec![0; 5];
println!("Vector: {:?}", vec);

这里,我们使用 alloc::vec 宏创建了一个包含 5 个重复的元素 0 的向量。

alloc::vec 宏在编译时会自动计算向量的容量,并确保分配足够的内存来存储初始值。这使得在创建向量时不需要手动指定容量,而是由编译器根据提供的初始值进行计算。这样可以提供更高的代码灵活性和可读性,同时减少了手动计算容量的繁琐工作。

注意:

  • alloc::vec 宏位于 alloc 模块中,而不是标准库的根模块中。因此在使用之前需要通过 use 语句将其导入到作用域中,即:
use alloc::vec;


7. Rust 向量的遍历

7.1 使用 for 循环遍历向量

我们可以使用 for 循环来遍历向量中的元素,例如:

fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    for item in &vec {
        println!("Item: {}", item);
    }
}

在上面的例子中,我们使用 for 循环遍历向量 vec 中的每个元素,并打印出元素的值。

不过需要指出的是,在 Rust 语言中使用 for 循环进行遍历的方式是一种语法糖,它隐藏了不过迭代器的使用。因此读者也可以参考使用迭代器的遍历方式。

7.2 迭代器

7.2.1 迭代器的概念

在 Rust 中,迭代器(Iterator)是一种序列的抽象,它提供了一种统一的方式来遍历和处理序列中的元素。迭代器是 Rust 标准库中的一个重要组件,广泛应用于向量、哈希表、文件、字符串等数据结构和类型。

迭代器实现了 Iteratortrait,这个 trait 定义了一组方法,用于操作和处理序列中的元素。通过使用迭代器,我们可以以一种统一的方式处理不同类型的序列,无论是数组、向量、哈希表还是文件等。这样的设计使得代码更具表达力和灵活性,同时也提供了更好的性能和安全性。

7.2.2 从向量获取迭代器

1. 使用 iter 方法获取迭代器

如果我们需要对向量中的元素进行可变的操作,可以使用 iter_mut 方法获取一个可变迭代器。例如:

fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    let mut iter = vec.iter();        // 使用 iter 方法获取迭代器
    // 使用 while let 循环遍历迭代器
    while let Some(item) = iter.next() {
        println!("Item: {}", item);
    }
}

2. 使用 iter_mut 方法获取迭代器

如果我们需要对向量中的元素进行可变的操作,可以使用 iter_mut 方法获取一个可变迭代器。

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    // 使用 iter_mut 方法获取可变迭代器
    for item in vec.iter_mut() {
        *item *= 2; // 修改元素
        println!("Item: {}", item);
    }
}

本例中,我们使用 iter_mut 方法获取向量 vec 的可变迭代器,并使用 for 循环遍历迭代器。在循环中,我们对每个元素进行了乘以 2 的操作,并打印出元素的值。

8. 关于 可变向量 与 不可变向量 说法的简要说明

8.1 不可变向量

不可变向量是指在创建后不能修改其内容的向量。通过使用 let 关键字声明向量时,如果没有使用 mut 修饰符,那么该向量就是不可变向量。

请参考博文 《Rust 语言中的常量与变量》

8.2 可变向量

可变向量是指在创建后可以修改其内容的向量。通过使用 let mut 关键字声明向量时,可以将其声明为可变向量。

请参考博文 《Rust 语言中的常量与变量》

目录
相关文章
|
11天前
|
Rust 安全 Java
探索Rust语言的并发编程模型
探索Rust语言的并发编程模型
|
18天前
|
Rust 安全 区块链
探索Rust语言:系统编程的新选择
【10月更文挑战第27天】Rust语言以其安全性、性能和并发性在系统编程领域受到广泛关注。本文介绍了Rust的核心特性,如内存安全、高性能和强大的并发模型,以及开发技巧和实用工具,展示了Rust如何改变系统编程的面貌,并展望了其在WebAssembly、区块链和嵌入式系统等领域的未来应用。
|
19天前
|
Rust 安全 Java
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第27天】Rust语言以其独特的特性和优势在编程领域迅速崛起。本文介绍Rust的核心特性,如所有权系统和强大的并发处理能力,以及其性能和安全性优势。通过实战示例,如“Hello, World!”和线程编程,帮助读者快速入门Rust。
37 1
|
20天前
|
Rust 安全 编译器
编程语言新宠:Rust语言的特性、优势与实战入门
【10月更文挑战第26天】Rust语言诞生于2006年,由Mozilla公司的Graydon Hoare发起。作为一门系统编程语言,Rust专注于安全和高性能。通过所有权系统和生命周期管理,Rust在编译期就能消除内存泄漏等问题,适用于操作系统、嵌入式系统等高可靠性场景。
30 2
|
11天前
|
Rust 安全 前端开发
探索Rust语言的异步编程模型
探索Rust语言的异步编程模型
|
21天前
|
Rust 安全 云计算
Rust语言入门:安全性与并发性的完美结合
【10月更文挑战第25天】Rust 是一种系统级编程语言,以其独特的安全性和并发性保障而著称。它提供了与 C 和 C++ 相当的性能,同时确保内存安全,避免了常见的安全问题。Rust 的所有权系统通过编译时检查保证内存安全,其零成本抽象设计使得抽象不会带来额外的性能开销。Rust 还提供了强大的并发编程工具,如线程、消息传递和原子操作,确保了数据竞争的编译时检测。这些特性使 Rust 成为编写高效、安全并发代码的理想选择。
17 0
|
1月前
|
Rust 安全 网络安全
在 Rust 语言中,寻找企业上网行为管理软件的突破
在数字化企业环境中,上网行为管理软件对于保障网络安全和提升工作效率至关重要。Rust 语言凭借其安全性、高性能和并发性,为开发此类软件提供了新机遇。本文通过几个 Rust 代码示例,展示了如何实现网址检查、访问频率统计及访问控制等功能,旨在探索 Rust 在企业上网行为管理中的应用潜力。
36 0
|
2月前
|
存储 Rust 安全
30天拿下Rust之向量
30天拿下Rust之向量
24 0
|
2月前
|
Rust Linux Go
Rust/Go语言学习
Rust/Go语言学习
|
3月前
|
开发者 vr&ar 机器学习/深度学习
Xamarin 开发者的未来趋势展望:掌握跨平台开发新机遇,引领移动应用创新潮流与技术变革方向
【8月更文挑战第31天】Xamarin 作为领先的跨平台开发框架,通过 C# 和 .NET 框架实现一次编写、多平台运行,简化了 iOS、Android 和 Windows 应用的开发流程。未来几年,Xamarin 开发者将面临跨平台开发普及、云集成、机器学习、AR/VR、性能优化及安全性等关键趋势。通过学习新技术并积极采用新工具,开发者能够提升应用质量和用户体验,如利用 Azure AD B2C 实现身份认证,从而在竞争激烈的市场中脱颖而出。
60 0