From 51a5b45811a074f0a018b16d2873a77b64d76469 Mon Sep 17 00:00:00 2001 From: sunface Date: Thu, 10 Mar 2022 22:21:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=EF=BC=9A?= =?UTF-8?q?=E6=89=8B=E6=8A=8A=E6=89=8B=E5=B8=A6=E4=BD=A0=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E9=93=BE=E8=A1=A8-=E5=9F=BA=E6=9C=AC=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bad-stack/basic-operations.md | 134 +++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/src/too-many-lists/bad-stack/basic-operations.md b/src/too-many-lists/bad-stack/basic-operations.md index 9e3d9979..104e7607 100644 --- a/src/too-many-lists/bad-stack/basic-operations.md +++ b/src/too-many-lists/bad-stack/basic-operations.md @@ -129,4 +129,136 @@ pub fn push(&mut self, elem: i32) { 但是不管怎样,我们成功的完成了 `push` 方法,下面再来看看 `pop`。 -## Pop \ No newline at end of file +## Pop +`push` 是插入元素,那 `pop` 自然就是推出一个元素,因此也需要使用 `&mut self`,除此之外,推出的元素需要被返回,这样调用者就可以获取该元素: +```rust +pub fn pop(&mut self) -> Option { + // TODO +} +``` + +我们还需要一个办法来根据 `Link` 是否有值进行不同的处理,这个可以使用 `match` 来进行模式匹配: +```rust +pub fn pop(&mut self) -> Option { + match self.head { + Link::Empty => { + // TODO + } + Link::More(node) => { + // TODO + } + }; +} +``` + +目前的代码显然会报错,因为函数的返回值是 `Option` 枚举,而目前的返回值是 [`()`](https://course.rs/basic/base-type/function.html#无返回值)。当然,我们可以返回一个`Option` 的枚举成员 `None`,但是一个更好的做法是使用 `unimplemented!()`,该宏可以明确地说明目前的代码还没有实现,一旦代码执行到 `unimplemented!()` 的位置,就会发生一个 `panic`。 + +```rust +pub fn pop(&mut self) -> Option { + match self.head { + Link::Empty => { + // TODO + } + Link::More(node) => { + // TODO + } + }; + unimplemented!() +} +``` +`panics` 是一种[发散函数](https://course.rs/basic/base-type/function.html?search=#永不返回的函数),该函数永不返回任何值,因此可以用于需要返回任何类型的地方。这句话很不好理解,但是从上面的代码中可以看出 `unimplemented!()` 是永不返回的函数,但是它却可以用于一个返回 `Option` 的函数中来替代返回值。 + +以上代码果不其然又报错了: +```shell +> cargo build + +error[E0507]: cannot move out of borrowed content + --> src/first.rs:28:15 + | +28 | match self.head { + | ^^^^^^^^^ + | | + | cannot move out of borrowed content + | help: consider borrowing here: `&self.head` +... +32 | Link::More(node) => { + | ---- data moved here + | +note: move occurs because `node` has type `std::boxed::Box`, which does not implement the `Copy` trait +``` + +好在编译器偷偷提示了我们使用借用来替代所有权转移: `&self.head`。修改后,如下: +```rust +pub fn pop(&mut self) -> Option { + match &self.head { + Link::Empty => { + // TODO + } + Link::More(node) => { + // TODO + } + }; + unimplemented!() +} +``` + +是时候填写相应的逻辑了: +```rust +pub fn pop(&mut self) -> Option { + let result; + match &self.head { + Link::Empty => { + result = None; + } + Link::More(node) => { + result = Some(node.elem); + self.head = node.next; + } + }; + result +} +``` + +当链表为 `Empty` 时,返回一个 `None`,表示我们没有 `pop` 到任何元素;若不为空,则返回第一个元素,并将 `head` 指向下一个节点 `node.next`。但是这段代码又报错了: +```shell +error[E0507]: cannot move out of `node.next` which is behind a shared reference + --> src/first.rs:37:29 + | +37 | self.head = node.next; + | ^^^^^^^^^ move occurs because `node.next` has type `Link`, which does not implement the `Copy` trait +``` + + +原因是试图转移 `node` 的所有权,但只有它的引用。回头仔细看看代码,会发现这里的关键是我们希望移除一些东西,这意味着需要通过值的方式获取链表的 head。看来只能故技重施了: +```rust +pub fn pop(&mut self) -> Option { + let result; + match std::mem::replace(&mut self.head, Link::Empty) { + Link::Empty => { + result = None; + } + Link::More(node) => { + result = Some(node.elem); + self.head = node.next; + } + }; + result +} +``` + +我们将 `self.head` 的值偷出来,然后再将 `Link::Empty` 填回到 `self.head` 中。此时用于 `match` 匹配的就是一个拥有所有权的值类型,而不是之前的引用类型。 + +事实上,上面的代码有些啰嗦,我们可以直接在 `match` 的两个分支中通过表达式进行返回: +```rust +pub fn pop(&mut self) -> Option { + match std::mem::replace(&mut self.head, Link::Empty) { + Link::Empty => None, + Link::More(node) => { + self.head = node.next; + Some(node.elem) + } + } +} +``` + +这样修改后,代码就更加简洁,可读性也更好了,至此链表的基本操作已经完成,下面让我们写一个测试代码来测试下它的功能和正确性。 \ No newline at end of file