专业编程基础技术教程

网站首页 > 基础教程 正文

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

ccvgpt 2024-12-06 13:09:11 基础教程 1 ℃

1. 为什么需要 Vector

1.1 Rust 数组的简单回顾

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

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

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

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

rust复制代码// 创建一个长度为5的整数数组
let array: [i32; 5] = [1, 2, 3, 4, 5];

// 访问数组元素
println!("第一个元素: {}", array[0]);
println!("第三个元素: {}", array[2]);

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

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

rust复制代码// 创建一个长度为3的字符串数组
let array: [String; 3] = [
    String::from("Hello"),
    String::from("World"),
    String::from("Rust"),
];

// 访问数组元素
for element in &array {
    println!("{}", element);
}

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

1.2 Vector 应运而生

1.2.1 Rust 数组的缺陷

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

rust复制代码// 创建一个长度为3的整数数组
let array: [i32; 3] = [1, 2, 3];

// 无法动态改变数组长度
// array.push(4); // 错误!数组无法调用 `push` 方法

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

rust复制代码// 创建一个长度为3的整数数组
let mut array: [i32; 3] = [1, 2, 3];

// 当数组已满时,需要手动重新分配内存
let new_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 动态大小

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

rust复制代码// 创建一个空的向量
let mut vector: Vec<i32> = Vec::new();

// 向向量添加元素
vector.push(1);
vector.push(2);
vector.push(3);

2.2 自动内存管理

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

rust复制代码// 创建一个空的向量
let mut vector: Vec<i32> = Vec::new();

// 向向量添加元素
vector.push(1);
vector.push(2);
vector.push(3);
vector.push(4); // 超过当前内存空间,向量会自动重新分配更大的空间

// 自动处理内存分配和元素的移动
// 无需手动重新分配内存和复制数据

2.3 灵活的元素类型

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

rust复制代码// 创建一个包含不同类型元素的向量
let mut vector: Vec<dyn std::fmt::Debug> = Vec::new();
vector.push(1);
vector.push("hello");
vector.push(true);

2.4 丰富的方法和功能

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

rust复制代码let vector = vec![1, 2, 3, 4, 5];

// 使用迭代器遍历向量
for element in &vector {
    println!("{}", element);
}

// 获取向量长度
let length = vector.len();
println!("向量长度: {}", length);

// 检查元素是否存在
let contains_two = vector.contains(&2);
println!("向量是否包含2? {}", contains_two);

3. Vec 结构体

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

rust复制代码pub struct Vec<T, A: Allocator = Global> { /* private fields */ }

Vec 的源码请参考:doc.rust-lang.org/src/alloc/v… 以下是部分摘抄:

rust复制代码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]
    pub const fn new() -> Self {
        Vec { buf: RawVec::NEW, len: 0 }
    }

    // ... 省略其它方法
}

impl<T, A: Allocator> Vec<T, A> {
    #[inline]
    #[unstable(feature = "allocator_api", issue = "32838")]
    pub const fn new_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")]
    pub fn resize(&mut self, new_len: usize, value: T) {
        let len = self.len();

        if new_len > len {
            self.extend_with(new_len - len, ExtendElement(value))
        } else {
            self.truncate(new_len);
        }
    }

    // ... 省略其它方法
}

impl<T, A: Allocator, const N: usize> Vec<[T; N], A> {
    #[unstable(feature = "slice_flatten", issue = "95629")]
    pub fn into_flattened(self) -> Vec<T, A> {
        let (ptr, len, cap, alloc) = self.into_raw_parts_with_alloc();
        let (new_len, new_cap) = if T::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))]
    fn extend_with<E: ExtendWith<T>>(&mut self, n: usize, mut value: E) {
        self.reserve(n);
        unsafe {
            let mut ptr = 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 mut local_len = SetLenOnDrop::new(&mut self.len);

            // Write all elements except the last one
            for _ in 1..n {
                ptr::write(ptr, value.next());
                ptr = ptr.add(1);
                // Increment the length in every step in case next() panics
                local_len.increment_len(1);
            }

            if n > 0 {
                // We can write the last element directly without cloning needlessly
                ptr::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]
    pub fn dedup(&mut self) {
        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 具体的例子

一个选用数组的例子

rust复制代码// 创建一个长度为10的整数数组,存储像素数据
let pixels: [u8; 10] = [255, 0, 128, 64, 200, 100, 0, 0, 255, 255];

// 创建一个长度为3的字符串数组,存储命令行参数
let args: [String; 3] = [
    String::from("program"),
    String::from("arg1"),
    String::from("arg2"),
];

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

一个选用向量的例子

rust复制代码// 创建一个空的整数向量,用于存储动态输入数据
let mut numbers: Vec<i32> = Vec::new();

// 向向量添加动态输入的数据
let input = 42;
numbers.push(input);

// 创建一个动态大小的字符串向量,用于存储不同类型的数据
let mut data: Vec<dyn std::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 方法

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

rust复制代码let vec = vec![1, 2, 3, 4, 5];
let capacity = vec.capacity();
println!("Capacity: {}", capacity);

5.2.2reserve 方法

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

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

5.2.3 reserve_exact 方法

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

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

5.2.4 shrink_to 方法

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

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

5.2.5 shrink_to_fit 方法

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

rust复制代码let mut vec = 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 的未使用的缓冲区。例如:

rust复制代码let mut vec = Vec::with_capacity(10);
let spare_capacity = vec.spare_capacity_mut();
unsafe {
    for i in 0..spare_capacity {
        // 直接修改未使用的缓冲区
        *spare_capacity.offset(i as isize) = i;
    }
    vec.set_len(vec.len() + spare_capacity);
}
println!("Modified Vec: {:?}", vec);

5.2.7 with_capacity 方法:

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

rust复制代码let vec: Vec<u32> = Vec::with_capacity(5);
println!("Capacity: {}", vec.capacity());

5.2.8 with_capacity_in 方法

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

rust复制代码use std::alloc::System;
let vec: Vec<u32, System> = Vec::with_capacity_in(5);
println!("Capacity: {}", vec.capacity());

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

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

5.3.1 as_mut_ptr 方法

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

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

5.3.2 as_mut_slice 方法

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

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

5.3.3 as_ptr 方法

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

rust复制代码let vec = vec![1, 2, 3];
let ptr = vec.as_ptr();
unsafe {
    println!("First element: {}", *ptr);
}

5.3.4 as_slice 方法

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

rust复制代码let vec = vec![1, 2, 3];
let slice = vec.as_slice();
println!("Slice: {:?}", slice);

5.3.5 get 方法

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

rust复制代码let vec = vec![1, 2, 3];
if let Some(element) = vec.get(1) {
    println!("Second element: {}", element);
} else {
    println!("Element not found");
}

5.3.6 get_mut 方法

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

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

5.3.7 insert 方法

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

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

5.3.8 pop 方法

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

rust复制代码let mut vec = vec![1, 2, 3];
if let Some(element) = vec.pop() {
    println!("Popped element: {}", element);
} else {
    println!("Vec is empty");
}

5.3.9 push 方法

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

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

5.3.10 push_within_capacity 方法

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

rust复制代码let mut vec = 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 方法

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

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

5.3.12 swap 方法

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

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

5.3.13 swap_remove 方法

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

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

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

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

5.4.1 drain 方法

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

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

5.4.2 drain_filter 方法

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

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

5.4.3 iter 方法

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

rust复制代码let vec = vec![1, 2, 3, 4, 5];
for element in vec.iter() {
    println!("Element: {}", element);
}

5.4.4 iter_mut 方法

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

rust复制代码let mut vec = vec![1, 2, 3, 4, 5];
for element in vec.iter_mut() {
    *element *= 2; // 修改元素的值
}
println!("Modified Vec: {:?}", vec);

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

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

5.5.1 append 方法

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

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

5.5.2 clear 方法

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

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

5.5.3 dedup 方法

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

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

5.5.4 dedup_by 方法

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

rust复制代码let mut vec = 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 中连续出现的相同键的元素,只保留一个副本。例如:

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

5.5.6 extend 方法

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

rust复制代码let mut vec = vec![1, 2, 3];
let other_vec

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

5.5.7 extend_from_slice 方法

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

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

5.5.8 extend_from_within 方法

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

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

5.5.9 replace 方法

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

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

5.5.10 resize 方法

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

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

5.5.11 resize_with 方法

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

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

5.5.12 retain 方法

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

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

5.5.13 retain_mut 方法

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

rust复制代码let mut vec = 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 的长度,将其设置为指定的长度,但不改变底层的内存分配。例如:

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

5.5.15 splice 方法

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

rust复制代码let mut vec = vec![1, 2, 3, 4, 5];
let spliced: 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 内存的分配器。例如:

rust复制代码use std::alloc::System;
use std::alloc::Layout;
use std::mem::MaybeUninit;

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

5.6.2 is_empty 方法

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

rust复制代码let vec: Vec<i32> = Vec::new();
println!("Is empty: {:?}", vec.is_empty()); // 输出: Is empty: true

let vec = vec![1, 2, 3];
println!("Is empty: {:?}", vec.is_empty()); // 输出: Is empty: false

5.6.3 len 方法

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

rust复制代码let vec: Vec<i32> = Vec::new();
println!("Length: {:?}", vec.len()); // 输出: Length: 0

let vec = 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,并拥有指定的内存区域。例如:

rust复制代码use std::mem;

let ptr = Box::into_raw(Box::new([1, 2, 3])) as *mut i32;
let len = 3;
let capacity = 3;
let vec = unsafe { Vec::from_raw_parts(ptr, len, capacity) };
println!("Vec: {:?}", vec);

5.7.2 from_raw_parts_in 方法

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

rust复制代码use std::alloc::System;
use std::mem;

let allocator = System;
let ptr = allocator.alloc(Layout::array::<i32>(3).unwrap()) as *mut i32;
let len = 3;
let capacity = 3;
let vec = unsafe { Vec::from_raw_parts_in(ptr, len, capacity, allocator) };
println!("Vec: {:?}", vec);

5.7.3 into_boxed_slice 方法

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

rust复制代码let vec = vec![1, 2, 3];
let boxed_slice: Box<[i32]> = vec.into_boxed_slice();
println!("Boxed slice: {:?}", boxed_slice);

5.7.4 into_flattened 方法

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

rust复制代码let vec = vec![vec![1, 2], vec![3, 4, 5], vec![6]];
let flattened: Vec<i32> = vec.into_flattened();
println!("Flattened Vec: {:?}", flattened);

5.7.5 into_raw_parts 方法

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

rust复制代码let vec = 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 分解为其原始数据、长度、容量和分配器,并返回它们的元组。例如:

rust复制代码use std::alloc::System;

let allocator = System;
let vec = 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 转换为一个静态生命周期的引用,并且不会执行内存释放。例如:

rust复制代码let vec: Vec<i32> = vec![1, 2, 3];
let leaked: &'static mut [i32] = Vec::leak(vec);
println!("Leaked slice: {:?}", leaked);

5.7.8 truncate 方法

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

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

5.7.9 try_reserve 方法

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

rust复制代码let mut vec = vec![1, 2, 3];
let new_len = vec.len() + 5;
if vec.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 的容量,使其能够容纳精确指定的元素数量。例如:

rust复制代码let mut vec = vec![1, 2, 3];
let new_len = vec.len() + 5;
if vec.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 向量 。

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

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

rust复制代码use alloc::vec;

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

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

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

rust复制代码use alloc::vec;

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

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

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

注意:

alloc::vec 宏位于 alloc 模块中,而不是标准库的根模块中。因此在使用之前需要通过 use 语句将其导入到作用域中,即: rust复制代码use alloc::vec; 更多细节可以参考:doc.rust-lang.org/src/alloc/m…

7. Rust 向量的遍历

7.1 使用 for 循环遍历向量

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

rust复制代码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 标准库中的一个重要组件,广泛应用于向量、哈希表、文件、字符串等数据结构和类型。

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

7.2.2 从向量获取迭代器

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

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

rust复制代码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 方法获取一个可变迭代器。

rust复制代码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 关键字声明向量时,可以将其声明为可变向量。

最近发表
标签列表