Rust学习笔记(13)——struct、Option和Box组合应用实现单向链表

前面深入学习了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);
    }
}

解释几点:

  1. 节点的next指向下一个节点,可能为空,故类型为Option;
  2. 避免编译器无法计算节点大小,用Box包裹Node;
  3. 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);
    }
}
  1. 将list.head 这个Option包含的值用前面学到的take()方法取出;
  2. 然后link到新节点后面;
  3. 头指针指向新的节点;

运行测试结果:

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所有权和各种引用、可变引用的所学,都用上了!

下次尝试实现尾部的插入和删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值