Rust for 循环遍历不同集合的方式
Rust 中 for 循环基础
在 Rust 编程语言里,for
循环是一种非常常用的结构,用于迭代集合中的元素。for
循环的基本语法如下:
for item in collection {
// 在这里对 item 进行操作
}
这里的 collection
可以是各种实现了 IntoIterator
trait 的类型,而 item
则是每次迭代从 collection
中取出的元素。
遍历数组(Array)
数组是 Rust 中一种固定大小的数据集合,其元素类型相同。例如,我们有一个包含整数的数组:
let numbers = [1, 2, 3, 4, 5];
for number in numbers.iter() {
println!("Number: {}", number);
}
在这个例子中,我们使用 iter()
方法来创建一个迭代器。数组本身实现了 IntoIterator
trait,iter()
方法会返回一个 Iterator
,它按顺序生成数组中的每一个元素。注意,这里 number
的类型是 &i32
,因为 iter()
返回的是一个借用的迭代器,这样可以避免在遍历过程中移动数组中的元素。
如果我们希望在遍历过程中修改数组元素,我们可以使用 iter_mut()
方法:
let mut numbers = [1, 2, 3, 4, 5];
for number in numbers.iter_mut() {
*number += 1;
println!("Number: {}", number);
}
这里 number
的类型是 &mut i32
,通过解引用 *number
,我们可以修改数组中的元素。
遍历向量(Vector)
向量(Vec
)是 Rust 中动态大小的数组,其大小可以在运行时改变。遍历向量的方式与数组类似:
let mut vec_numbers = vec![1, 2, 3, 4, 5];
for number in &vec_numbers {
println!("Number: {}", number);
}
这里我们使用 &vec_numbers
,这会自动调用 iter()
方法,因为 Vec
实现了 IntoIterator
trait。如果我们想要修改向量中的元素,同样可以使用 iter_mut()
:
let mut vec_numbers = vec![1, 2, 3, 4, 5];
for number in vec_numbers.iter_mut() {
*number += 1;
println!("Number: {}", number);
}
遍历切片(Slice)
切片(&[T]
)是对一块连续内存区域的引用,它可以指向数组或向量的一部分。遍历切片也很简单:
let numbers = [1, 2, 3, 4, 5];
let slice = &numbers[1..3];
for number in slice.iter() {
println!("Number: {}", number);
}
这里我们创建了一个指向 numbers
数组部分元素的切片 slice
,通过 iter()
方法遍历切片中的元素。同样,如果需要修改切片中的元素,可以使用 iter_mut()
:
let mut numbers = [1, 2, 3, 4, 5];
let slice = &mut numbers[1..3];
for number in slice.iter_mut() {
*number += 1;
println!("Number: {}", number);
}
遍历字符串(String)
Rust 中的字符串类型有两种:String
和 &str
。String
是可增长、可变的字符串类型,而 &str
是字符串切片,是不可变的。
遍历 String
let s = String::from("hello");
for c in s.chars() {
println!("Character: {}", c);
}
这里我们使用 chars()
方法来遍历 String
中的每一个字符。chars()
方法会把字符串拆分成一个个独立的 char
类型。
如果我们想按字节遍历 String
,可以使用 bytes()
方法:
let s = String::from("hello");
for byte in s.bytes() {
println!("Byte: {}", byte);
}
遍历 &str
&str
类型同样支持 chars()
和 bytes()
方法来遍历字符和字节:
let s = "hello";
for c in s.chars() {
println!("Character: {}", c);
}
遍历哈希表(HashMap)
哈希表(HashMap
)是 Rust 标准库中提供的一种键值对集合。要遍历 HashMap
,我们可以使用 for
循环结合 iter()
方法:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"), 200);
for (key, value) in scores.iter() {
println!("{}: {}", key, value);
}
在这个例子中,scores.iter()
返回一个迭代器,每次迭代产生一个 (&K, &V)
类型的元组,这里 K
是键的类型,V
是值的类型。
如果我们想要在遍历过程中修改 HashMap
,可以使用 iter_mut()
:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Alice"), 100);
scores.insert(String::from("Bob"), 200);
for (key, value) in scores.iter_mut() {
if key == "Alice" {
*value += 10;
}
}
遍历 B 树映射(BTreeMap)
B 树映射(BTreeMap
)也是一种键值对集合,与 HashMap
不同的是,BTreeMap
会按键的顺序存储键值对。遍历 BTreeMap
的方式与 HashMap
类似:
use std::collections::BTreeMap;
let mut scores = BTreeMap::new();
scores.insert(1, String::from("Alice"));
scores.insert(2, String::from("Bob"));
for (key, value) in scores.iter() {
println!("{}: {}", key, value);
}
同样,scores.iter()
返回一个迭代器,每次迭代产生一个 (&K, &V)
类型的元组。如果需要修改 BTreeMap
,可以使用 iter_mut()
。
遍历链表(LinkedList)
链表(LinkedList
)是一种动态数据结构,由节点组成,每个节点包含数据和指向下一个节点的指针。在 Rust 中,std::collections::LinkedList
提供了链表的实现。
use std::collections::LinkedList;
let mut list = LinkedList::new();
list.push_back(1);
list.push_back(2);
list.push_back(3);
for number in list.iter() {
println!("Number: {}", number);
}
这里我们使用 iter()
方法遍历链表中的元素。如果需要修改链表中的元素,可以使用 iter_mut()
:
use std::collections::LinkedList;
let mut list = LinkedList::new();
list.push_back(1);
list.push_back(2);
list.push_back(3);
for number in list.iter_mut() {
*number += 1;
println!("Number: {}", number);
}
遍历迭代器适配器(Iterator Adapters)
Rust 的迭代器有很多强大的适配器方法,这些方法可以对迭代器进行转换、过滤等操作。例如,我们可以使用 filter()
方法过滤掉不符合条件的元素,然后再遍历:
let numbers = vec![1, 2, 3, 4, 5];
for number in numbers.iter().filter(|&&n| n % 2 == 0) {
println!("Even number: {}", number);
}
在这个例子中,filter(|&&n| n % 2 == 0)
会过滤掉所有奇数,只留下偶数,然后我们遍历剩下的偶数。
另一个常用的适配器方法是 map()
,它可以对迭代器中的每个元素应用一个函数:
let numbers = vec![1, 2, 3, 4, 5];
for squared in numbers.iter().map(|&n| n * n) {
println!("Squared: {}", squared);
}
这里 map(|&n| n * n)
会将每个元素平方,然后我们遍历平方后的结果。
遍历嵌套集合
在实际编程中,我们经常会遇到嵌套集合的情况,比如一个向量中包含多个向量,或者一个哈希表中值的类型是另一个哈希表。以嵌套向量为例:
let nested_vec = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
for inner_vec in nested_vec.iter() {
for number in inner_vec.iter() {
println!("Number: {}", number);
}
}
这里外层循环遍历 nested_vec
中的每个内部向量,内层循环遍历每个内部向量中的元素。
对于嵌套哈希表,假设我们有一个哈希表,其值又是一个哈希表:
use std::collections::HashMap;
let mut outer_map = HashMap::new();
let mut inner_map1 = HashMap::new();
inner_map1.insert(String::from("a"), 1);
let mut inner_map2 = HashMap::new();
inner_map2.insert(String::from("b"), 2);
outer_map.insert(String::from("key1"), inner_map1);
outer_map.insert(String::from("key2"), inner_map2);
for (outer_key, inner_map) in outer_map.iter() {
println!("Outer key: {}", outer_key);
for (inner_key, value) in inner_map.iter() {
println!("Inner key: {}, Value: {}", inner_key, value);
}
}
遍历自定义集合
我们还可以创建自己的集合类型,并实现 IntoIterator
trait 来使其可以被 for
循环遍历。假设我们有一个简单的自定义结构体 MyList
:
struct MyList<T> {
data: Vec<T>,
}
impl<T> MyList<T> {
fn new() -> Self {
MyList { data: Vec::new() }
}
fn push(&mut self, item: T) {
self.data.push(item);
}
}
impl<T> IntoIterator for MyList<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
let mut my_list = MyList::new();
my_list.push(1);
my_list.push(2);
my_list.push(3);
for number in my_list {
println!("Number: {}", number);
}
在这个例子中,我们为 MyList
结构体实现了 IntoIterator
trait。IntoIterator
有两个关联类型:Item
表示迭代器产生的元素类型,IntoIter
表示迭代器的类型。通过实现 into_iter
方法,我们将 MyList
中的 Vec
的迭代器返回,这样 MyList
就可以被 for
循环遍历了。
循环控制
在 for
循环中,我们可以使用 break
和 continue
语句来控制循环的执行。break
语句会立即终止循环:
let numbers = vec![1, 2, 3, 4, 5];
for number in numbers.iter() {
if *number == 3 {
break;
}
println!("Number: {}", number);
}
在这个例子中,当 number
等于 3 时,break
语句会终止循环,所以只会输出 1 和 2。
continue
语句会跳过当前迭代,继续下一次迭代:
let numbers = vec![1, 2, 3, 4, 5];
for number in numbers.iter() {
if *number == 3 {
continue;
}
println!("Number: {}", number);
}
这里当 number
等于 3 时,continue
语句会跳过当前迭代,继续下一次迭代,所以会输出 1、2、4 和 5。
遍历与所有权
在 Rust 中,所有权是一个非常重要的概念。当我们遍历集合时,需要注意所有权的转移和借用规则。例如,当我们使用 for
循环遍历一个向量并消耗其中的元素时,所有权会发生转移:
let numbers = vec![1, 2, 3, 4, 5];
for number in numbers {
println!("Number: {}", number);
}
// 这里 numbers 已经被消耗,不能再使用
在这个例子中,numbers
的所有权转移到了 for
循环中的 number
,循环结束后,numbers
不再有效。
而当我们使用 iter()
方法时,我们只是借用了集合中的元素,集合的所有权仍然归原变量所有:
let numbers = vec![1, 2, 3, 4, 5];
for number in numbers.iter() {
println!("Number: {}", number);
}
// 这里 numbers 仍然可以使用
并行遍历
在 Rust 中,我们可以使用 rayon
库来实现并行遍历集合。rayon
提供了 ParallelIterator
trait,许多集合类型都可以通过扩展方法转换为并行迭代器。
首先,在 Cargo.toml
文件中添加 rayon
依赖:
[dependencies]
rayon = "1.5.1"
然后,我们可以这样使用并行遍历:
use rayon::prelude::*;
let numbers = (1..10000).collect::<Vec<_>>();
let sum: i32 = numbers.par_iter().sum();
println!("Sum: {}", sum);
在这个例子中,par_iter()
方法将 Vec
转换为并行迭代器,sum()
方法会并行计算所有元素的和。
遍历性能考量
不同的集合类型在遍历性能上可能会有差异。例如,数组和向量的遍历通常比较快,因为它们在内存中是连续存储的,CPU 可以利用缓存预取技术提高访问效率。而链表由于节点在内存中是分散存储的,遍历链表可能会有更多的缓存不命中,导致性能相对较低。
另外,使用迭代器适配器方法时,也要注意性能。例如,过多的链式适配器调用可能会增加代码的复杂性和运行时开销。在一些性能敏感的场景下,可能需要手动展开循环来优化性能。
同时,并行遍历虽然可以利用多核 CPU 的优势提高计算速度,但也会引入额外的线程创建、同步等开销。在数据量较小的情况下,并行遍历可能反而会降低性能,所以需要根据实际情况进行权衡。
在 Rust 中,for
循环提供了一种简洁而强大的方式来遍历各种集合。通过深入理解不同集合的遍历方式,以及相关的所有权、性能等概念,开发者可以编写出高效、安全的代码。无论是简单的数组遍历,还是复杂的嵌套集合、并行遍历,Rust 都提供了丰富的工具和方法来满足各种需求。