From e8b2011741571058ebcf725d7e85adfdcf8d609d Mon Sep 17 00:00:00 2001 From: sunface Date: Fri, 11 Mar 2022 15:03:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=AB=A0=E8=8A=82=EF=BC=9AOk?= =?UTF-8?q?=20=E5=8D=95=E9=93=BE=E8=A1=A8=E6=A0=88=20-=20Peek=20=E5=87=BD?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SUMMARY.md | 2 +- .../ok-stack/advanced-operations.md | 1 - src/too-many-lists/ok-stack/peek.md | 137 ++++++++++++++++++ .../ok-stack/type-optimizing.md | 11 ++ 4 files changed, 149 insertions(+), 2 deletions(-) delete mode 100644 src/too-many-lists/ok-stack/advanced-operations.md create mode 100644 src/too-many-lists/ok-stack/peek.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c45d9045..800199b7 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -161,7 +161,7 @@ - [最后实现](too-many-lists/bad-stack/final-code.md) - [还可以的单向链表](too-many-lists/ok-stack/intro.md) - [优化类型定义](too-many-lists/ok-stack/type-optimizing.md) - - [进阶操作](too-many-lists/ok-stack/advanced-operations.md) + - [定义 Peek 函数](too-many-lists/ok-stack/peek.md) - [易混淆概念解析](confonding/intro.md) - [切片和切片引用](confonding/slice.md) diff --git a/src/too-many-lists/ok-stack/advanced-operations.md b/src/too-many-lists/ok-stack/advanced-operations.md deleted file mode 100644 index f8dde501..00000000 --- a/src/too-many-lists/ok-stack/advanced-operations.md +++ /dev/null @@ -1 +0,0 @@ -# 进阶操作 diff --git a/src/too-many-lists/ok-stack/peek.md b/src/too-many-lists/ok-stack/peek.md new file mode 100644 index 00000000..a268b40f --- /dev/null +++ b/src/too-many-lists/ok-stack/peek.md @@ -0,0 +1,137 @@ +# Peek 函数 +在之前章节中,我们定义了 `push`、`pop` 等基础操作,下面一起添加几个进阶操作,让我们的链表有用起来。 + + +首先实现的就是 `peek` 函数,它会返回链表的表头元素的引用: +```rust +pub fn peek(&self) -> Option<&T> { + self.head.map(|node| { + &node.elem + }) +} +``` + +```shell +> cargo build + +error[E0515]: cannot return reference to local data `node.elem` + --> src/second.rs:37:13 + | +37 | &node.elem + | ^^^^^^^^^^ returns a reference to data owned by the current function + +error[E0507]: cannot move out of borrowed content + --> src/second.rs:36:9 + | +36 | self.head.map(|node| { + | ^^^^^^^^^ cannot move out of borrowed content +``` + +哎,Rust 大爷,您又哪里不满意了。不过问题倒是也很明显: `map` 方法是通过 `self` 获取的值,我们相当于把内部值的引用返回给函数外面的调用者。 + +一个比较好的解决办法就是让 `map` 作用在引用上,而不是直接作用在 `self.head` 上,为此我们可以使用 `Option` 的 `as_ref` 方法: +```rust +impl Option { + pub fn as_ref(&self) -> Option<&T>; +} +``` + +该方法将一个 `Option` 变成了 `Option<&T>`,然后再调用 `map` 就会对引用进行处理了: +```rust +pub fn peek(&self) -> Option<&T> { + self.head.as_ref().map(|node| { + &node.elem + }) +} +``` + +```shell +cargo build + + Finished dev [unoptimized + debuginfo] target(s) in 0.32s +``` + +当然,我们还可以通过类似的方式获取一个可变引用: +```rust +pub fn peek_mut(&mut self) -> Option<&mut T> { + self.head.as_mut().map(|node| { + &mut node.elem + }) +} +``` + +至此 `peek` 已经完成,为了测试它的功能,我们还需要编写一个测试用例: +```rust +#[test] +fn peek() { + let mut list = List::new(); + assert_eq!(list.peek(), None); + assert_eq!(list.peek_mut(), None); + list.push(1); list.push(2); list.push(3); + + assert_eq!(list.peek(), Some(&3)); + assert_eq!(list.peek_mut(), Some(&mut 3)); + list.peek_mut().map(|&mut value| { + value = 42 + }); + + assert_eq!(list.peek(), Some(&42)); + assert_eq!(list.pop(), Some(42)); +} +``` + +```shell +> cargo test + +error[E0384]: cannot assign twice to immutable variable `value` + --> src/second.rs:100:13 + | +99 | list.peek_mut().map(|&mut value| { + | ----- + | | + | first assignment to `value` + | help: make this binding mutable: `mut value` +100 | value = 42 + | ^^^^^^^^^^ cannot assign twice to immutable variable ^~~~~ +``` + +天呐,错误源源不断,这次编译器抱怨说 `value` 是不可变的,但是我们明明使用 `&mut value`,发生了什么?难道说在闭包中通过这种方式申明的可变性实际上没有效果? + +实际上 `&mut value` 是一个模式匹配,它用 `&mut value` 模式去匹配一个可变的引用,此时匹配出来的 `value` 显然是一个值,而不是可变引用,因为只有完整的形式才是可变引用! + +因此我们没必要画蛇添足,这里直接使用 `|value|` 来匹配可变引用即可,那么此时匹配出来的 `value` 就是一个可变引用。 + +```rust +#[test] +fn peek() { + let mut list = List::new(); + assert_eq!(list.peek(), None); + assert_eq!(list.peek_mut(), None); + list.push(1); list.push(2); list.push(3); + + assert_eq!(list.peek(), Some(&3)); + assert_eq!(list.peek_mut(), Some(&mut 3)); + + list.peek_mut().map(|value| { + *value = 42 + }); + + assert_eq!(list.peek(), Some(&42)); + assert_eq!(list.pop(), Some(42)); +} +``` + +这次我们直接匹配出来可变引用 `value`,然后对其修改即可。 + +```shell +cargo test + + Running target/debug/lists-5c71138492ad4b4a + +running 3 tests +test first::test::basics ... ok +test second::test::basics ... ok +test second::test::peek ... ok + +test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured +``` \ No newline at end of file diff --git a/src/too-many-lists/ok-stack/type-optimizing.md b/src/too-many-lists/ok-stack/type-optimizing.md index bd8669a7..05667807 100644 --- a/src/too-many-lists/ok-stack/type-optimizing.md +++ b/src/too-many-lists/ok-stack/type-optimizing.md @@ -142,6 +142,17 @@ test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured ## 泛型 为了让链表支持任何类型的元素,泛型就是绕不过去的坎,首先将所有的类型定义修改为泛型实现: ```rust +pub struct List { + head: Link, +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + impl List { pub fn new() -> Self { List { head: None }