将 "结点" 改为 "节点"

This commit is contained in:
wtklbm 2020-06-27 08:37:28 +08:00
parent 4d3e694894
commit 53f8710e5e
2 changed files with 17 additions and 17 deletions

View File

@ -3,7 +3,7 @@
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/src/ch15-04-rc.md) > <br>
> commit 6f292c8439927b4c5b870dd4afd2bfc52cc4eccc
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的结点,而这个结点从概念上讲为所有指向它的边所拥有。结点直到没有任何边指向它之前都不应该被清理。
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理。
为了启用多所有权Rust 有一个叫做 `Rc<T>` 的类型。其名称为 **引用计数**_reference counting_的缩写。引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如果某个值有零个引用就代表没有任何有效引用并可以被清理。

View File

@ -117,7 +117,7 @@ a rc count after changing a = 2
创建引用循环并不容易,但也不是不可能。如果你有包含 `Rc<T>``RefCell<T>` 值或类似的嵌套结合了内部可变性和引用计数的类型,请务必小心确保你没有形成一个引用循环;你无法指望 Rust 帮你捕获它们。创建引用循环是一个程序上的逻辑 bug你应该使用自动化测试、代码评审和其他软件开发最佳实践来使其最小化。
另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有。换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃。在示例 15-25 中,我们总是希望 `Cons` 成员拥有其列表,所以重新组织数据结构是不可能的。让我们看看一个由父结点和子结点构成的图的例子,观察何时是使用无所有权的关系来避免引用循环的合适时机。
另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有。换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃。在示例 15-25 中,我们总是希望 `Cons` 成员拥有其列表,所以重新组织数据结构是不可能的。让我们看看一个由父节点和子节点构成的图的例子,观察何时是使用无所有权的关系来避免引用循环的合适时机。
### 避免引用循环:将 `Rc<T>` 变为 `Weak<T>`
@ -129,7 +129,7 @@ a rc count after changing a = 2
我们会创建一个某项知道其子项**和**父项的树形结构的例子,而不是只知道其下一项的列表。
#### 创建树形数据结构:带有子点的 `Node`
#### 创建树形数据结构:带有子点的 `Node`
在最开始,我们将会构建一个带有子节点的树。让我们创建一个用于存放其拥有所有权的 `i32` 值和其子节点引用的 `Node`
@ -146,9 +146,9 @@ struct Node {
}
```
我们希望能够 `Node` 拥有其子点,同时也希望通过变量来共享所有权,以便可以直接访问树中的每一个 `Node`,为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>`。我们还希望能修改其他结点的子结点,所以 `children``Vec<Rc<Node>>` 被放进了 `RefCell<T>`
我们希望能够 `Node` 拥有其子点,同时也希望通过变量来共享所有权,以便可以直接访问树中的每一个 `Node`,为此 `Vec<T>` 的项的类型被定义为 `Rc<Node>`。我们还希望能修改其他节点的子节点,所以 `children``Vec<Rc<Node>>` 被放进了 `RefCell<T>`
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子结点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子结点的实例 `branch`,如示例 15-27 所示:
接下来,使用此结构体定义来创建一个叫做 `leaf` 的带有值 3 且没有子节点的 `Node` 实例,和另一个带有值 5 并以 `leaf` 作为子节点的实例 `branch`,如示例 15-27 所示:
<span class="filename">文件名: src/main.rs</span>
@ -175,15 +175,15 @@ fn main() {
}
```
<span class="caption">示例 15-27创建没有子结点的 `leaf` 结点和以 `leaf` 作为子结点的 `branch`</span>
<span class="caption">示例 15-27创建没有子节点的 `leaf` 节点和以 `leaf` 作为子节点的 `branch`</span>
这里克隆了 `leaf` 中的 `Rc<Node>` 并储存在了 `branch` 中,这意味着 `leaf` 中的 `Node` 现在有两个所有者:`leaf`和`branch`。可以通过 `branch.children``branch` 中获得 `leaf`,不过无法从 `leaf``branch`。`leaf` 没有到 `branch` 的引用且并不知道他们相互关联。我们希望 `leaf` 知道 `branch` 是其父点。稍后我们会这么做。
这里克隆了 `leaf` 中的 `Rc<Node>` 并储存在了 `branch` 中,这意味着 `leaf` 中的 `Node` 现在有两个所有者:`leaf`和`branch`。可以通过 `branch.children``branch` 中获得 `leaf`,不过无法从 `leaf``branch`。`leaf` 没有到 `branch` 的引用且并不知道他们相互关联。我们希望 `leaf` 知道 `branch` 是其父点。稍后我们会这么做。
#### 增加从子到父的引用
为了使子结点知道其父结点,需要在 `Node` 结构体定义中增加一个 `parent` 字段。问题是 `parent` 的类型应该是什么。我们知道其不能包含 `Rc<T>`,因为这样 `leaf.parent` 将会指向 `branch``branch.children` 会包含 `leaf` 的指针,这会形成引用循环,会造成其 `strong_count` 永远也不会为 0.
为了使子节点知道其父节点,需要在 `Node` 结构体定义中增加一个 `parent` 字段。问题是 `parent` 的类型应该是什么。我们知道其不能包含 `Rc<T>`,因为这样 `leaf.parent` 将会指向 `branch``branch.children` 会包含 `leaf` 的指针,这会形成引用循环,会造成其 `strong_count` 永远也不会为 0.
现在换一种方式思考这个关系,父结点应该拥有其子结点:如果父结点被丢弃了,其子结点也应该被丢弃。然而子结点不应该拥有其父结点:如果丢弃子结点,其父结点应该依然存在。这正是弱引用的例子!
现在换一种方式思考这个关系,父节点应该拥有其子节点:如果父节点被丢弃了,其子节点也应该被丢弃。然而子节点不应该拥有其父节点:如果丢弃子节点,其父节点应该依然存在。这正是弱引用的例子!
所以 `parent` 使用 `Weak<T>` 类型而不是 `Rc<T>`,具体来说是 `RefCell<Weak<Node>>`。现在 `Node` 结构体定义看起来像这样:
@ -201,7 +201,7 @@ struct Node {
}
```
这样,一个结点就能够引用其父结点,但不拥有其父结点。在示例 15-28 中,我们更新 `main` 来使用新定义以便 `leaf` 结点可以通过 `branch` 引用其父结点:
这样,一个节点就能够引用其父节点,但不拥有其父节点。在示例 15-28 中,我们更新 `main` 来使用新定义以便 `leaf` 节点可以通过 `branch` 引用其父节点:
<span class="filename">文件名: src/main.rs</span>
@ -237,19 +237,19 @@ fn main() {
}
```
<span class="caption">示例 15-28一个 `leaf` 结点,其拥有指向其父结`branch``Weak` 引用</span>
<span class="caption">示例 15-28一个 `leaf` 节点,其拥有指向其父节`branch``Weak` 引用</span>
创建 `leaf` 结点类似于示例 15-27 中如何创建 `leaf` 结点的,除了 `parent` 字段有所不同:`leaf` 开始时没有父结点,所以我们新建了一个空的 `Weak` 引用实例。
创建 `leaf` 节点类似于示例 15-27 中如何创建 `leaf` 节点的,除了 `parent` 字段有所不同:`leaf` 开始时没有父节点,所以我们新建了一个空的 `Weak` 引用实例。
此时,当尝试使用 `upgrade` 方法获取 `leaf` 的父点引用时,会得到一个 `None` 值。如第一个 `println!` 输出所示:
此时,当尝试使用 `upgrade` 方法获取 `leaf` 的父点引用时,会得到一个 `None` 值。如第一个 `println!` 输出所示:
```text
leaf parent = None
```
当创建 `branch` 点时,其也会新建一个 `Weak<Node>` 引用,因为 `branch` 并没有父结点。`leaf` 仍然作为 `branch` 的一个子结点。一旦在 `branch` 中有了 `Node` 实例,就可以修改 `leaf` 使其拥有指向父点的 `Weak<Node>` 引用。这里使用了 `leaf``parent` 字段里的 `RefCell<Weak<Node>>``borrow_mut` 方法,接着使用了 `Rc::downgrade` 函数来从 `branch` 中的 `Rc` 值创建了一个指向 `branch``Weak<Node>` 引用。
当创建 `branch` 点时,其也会新建一个 `Weak<Node>` 引用,因为 `branch` 并没有父节点。`leaf` 仍然作为 `branch` 的一个子节点。一旦在 `branch` 中有了 `Node` 实例,就可以修改 `leaf` 使其拥有指向父点的 `Weak<Node>` 引用。这里使用了 `leaf``parent` 字段里的 `RefCell<Weak<Node>>``borrow_mut` 方法,接着使用了 `Rc::downgrade` 函数来从 `branch` 中的 `Rc` 值创建了一个指向 `branch``Weak<Node>` 引用。
当再次打印出 `leaf` 的父点时,这一次将会得到存放了 `branch``Some` 值:现在 `leaf` 可以访问其父点了!当打印出 `leaf` 时,我们也避免了如示例 15-26 中最终会导致栈溢出的循环:`Weak<Node>` 引用被打印为 `(Weak)`
当再次打印出 `leaf` 的父点时,这一次将会得到存放了 `branch``Some` 值:现在 `leaf` 可以访问其父点了!当打印出 `leaf` 时,我们也避免了如示例 15-26 中最终会导致栈溢出的循环:`Weak<Node>` 引用被打印为 `(Weak)`
```text
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
@ -326,9 +326,9 @@ fn main() {
当内部作用域结束时,`branch` 离开作用域,`Rc<Node>` 的强引用计数减少为 0所以其 `Node` 被丢弃。来自 `leaf.parent` 的弱引用计数 1 与 `Node` 是否被丢弃无关,所以并没有产生任何内存泄露!
如果在内部作用域结束后尝试访问 `leaf` 的父点,会再次得到 `None`。在程序的结尾,`leaf` 中 `Rc<Node>` 的强引用计数为 1弱引用计数为 0因为现在 `leaf` 又是 `Rc<Node>` 唯一的引用了。
如果在内部作用域结束后尝试访问 `leaf` 的父点,会再次得到 `None`。在程序的结尾,`leaf` 中 `Rc<Node>` 的强引用计数为 1弱引用计数为 0因为现在 `leaf` 又是 `Rc<Node>` 唯一的引用了。
所有这些管理计数和值的逻辑都内建于 `Rc<T>``Weak<T>` 以及它们的 `Drop` trait 实现中。通过在 `Node` 定义中指定从子结点到父结点的关系为一个`Weak<T>`引用,就能够拥有父结点和子结点之间的双向引用而不会造成引用循环和内存泄露。
所有这些管理计数和值的逻辑都内建于 `Rc<T>``Weak<T>` 以及它们的 `Drop` trait 实现中。通过在 `Node` 定义中指定从子节点到父节点的关系为一个`Weak<T>`引用,就能够拥有父节点和子节点之间的双向引用而不会造成引用循环和内存泄露。
## 总结