正式开始
主要的数据结构
集合容器
- 集合容器就是把一系列拥有相同类型的数据放在一起,统一处理.
- 比如字符串 String、数组 [T; n]、列表 Vec 和哈希表 HashMap、切片slice、循环缓冲区 VecDeque、双向列表 LinkedList
切片
-
切片是描述一组属于同一类型、长度不确定的、在内存中连续存放的数据结构
-
用 [T] 来表述,因为长度不确定,所以切片是个 DST(Dynamically Sized Type)
-
切片一般只出现在数据结构的定义中,不能直接访问,在使用中主要用以下形式
a. &[T]:表示一个只读的切片引用
b. &mut [T]:表示一个可写的切片引用
c. Box<[T]>:一个在堆上分配的切片
fn main() {
// 数组,存放在栈上
let arr = [1, 2, 3, 4, 5];
// 向量,存放在堆上
let vec = vec![1, 2, 3, 4, 5];
// s1和s2切片相似,对于相同内容数据的相同切片是等价的
let s1 = &arr[..2];
let s2 = &vec[..2];
println!("s1: {:?}, s2: {:?}", s1, s2);
// &[T] 和 &[T] 是否相等取决于长度和内容是否相等
assert_eq!(s1, s2);
// &[T] 可以和 Vec<T>/[T;n] 比较,也会看长度和内容
assert_eq!(&arr[..], vec);
assert_eq!(&vec[..], arr);
}
&[T] 和 &Vec 的区别
-
在使用的时候,可以根据需要将支持切片的具体数据类型,解引用转换成切片类型
-
比如 Vec 和 [T; n] 会转化成为 &[T]
a. Vec 实现了 Deref trait
b. array 内建了到 &[T] 的解引用
use std::fmt;
fn main() {
let v = vec![1, 2, 3, 4];
// Vec 实现了 Deref,&Vec<T> 会被自动解引用为 &[T],符合接口定义
print_slice(&v);
// 直接是 &[T],符合接口定义
print_slice(&v[..]);
// &Vec<T> 支持 AsRef<[T]>
print_slice1(&v);
// &[T] 支持 AsRef<[T]>
print_slice1(&v[..]);
// Vec<T> 也支持 AsRef<[T]>
print_slice1(v);
let arr = [1, 2, 3, 4];
// 数组虽没有实现 Deref,但它的解引用就是 &[T]
print_slice(&arr);
print_slice(&arr[..]);
print_slice1(&arr);
print_slice1(&arr[..]);
print_slice1(arr);
}
// 注意下面的泛型函数的使用
fn print_slice<T: fmt::Debug>(s: &[T]) {
println!("{:?}", s);
}
fn print_slice1<T, U>(s: T)
where
T: AsRef<[U]>,
U: fmt::Debug,
{
println!("{:?}", s.as_ref());
}
切片和迭代器 Iterator
- 切片是集合数据的视图,而迭代器定义了对集合数据的各种各样的访问操作。
- 通过切片的 iter() 方法,我们可以生成一个迭代器,对切片进行迭代
- Rust 下的迭代器是个懒接口(lazy interface
举例
fn main() {
// 这里 Vec<T> 在调用 iter() 时被解引用成 &[T],所以可以访问 iter()
let result = vec![1, 2, 3, 4]
.iter()
.map(|v| v * v)
.filter(|v| *v < 16)
.take(1)
.collect::<Vec<_>>();
println!("{:?}", result);
}
整个过程是这样的
- 在 collect() 执行的时候,它实际试图使用 FromIterator 从迭代器中构建一个集合类型,这会不断调用 next() 获取下一个数据
- 此时的 Iterator 是 Take,Take 调自己的 next(),也就是它会调用 Filter 的 next()
- Filter 的 next() 实际上调用自己内部的 iter 的 find(),此时内部的 iter 是 Map,find() 会使用 try_fold(),它会继续调用 next(),也就是 Map 的 next()
- Map 的 next() 会调用其内部的 iter 取 next() 然后执行 map 函数。而此时内部的 iter 来自 Vec<i32>
只有在 collect() 时,才触发代码一层层调用下去,并且调用会根据需要随时结束
特殊的切片:&str
String 是一个特殊的 Vec,所以在 String 上做切片,也是一个特殊的结构 &str。
String 在解引用时,会转换成 &str
use std::fmt;
fn main() {
let s = String::from("hello");
// &String 会被解引用成 &str
print_slice(&s);
// &s[..] 和 s.as_str() 一样,都会得到 &str
print_slice(&s[..]);
// String 支持 AsRef<str>
print_slice1(&s);
print_slice1(&s[..]);
print_slice1(s.clone());
// String 也实现了 AsRef<[u8]>,所以下面的代码成立
// 打印出来是 [104, 101, 108, 108, 111]
print_slice2(&s);
print_slice2(&s[..]);
print_slice2(s);
}
fn print_slice(s: &str) {
println!("{:?}", s);
}
fn print_slice1<T: AsRef<str>>(s: T) {
println!("{:?}", s.as_ref());
}
fn print_slice2<T, U>(s: T)
where
T: AsRef<[U]>,
U: fmt::Debug,
{
println!("{:?}", s.as_ref());
}
字符的列表和字符串有什么关系和区别?
use std::iter::FromIterator;
fn main() {
let arr = ['h', 'e', 'l', 'l', 'o'];
let vec = vec!['h', 'e', 'l', 'l', 'o'];
let s = String::from("hello");
let s1 = &arr[1..3];
let s2 = &vec[1..3];
// &str 本身就是一个特殊的 slice
let s3 = &s[1..3];
println!("s1: {:?}, s2: {:?}, s3: {:?}", s1, s2, s3);
// &[char] 和 &[char] 是否相等取决于长度和内容是否相等
assert_eq!(s1, s2);
// &[char] 和 &str 不能直接对比,我们把 s3 变成 Vec<char>
assert_eq!(s2, s3.chars().collect::<Vec<_>>());
// &[char] 可以通过迭代器转换成 String,String 和 &str 可以直接对比
assert_eq!(String::from_iter(s2), s3);
}
字符列表可以通过迭代器转换成 String,String 也可以通过 chars() 函数转换成字符列表
切片的引用和堆上的切片,它们是一回事么?
Box<[T]>和Vec<T>的区别
- Vec<T> 有额外的 capacity,可以增长
- Box<[T]> 一旦生成就固定下来,没有 capacity,也无法增长
Box<[T]>和切片的引用&[T]的区别
它们都是在栈上有一个包含长度的胖指针,指向存储数据的内存位置
- Box<[T]> 只会指向堆
- &[T] 指向的位置可以是栈也可以是堆
- Box<[T]> 对数据具有所有权,而 &[T] 只是一个借用
如何产生Box<[T]>呢
从已有的 Vec<T> 中转换
use std::ops::Deref;
fn main() {
let mut v1 = vec![1, 2, 3, 4];
v1.push(5);
println!("cap should be 8: {}", v1.capacity());
// 从 Vec<T> 转换成 Box<[T]>,此时会丢弃多余的 capacity
let b1 = v1.into_boxed_slice();
let mut b2 = b1.clone();
let v2 = b1.into_vec();
println!("cap should be exactly 5: {}", v2.capacity());
assert!(b2.deref() == v2);
// Box<[T]> 可以更改其内部数据,但无法 push
b2[0] = 2;
// b2.push(6);
println!("b2: {:?}", b2);
// 注意 Box<[T]> 和 Box<[T; n]> 并不相同
let b3 = Box::new([2, 2, 3, 4, 5]);
println!("b3: {:?}", b3);
// b2 和 b3 相等,但 b3.deref() 和 v2 无法比较
assert!(b2 == b3);
// assert!(b3.deref() == v2);
}
- Vec<T> 可以通过 into_boxed_slice() 转换成 Box<[T]>,Box<[T]> 也可以通过 into_vec() 转换回 Vec<T>
- 当 Vec<T> 转换成 Box<[T]> 时,没有使用到的容量就会被丢弃,整体Box<[T]> 有一个很好的特性是,不像 Box<[T;n]> 那样在编译时就要确定大小,它可以在运行期生成,以后大小不会再改变占用的内存可能会降低
- Box<[T]>不像 Box<[T;n]> 那样在编译时就要确定大小,它可以在运行期生成,以后大小不会再改变
当我们需要在堆上创建固定大小的集合数据,且不希望自动增长,那么,可以先创建 Vec,再转换成 Box<[T]>
小结
下图描述了切片和数组 [T;n]、列表 Vec<T>、切片引用 &[T] /&mut [T],以及在堆上分配的切片 Box<[T]> 之间的关系
链接
- PartialEq trait
- 切片文档
- 切片的iter方法
- Iterator Adaptor
- Iterator的Map
- FromIterator
- Iterator take
- Iterator Filter
- Iterator try_fold
- 扩展增强的Iterator itertools
- tokio broadcast channel
精选问答
-
为什么rust解引用是用&T 来表示,而不是用*T
a. *&T 是引用,T 是解引用。比如你有一个 b = &mut u32,你可以 *b = 10 来解引用更改 b 指向的内存
b. Rust 大部分情况下都会做自动解引用(使用 . 的时候)。所以你会感觉很少需要用 *。stackoverflow.com/questions/2…