Skip to content
This repository has been archived by the owner on Aug 24, 2024. It is now read-only.

Update 05-pointers.md #26

Merged
merged 1 commit into from
Jan 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions 05-pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ fn levelUp(user: User) void {

虽然编译成功了,但输出结果却是`User 1 has power of 100`,而我们代码的目的显然是让 `levelUp` 将用户的 `power` 提升到 101。这是怎么回事?

要理解这一点,可以将数据与内存联系起来,而变量则是将一种类型与特定内存位置联系起来的标签。例如,在`main` 中,我们创建了一个用户。内存中数据的简单可视化表示如下

要理解这一点,我们可以将数据与内存联系起来,变量只是将类型与特定内存位置关联起来的标签。例如,在 `main` 中,我们创建了一个用户。内存中数据的简单可视化表示如下
要理解这一点,我们可以将数据与内存联系起来,而变量只是将类型与特定内存位置关联起来的标签。例如,在 `main` 中,我们创建了一个`User`。内存中数据的简单可视化表示如下

```text
user -> ------------ (id)
Expand All @@ -59,14 +57,14 @@ user -> ------------ (id)

有两点需要注意:

1. 我们的用户变量指向结构的起点
1. 我们的`user`变量指向结构的起点
2. 字段是按顺序排列的

请记住,我们的用户也有一个类型。该类型告诉我们 `id` 是一个 64 位整数,`power` 是一个 32 位整数。有了对数据起始位置和类型的引用,编译器就可以将 `user.power` 转换为:访问位置在结构体第 64 位上的一个 32 位整数。这就是变量的威力,它们可以引用内存,并包含以有意义的方式理解和操作内存所需的类型信息。
请记住,我们的`user`也有一个类型。该类型告诉我们 `id` 是一个 64 位整数,`power` 是一个 32 位整数。有了对数据起始位置和类型的引用,编译器就可以将 `user.power` 转换为:访问位置在结构体第 64 位上的一个 32 位整数。这就是变量的威力,它们可以引用内存,并包含以有意义的方式理解和操作内存所需的类型信息。

> 默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 `packed struct`时,我们才能获得内存布局的有力保证。尽管如此,我们对用户的可视化还是合理而有用的
> 默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 `packed struct`时,我们才能获得内存布局的有力保证。尽管如此,我们对`user`的可视化还是合理而有用的

下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是用户变量引用的内存地址,也是第一个字段 `id` 的值所在的位置。由于 `id` 是一个 64 位整数,需要 8 字节内存。因此,`power` 必须位于 `$start_address + 8` 上:
下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是`user`变量引用的内存地址,也是第一个字段 `id` 的值所在的位置。由于 `id` 是一个 64 位整数,需要 8 字节内存。因此,`power` 必须位于 `$start_address + 8` 上:

```text
user -> ------------ (id: 1043368d0)
Expand Down Expand Up @@ -188,7 +186,7 @@ pub const User = struct {

我不止一次暗示过,在默认情况下,Zig 会传递一个值的副本(称为 "按值传递")。很快我们就会发现,实际情况要更微妙一些(提示:嵌套对象的复杂值怎么办?)

即使坚持使用简单类型,事实也是 Zig 可以随心所欲地传递参数,只要它能保证代码的意图不受影响。在我们最初的 `levelUp` 中,参数是一个用户,Zig 可以传递用户的副本或对 `main.user` 的引用,只要它能保证函数不会对其进行更改即可。(我知道我们最终确实希望它被变异,但通过创建 `User` 类型,我们告诉编译器我们不希望它被变异)。
即使坚持使用简单类型,事实也是 Zig 可以随心所欲地传递参数,只要它能保证代码的意图不受影响。在我们最初的 `levelUp` 中,参数是一个`User`,Zig 可以传递用户的副本或对 `main.user` 的引用,只要它能保证函数不会对其进行更改即可。(我知道我们最终确实希望它被变异,但通过创建 `User` 类型,我们告诉编译器我们不希望它被变异)。

这种自由度允许 Zig 根据参数类型使用最优策略。像 User 这样的小类型可以通过值传递(即复制),成本较低。较大的类型通过引用传递可能更便宜。只要代码的意图得以保留,Zig 可以使用任何方法。在某种程度上,使用常量函数参数可以做到这一点。

Expand Down Expand Up @@ -232,7 +230,7 @@ fn levelUp(user: *User) void {

上面打印了`user`变量引用的地址及其值,这个值就是`main`函数中的`user`的地址。

如果`user`的类型是`*User`,那么`&user`呢?它的类型是`**User`, 或者说是一个指向用户指针的指针。我可以一直这样做,直到内存溢出!
如果`user`的类型是`*User`,那么`&user`呢?它的类型是`**User`, 或者说是一个指向`User`指针的指针。我可以一直这样做,直到内存溢出!

我们可以使用多级间接指针,但这并不是我们现在所需要的。本节的目的是说明指针并不特殊,它只是一个值,即一个地址和一种类型。

Expand Down Expand Up @@ -351,7 +349,7 @@ pub const User = struct {

## 递归结构

有时你需要一个递归结构。在保留现有代码的基础上,我们为 `User` 添加一个可选的 `manager` 字段,类型为 `?User`。同时,我们将创建两个用户,并将其中一个指定为另一个的管理者:
有时你需要一个递归结构。在保留现有代码的基础上,我们为 `User` 添加一个可选的 `manager` 字段,类型为 `?User`。同时,我们将创建两个`User`,并将其中一个指定为另一个的管理者:

```zig
const std = @import("std");
Expand Down Expand Up @@ -383,7 +381,7 @@ pub const User = struct {

我们在添加 `name` 时没有遇到这个问题,尽管 `name`可以有不同的长度。问题不在于值的大小,而在于类型本身的大小。name 是一个切片,即 `[]const u8`,它有一个已知的大小:16 字节,其中 `len` 8 字节,`ptr` 8 字节。

你可能会认为这对任何 `Optional`或 `union` 来说都是个问题。但对于它们来说,最大字段的大小是已知的,这样 Zig 就可以使用它。递归结构没有这样的上限,该结构可以递归一次、两次或数百万次。这个次数会因用户而异,在编译时是不知道的。
你可能会认为这对任何 `Optional`或 `union` 来说都是个问题。但对于它们来说,最大字段的大小是已知的,这样 Zig 就可以使用它。递归结构没有这样的上限,该结构可以递归一次、两次或数百万次。这个次数会因`User`而异,在编译时是不知道的。

我们通过 `name` 看到了答案:使用指针。指针总是占用 `usize` 字节。在 64 位平台上,指针占用 8 个字节。就像`Goku`并没有与 `user`一起存储一样,使用指针意味着我们的`manager`不再与`user`的内存布局绑定。

Expand Down Expand Up @@ -419,4 +417,4 @@ pub const User = struct {

---

很多开发人员都在为指针而苦恼,因为指针总是难以捉摸。它们给人的感觉不像整数、字符串或用户那样具体。虽然你现在不必完全理解这些概念,但掌握它们是值得的,而且不仅仅是为了 Zig。这些细节可能隐藏在 Ruby、Python 和 JavaScript 等语言中,其次是 C#、Java 和 Go。它影响着你如何编写代码以及代码如何运行。因此,请慢慢来,多看示例,添加调试打印语句来查看变量及其地址。你探索得越多,就会越清楚。
很多开发人员都在为指针而苦恼,因为指针总是难以捉摸。它们给人的感觉不像整数、字符串或`User`那样具体。虽然你现在不必完全理解这些概念,但掌握它们是值得的,而且不仅仅是为了 Zig。这些细节可能隐藏在 Ruby、Python 和 JavaScript 等语言中,其次是 C#、Java 和 Go。它影响着你如何编写代码以及代码如何运行。因此,请慢慢来,多看示例,添加调试打印语句来查看变量及其地址。你探索得越多,就会越清楚。