add links/basic-operations

pull/549/head
sunface 3 years ago
parent 27c8f56536
commit 8e27a57bed

@ -157,8 +157,8 @@
- [我们到底需不需要链表](too-many-lists/do-we-need-it.md) - [我们到底需不需要链表](too-many-lists/do-we-need-it.md)
- [不太优秀的单向链表:栈](too-many-lists/bad-stack/intro.md) - [不太优秀的单向链表:栈](too-many-lists/bad-stack/intro.md)
- [数据布局](too-many-lists/bad-stack/layout.md) - [数据布局](too-many-lists/bad-stack/layout.md)
<!-- - [New](too-many-lists/bad-stack/new.md) - [基本操作](too-many-lists/bad-stack/basic-operations.md)
- [Ownership 101](too-many-lists/bad-stack/ownership.md) <!-- - [Ownership 101](too-many-lists/bad-stack/ownership.md)
- [Push](too-many-lists/bad-stack/push.md) - [Push](too-many-lists/bad-stack/push.md)
- [Pop](too-many-lists/bad-stack/pop.md) - [Pop](too-many-lists/bad-stack/pop.md)
- [Drop](too-many-lists/bad-stack/drop.md) - [Drop](too-many-lists/bad-stack/drop.md)

@ -72,6 +72,7 @@ fn main() {
`impl Rectangle {}` 表示为 `Rectangle` 实现方法(`impl` 是实现 _implementation_ 的缩写),这样的写法表明 `impl` 语句块中的一切都是跟 `Rectangle` 相关联的。 `impl Rectangle {}` 表示为 `Rectangle` 实现方法(`impl` 是实现 _implementation_ 的缩写),这样的写法表明 `impl` 语句块中的一切都是跟 `Rectangle` 相关联的。
#### self、&self 和 &mut self
接下里的内容非常重要,请大家仔细看。在 `area` 的签名中,我们使用 `&self` 替代 `rectangle: &Rectangle``&self` 其实是 `self: &Self` 的简写(注意大小写)。在一个 `impl` 块内,`Self` 指代被实现方法的结构体类型,`self` 指代此类型的实例,换句话说,`self` 指代的是 `Rectangle` 结构体实例,这样的写法会让我们的代码简洁很多,而且非常便于理解:我们为哪个结构体实现方法,那么 `self` 就是指代哪个结构体的实例。 接下里的内容非常重要,请大家仔细看。在 `area` 的签名中,我们使用 `&self` 替代 `rectangle: &Rectangle``&self` 其实是 `self: &Self` 的简写(注意大小写)。在一个 `impl` 块内,`Self` 指代被实现方法的结构体类型,`self` 指代此类型的实例,换句话说,`self` 指代的是 `Rectangle` 结构体实例,这样的写法会让我们的代码简洁很多,而且非常便于理解:我们为哪个结构体实现方法,那么 `self` 就是指代哪个结构体的实例。
需要注意的是,`self` 依然有所有权的概念: 需要注意的是,`self` 依然有所有权的概念:

@ -0,0 +1,132 @@
# 定义基本操作
这个章节我们一起来为新创建的 `List` 定义一些基本操作,首先从创建链表开始。
## New
为了将实际的代码跟类型关联在一起,我们需要使用 `impl` 语句块:
```rust
impl List {
// TODO
}
```
下一步就是创建一个关联函数,用于构建 `List` 的新实例,该函数的作用类似于其他语言的构造函数。
```rust
impl List {
pub fn new() -> Self {
List { head: Link::Empty }
}
}
```
> 学习链接: [impl、关联函数](https://course.rs/basic/method.html#关联函数)、[Self](https://course.rs/basic/trait/trait-object.html?highlight=Self#self-与-self)
## Push
在开始实现之前,你需要先了解 [self、&self、&mut sef](https://course.rs/basic/method.html#selfself-和-mut-self) 这几个概念。
在创建链表后,下一步就是往链表中插入新的元素,由于 `push` 会改变链表,因此我们使用 `&mut self` 的方法签名:
```rust
impl List {
pub fn push(&mut self, elem: i32) {
// TODO
}
}
```
根据之前的数据定义,首先需要创建一个 `Node` 来存放该元素:
```rust
pub fn push(&mut self, elem: i32) {
let new_node = Node {
elem: elem,
next: ?????
};
}
```
下一步需要让该节点指向之前的旧 `List`:
```rust
pub fn push(&mut self, elem: i32) {
let new_node = Node {
elem: elem,
next: self.head,
};
}
```
```shell
error[E0507]: cannot move out of `self.head` which is behind a mutable reference
--> src/first.rs:23:19
|
23 | next: self.head,
| ^^^^^^^^^ move occurs because `self.head` has type `Link`, which does not implement the `Copy` trait
```
但是,如上所示,这段代码会报错,因为试图将借用的值 `self` 中的 `head` 字段的所有权转移给 `next` ,在 Rust 中这是不被允许的。那如果我们试图将值再放回去呢?
```rust
pub fn push(&mut self, elem: i32) {
let new_node = Box::new(Node {
elem: elem,
next: self.head,
});
self.head = Link::More(new_node);
}
```
其实在写之前,应该就预料到结果了,显然这也是不行的,虽然从我们的角度来看还挺正常的,但是 Rust 并不会接受(有多种原因,其中主要的是[Exception safety](https://doc.rust-lang.org/nightly/nomicon/exception-safety.html))。
我们需要一个办法,让 Rust 不再阻挠我们,其中一个可行的办法是使用 `clone`:
```rust
pub struct List {
head: Link,
}
#[derive(Clone)]
enum Link {
Empty,
More(Box<Node>),
}
#[derive(Clone)]
struct Node {
elem: i32,
next: Link,
}
impl List {
pub fn new() -> Self {
List { head: Link::Empty }
}
pub fn push(&mut self, elem: i32) {
let new_node = Node {
elem: elem,
next: self.head.clone(),
};
}
}
```
`clone` 用起来简单难,且可解万愁,但是。。。既然是链表,性能那自然是很重要的,特别是要封装成库给其他代码使用时,那性能更是重中之重。
没办法了,我们只能向大名鼎鼎的 Rust 黑客 Indiana Jones求助了:
<img src="https://rust-unofficial.github.io/too-many-lists/img/indy.gif" />
经过一番诚心祈愿Indy 建议我们使用 `mem::replace` 秘技。这个非常有用的函数允许我们从一个借用中偷出一个值的同时再放入一个新值。
```rust
pub fn push(&mut self, elem: i32) {
let new_node = Box::new(Node {
elem: elem,
next: std::mem::replace(&mut self.head, Link::Empty),
});
self.head = Link::More(new_node);
}
```
这里,我们从借用 `self` 中偷出了它的值 `head` 并赋予给 `next` 字段,同时将一个新值 `Link::Empty` 放入到 `head` 中,成功完成偷梁换柱。不得不说,这个做法非常刺激,但是很不幸的是,目前为止,最好的办法可能也只能是它了。
但是不管怎样,我们成功的完成了 `push` 方法,下面再来看看 `pop`
## Pop
Loading…
Cancel
Save