我正在尝试用 rust lang 编写一个双向链表。主要两种类型定义为:
struct IntList {
head: *mut Node,
tail: *mut Node,
size: usize,
}
#[derive(Copy)]
#[derive(Clone)]
struct Node {
data: i32,
prev: *mut Node,
next: *mut Node,
}
我使用了 2 个 util 函数:
pub unsafe fn raw_into_box<T>(raw: *mut T) -> Box<T> {
mem::transmute(raw)
}
pub fn box_into_raw<T>(b: Box<T>) -> *mut T {
unsafe { mem::transmute(b) }
}
而且,我想为列表类型创建一个插入方法:
impl IntList {
fn new() -> Self {
IntList {
head: ptr::null_mut(),
tail: ptr::null_mut(),
size: 0,
}
}
unsafe fn add_last(&mut self, v: i32) {
let node = Node { data: v, prev: self.tail, next: ptr::null_mut() };
let raw = box_into_raw(Box::new(node));
let node = *raw;
match self.size {
0 => {
self.head = raw;
self.tail = raw;
}
_ => unsafe {
// let mut last = *self.tail; // Notice here
// last.next = raw;
// not work
self.tail = raw;
}
}
self.size += 1;
println!("added:{}", node);
}
// ...
看哪,注释的行没有按预期工作。列表中的元素只有一种大小。然而,我改变了它们,就像这样
(*self.tail).next = raw;
结果将是正确的。
所以我的问题是,二分法有什么区别:
(*self.tail).next = raw;
对
let mut last = *self.tail;
last.next = raw;
前者有效,后者无效。为什么我不能使用中间对象?
*self.tail
是一个位置表达式(它代表self.tail
指向的内存位置);但是,表达式可以在不同的上下文中求值:
在
(*self.tail).next = raw;
中,*self.tail
是字段表达式的左操作数,因此位于位置表达式上下文中;然而
在
let mut last = *self.tail;
中,*self.tail
是let
语句的初始化器,因此位于值表达式上下文中。
作为 Rust 参考文档移动和复制类型:
当在值表达式上下文中计算位置表达式时,或者由模式中的值绑定时,它表示该内存位置中保存的值。如果该值的类型实现了
Copy
,那么该值将被复制。 [...]在你的例子中,值的类型是
Node
,它确实实现了
Copy
,所以let mut last = *self.tail;
将
Node
从*self.tail
复制到堆栈变量
last
。然后,您对该副本进行变异,当
last
超出范围时,该副本将被删除;
*self.tail
中的内存保持不变。
您可以通过创建对地点表达式的引用来借用地点表达式,这将实现您的预期:
let last = &mut *self.tail;
last
的类型,差异会更加明显。在您最初的尝试中,last
是
Node
类型,而在上面它是
&mut Node
类型。总而言之,实际上根本没有必要使用原始指针和
unsafe
强烈鼓励您不要这样做)。例如,您可以按照以下方式执行操作:
use std::{cell::Cell, rc::Rc};
struct IntList {
head: Option<Rc<Node>>,
tail: Option<Rc<Node>>,
size: usize,
}
#[derive(Clone)]
struct Node {
data: Cell<i32>,
prev: Option<Rc<Node>>,
next: Option<Rc<Node>>,
}
impl IntList {
fn new() -> Self {
IntList {
head: None,
tail: None,
size: 0,
}
}
fn add_last(&mut self, v: i32) {
let prev = self.tail.take();
let node = self.tail.insert(Rc::new(Node { data: Cell::new(v), prev, next: None }));
// if self.size == 0 {
// self.head = Some(node.clone());
// }
self.size += 1;
println!("added:{node}");
}
}
但是,当标准库为您提供 std::collections::LinkedList
(“具有自有节点的双向链表”)时,为什么要重新发明轮子。但请注意:
注意:使用Vec
VecDeque
几乎总是更好,因为基于数组的容器通常速度更快,内存效率更高,并且可以更好地利用 CPU 缓存。