前面深入学习了struct、Option和Box的所有权约束及各种场景的使用,现在用struct、Option和Box组合来实现一个单向链表,并实现链表常见的头部插入、删除,尾部插入、删除,中间插入、删除,链表反转,遍历等操作。
一、链表和节点定义
为了方便演示,我们先定义节点,以及节点的new 和link操作,然后再定义一个链表结构体,其中仅有一个头指针成员。
1.1 先定义节点及节点的new / link 操作
#[derive(Debug)]
struct Node {
val: i32,
next: Option<Box<Node>>, // 'next' has the ownership of the next node.
}
impl Node {
fn new(val: i32) -> Box<Node> {
Box::new(Node { val, next: None })
}
fn link(&mut self, node: Box<Node>) {
self.next = Some(node);
}
}
解释几点:
- 节点的next指向下一个节点,可能为空,故类型为Option;
- 避免编译器无法计算节点大小,用Box包裹Node;
- next实际上是用Option包裹的指向下个节点的Box指针,并且拥有下个节点的所有权;
1.2 定义链表结构体
#[derive(Debug)]
struct BoxedLinkedList {
head: Option<Box<Node>>,
}
impl BoxedLinkedList {
fn new() -> BoxedLinkedList {
BoxedLinkedList { head: None }
}
fn display(&self) {
println!("{:?}", self);
}
}
二、头部插入、删除
2.1 头部插入
头部的插入时,首先判断头指针是否为空(即链表为空),如果为空,直接让头指针指向新节点即可,否则,让新节点指向头指针,然后再让头指针指向新节点。按照C/C++的习惯,代码如下:
fn push_front(list: &mut BoxedLinkedList, value: i32) {
let mut new_node = Node::new(value);
new_node.next = list.head; // Error! cannot move out of `list.head` which is behind a mutable reference
list.head = Some(new_node);
}
编译错误!函数体的第二行编译错误,提示如注释。正确的代码如下,这里直接改成了BoxedLinkedList的方法:
impl BoxedLinkedList {
fn push_front(&mut self, val: i32) {
let mut new_node = Node::new(val);
if let Some(node) = self.head.take() {
new_node.link(node);
}
self.head = Some(new_node);
}
}
- 将list.head 这个Option包含的值用前面学到的take()方法取出;
- 然后link到新节点后面;
- 头指针指向新的节点;
运行测试结果:
fn main() {
let mut list = BoxedLinkedList::new();
for i in 1..3 {
list.push_front(i);
}
list.display();
}
输出:
BoxedLinkedList { head: Some(Node { val: 2, next: Some(Node { val: 1, next: None }) }) }
2.2 头部删除
头部删除实现的原理类似,思路就不说了,具体实现时也不能像c语言的写法那样:
head = head.next;
仍然需要所有权的获取和转移。先为List定义一个判断是否为空的方法:
impl BoxedLinkedList {
fn is_empty(&self) -> bool {
self.head.is_none()
}
}
从头部删除元素的方法:
impl BoxedLinkedList {
fn pop_front(&mut self) -> Option<i32> {
// 获取头指针的包裹的值;生成一个新的Option;
match self.head.take() {
None => None, // 为空的话直接返回None;
Some(node) => {
// 注意此时拥有 node 的所有权;
self.head = node.next; // Box自动解引用,然后将node的next的所有权转移给变量head;
Some(node.val)
}
}
}
}
实验如下:
fn main() {
let mut list = BoxedLinkedList::new();
for i in 1..3 {
list.push_front(i);
}
list.display();
println!();
while !list.is_empty() {
let v = list.pop_front().unwrap();
println!("pop '{}' from head of list", v);
print!("now list is : ");
list.display();
}
}
输出如下:
BoxedLinkedList { head: Some(Node { val: 2, next: Some(Node { val: 1, next: None }) }) }
pop '2' from head of list
now list is : BoxedLinkedList { head: Some(Node { val: 1, next: None }) }
pop '1' from head of list
now list is : BoxedLinkedList { head: None }
OK!只用智能指针,不用unsafe,也可以实现!这些实现来自于前面对于rust所有权和各种引用、可变引用的所学,都用上了!
下次尝试实现尾部的插入和删除。