Contiguous regions of memory
Rust用以下三种类型来处理连续的内存:
- Vec\
:可变大小的堆向量数组 - [T; N]:编译器确定大小的数组
- [T]:用于代表连续内存的slice
一个Slice只能通过特定类型的指针来操作,指针类型为以下三种:
- &[T]
- &mut [T]
- Box<[T]>
如果我们有一个Vec<T>
变量,那么我们可以通过下面的方法得到对应的Slice:
1 | let vec = vec![12, 3, 4]; |
当然也可以直接创建一个Slice变量:
1 | let str_slice: &[&str] = &["one", "two", "three"]; |
[T; N]
[T; N]
是在编译器就确定长度的数组,有两种方式创建这种数组:
- [x, y, z]
- [x; N]:
x
被重复N次
如果数组的长度小于等于32,那么如果数组的元素类型允许(即元素类型实现了对应的Trait),那么数组自动实现以下Trait:
Clone
(T: Copy
)Debug
IntoIterator
(&[T; N]
和&mut [T; N]
实现了这个Trait)PartialEq
,PartialOrd
,Ord
,Eq
Hash
AsRef
,AsMut
Borrow
,BorrowMut
Default
之所以有长度的限制,是因为Rust不支持在数组长度上的泛型,所以就实现到了长度32为止。
Iterator
迭代器可以说在Rust中占有重要的位置,在日常编码过程中我们经常会用到迭代器。这里我只是记录几点自己看标准库文档的心得,具体使用还是请看标准库文档吧。
编写一个迭代器
编写一个迭代器很简单,我们只要实现Iterator
这个Trait中的next
方法就好了,其余的方法我们都可以免费获得:
1 | struct Counter { |
再来看一下Iterator
这个Trait:
1 | pub trait Iterator { |
我们必须要实现的就只是next
方法,其余的都是构建在next
之上,也就是说我们可以直接使用其余的方法。
Collections to Iterator
有三种方法得到一个集合的迭代器:
- iter:迭代类型为
&T
- iter_mut:迭代类型为
&mut T
- into_iter:迭代类型为
T
前两个方法是Slice中定义的方法,因为一个集合是一段连续的内存,所以可以调用前面两个方法。第三个方法是TraitIntoIterator
中定义的接口:
1 | pub trait IntoIterator where Self::IntoIter::Item == Self::Item { |
这里有一个地方需要注意,对于一个集合,比如说Vec<T>
,我们在该变量上调用into_iter
,那么迭代的对象是集合的类型,在迭代之后变量的所有权就被转移了:
1 | let v = vec![1, 2, 3]; |
但是如果是一个Slice呢,在它上面调用into_iter
,迭代的对象实际上是&T
:
1 | let v = &[1, 2, 3]; |
这是为什么呢?我们来看一下Slice文档中关于IntoIterator
这个Trait的实现说明:
1 | impl<'a, T> IntoIterator for &'a [T] type Item = &'a T The type of the elements being iterated over type IntoIter = Iter<'a, T> Which kind of iterator are we turning this into? |
只有一句话写在那里我们也不能很相信它,于是再来看看std::iter::Iter
这结构中关于Iterator
实现的说明:
1 | impl<'a, T> Iterator for Iter<'a, T> type Item = &'a T The type of the elements being iterated over. fn next(&mut self) -> Option<&'a T> |
可以看到Iterator
这个Trait的关联类型是&T
,所以我们迭代的对象的类型实际上是&T
,所以在迭代之后Slice的所有权并没有转移。