diff --git a/README.md b/README.md index 48f54fd7..b2c4ca20 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ bun dev # 启动热更开发服务 - 等待 Review - 合并到上游仓库,并由 GitHub Action 自动构建 -**开发命令:** +**开发命令:** ```sh bun i # 安装依赖 @@ -61,4 +61,4 @@ bun run preview # 运行预览 > [!NOTE] > 本文档所使用的构建工具为 [bunjs](https://bun.sh/),在提交时请勿将其他 nodejs 的包管理工具的额外配置文件添加到仓库中。 -> 如需要更新依赖,请参照此处 [Lockfile](https://bun.sh/docs/install/lockfile) 先设置 git 使用 bun 来 diff 文件! \ No newline at end of file +> 如需要更新依赖,请参照此处 [Lockfile](https://bun.sh/docs/install/lockfile) 先设置 git 使用 bun 来 diff 文件! diff --git a/bun.lock b/bun.lock index b46a434f..5ddd6929 100644 --- a/bun.lock +++ b/bun.lock @@ -6,8 +6,8 @@ "@giscus/vue": "^3.1.1", }, "devDependencies": { - "@types/node": "^24.0.7", - "prettier": "^3.6.0", + "@types/node": "^24.0.10", + "prettier": "^3.6.2", "vitepress": "^1.6.3", "vitepress-export-pdf": "^1.0.0", }, @@ -204,7 +204,7 @@ "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], - "@types/node": ["@types/node@24.0.7", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw=="], + "@types/node": ["@types/node@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="], "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], @@ -548,7 +548,7 @@ "preact": ["preact@10.20.1", "", {}, "sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw=="], - "prettier": ["prettier@3.6.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw=="], + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], diff --git a/course/advanced/assembly.md b/course/advanced/assembly.md index 87bde2b6..2f1df5f4 100644 --- a/course/advanced/assembly.md +++ b/course/advanced/assembly.md @@ -13,7 +13,7 @@ outline: deep 3. 内核的开发,现代化内核编写时均会使用汇编来完成一些初始化工作,如 bootloader,分段分页,中断处理等。 4. 程序的优化,高级语言的编译器并不是完美的,它有时会做出反而使程序变慢的“优化”,而汇编语言完全由程序员控制。 -在 zig 中使用汇编有两种方式,引入外部的内联汇编,内联汇编大概是使用最多的情况。 +在 Zig 中使用汇编有两种主要方式:外部汇编和内联汇编。内联汇编是更常用的方式。 ::: info 🅿️ 提示 @@ -47,7 +47,7 @@ outline: deep ## 内联汇编 -内联汇编给予了我们可以将 `low-level` 的汇编代码和高级语言相组合,实现更加高效或者更直白的操作。 +内联汇编允许我们将底层汇编代码与高级语言相结合,以实现更高效或更直接的操作。 <<<@/code/release/assembly.zig#inline_assembly diff --git a/course/basic/advanced_type/array.md b/course/basic/advanced_type/array.md index 104c0af9..39097f94 100644 --- a/course/basic/advanced_type/array.md +++ b/course/basic/advanced_type/array.md @@ -4,9 +4,9 @@ outline: deep # 数组 -数组是日常敲代码使用相当频繁的类型之一,在 zig 中,数组的分配和 C 类似,均是在内存中连续分配且固定数量的相同类型元素。 +数组是日常编程中使用相当频繁的数据类型之一,在 Zig 中,数组的内存分配方式与 C 类似:在内存中连续分配固定数量的相同类型的元素。 -因此数组有以下三点特性: +因此,数组具有以下三个特性: - 长度固定 - 元素必须有相同的类型 @@ -14,47 +14,47 @@ outline: deep ## 创建数组 -在 zig 中,你可以使用以下的方法,来声明并定义一个数组: +在 Zig 中,你可以使用以下方式来声明并定义一个数组: <<<@/code/release/array.zig#create_array -以上代码展示了定义一个字面量数组的方式,其中你可以选择指明数组的大小或者使用 `_` 代替。使用 `_` 时,zig 会尝试自动计算数组的长度。 +以上代码展示了如何定义一个字面量数组。其中,你可以明确指定数组的大小,也可以使用 `_` 让 Zig 自动推断数组的长度。 -数组元素是连续放置的,故我们可以使用下标来访问数组的元素,下标索引从 `0` 开始! +数组元素是连续存放的,因此我们可以通过下标来访问数组元素。下标索引从 `0` 开始。 -关于[越界问题](https://ziglang.org/documentation/master/#Index-out-of-Bounds),zig 在编译期和运行时均有完整的越界保护和完善的堆栈错误跟踪。 +关于[越界问题](https://ziglang.org/documentation/master/#Index-out-of-Bounds),Zig 在编译期和运行时都提供了完整的越界保护和完善的堆栈错误跟踪。 ### 解构数组 -我们在变量声明的章节提到了,数组可以结构,再来回忆一下: +我们在变量声明的章节中提到过,数组可以被解构。让我们回顾一下: <<<@/code/release/array.zig#deconstruct ### 多维数组 -多维数组(矩阵)实际上就是嵌套数组,我们很容易就可以创建一个多维数组出来: +多维数组(矩阵)实际上就是嵌套的数组。我们可以很容易地创建一个多维数组: <<<@/code/release/array.zig#matrix -在以上的示例中,我们使用了 [for](/basic/process_control/loop) 循环,来进行矩阵的打印,关于循环我们放在后面再聊。 +在以上示例中,我们使用了 [for](/basic/process_control/loop) 循环来打印矩阵。关于循环的更多细节,我们将在后续章节讨论。 ## 哨兵数组(标记终止数组) -> 很抱歉,这里的名字是根据官方的文档直接翻译过来的,原文档应该是 ([Sentinel-Terminated Arrays](https://ziglang.org/documentation/master/#toc-Sentinel-Terminated-Arrays)) 。 +> 该名称直接翻译自官方文档的 ([Sentinel-Terminated Arrays](https://ziglang.org/documentation/master/#toc-Sentinel-Terminated-Arrays))。 :::info -本质上来说,这是为了兼容 C 中的规定的字符串结尾字符`\0` +其本质是为了兼容 C 语言中以 `\0` 作为结尾的字符串。 ::: -我们使用语法 `[N:x]T` 来描述一个元素为类型 `T`,长度为 `N` 的数组,在它对应 `N` 的索引处的值应该是 `x`。前面的说法可能比较复杂,换种说法,就是这个语法表示数组的长度索引处的元素应该是 `x`,具体可以看下面的示例: +我们使用 `[N:x]T` 语法来定义一个哨兵数组。它表示一个长度为 `N`、元素类型为 `T` 的数组,并且在索引 `N` 处的值固定为 `x`。换言之,在数组末尾有一个值为 `x` 的哨兵元素。请看下面的示例: <<<@/code/release/array.zig#terminated_array :::info 🅿️ 提示 -注意:只有在使用哨兵时,数组才会有索引为数组长度的元素! +注意:只有哨兵数组才能访问到索引为数组长度的元素! ::: @@ -62,36 +62,36 @@ outline: deep :::info -以下操作都是编译期 (comptime) 的,如果你需要运行时地处理数组操作,请使用 `std.mem`。 +以下操作都是在编译期(comptime)执行的。如果需要在运行时处理数组,请使用 `std.mem`。 ::: ### 乘法 -可以使用 `**` 对数组做乘法操作,运算符左侧是数组,右侧是倍数,进行矩阵的叠加。 +可以使用 `**` 对数组进行乘法操作。运算符左侧是数组,右侧是重复的次数,最终会生成一个更长的数组。 <<<@/code/release/array.zig#multiply ### 串联 -数组之间可以使用 `++` 进行串联操作(编译期),只要两个数组的元素类型相同,它们就可以串联! +可以使用 `++` 在编译期对数组进行串联。只要两个数组的元素类型相同,它们就可以被连接起来。 <<<@/code/release/array.zig#connect ## 奇技淫巧 -受益于 zig 自身的语言特性,我们可以实现某些其他语言所不具备的方式来操作数组。 +得益于 Zig 独特的语言特性,我们可以用一些在其他语言中不常见的方式来操作数组。 ### 使用函数初始化数组 -可以使用函数来初始化数组,函数要求返回一个数组的元素或者一个数组。 +我们可以使用函数来初始化数组。该函数需要返回数组的单个元素或整个数组。 <<<@/code/release/array.zig#func_init_array ### 编译期初始化数组 -通过编译期来初始化数组,以此来抵消运行时的开销! +我们可以在编译期初始化数组,从而避免运行时的开销。 <<<@/code/release/array.zig#comptime_init_array -这个示例中,我们使用了编译期的功能,来帮助我们实现这个数组的初始化,同时还利用了 `blocks` 和 `break` 的性质,关于这个我们会在 [循环](/basic/process_control/loop) 讲解! +在这个示例中,我们利用编译期执行的特性来初始化数组,并结合了 `blocks` 和 `break` 的用法。关于这些,我们将在[循环](/basic/process_control/loop)章节中详细讲解。 diff --git a/course/basic/advanced_type/enum.md b/course/basic/advanced_type/enum.md index 2045774b..af0d5d49 100644 --- a/course/basic/advanced_type/enum.md +++ b/course/basic/advanced_type/enum.md @@ -4,17 +4,17 @@ outline: deep # 枚举 -> 枚举常常用来列出一个有限集合的任何成员,或者对某一种特定对象的计数。 +> 枚举常用于表示一个有限集合的成员,或对特定类型的对象进行分类。 -枚举是一种相对简单,但用处颇多的类型。 +枚举是一种相对简单但用途广泛的类型。 ## 声明枚举 -我们可以通过使用 `enum` 关键字来很轻松地声明并使用枚举: +我们可以通过 `enum` 关键字轻松地声明和使用枚举: <<<@/code/release/enum.zig#basic_enum -同时,zig 还允许我们访问并操作枚举的标记值: +同时,Zig 还允许我们访问和操作枚举的标记值: <<<@/code/release/enum.zig#enum_with_value @@ -24,33 +24,33 @@ outline: deep ::: info 🅿️ 提示 -枚举类型支持使用 `if` 和 `switch` 进行匹配,具体见对应章节。 +枚举类型支持使用 `if` 和 `switch` 进行匹配,具体细节请参见相应章节。 ::: ## 枚举方法 -没错,枚举也可以拥有方法,实际上枚举仅仅是一种命名空间(你可以看作是一类 struct)。 +是的,枚举也可以拥有方法。实际上,枚举在 Zig 中是一种特殊的命名空间(可以看作一种特殊的 `struct`)。 <<<@/code/release/enum.zig#enum_with_method ## 枚举大小 -要注意的是,枚举的大小是会经过 zig 编译器进行严格的计算,如以上的枚举类型 `Type` ,它大小等效于 `u1`。 +需要注意的是,Zig 编译器会严格计算枚举的大小。例如,前面示例中的 `Type` 枚举,其大小等效于 `u1`。 -以下示例中,我们使用了内建函数 `@typeInfo` 和 `@tagName` 来获取枚举的大小和对应的 tag name: +以下示例中,我们使用了内建函数 `@typeInfo` 和 `@tagName` 来获取枚举的大小和对应的标签名称(tag name): <<<@/code/release/enum.zig#enum_size ## 枚举推断 -枚举也支持让 zig 编译器自动进行推断(使用结果位置语义),即在已经知道枚举的类型情况下仅使用字段来指定枚举的值: +枚举也支持类型推断(通过结果位置语义),即在已知枚举类型的情况下,可以仅使用字段名来指定枚举值: <<<@/code/release/enum.zig#enum_reference ## 非详尽枚举 -zig 允许我们不列出所有的枚举值,未列出枚举值可以使用 `_` 代替。由于未列出枚举值的存在,枚举的大小无法自动推断,必须显式指定。 +Zig 允许我们定义非详尽枚举,即在定义时无需列出所有可能的成员。未列出的成员可以使用 `_` 来表示。由于存在未列出的成员,编译器无法自动推断枚举的大小,因此必须显式指定其底层类型。 <<<@/code/release/enum.zig#non_exhaustive_enum @@ -68,25 +68,25 @@ zig 允许我们不列出所有的枚举值,未列出枚举值可以使用 `_` ::: info 🅿️ 提示 -此部分内容并非是初学者需要掌握的内容,它涉及到 zig 本身的类型系统和 [编译期反射](../../advanced/reflection#构建新的类型),可以暂且跳过! +此部分内容并非初学者需要掌握的内容,它涉及到 Zig 的类型系统和[编译期反射](../../advanced/reflection#构建新的类型),可以暂且跳过! ::: -zig 还包含另外一个特殊的类型 `EnumLiteral`,它是 [`std.builtin.Type`](https://ziglang.org/documentation/master/std/#std.builtin.Type) 的一部分。 +Zig 还包含一个特殊的类型 `EnumLiteral`,它是 [`std.builtin.Type`](https://ziglang.org/documentation/master/std/#std.builtin.Type) 的一部分。 -可以将它称之为“枚举字面量”,它是一个与 `enum` 完全不同的类型,可以查看 zig 类型系统对 `enum` 的 [定义](https://ziglang.org/documentation/master/std/#std.builtin.Type.Enum),并不包含 `EnumLiteral`! +我们可以称之为“枚举字面量”。它是一个与 `enum` 完全不同的类型。可以查看 Zig 类型系统对 `enum` 的[定义](https://ziglang.org/documentation/master/std/#std.builtin.Type.Enum),其中并不包含 `EnumLiteral`。 -它的具体使用如下: +它的具体用法如下: <<<@/code/release/enum.zig#enum_literal -注意:此类型常用于作为函数参数! +注意:此类型常用于函数参数。 ## extern -注意,我们不在这里使用 `extern` 关键字。 +注意,我们通常不直接对枚举使用 `extern` 关键字。 -默认情况下,zig 不保证枚举和 C ABI 兼容,但是我们可以通过指定标记类型来达到这一效果: +默认情况下,Zig 不保证枚举与 C ABI 兼容,但我们可以通过指定其标记类型来确保兼容性: ```zig const Foo = enum(c_int) { a, b, c }; diff --git a/course/basic/advanced_type/opaque.md b/course/basic/advanced_type/opaque.md index 1310e232..e7a38044 100644 --- a/course/basic/advanced_type/opaque.md +++ b/course/basic/advanced_type/opaque.md @@ -4,19 +4,19 @@ outline: deep # opaque -`opaque` 类型声明一个具有未知(但非零)大小和对齐方式的新类型,它的内部可以包含与结构、联合和枚举相同的声明。 +`opaque` 类型用于声明一个大小和对齐方式未知(但非零)的新类型。其内部可以像结构体、联合或枚举一样包含声明。 -这通常用于保证与不公开结构详细信息的 C 代码交互时的类型安全。 +这通常用于与 C 代码交互时,确保类型安全,尤其是在 C 代码没有公开结构体细节的情况下。 <<<@/code/release/opaque.zig#opaque ## anyopaque -`anyopaque` 是一个比较特殊的类型,代表可以接受任何类型的 `opaque`(由于 `opaque` 拥有不同的变量/常量声明和方法的定义,故是不同的类型),常用于与 C 交互的函数中,可以当作 C 的 `void` 类型来使用,一般用于类型擦除指针! +`anyopaque` 是一个特殊的类型,它可以代表任意一种 `opaque` 类型(因为每个 `opaque` 类型根据其内部声明的不同而被视为不同类型)。它常用于与 C 交互的函数中,类似于 C 的 `void*`,通常用于类型擦除的指针。 :::info 🅿️ 提示 -需要注意,`void` 的已知大小为 0 字节,而 `anyopaque` 大小未知,但非零。 +需要注意的是,`void` 的大小为 0 字节,而 `anyopaque` 的大小未知但非零。 ::: diff --git a/course/basic/advanced_type/pointer.md b/course/basic/advanced_type/pointer.md index 5fdaf92a..d07ecd58 100644 --- a/course/basic/advanced_type/pointer.md +++ b/course/basic/advanced_type/pointer.md @@ -4,27 +4,27 @@ outline: deep # 指针 -> zig 作为一门 low level 语言,那肯定要有指针的。 +> 作为一门底层语言,Zig 自然支持指针。 -指针是指向一块内存区域地址的变量,它存储了一个地址,我们可以通过指针来操作其指向内存区域。 +指针是一个变量,它存储了另一个变量的内存地址。通过指针,我们可以间接访问和操作其指向的内存区域。 **取地址**:通过 `&` 符号来获取某个变量所对应的内存地址,如 `&integer` 就是获取变量 `integer` 的内存地址。 -与 C 不同,Zig 中的指针类型要分为两种(一种是单项指针,一种是多项指针),它们主要是对指向的元素做了区分,便于更好地使用。下图展示了它们指向元素的不同: +与 C 不同,Zig 将指针分为两种类型:单项指针和多项指针。这种区分主要是为了明确指针指向的是单个元素还是多个元素,从而更安全、高效地使用指针。下图展示了它们指向元素的不同: ![pointer representation](/picture/basic/pointer-representation.svg) :::info 🅿️ 提示 -上图中包含了切片(slice)类型,严格来说它不是指针,但其是由指针构成的(一般称为胖指针),而且在代码中用的更为普遍,因此列在一起便于读者比较。 +上图中包含了切片(slice)类型。严格来说,切片不是指针,但它内部包含一个指针(通常被称为“胖指针”),并且在实际编码中更为常用,因此在这里一并列出以便读者比较。 ::: :::warning 关于指针运算 -zig 本身支持指针运算(加减操作),但有一点需要注意:最好将指针分配给 `[*]T` 类型后再进行计算。 +Zig 支持指针的加减运算,但建议在进行运算前,将指针转换为 `[*]T` 类型。 -尤其是在切片中,不可直接对其指针进行更改,这会破坏切片的内部结构! +尤其是在处理切片时,不应直接修改切片的内部指针,因为这会破坏切片的内部结构。 ::: @@ -32,7 +32,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 单项指针指向单个元素。 -单项指针的类型为:`*T`,`T`是所指向内存区域的类型,解引用方法是 `ptr.*`。 +单项指针的类型为 `*T`,其中 `T` 是所指向的数据类型。解引用操作使用 `ptr.*`。 <<<@/code/release/pointer.zig#single_pointer @@ -52,11 +52,11 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 ## 多项指针 -多项指针指向未知数量的多个元素。 +多项指针指向一个或多个连续的元素,但其数量在编译期是未知的。 -多项指针的类型为:`[*]T`,`T`是所指向内存区域的类型,且该类型必须具有明确的大小(这意味着它不能是 [`anyopaque`](https://ziglang.org/documentation/master/#toc-C-Type-Primitives) 和其他任意[不透明类型](https://ziglang.org/documentation/master/#opaque))。 +多项指针的类型为 `[*]T`,其中 `T` 是所指向的数据类型,且 `T` 的大小必须是明确的(这意味着它不能是 [`anyopaque`](https://ziglang.org/documentation/master/#toc-C-Type-Primitives) 或其他[不透明类型](https://ziglang.org/documentation/master/#opaque))。 -解引用方法支持以下几种: +它支持以下几种操作: - 索引语法 `ptr[i]` - 切片语法 `ptr[start..end]` 和 `ptr[start..]` @@ -69,7 +69,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 数组和切片都与指针有紧密的联系。 -`*[N]T`:这是指向一个数组的单项指针,数组的长度为 N。也可以将其理解为指向 N 个元素的指针。 +`*[N]T`:这是指向一个数组的单项指针,数组的长度为 N。也可以理解为指向一个包含 N 个元素数组的指针。 支持这些语法: @@ -78,14 +78,15 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 - `len` 属性:`array_ptr.len` - 指针减法:`array_ptr - array_ptr` -`[]T`:这是切片,相当于一个胖指针,包含了一个类型为 `[*]T` 的指针和一个长度。 +`[]T`:这是切片。它是一个“胖指针”,内部包含一个 `[*]T` 类型的指针和一个长度值。 支持这些语法: - 索引语法:`slice[i]` - 切片语法:`slice[start..end]` - `len` 属性:`slice.len` - 数组指针的类型中就包含了长度信息,而切片中则实际存储着长度。数组指针和切片的长度都可以通过 `len` 属性来获取。 + +数组指针的类型本身就包含了长度信息,而切片则在运行时存储其长度。数组指针和切片的长度都可以通过 `len` 属性来获取。 :::details 示例 @@ -97,105 +98,106 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 :::info -本质上来说,这是为了兼容 C 中的规定的字符串结尾字符`\0` +其本质是为了兼容 C 语言中以 `\0` 作为结尾的字符串。 ::: -哨兵指针就和哨兵数组类似,我们使用语法 `[*:x]T`,这个指针标记了边界的值,故称为“哨兵”。 +哨兵指针与哨兵数组类似。我们使用 `[*:x]T` 语法来定义哨兵指针。这种指针通过一个特定的“哨兵”值来标记其边界。 -它的长度有标记值 `x` 来确定,这样做的好处就是提供了针对缓冲区溢出和过度读取的保护。 +其长度由哨兵值 `x` 的位置决定,这样做的好处是提供了针对缓冲区溢出和过度读取的保护。 :::details 示例 -我们接下来演示一个示例,该示例中使用了 zig 可以无缝与 C 交互的特性,故你可以暂时略过这里! +我们接下来演示一个示例。该示例利用了 Zig 与 C 无缝交互的特性,因此,如果你对 C 交互不熟悉,可以暂时跳过。 <<<@/code/release/pointer.zig#st_pointer -以上代码编译需要额外连接 libc,你只需要在你的 `build.zig` 中添加 `exe.linkLibC();` 即可。 +以上代码编译需要额外链接 libc,你只需要在你的 `build.zig` 中添加 `exe.linkLibC();` 即可。 ::: ## 多项指针和单向指针区别 -本部分专门用于解释并区别单向指针和多项指针! +本节专门解释单项指针和多项指针的区别。 先列出以下类型: - `[4] const u8` -该类型代表的是一个长度为 4 的数组,数组内的元素类型为 `const u8`。 +该类型代表一个长度为 4 的数组,数组内的元素类型为 `const u8`。 - `[] const u8` -该类型代表的是一个切片,切片内元素类型为 `const u8`。 +该类型代表一个切片,切片内元素类型为 `const u8`。 - `*[4] const u8` -该类型代表的是一个指针,它指向一个内存地址,内存中该地址存储着一个长度为 4 的数组,数组内的元素类型为 `const u8`。 +该类型代表一个指针,它指向一个内存地址,该地址存储着一个长度为 4 的数组,数组内的元素类型为 `const u8`。 - `*[] const u8` -该类型代表的是一个指针,它指向一个内存地址,内存中该地址存储着一个切片。 +该类型代表一个指针,它指向一个内存地址,该地址存储着一个切片。 - `[*] const u8` -该类型代表的是一个指针,它指向一个内存地址,内存中该地址存储着一个数组,但长度未知!! +该类型代表一个指向内存地址的指针,该地址存储了一个或多个 `const u8` 类型的元素,但其数量是未知的。 -其中 `[*] const u8` 可以看作是 C 中的 `* const char`,这是因为在 C 语言中一个普通的指针也可以指向一个数组,zig 仅仅是单独把这种令人迷惑的行为单独作为一个语法而已! +其中 `[*] const u8` 可以看作是 C 中的 `*const char`。这类似于 C 语言中的 `const char*`,因为在 C 中,一个普通指针既可以指向单个字符,也可以指向一个字符数组。Zig 则通过 `[*]T` 这种专门的语法来明确表示指向多个元素的情况,避免了歧义。 ## 指针和整数互转 -[`@ptrFromInt`](https://ziglang.org/documentation/master/#ptrFromInt) 可以将整数地址转换为指针,[`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 可以将指针转换为整数: +[`@ptrFromInt`](https://ziglang.org/documentation/master/#ptrFromInt) 可以将一个整数值当作内存地址来创建一个指针,而 [`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 可以将指针转换为整数: <<<@/code/release/pointer.zig#ptr2int ## 指针强制转换 -内置函数 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 可以将将指针的元素类型转换为另一种类型,也就是不同类型的指针强制转换。 +内置函数 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 可以将一个指针的类型强制转换为另一个指针类型,即改变指针所指向的数据类型。 -一般情况下,应当尽量避免使用 `@ptrCast`,这会创建一个新的指针,根据通过它的加载和存储操作,可能导致无法检测的非法行为。 +一般情况下,应当尽量避免使用 `@ptrCast`。这会创建一个新的指针,通过它进行的加载和存储操作可能会导致难以检测的非法行为。 <<<@/code/release/pointer.zig#ptr_cast ## 额外特性 -以下的是指针的额外特性,初学者可以直接略过以下部分,等到你需要时再来学习即可! +以下是指针的一些额外特性。初学者可以暂时跳过这些内容,在需要时再来学习。 ### `volatile` -> 如果不知道什么是指针操作的“_副作用_”,那么这里你可以略过,等你需要时再来查看! +> 如果你不知道指针操作的“副作用”是什么,可以暂时跳过本节,在需要时再来查看。 -对指针的操作应假定为没有副作用。如果存在副作用,例如使用内存映射输入输出(Memory Mapped Input/Output),则需要使用 `volatile` 关键字来修饰。 +对指针的操作通常被假定为没有副作用。但如果存在副作用,例如在使用内存映射输入输出(Memory Mapped Input/Output)时,就需要使用 `volatile` 关键字来修饰指针。 -在以下代码中,保证使用 `mmio_ptr` 的值进行操作(这里你看起来可能会感到迷惑,在编译代码时,编译器可以能会让值在实际运行过程中进行缓存,这里保证每次都使用 `mmio_ptr` 的值,以确保正确触发“副作用”),并保证了代码执行的顺序。 +在以下代码中,`volatile` 确保了对 `mmio_ptr` 的每次访问都会直接读写内存,而不是从缓存中读取。当与硬件交互时,编译器可能会将值缓存在寄存器中,而 `volatile` 则强制每次都从内存中读取,以确保“副作用”能够正确触发,并保证了代码的执行顺序。 <<<@/code/release/pointer.zig#volatile -该节内容,此处仅仅讲述了少量内容,如果要了解更多,你可能需要查看[官方文档](https://ziglang.org/documentation/master/#toc-volatile)! +本节仅简要介绍,如果要了解更多,你可能需要查看[官方文档](https://ziglang.org/documentation/master/#toc-volatile)。 ### 对齐 -> 如果你不知道内存对齐的含义是什么,那么本节内容你可以跳过了,等到你需要时再来查看! +> 如果你不知道内存对齐的含义,可以暂时跳过本节,在需要时再来查看。 + +每种类型都有一个对齐方式,即一个字节数。当从内存中加载或存储该类型的值时,其内存地址必须是该字节数的整数倍。我们可以使用 `@alignOf` 来获取任何类型的内存对齐大小。 -每种类型都有一个对齐方式——也就是数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。 +内存对齐大小取决于 CPU 架构,但始终是 2 的幂,并且小于 `1 << 29`。 -内存对齐大小取决于 CPU 架构,但始终是 2 的幂,并且小于 1 << 29。 :::info -`align(0)` 表意是:无需对齐,这在高性能、内存紧迫场景下用处很大。 +`align(0)` 表示无需对齐,这在高性能、内存紧迫的场景下非常有用。 参考:[allow align(0) on struct fields](https://github.com/ziglang/zig/issues/3802) ::: -在 Zig 中,指针类型具有对齐值。如果该值等于基础类型的对齐方式,则可以从类型中省略它: +在 Zig 中,指针类型也具有对齐值。如果该值等于其基础类型的对齐方式,则可以从类型声明中省略它: <<<@/code/release/pointer.zig#align :::info 🅿️ 提示 -和 `*i32` 类型可以强制转换为 `*const i32` 类型类似,具有较大对齐大小的指针可以隐式转换为具有较小对齐大小的指针,但反之则不然。 +和 `*i32` 类型可以隐式转换为 `*const i32` 类型类似,具有更严格(更大)对齐要求的指针可以隐式转换为对齐要求更宽松(更小)的指针,但反之则不行。 -如果有一个指针或切片的对齐方式较小,但知道它实际上具有较大的对齐方式,请使用 `@alignCast` 将指针更改为更对齐的指针,例如:`@as([]align(4) u8, @alignCast(slice4))`,这在运行时无操作,但插入了安全检查。 +如果有一个指针或切片的对齐方式较小,但我们确定它实际上满足更严格的对齐要求,可以使用 `@alignCast` 将其转换成具有更严格对齐的指针类型,例如:`@as([]align(4) u8, @alignCast(slice4))`。这个转换在运行时没有开销,但会插入一个安全检查。 :::details 示例 @@ -203,9 +205,9 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 ::: -如果有一个指针或切片,它的对齐很小,但我们知道它实际上有一个更大的对齐,那么使用 [`@alignCast`](https://ziglang.org/documentation/master/#alignCast) 让其 `align` 更大。在运行时是无操作的,但会额外加入一个 [安全检查](https://ziglang.org/documentation/master/#Incorrect-Pointer-Alignment): +如果有一个指针或切片,它的对齐要求很宽松,但我们知道它实际上有一个更严格的对齐方式,那么可以使用 [`@alignCast`](https://ziglang.org/documentation/master/#alignCast) 来获得一个对齐要求更严格的指针。这在运行时没有开销,但会额外加入一个[安全检查](https://ziglang.org/documentation/master/#Incorrect-Pointer-Alignment): -> 例如这段代码就是错误的,不会被正常执行 +> 例如,以下代码是错误的,不会被正常执行: ```zig const std = @import("std"); @@ -250,11 +252,11 @@ error: the following test command crashed: ### 零指针 -零指针实际上是一个未定义的错误行为([Pointer Cast Invalid Null](https://ziglang.org/documentation/master/#Pointer-Cast-Invalid-Null)),但是当我们给指针增加上 `allowzero` 修饰符后,它就变成合法的行为了! +将零地址转换为指针通常是未定义行为([Pointer Cast Invalid Null](https://ziglang.org/documentation/master/#Pointer-Cast-Invalid-Null)),但如果我们为指针类型添加 `allowzero` 修饰符,那么值为零的指针就成为合法的了。 :::warning 关于零指针的使用 -请只在目标 OS 为 `freestanding` 时使用零指针,如果想表示 `null` 指针,请使用[可选类型](/basic/optional_type)! +请只在目标 OS 为 `freestanding` 时使用零指针。如果想表示 `null` 指针,请使用[可选类型](/basic/optional_type)。 ::: @@ -262,10 +264,10 @@ error: the following test command crashed: ### 编译期 -只要代码不依赖于未定义的内存布局,那么指针也可以在编译期发挥作用! +只要代码不依赖于运行时才确定的内存布局,指针也可以在编译期使用。 <<<@/code/release/pointer.zig#comptime_pointer -只要指针从未被取消引用,Zig 就能够保留 `comptime` 代码中的内存地址: +只要指针从未被解引用,Zig 就能在 `comptime` 代码中保留其内存地址: <<<@/code/release/pointer.zig#comp_pointer diff --git a/course/basic/advanced_type/slice.md b/course/basic/advanced_type/slice.md index 636fa4b5..424ee983 100644 --- a/course/basic/advanced_type/slice.md +++ b/course/basic/advanced_type/slice.md @@ -4,11 +4,11 @@ outline: deep # 切片 -切片和数组看起来上很像,在实际使用时,你可能会想要使用切片,因为它相对数组来说,要更加灵活! +切片与数组非常相似。在实际应用中,由于其灵活性,切片的使用比数组更普遍。 -_你可以对数组、切片、数组指针进行切片操作!_ +_你可以从数组、其他切片或数组指针创建新的切片。_ -接下来我们演示切片的使用方式: +接下来,我们来演示如何使用切片: ::: code-group @@ -28,27 +28,27 @@ slice 类型为[]i32 slice_2 类型为[]i32 ``` -切片的使用方式就是类似数组,不过`[]`中的是索引的边界值,遵循“左闭右开”规则。 +创建切片的方式与访问数组成员类似,但方括号 `[]` 中指定的是索引的边界,遵循“左闭右开”原则。 -以上我们对数组取切片,左边界值为 0,右边界值为 `len` 变量。 +在上面的例子中,我们从一个数组创建切片,其左边界为 0,右边界为变量 `len`。 -注意,这里说的是边界值有一个是变量(运行时可知),如果两个边界值均是编译期可知的话,编译器会直接将切片优化为数组指针。 +注意:如果切片的两个边界值都是编译期常量,编译器会将其优化为数组指针;但如果至少有一个边界值是运行时变量,那么它就是一个真正的切片。 :::info 🅿️ 提示 -切片的本质:它本质是一个胖指针,包含了一个 指针类型 `[*]T` 和 长度。 +切片的本质是一个“胖指针”(fat pointer),它内部包含一个 `[*]T` 类型的指针和一个长度值。 -同时,它的指针 `slice.ptr` 和长度 `slice.len` 均是可以操作的,但在实践中,请不要操作它们,这容易破坏切片的内部结构(除非你有把握每次都能正确的处理它们)。 +虽然 `slice.ptr`(指针)和 `slice.len`(长度)都是可以访问和修改的,但在实践中应避免直接操作它们,因为这很容易破坏切片的内部结构,导致不可预期的行为(除非你完全清楚自己在做什么)。 ::: ## 切片指针 -切片本身除了具有 `len` 属性外,还具有 `ptr` 属性,这意味着我们可以通过语法 `slice.ptr` 来操作切片的指针,它是一个多项指针! +切片除了有 `len` 属性,还有一个 `ptr` 属性,它是一个指向切片数据起始位置的多项指针。我们可以通过 `slice.ptr` 来访问它。 -当我们对切片元素取地址(`&`)时,得到的是单项指针。 +当我们对切片中的单个元素取地址(`&`)时,会得到一个单项指针。 -同时,切片本身还有边界检查,但是对切片指针做操作则不会有边界检查! +需要注意的是:直接对切片进行索引操作会进行边界检查,而通过 `slice.ptr` 对指针进行操作则不会有边界检查,需要开发者自行确保安全。 ::: code-group @@ -68,11 +68,11 @@ slice 的索引 0 取地址,得到指针类型为*i32 ## 哨兵切片(标记终止切片) -语法 `[:x]T` 是一个切片,它具有运行时已知的长度,并且还保证由该长度索引的元素的标记值。该类型不保证在此之前不存在哨兵元素,哨兵终止的切片允许元素访问 len 索引。 +语法 `[:x]T` 定义了一个哨兵切片。它与普通切片类似,长度在运行时确定,但额外保证在索引 `len` 处存在一个值为 `x` 的哨兵元素。该类型不保证哨兵值不会出现在切片内容中。哨兵切片允许访问索引为 `len` 的元素(即哨兵本身)。 -哨兵切片也可以使用切片语法 `data[start..end :x]` 的变体来创建,其中 data 是多项指针、数组或切片,x 是哨兵值。 +我们也可以通过 `data[start..end :x]` 语法从多项指针、数组或普通切片中创建一个哨兵切片,其中 `x` 是哨兵值。 -哨兵切片认定哨兵位置处的元素是哨兵值,如果不是这种情况,则会触发安全保护中的未定义问题。 +创建哨兵切片时,Zig 会假定在哨兵位置(即 `end` 索引处)的元素值就是指定的哨兵值。如果实际情况并非如此,将会触发安全保护机制并导致未定义行为。 ::: code-group diff --git a/course/basic/advanced_type/string.md b/course/basic/advanced_type/string.md index 7e93bbd5..9ad5583e 100644 --- a/course/basic/advanced_type/string.md +++ b/course/basic/advanced_type/string.md @@ -1,36 +1,30 @@ ---- -outline: deep ---- - # 字符串 -在讲解字符串之前,我们需要先说明一个基础的概念: - -> 那就是所有数据在计算机中均是以 0 和 1 两种形式存储的。 +在深入探讨字符串之前,我们首先明确一个基本概念:计算机中的所有数据都以二进制(0 和 1)形式存储。 -大家都知道字符串是“Hello, world!”这种格式,我们经常需要在源代码中定义字符串,例如某些提示信息。通常这种直接写在源代码中的字符串被称之为是硬编码于源代码中的。 +大家都知道字符串是“Hello, world!”这种格式。我们经常需要在源代码中定义字符串,例如某些提示信息。通常,这种直接嵌入源代码的字符串被称为硬编码字符串。 那么,这些字符串被放在哪里呢? -由于字符串都是只读的(运行时所生成的字符串那是另一种东西),所以它们都存放在二进制程序的只读数据段中,并且对于同一个字符串,只会保留一个副本,这被称之为 [字符串驻留](https://en.wikipedia.org/wiki/String_interning)(String interning)。 +由于字符串通常是只读的(运行时生成的字符串是另一种情况),它们被存储在二进制程序的只读数据段中。对于相同的字符串字面量,编译器通常只会保留一个副本,这被称为 [字符串驻留](https://en.wikipedia.org/wiki/String_interning)(String interning)。 ## 字符串定义 -**字符串在 zig 中是指向以 `null` 结尾的 `u8` 数组的常量单项指针,因此它可以转化为切片和哨兵指针,而对它取消引用则会将其转化为数组**。 +**在 Zig 中,字符串字面量是一个指向以 `null` 结尾的 `u8` 数组的常量单项指针。因此,它可以隐式转换为切片和哨兵指针,对其解引用则会得到数组本身。** 例如: <<<@/code/release/string.zig#string_type -将显示 `foo` 的类型是 `*const [6:0]u8` ,实际上是一个指针。 +例如,`foo` 的类型将显示为 `*const [6:0]u8`,这表明它实际上是一个指针。 -值得注意的是,Zig 会将字符串假定为 UTF-8 编码,这是由于 zig 的源文件本身就是 UTF-8 编码的,任何的非 ASCII 字节均会被作为 UTF-8 字符看待,并且编译器不会对字节进行修改,因此如果想把非 UTF-8 字节放入字符串中,可以使用转义 `\xNN`。 +值得注意的是,Zig 默认将字符串字面量视为 UTF-8 编码。由于 Zig 源文件本身就是 UTF-8 编码,任何非 ASCII 字节都会被视为 UTF-8 字符。编译器不会修改这些字节,因此如果需要将非 UTF-8 字节放入字符串中,可以使用 `\xNN` 转义序列。 -Unicode 码点字面量类型是 `comptime_int`,所有的转义字符均可以在字符串和 Unicode 码点中使用。 +Unicode 码点字面量类型是 `comptime_int`。所有转义字符都可以在字符串字面量和 Unicode 码点字面量中使用。 > 对包含非 ASCII 字节的字符串进行索引会返回单个字节,无论是否为有效的 UTF-8。 -为了方便处理 UTF-8 和 Unicode,zig 的标准库 `std.unicode` 中实现了相关的函数来处理它们。 +为了方便处理 UTF-8 和 Unicode,Zig 的标准库 `std.unicode` 提供了相关的函数。 关于字符串有一个示例: @@ -51,12 +45,12 @@ Unicode 码点字面量类型是 `comptime_int`,所有的转义字符均可以 ### 多行字符串 -如果要使用多行字符串,可以使用 `\\`,多行字符串没有转义,最后一行行尾的换行符号不会包含在字符串中。示例如下: +要定义多行字符串,可以使用 `\\`。多行字符串字面量不会进行转义,并且最后一行行尾的换行符不会包含在字符串中。示例如下: :::info -字符串中不能出现``(在 Zig 中任何``都是不被允许的),但是可以用`\t`或者`@embedFile`实现平行功能。 -参考:[enum-backed address spaces](https://github.com/ziglang/zig-spec/issues/38) +字符串字面量中不能直接包含``字符(Zig 语言规范不允许在源代码中使用``)。但可以使用`\t`转义序列或`@embedFile`内建函数来实现类似的功能。 +参考:[enum-backed address spaces](https://github.com/ziglang/zig-spec/issues/38] ::: @@ -68,38 +62,36 @@ Unicode 码点字面量类型是 `comptime_int`,所有的转义字符均可以 #### 编译错误 `*const [*:0]u8` -新手常常会写出下面的代码: +新手常会遇到以下代码导致的编译错误: <<<@/code/release/string.zig#print_string_err -嗯,这段代码看着多么的正常啊,它一定能够编译通过的! - -等到编译时,编译器给出了令人迷惑的报错: +这段代码看起来很正常,但它无法通过编译。 ```sh ./example.zig:4:15: error: expected type '[]u8', found '*const [6:0]u8' funnyPrint("banana"); ``` -我相信新手看到这里一定不知所措,我要一个可以打印所有字符串的函数,它不就应该使用切片吗?为什么不行? +新手可能会对此感到困惑:一个旨在打印所有字符串的函数,难道不应该使用切片吗?为什么会报错? -我们回到最初的说明,字符串(我们这里的字符串是指硬编码于源代码中的)是只读的,所以它只能是常量,这意味着应该使用 `[]const u8`。 +回顾字符串的定义:硬编码于源代码中的字符串是只读的常量。因此,需要使用 `[]const u8` 类型来接收。 -此处有一个关于自动转换的问题:没错,指向数组的指针强制转换为切片,但常量性是一条单向路,这意味着我们可以将可变指针/切片传递给需要常量指针/切片的函数,但不能反之亦然。 +这里涉及到一个自动转换的规则:指向数组的指针可以隐式转换为切片。然而,常量性是单向的,这意味着可以将可变指针/切片传递给需要常量指针/切片的函数,但反之则不行。 再解答另一个问题:为什么字符串是 `const` 呢? -这与字符串驻留有关,我们来仔细想想,很显然,字符串在编译时会被删除重复的,因此无法在不影响其他实例的情况下更改一个实例,这样设计决策是明智且合理的。 +这与字符串驻留有关。在编译时,重复的字符串字面量会被去重。因此,如果允许修改其中一个实例,将会影响所有引用该字符串的地方,这显然是不合理的。这种设计决策是明智且安全的。 #### 试图让字符串可变 -有些新手大概会想通过将字符串分配给变量来使得其“可变”,例如: +有些新手可能会尝试通过将字符串字面量赋值给 `var` 变量来使其“可变”,例如: ```zig var msg = "banana"; ``` -这样定义的结果是 `msg` 的类型是 `*const [6:0]u8`,它的意思是一个指向 `const [6:0]u8` 的指针,也就是说可变的是指针的值,而指针指向的常量哨兵数组是不可变的。 +这样定义的 `msg` 类型是 `*const [6:0]u8`。这意味着 `msg` 是一个指向 `const [6:0]u8` 类型的指针。可变的是指针本身(即 `msg` 可以指向其他字符串),但它所指向的常量哨兵数组的内容是不可变的。 #### 定义字符串的多种方式 @@ -107,8 +99,8 @@ var msg = "banana"; <<<@/code/release/string.zig#define_string -第一种是最常用的方式,编译器会自动推导出来。 +第一种是最常用的方式,编译器会自动推断其类型。 -第二种是我们尝试手动定义一个常量的 `u8` 数组,并由编译器推导出长度。 +第二种是手动定义一个常量 `u8` 数组,并由编译器推断其长度。 -第三种是我们手动定义一个常量的 `u8` 数组,并取指针,编译器会帮我们自动转换为常量的 `u8` 切片。 +第三种是手动定义一个常量 `u8` 数组并获取其指针,编译器会将其自动转换为常量 `u8` 切片。 diff --git a/course/basic/advanced_type/struct.md b/course/basic/advanced_type/struct.md index c2963f44..49bbb13c 100644 --- a/course/basic/advanced_type/struct.md +++ b/course/basic/advanced_type/struct.md @@ -1,19 +1,15 @@ ---- -outline: deep ---- - # 结构体 -> 在 zig 中,类型是一等公民! +> 在 Zig 中,类型是“一等公民”! -结构体本身是一个高级的数据结构,用于将多个数据表示为一个整体。 +结构体是一种高级数据结构,用于将多个相关数据组织成一个单一的实体。 ## 基本语法 结构体的组成: - 首部关键字 `struct` -- 和变量定义一样的结构体名字 +- 与变量定义相同的结构体名称 - 多个字段 - 方法 - 多个声明 @@ -28,22 +24,22 @@ outline: deep ::: -上方的代码的内容: +上面的代码展示了以下内容: - 定义了一个结构体 `Circle`,用于表示一个圆 -- 包含字段 `radius` -- 一个声明 `PI` +- 包含 `radius` 字段 +- 一个常量声明 `PI` - 包含两个方法 `init` 和 `area` :::info 🅿️ 提示 -值得注意的是,结构体的方法除了使用 `.` 语法来使用外,和其他的函数没有任何区别!这意味着你可以在任何你用普通函数的地方使用结构体的方法。 +值得注意的是,结构体的方法除了可以使用 `.` 语法调用外,与普通函数并无本质区别。这意味着你可以在任何可以使用普通函数的地方使用结构体的方法。 ::: ## 自引用 -常见的自引用方式是函数第一个参数为结构体指针类型,例如: +常见的自引用方式是将结构体自身的指针作为函数的第一个参数,例如: ::: code-group @@ -53,11 +49,11 @@ outline: deep ::: -平常使用过程中会面临另外的一个情况,就是匿名结构体要如何实现自引用呢? +在实际使用中,匿名结构体如何实现自引用是一个常见问题。 -答案是使用 [`@This`](https://ziglang.org/documentation/master/#This),这是 zig 专门为匿名结构体和文件类的类型声明(此处可以看 [命名空间](../../basic/define-variable.md#容器))提供的处理方案。 +答案是使用 [`@This`](https://ziglang.org/documentation/master/#This)。这是 Zig 专门为匿名结构体和文件级别的类型声明(可以参考[命名空间](../../basic/define-variable.md#容器)章节)提供的解决方案。 -此函数会返回一个当前包裹它的容器的类型! +它会返回当前包裹它的容器的类型。 例如: @@ -71,7 +67,7 @@ outline: deep :::details 更复杂的例子 -下面是一个日常会用到的一个结构体例子,系统账号管理的使用: +下面是一个日常会用到的结构体例子,关于系统账号管理的使用: ::: code-group @@ -79,23 +75,23 @@ outline: deep <<<@/code/release/struct.zig#more_self_reference3 [more] -在以上的代码中,我们使用了内存分配的功能,并且使用了切片和多行字符串,以及 `defer` 语法(在当前作用域的末尾执行语句)。 +在以上代码中,我们使用了内存分配功能,并结合了切片、多行字符串以及 `defer` 语法(在当前作用域结束时执行语句)的应用。 ::: ## 自动推断 -zig 在使用结构体的时候还支持省略结构体类型,此时 zig 将会使用结果位置语义来对类型进行推导,例如: +Zig 在使用结构体时支持省略类型声明,此时 Zig 会利用结果位置语义进行类型推断,例如: <<<@/code/release/struct.zig#auto_reference -_这种基于结果位置语义的推导还支持结构体本身的方法和其内部中定义的其他类型(此处指的并不是结构体字段)!_ +_这种基于结果位置语义的推导也适用于结构体自身的方法以及其内部定义的其他类型(此处不指结构体字段)。_ ## 泛型实现 -依托于“类型是 zig 的一等公民”,我们可以很容易的实现泛型。 +得益于“类型是 Zig 的一等公民”这一特性,我们可以轻松实现泛型。 -此处仅仅是简单提及一下该特性,后续我们会专门讲解泛型这一个利器! +此处仅简单提及该特性,后续我们将专门讲解泛型这一强大工具! 以下是一个链表的类型实现: @@ -103,19 +99,19 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 :::info 🅿️ 提示 -当然这种操作不局限于声明变量,你在函数中也可以使用(当编译器无法完成推断时,它会给出一个包含完整堆栈跟踪的报错)! +当然,这种操作不局限于变量声明,在函数中也可以使用(当编译器无法完成推断时,会给出包含完整堆栈跟踪的错误提示)! ::: ## 字段默认值 -结构体允许使用默认值,只需要在定义结构体的时候声明默认值即可: +结构体允许为字段设置默认值,只需在定义结构体时声明即可: <<<@/code/release/struct.zig#default_field -但仅仅这样还不够,因为在使用时很容易遇到只初始化部分字段,而其他字段使用默认值的情况,此时如果我们对结构体的字段值并无默认不变性的要求,那么这种默认值方案已经足够我们使用。 +然而,仅此还不够。在实际使用中,我们可能只初始化部分字段,而其他字段使用默认值。如果对结构体字段的默认值没有不变性要求,那么这种默认值方案已经足够使用。 -但如果要求结构体的字段值具有默认不变性(要么都是默认值,要么全部由使用者手动赋值),那么可以采用以下方案: +但如果要求结构体字段的值具有默认不变性(即要么全部使用默认值,要么全部由使用者手动赋值),则可以采用以下方案: <<<@/code/release/struct.zig#all_default @@ -133,38 +129,38 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 :::info 🅿️ 提示 -使用 Go 的朋友对这个可能很熟悉,在 Go 中经常用空结构体做实体在 chan 中传递,它的内存大小为 0! -而在 C++ 中,这样的空结构体的内存大小则是 1。 +熟悉 Go 语言的读者可能对此很熟悉,在 Go 中空结构体常用于在 `chan` 中传递实体,其内存大小为 0。 +而在 C++ 中,空结构体的内存大小通常为 1 字节。 ::: ## 通过字段获取基指针(基于字段的指针) -为了获得最佳的性能,结构体字段的顺序是由编译器决定的,但是,我们可以仍然可以通过结构体字段的指针来获取到基指针! +为了获得最佳性能,结构体字段的内存布局顺序由编译器决定。然而,我们仍然可以通过结构体字段的指针来获取其基指针! <<<@/code/release/struct.zig#base_ptr -这里使用了内建函数 [`@fieldParentPtr`](https://ziglang.org/documentation/master/#fieldParentPtr) ,它会根据给定字段指针,返回对应的结构体基指针。 +这里使用了内建函数 [`@fieldParentPtr`](https://ziglang.org/documentation/master/#fieldParentPtr),它会根据给定的字段指针,返回对应的结构体基指针。 ## 元组 -元组实际上就是不指定字段的(匿名)结构体。 +元组实际上是不指定字段名的(匿名)结构体。 -由于没有指定字段名,zig 会使用从 0 开始的整数依次为字段命名。但整数并不是有效的标识符,所以使用 `.` 语法访问字段的时候需要将数字写在 `@""` 中。 +由于没有指定字段名,Zig 会使用从 0 开始的整数依次为字段命名。但整数并不是有效的标识符,因此在使用 `.` 语法访问字段时,需要将数字写在 `@""` 中。 <<<@/code/release/struct.zig#tuple -当然,以上的语法很啰嗦,所以 zig 提供了类似**数组的语法**来访问元组,例如 `values[3]` 的值就是 "hi"。 +当然,上述语法较为繁琐,因此 Zig 提供了类似**数组的语法**来访问元组,例如 `values[3]` 的值就是 "hi"。 :::info 🅿️ 提示 -元组还有一个和数组一样的字段 `len`,并且支持 `++` 和 `**` 运算符,以及[内联 for](../process_control/loop.md#内联-inline)。 +元组还有一个与数组相同的 `len` 字段,并且支持 `++` 和 `**` 运算符,以及[内联 for](../process_control/loop.md#内联-inline)。 ::: :::info 🅿️ 提示 -元组同时还支持解构语法!! +元组也支持解构语法! <<<@/code/release/struct.zig#destruct_tuple @@ -172,31 +168,31 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 ## 高级特性 -以下特性如果你连名字都没有听说过,那就代表你目前无需了解以下部分,待需要时再来学习即可! +以下特性可能对初学者来说较为陌生。如果你从未听说过这些概念,则目前无需深入了解,待需要时再来学习即可! -> zig 并不保证结构体字段的顺序和结构体大小,但保证它是 ABI 对齐的。 +> Zig 并不保证结构体字段的顺序和结构体大小,但保证其符合 ABI 对齐要求。 ### extern `extern` 关键字用于修饰结构体,使其内存布局保证匹配对应目标的 C ABI。 -这个关键字适合使用于嵌入式或者裸机器上,其他情况下建议使用 `packed` 或者普通结构体。 +该关键字适用于嵌入式系统或裸机编程,其他情况下建议使用 `packed` 或普通结构体。 ### packed -`packed` 关键字修饰结构体,普通结构体不同,它保证了内存布局: +`packed` 关键字修饰的结构体与普通结构体不同,它保证了以下内存布局特性: - 字段严格按照声明的顺序排列 -- 在不同字段之间不会存在位填充(不会发生内存对齐) -- zig 支持任意位宽的整数(通常不足 8 位的仍然使用 8 位),但在 `packed` 下,会只使用它们的位宽 +- 字段之间不会存在位填充(即不进行内存对齐) +- Zig 支持任意位宽的整数(通常不足 8 位的仍会占用 8 位),但在 `packed` 结构体中,字段将只占用其声明的位宽。 - `bool` 类型的字段,仅有一位 -- 枚举类型只使用其整数标志位的位宽 -- 联合类型只使用其最大位宽 -- 根据目标的字节顺序,非 ABI 字段会被尽量压缩为占用尽可能小的 ABI 对齐整数的位宽。 +- 枚举类型只占用其整数标志位的位宽 +- 联合类型只占用其最大成员的位宽 +- 根据目标平台的字节序,非 ABI 字段会被尽量压缩,以占用尽可能小的 ABI 对齐整数的位宽。 -以上几个特性就有很多有意思的点值得我们使用和注意。 +以上特性在使用时有许多值得注意的有趣之处。 -1. zig 允许我们获取字段指针。如果字段涉及位偏移,那么该字段的指针就无法赋值给普通指针(因为位偏移也是指针对齐信息的一部分)。这个情况可以使用 [`@bitOffsetOf`](https://ziglang.org/documentation/master/#bitOffsetOf) 和 [`@offsetOf`](https://ziglang.org/documentation/master/#offsetOf) 观察到: +1. Zig 允许我们获取字段指针。如果字段存在位偏移,那么该字段的指针将无法直接赋值给普通指针(因为位偏移也是指针对齐信息的一部分)。这种情况可以通过 [`@bitOffsetOf`](https://ziglang.org/documentation/master/#bitOffsetOf) 和 [`@offsetOf`](https://ziglang.org/documentation/master/#offsetOf) 来观察: :::details 示例 @@ -204,7 +200,7 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 ::: -2. 使用位转换 [`@bitCast`](https://ziglang.org/documentation/master/#bitCast) 和指针转换 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 来强制对 `packed` 结构体进行转换操作: +2. 可以使用位转换 [`@bitCast`](https://ziglang.org/documentation/master/#bitCast) 和指针转换 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 来强制对 `packed` 结构体进行类型转换: :::details 示例 @@ -212,7 +208,7 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 ::: -3. 还可以对 `packed` 的结构体的指针设置内存对齐来访问对应的字段: +3. 还可以对 `packed` 结构体的指针设置内存对齐,以访问对应的字段: :::details 示例 @@ -220,7 +216,7 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 ::: -4. `packed struct` 会保证字段的顺序以及在字段间不存在额外的填充,但针对结构体本身可能仍然存在额外的填充: +4. `packed struct` 保证了字段的顺序以及字段间没有额外的填充。然而,结构体本身可能仍然存在额外的填充: :::details 示例 @@ -247,14 +243,14 @@ _这种基于结果位置语义的推导还支持结构体本身的方法和其 ### 命名规则 -由于在 zig 中很多结构是匿名的(例如可以把一个源文件看作是一个匿名的结构体),所以 zig 基于一套规则来进行命名: +由于在 Zig 中许多结构是匿名的(例如一个源文件可以被视为一个匿名结构体),因此 Zig 遵循一套命名规则: -- 如果一个结构体位于变量的初始化表达式中,它就以该变量命名(实际上就是声明结构体类型)。 -- 如果一个结构体位于 `return` 表达式中,那么它以返回的函数命名,并序列化参数。 -- 其他情况下,结构体会获得一个类似 `filename.funcname.__struct_ID` 的名字。 -- 如果该结构体在另一个结构体中声明,它将以父结构体和前面的规则推断出的名称命名,并用点分隔。 +- 如果一个结构体位于变量的初始化表达式中,它将以该变量命名(实际上是声明结构体类型)。 +- 如果一个结构体位于 `return` 表达式中,它将以返回的函数命名,并序列化参数。 +- 其他情况下,结构体将获得一个类似 `filename.funcname.__struct_ID` 的名称。 +- 如果该结构体在另一个结构体中声明,它将以父结构体和前面规则推断出的名称命名,并用点分隔。 -上面几条规则看着很模糊是吧,我们来几个小小的示例来演示一下: +上述规则可能有些抽象,下面通过几个示例来演示: ::: code-group diff --git a/course/basic/advanced_type/vector.md b/course/basic/advanced_type/vector.md index 2fefdc70..e83a677e 100644 --- a/course/basic/advanced_type/vector.md +++ b/course/basic/advanced_type/vector.md @@ -1,40 +1,36 @@ ---- -outline: deep ---- - # 向量 -> 向量(Vector)为我们提供了并行操纵一组同类型(布尔、整型、浮点、指针)的值的方法,它尽可能使用 `SIMD` 指令。 +> 向量(Vector)提供了一种对一组同类型(布尔、整型、浮点、指针)值进行并行操作的方法,它会尽可能利用 `SIMD`(单指令多数据)指令集。 -向量类型使用内置函数 [@Vector](https://ziglang.org/documentation/master/#Vector) 创建 +向量类型通过内置函数 [@Vector](https://ziglang.org/documentation/master/#Vector) 创建。 ## 基本使用 -向量支持与底层基本类型相同的内置运算符。这些操作是按元素执行,并返回与输入向量长度相同的向量,包括: +向量支持与底层基本类型相同的内置运算符。这些操作都是按元素执行的,并返回一个与输入向量长度相同的向量。支持的运算符包括: - 算术运算符 (`+`, `-`, `/`, `*`, `@divFloor`, `@sqrt`, `@ceil`, `@log`, ... ) - 位操作符 (`>>`, `<<`, `&`, `|`,`~`, ... ) -- 比较远算符 (`<`, `>`, `==`, ...) +- 比较运算符 (`<`, `>`, `==`, ...) -禁止对标量(单个数字)和向量的混合使用数学运算符,Zig 提供了 [`@splat`](https://ziglang.org/documentation/master/#splat) 内建函数来轻松从标量转换为向量,并且它支持 [`@reduce`](https://ziglang.org/documentation/master/#reduce) 和数组索引语法以从向量转换为标量,向量还支持对具有已知长度的固定长度数组进行赋值,如果需要重新排列元素,可以使用 [`@shuffle`](https://ziglang.org/documentation/master/#shuffle) 和 [`@select`](https://ziglang.org/documentation/master/#select) 函数。 +禁止混合使用标量(单个数字)和向量进行数学运算。Zig 提供了 [`@splat`](https://ziglang.org/documentation/master/#splat) 内建函数,可以方便地将标量转换为向量。同时,可以使用 [`@reduce`](https://ziglang.org/documentation/master/#reduce) 和数组索引语法将向量转换为标量。向量还支持直接赋值给已知长度的固定长度数组。如果需要重新排列元素,可以使用 [`@shuffle`](https://ziglang.org/documentation/master/#shuffle) 和 [`@select`](https://ziglang.org/documentation/master/#select) 函数。 <<<@/code/release/vector.zig#basic ::: info 🅿️ 提示 -可以使用 `@as` 将向量转为数组。 +可以使用 `@as` 将向量强制转换为数组。 -比目标机器的 SIMD 大小短的向量的操作通常会编译为单个 SIMD 指令,而比目标机器 SIMD 大小长的向量将编译为多个 SIMD 指令。 +如果向量的长度小于目标机器的 SIMD 寄存器宽度,操作通常会编译为单个 SIMD 指令;如果向量长度更长,则会编译为多个 SIMD 指令。 -如果给定的目标体系架构上不支持 SIMD,则编译器将默认依次对每个向量元素进行操作。 +如果目标体系结构不支持 SIMD,编译器将默认依次对每个向量元素进行操作。 -Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向量长度(例如 2^20)可能会导致当前版本的 Zig 上的编译器崩溃。 +Zig 支持最大 `2^32 - 1` 的向量长度。请注意,过长的向量长度(例如 `2^20`)可能会导致当前版本的 Zig 编译器崩溃。 ::: ## 解构向量 -和数组一样,向量也可以被解构: +与数组类似,向量也可以被解构: <<<@/code/release/vector.zig#deconstruct @@ -42,7 +38,7 @@ Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向 `@splat(scalar: anytype) anytype` -生成一个向量,向量的每个元素均是传入的参数 `scalar`,向量的类型和长度由编译器推断。 +生成一个向量,其所有元素都与传入的 `scalar` 参数相同。向量的类型和长度由编译器推断。 <<<@/code/release/vector.zig#splat @@ -50,17 +46,17 @@ Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向 `@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E` -使用传入的运算符对向量进行水平按顺序合并(_sequential horizontal reduction_),最终得到一个标量。 +使用传入的运算符对向量进行水平归约(_sequential horizontal reduction_),最终得到一个标量。 <<<@/code/release/vector.zig#reduce ::: info 🅿️ 提示 -1. 所有的运算符均可用于整型 -2. `.And`, `.Or`, `.Xor` 还可用于布尔。 -3. `.Min`, `.Max`, `.Add`, `.Mul` 还可以用于浮点型。 +1. 所有运算符均可用于整型。 +2. `.And`, `.Or`, `.Xor` 也可用于布尔类型。 +3. `.Min`, `.Max`, `.Add`, `.Mul` 还可用于浮点型。 -注意:`.Add` 和 `.Mul` 在整型上的操作是 **wrapping**。 +注意:`.Add` 和 `.Mul` 在整型上的操作是**环绕(wrapping)**的。 @@ -77,14 +73,14 @@ Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向 ) @Vector(mask_len, E) ``` -根据掩码`mask`(一个向量 Vector),返回向量 a 或者向量 b 的值,组成一个新的向量,mask 的长度决定返回的向量的长度,并且逐个根据 mask 中的值,来从 a 或 b 选出值,正数是从 a 选出指定索引的值(从 0 开始,变大),负数是从 b 选出指定索引的值(从 -1 开始,变小)。 +根据掩码 `mask`(一个向量),从向量 `a` 或向量 `b` 中选择元素,组成一个新的向量。`mask` 的长度决定了返回向量的长度。`mask` 中的每个值逐个指定从 `a` 或 `b` 中选择哪个元素:正数表示从 `a` 中选择指定索引的元素(索引从 0 开始递增),负数表示从 `b` 中选择指定索引的元素(索引从 -1 开始递减)。 ::: info 🅿️ 提示 -- 建议对 b 中的索引使用 `~` 运算符,以便两个索引都可以从 0 开始(即 `~@as(i32, 0)` 为 -1)。 -- 对于每个 mask 挑选出来的元素,如果它从 A 或 B 中的选出的值是 `undefined`,则结果元素也是 `undefined`。 -- mask 中的元素索引越界会产生编译错误。 -- 如果 a 或 b 是 `undefined`,该变量长度相当于另一个非 `undefined` 变量的长度。如果两个向量均是 `undefined`,则 `@shuffle` 返回所有元素是 `undefined` 的向量 +- 建议对 `b` 中的索引使用 `~` 运算符,这样两个向量的索引都可以从 0 开始(例如,`~@as(i32, 0)` 结果为 -1)。 +- 如果 `mask` 中选择的元素在 `a` 或 `b` 中是 `undefined`,则结果向量中对应的元素也将是 `undefined`。 +- `mask` 中的元素索引越界会导致编译错误。 +- 如果 `a` 或 `b` 是 `undefined`,则其长度被视为与另一个非 `undefined` 向量的长度相同。如果 `a` 和 `b` 都是 `undefined`,`@shuffle` 将返回一个所有元素都是 `undefined` 的向量。 ::: @@ -101,6 +97,6 @@ Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向 ) @Vector(len, T) ``` -根据 pred(一个元素全为布尔类型的向量)从 a 或 b 中按元素选择值。如果 `pred[i]` 为 `true`,则结果中的相应元素将为 `a[i]`,否则为 `b[i]`。 +根据 `pred`(一个布尔类型的向量),按元素从 `a` 或 `b` 中选择值。如果 `pred[i]` 为 `true`,则结果向量中对应的元素为 `a[i]`;否则为 `b[i]`。 <<<@/code/release/vector.zig#select diff --git a/course/basic/basic_type/char-and-boolean.md b/course/basic/basic_type/char-and-boolean.md index 22af7efc..43bf586c 100644 --- a/course/basic/basic_type/char-and-boolean.md +++ b/course/basic/basic_type/char-and-boolean.md @@ -4,59 +4,59 @@ outline: deep # 字符与布尔值 -> 计算机中的字符并非我们平常所指的单个文字。特定的编码方案首先需要将单个文字对应到若干个码位,存储码位时还需要转换为字节序列。 +> 计算机中的“字符”并非我们日常所指的单个文字。要表示一个文字,首先需要通过特定的编码方案将其映射到唯一的码位(Code Point),然后将码位转换为字节序列进行存储。 > -> 布尔值往往通过二进制的 0 和 1 来表示。 +> 布尔值通常通过二进制的 0 和 1 来表示。 ## 字符 -我们通常简单地将“字符”解释为我们能够看到、操作的单个文字,例如 `Z`、`天`、`👍`。 +我们通常将“字符”简单地理解为可读、可操作的单个文字,例如 `Z`、`天`、`👍`。 -但在计算机中则不同:与整数、实数的表示一样,要表示字符,就需要首先划定能够表示的字符范围,为这个范围内的字符分配唯一的数字。在 Unicode 中,这个唯一的数字被称为“码位”(Code Point)。 +然而,在计算机中,字符的表示方式与整数、实数不同。要表示字符,首先需要确定可表示的字符范围,并为该范围内的每个字符分配一个唯一的数字。在 Unicode 标准中,这个唯一的数字被称为“码位”(Code Point)。 -尽管大多数情况下,码位与字符一一对应,但实际仍然存在很多特殊情况。 +尽管在大多数情况下,码位与字符是一一对应的,但仍存在许多特殊情况。 -比如 `à` 和 `à` 看上去是同一个字符、编辑时删起来也感觉是单个字符,但其实前者只占一个码位(U+00E0),而后者则是普通拉丁字母 `a`(U+0061)与组合用重音符 `◌̀`(U+0300)的序列,占两个码位。日常生活中常见的 emoji 也有很多需要多个码位才能够表示。 +例如,`à` 和 `à` 在视觉上是相同的,编辑时也感觉是单个字符,但前者只占用一个码位(U+00E0),而后者是普通拉丁字母 `a`(U+0061)与组合用重音符 `◌̀`(U+0300)的序列,占用两个码位。日常生活中常见的 emoji 也有许多需要多个码位才能表示。 -因此,现代编程语言中常常会避免可能存在歧义的“字符”,转而使用“码位”或特定编码的“编码单元”作为最基础的单位来描述字符串相关的概念。 +因此,现代编程语言通常会避免使用可能存在歧义的“字符”概念,转而使用“码位”或特定编码的“编码单元”作为描述字符串相关概念的最基本单位。 ### Unicode 码位字面量 -zig 中并没有专门的字符类型,与之最接近的是 Unicode 码位字面量。 +Zig 中没有专门的字符类型。与之最接近的是 Unicode 码位字面量。 -使用单引号将文字包围,就是 Unicode 码位字面量。这是一种 `comptime_int` 类型的值,表示的是单个 Unicode 码位。 +使用单引号将文字包围,即可创建 Unicode 码位字面量。这是一种 `comptime_int` 类型的值,表示单个 Unicode 码位。 <<<@/code/release/char-and-boolean.zig#char ### 字符串字面量 -zig 中的字符串字面量是一个[单项指针](../advanced_type/pointer#单项指针),指向的是将字符串进行 UTF-8 编码后得到的字节数组。这个数组和 C 语言中的字符串一样,都是[以 NUL 字符结尾](../advanced_type/array#哨兵数组)的。因此,字符串字面量既可以隐式转换为 `u8` 切片,也可以隐式转换为以 0 结尾的指针。 +Zig 中的字符串字面量是一个[单项指针](../advanced_type/pointer#单项指针),它指向将字符串进行 UTF-8 编码后得到的字节数组。这个字节数组与 C 语言中的字符串类似,都是[以 NUL 字符结尾](../advanced_type/array#哨兵数组)的。因此,字符串字面量既可以隐式转换为 `u8` 切片,也可以隐式转换为以 0 结尾的指针。 <<<@/code/release/char-and-boolean.zig#string-literal -与 C 语言不同的是,zig 中的字符串字面量与 Unicode 码位字面量并没有任何的关系。对字符串字面量求索引得到的是编码序列中的某个字节,并不是 Unicode 码位。但由于 UTF-8 编码兼容 ASCII,所以对于 ASCII 字符而言,两者的数值恰巧相等。 +与 C 语言不同,Zig 中的字符串字面量与 Unicode 码位字面量之间没有直接关系。对字符串字面量进行索引操作,得到的是编码序列中的单个字节,而非 Unicode 码位。然而,由于 UTF-8 编码兼容 ASCII,对于 ASCII 字符而言,两者的数值恰好相等。 -正确处理 UTF-8 字符串是一件相当复杂的工作。标准库在 [`std.unicode`](https://ziglang.org/documentation/master/std/#std.unicode) 包中提供了用来操作 Unicode 码位和 UTF-8 编码序列的工具。 +正确处理 UTF-8 字符串是一项复杂的工作。标准库在 [`std.unicode`](https://ziglang.org/documentation/master/std/#std.unicode) 包中提供了用于操作 Unicode 码位和 UTF-8 编码序列的工具。 :::tip 🅿️ 只能是 UTF-8 编码吗? -其实并不是。字符串字面量可以借助 `\xNN` 转义序列存放任意形式的数据。 +并非如此。字符串字面量可以借助 `\xNN` 转义序列来存放任意形式的字节数据。 -但 zig 源文件使用 UTF-8 编码,并且 `\u{NNNNNN}` 转义序列固定生成的是码位对应的 UTF-8 编码序列,所以字符串字面量在正常情况下存放的都是 UTF-8 数据。 +但由于 Zig 源文件使用 UTF-8 编码,并且 `\u{NNNNNN}` 转义序列固定生成码位对应的 UTF-8 编码序列,因此字符串字面量在正常情况下存放的都是 UTF-8 数据。 ::: -多行字符串字面量使用 `\\` 开头,不会执行任何转义,不包含最后一行的换行。 +多行字符串字面量以 `\\` 开头,不会执行任何转义,并且不包含最后一行的换行符。 <<<@/code/release/char-and-boolean.zig#multiline-string-literal ### C 中的字符类型 -zig 还提供了 `c_char` 类型,对应 C 中的 `char` 类型。主要用于与 C 的交互,其他情况下不建议使用。 +Zig 还提供了 `c_char` 类型,它对应 C 语言中的 `char` 类型。该类型主要用于与 C 语言的交互,在其他情况下不建议使用。 :::details `u8` 和 `c_char` -`u8` 和 `c_char` 并不是全等的,因为 `c_char` 虽然是 8 位,但是它是否有符号取决于 target (目标机器)。 +`u8` 和 `c_char` 并非完全等价。尽管 `c_char` 也是 8 位,但其是否有符号取决于目标机器(target)。 ::: @@ -64,4 +64,4 @@ zig 还提供了 `c_char` 类型,对应 C 中的 `char` 类型。主要用于 > 常用于流程控制 -在 zig 中,布尔值有两个,分别是 `true` 和 `false`,它们在内存中占用的大小为 1 个字节。 +在 Zig 中,布尔值只有 `true` 和 `false` 两个。它们在内存中占用 1 个字节。 diff --git a/course/basic/basic_type/function.md b/course/basic/basic_type/function.md index aab2c679..837356c3 100644 --- a/course/basic/basic_type/function.md +++ b/course/basic/basic_type/function.md @@ -1,28 +1,24 @@ ---- -outline: deep ---- - # 函数 > 函数是编程语言中最为基本的语句。 ## 基本使用 -zig 的函数明显,你可以一眼就看出来它的组成,我们来用一个简单的函数作为说明: +Zig 的函数结构清晰,你可以一眼看出其组成部分。我们来用一个简单的函数作为说明: <<<@/code/release/function.zig#add > 如果你有 C 的使用经验,一眼就可以看出来各自的作用。 -下面来进行说明: +具体说明如下: -1. `pub` 是访问修饰符,有且只有一个选择,那就是 `pub`,这代表着函数是公共可访问的(其他的文件 import 该文件后,可以直接使用这个函数)。 -2. `fn` 是关键字,代表着我们接下来定义了一个函数。 -3. `add` 是标识符,作为函数的名字。 -4. `a: u8` 是参数的标识符和类型,这里有两个参数,分别是 `a` 和 `b`,它们的类型均是 `u8`。 -5. `u8` 是函数的返回类型,在 zig 中,一个函数只能返回一个值。 +1. `pub` 是访问修饰符,表示函数是公共可访问的(其他文件 `import` 该文件后可以直接使用此函数)。 +2. `fn` 是关键字,用于定义函数。 +3. `add` 是标识符,作为函数名。 +4. `a: u8` 是参数的标识符和类型。这里有两个参数 `a` 和 `b`,它们的类型都是 `u8`。 +5. `u8` 是函数的返回类型。在 Zig 中,一个函数只能返回一个值。 -如果没有返回值,请使用 `void`,**_zig 原则上不允许忽略函数的返回值_**,如果需要忽略可将返回值分配给 `_`,编译器将自动忽略该返回值。 +如果没有返回值,请使用 `void`。**_Zig 原则上不允许忽略函数的返回值_**;如果需要忽略,可将返回值分配给 `_`,编译器将自动忽略该返回值。 :::info 🅿️ 提示 @@ -30,7 +26,7 @@ zig 的函数明显,你可以一眼就看出来它的组成,我们来用一 <<<@/code/release/function.zig#max -其中的 `comptime T: type` 你可能很陌生,这是[编译期](../../advanced/comptime.md)参数,它是用来实现鸭子类型(泛型)的关键语法! +其中 `comptime T: type` 可能对你来说比较陌生,这是[编译期](../../advanced/comptime.md)参数,它是实现鸭子类型(泛型)的关键语法。 ::: @@ -38,31 +34,31 @@ zig 的函数明显,你可以一眼就看出来它的组成,我们来用一 这里命名规则没什么强制性的要求,你只需要保证符合变量声明的 [_标识符规范_](/basic/define-variable.html#标识符命名) 即可。 -如果你需要一个命名的推荐规则的话,可以参照 zig 源码的命名方式,它使用的是[小驼峰命名法](#)。 +如果你需要命名规范的建议,可以参考 Zig 源码的命名方式,它使用的是[小驼峰命名法](#)。 ::: ::: warning -函数体和函数指针之间是有区别的,函数体是仅限编译时的类型,而函数指针可能是运行时已知的。 +函数体和函数指针之间存在区别:函数体是仅限编译时的类型,而函数指针可能是运行时已知的。 -关于函数指针,`*const fn (a: i8, b: i8) i8` 就是一个函数指针类型。 +例如,`*const fn (a: i8, b: i8) i8` 就是一个函数指针类型。 ::: ## 参数传递 -> 参数传递是一个非常古老的问题,在高级语言的角度来看,存在着“值传递”和“引用传递”这两种情况,这无疑大大增加了程序员在编程时的心智负担。 +> 参数传递是一个经典问题。在高级语言中,存在“值传递”和“引用传递”两种情况,这无疑增加了程序员的认知负担。 -zig 在这方面的处理则是,原始类型(整型、布尔这种)传递完全使用值传递,针对原始类型的这种策略开销非常小,通常只需要设置对应的寄存器即可。 +Zig 在这方面的处理是:原始类型(如整型、布尔)完全使用值传递。对于原始类型,这种策略开销非常小,通常只需设置对应的寄存器即可。 -像复合类型(结构体、联合、数组等),这些传递均是由编译器来决定究竟是使用“值传递”还是“引用传递”。 +对于复合类型(如结构体、联合、数组),编译器会根据具体情况决定是使用“值传递”还是“引用传递”。 -但作为开发者,只需要记住,**_函数的参数是不可变的_** 就行了。 +但作为开发者,你只需要记住:**_函数的参数是不可变的_**。 :::info 🅿️ 提示 -对于外部函数 (使用 `extern` 修饰),Zig 遵循 C ABI 按值传递结构和联合类型。 +对于外部函数(使用 `extern` 修饰),Zig 遵循 C ABI(应用程序二进制接口)按值传递结构体和联合类型。 ::: @@ -70,17 +66,17 @@ zig 在这方面的处理则是,原始类型(整型、布尔这种)传递 内建函数由编译器提供,并以 `@` 为前缀。参数上的 `comptime` 关键字意味着该参数必须在编译期已知。 -介于内建函数的数目过多,故不进行系统讲解,仅在对应章节说明涉及到的内建函数。 +由于内建函数数量众多,本教程不进行系统讲解,仅在相关章节说明涉及到的内建函数。 -更多的内建函数文档请看 [这里](https://ziglang.org/documentation/master/#Builtin-Functions)。 +更多内建函数的文档请参考[这里](https://ziglang.org/documentation/master/#Builtin-Functions)。 ## 高阶使用 -以下是一些更加高级地使用,可以之后在学习! +以下是一些更加高级的用法,可以之后再学习! ### 闭包 -先来看看维基百科的定义: +首先,我们来看维基百科对闭包的定义: > 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。 > @@ -90,11 +86,11 @@ zig 在这方面的处理则是,原始类型(整型、布尔这种)传递 > > 捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如 C++)。 -在 zig 中,由于语言本身的限制(这是出于某种角度考虑而做出的限制),我们无法自由地使用闭包特性。 +在 Zig 中,由于语言设计上的某些限制,我们无法像在某些其他语言中那样自由地使用闭包特性。 -先来说一下广义的闭包:**闭包是指一个函数与其引用的词法作用域(lexical scope)形成的组合。简言之,闭包允许一个函数访问其外部作用域中的变量,即使这个函数在其外部作用域之外被调用,其的主要特性是能够“记住”其创建时的环境。** +广义上讲,**闭包是指一个函数与其引用的词法作用域(lexical scope)形成的组合。简言之,闭包允许一个函数访问其外部作用域中的变量,即使这个函数在其外部作用域之外被调用。其主要特性是能够“记住”其创建时的环境。** -在很多支持内存垃圾回收(GC)的语言中,它们的使用大概是这样的: +在许多支持内存垃圾回收(GC)的语言中,它们的使用大概是这样的: ```python def outer_function(): @@ -112,22 +108,22 @@ closure = outer_function() closure() # 输出:Hello, World! ``` -以上是一段 `python` 代码,其中 `outer_function` 函数最后返回一个函数类型 (实际上它返回了函数 `inner_function`,但这不严谨,因为在解释运行的时候它不会叫做 `inner_function`)。 +以上是一段 Python 代码。其中 `outer_function` 函数最终返回一个函数类型(实际上它返回了 `inner_function` 函数,但在解释执行时,它不再以 `inner_function` 的名称存在)。 -Zig 语言不允许在函数内声明函数,也不允许直接创建匿名函数。这两个功能构成了在其他编程语言(例如 JavaScript、PowerQuery-M 等)中实现闭包模式的通用模式。 +Zig 语言不允许在函数内部声明函数,也不允许直接创建匿名函数。这两个特性在其他编程语言(例如 JavaScript、PowerQuery-M 等)中是实现闭包模式的常见方式。 -这就导致我们的闭包不得不在编译期是已知的: +因此,在 Zig 中实现闭包,通常需要其在编译期是已知的: <<<@/code/release/function.zig#closure -函数 `bar` 返回一个函数类型`fn (i32) i32`,接收一个编译期的参数,这使得该函数可以访问编译期传入的数据,并且我们需要注意,我们还是使用了匿名结构体方法 +函数 `bar` 返回一个 `fn (i32) i32` 类型的函数,并接收一个编译期参数。这使得该函数可以访问编译期传入的数据。需要注意的是,这里我们使用了匿名结构体方法。 -关于闭包的更多内容可以参考以下文章或者帖子: +关于闭包的更多内容可以参考以下文章或帖子: - [Implementing Closures and Monads in Zig](https://zig.news/andrewgossage/implementing-closures-and-monads-in-zig-23kf) - [Closure Pattern in Zig Zig](https://zig.news/houghtonap/closure-pattern-in-zig-19i3#zig-closure-pattern) -关于安德鲁拒绝匿名函数提案的解释: +关于 Andrew Kelley 拒绝匿名函数提案的解释: - [RFC: Make function definitions expressions](https://github.com/ziglang/zig/issues/1717#issuecomment-1627790251) @@ -139,7 +135,7 @@ Zig 语言不允许在函数内声明函数,也不允许直接创建匿名函 ### `noreturn` -`noreturn` 是一个特殊的类型,它代表以下内容: +`noreturn` 是一个特殊类型,表示函数不会返回。它通常用于以下情况: - `break` - `continue` @@ -147,40 +143,40 @@ Zig 语言不允许在函数内声明函数,也不允许直接创建匿名函 - `unreachable` - `while (true) {}` -当一个函数不会返回时,你可以使用它来代替 `void`。 +当一个函数不会返回时,可以使用它来代替 `void`。 -该类型一般用在内核开发中,因为内核本身应当是一个不会退出的程序,还有一种使用场景是 `exit` 函数。 +该类型常用于内核开发,因为内核本身通常是一个不会退出的程序。另一个使用场景是 `exit` 函数。 <<<@/code/release/function.zig#ExitProcess ### `export` -`export` 关键字保证函数可以在生成的 object 文件中可见,并且使用 C ABI。 +`export` 关键字确保函数在生成的对象文件中可见,并遵循 C ABI。 <<<@/code/release/function.zig#sub ### `extern` -`extern` 说明符用于声明一个将在链接时解析的函数(也就是该函数并非由 zig 定义,而是由外部库定义,一般遵循 C ABI,但 C ABI 本身有多种规范),链接可以是静态链接,也可以是动态链接。`extern` 关键字后面的引号中的标识符指定了包含该函数的库(例如 `c` -> `libc.so` )。`callconv` 说明符用于更改函数的调用约定。 +`extern` 说明符用于声明一个将在链接时解析的函数(即该函数并非由 Zig 定义,而是由外部库定义,通常遵循 C ABI,但 C ABI 本身有多种规范)。链接可以是静态链接或动态链接。`extern` 关键字后面引号中的标识符指定了包含该函数的库(例如 `c` -> `libc.so`)。`callconv` 说明符用于更改函数的调用约定。 <<<@/code/release/function.zig#atan2 ::: tip -`extern` 和 `export` 的区别就是 `extern` 来引用非 Zig 实现的库,而 `export` 是 zig 将函数对外暴露供其他语言使用! +`extern` 用于引用非 Zig 实现的库,而 `export` 则是 Zig 将函数对外暴露供其他语言使用! ::: ### `@setCold` `@setCold(comptime is_cold: bool) void` -告诉优化器当前函数很少被调用(或不被调用),该函数仅在函数作用域内有效。 +告诉优化器当前函数很少被调用(或不被调用)。该函数仅在函数作用域内有效。 <<<@/code/release/function.zig#abort ### `callconv` -`callconv` 关键字告诉函数的调用约定,这在对外暴露函数或者裸汇编时会很有用。 +`callconv` 关键字用于指定函数的调用约定,这在对外暴露函数或编写裸汇编时非常有用。 <<<@/code/release/function.zig#shiftLeftOne -关于可以使用的调用约定格式,可以参考这里[`std.builtin.CallingConvention`](https://ziglang.org/documentation/master/std/#std.builtin.CallingConvention)。 +关于可用的调用约定格式,请参考[`std.builtin.CallingConvention`](https://ziglang.org/documentation/master/std/#std.builtin.CallingConvention)。 diff --git a/course/basic/basic_type/number.md b/course/basic/basic_type/number.md index a4587fd6..40389f39 100644 --- a/course/basic/basic_type/number.md +++ b/course/basic/basic_type/number.md @@ -1,16 +1,12 @@ ---- -outline: deep ---- - # 数值类型 -> 数值类型是语言运行时的基本类型,当它编译为机器码时,其中包含着许多的 _CPU 运算器_ 的操作指令。 +> 数值类型是编程语言中最基本的数据类型之一。当它们被编译成机器码时,通常会对应到 CPU 运算器的操作指令。 ## 整数 ### 类型 -在 zig 中,对整数的类型划分很详细,以下是类型表格: +在 Zig 中,整数类型划分非常详细,具体如下表所示: | 类型 | 对应 C 类型 | 描述 | | -------------- | -------------------- | ------------------------------ | @@ -30,10 +26,10 @@ outline: deep <<<@/code/release/number.zig#type -同时 zig 支持任意位宽的整数,使用 `u` 或者 `i` 后面加数字即可,例如 `i7` 代表有符号的 7 位整数,整数类型允许的最大位宽为`65535`。 +同时,Zig 支持任意位宽的整数。通过在 `u`(无符号)或 `i`(有符号)后加上数字即可定义,例如 `i7` 代表有符号的 7 位整数。整数类型允许的最大位宽为 `65535`。 ::: tip 🅿️ 提示 -`usize` 和 `isize` 这两种类型的的大小取决于,运行程序的目标计算机 CPU 的类型:32 位 CPU 则两个类型均为 32 位,64 位同理。 +`usize` 和 `isize` 这两种类型的大小取决于目标 CPU 架构:在 32 位系统上它们是 32 位,在 64 位系统上则是 64 位。 ::: ### 不同进制 @@ -49,10 +45,10 @@ outline: deep ### 除零 -zig 编译器对于除零的处理是分别在编译期和运行时(除 `ReleaseSmall` 构建模式外)进行检测,编译时检测出错误则直接停止编译,运行时如果出错会给出完整的堆栈跟踪。 +Zig 编译器会在编译期和运行时(`ReleaseSmall` 构建模式除外)对除零操作进行检测。编译时检测到错误会直接停止编译;运行时如果发生除零,则会给出完整的堆栈跟踪。 ::: details 小细节 -这里的“除零”包括了 _除法_ 和 _求余_ 两种操作! +这里的“除零”包括了除法和求余两种操作。 ::: 编译期: @@ -105,7 +101,7 @@ thread 2456131 panic: division by zero ### 溢出 -zig 中,有以下默认操作可以导致溢出: +在 Zig 中,以下默认操作可能导致溢出: - `+`(加法) - `-`(减法) @@ -116,11 +112,11 @@ zig 中,有以下默认操作可以导致溢出: - [`@divFloor`](https://ziglang.org/documentation/master/#divFloor)(除法) - [`@divExact`](https://ziglang.org/documentation/master/#divExact)(除法) -还有在标准库 `@import("std").math` 中的函数可能导致溢出发生。 +标准库 `@import("std").math` 中的某些函数也可能导致溢出。 -在编译期和运行时也分别有类似“除零”操作的检测和堆栈跟踪。 +在编译期和运行时,Zig 也会对溢出进行检测,并提供类似“除零”操作的堆栈跟踪。 -**处理溢出**有两种方式,一种是使用内置的溢出处理函数,一种是环绕操作符。 +**处理溢出**有两种主要方式:使用内置的溢出处理函数,或使用环绕操作符。 内置溢出处理函数: @@ -129,7 +125,7 @@ zig 中,有以下默认操作可以导致溢出: - [`@mulWithOverflow`](https://ziglang.org/documentation/master/#mulWithOverflow) - [`@shlWithOverflow`](https://ziglang.org/documentation/master/#shlWithOverflow) -这些内建函数返回一个元组,其中包含是否存在溢出(作为 `u1`)以及操作中可能溢出的位。 +这些内建函数返回一个元组,其中包含一个布尔值(`u1` 类型)指示是否发生溢出,以及操作结果。 环绕操作符: @@ -138,21 +134,21 @@ zig 中,有以下默认操作可以导致溢出: - `-%`(取反环绕) - `*%`(乘法环绕) -这些操作符保证了环绕语义(它们会取计算后溢出的值)。 +这些操作符保证了环绕语义(即当结果超出类型范围时,会从另一端“环绕”回来)。 ## 浮点数 -浮点数就是表示带有小数点的数字。在 zig 中,浮点数有 `f16`、`f32`、`f64`、`f80`、`f128`、`c_longdouble`(对应 C ABI 的 `long double` )。 +浮点数用于表示带有小数点的数字。在 Zig 中,浮点数类型包括 `f16`、`f32`、`f64`、`f80`、`f128`,以及 `c_longdouble`(对应 C ABI 的 `long double`)。 -值得注意的是,`comptime_float` 具有 `f128` 的精度和运算。 +值得注意的是,`comptime_float` 具有 `f128` 的精度和运算能力。 -浮点字面量可以隐式转换为 _任意浮点类型_,如果没有小数部分的话还能够隐式转换为 _任意整数类型_。 +浮点字面量可以隐式转换为**任意浮点类型**。如果浮点字面量没有小数部分,它还可以隐式转换为**任意整数类型**。 -浮点运算时遵循 `Strict` 模式,但是可以使用 `@setFloatMode(.Optimized)` 切换到 `Optimized` 模式,有关浮点运算的模式,详见 [`@setFloatMode`](https://ziglang.org/documentation/master/#setFloatMode)。 +浮点运算默认遵循 `Strict` 模式,但可以使用 `@setFloatMode(.Optimized)` 切换到 `Optimized` 模式。有关浮点运算模式的详细信息,请参见 [`@setFloatMode`](https://ziglang.org/documentation/master/#setFloatMode)。 ::: info 🅿️ 提示 -zig 并未像其他语言那样默认提供了 NaN、无穷大、负无穷大这些语法,如果需要使用它们,请使用标准库: +Zig 并未像其他语言那样默认提供 `NaN`、无穷大、负无穷大等字面量语法。如果需要使用它们,请通过标准库获取: <<<@/code/release/number.zig#float @@ -160,9 +156,9 @@ zig 并未像其他语言那样默认提供了 NaN、无穷大、负无穷大这 ::: details 注意浮点数陷阱 -1. 由于计算机是二进制的特性,导致浮点数往往是以近似值的方式存储(受制于浮点精度,例如有些分数无法用小数表示)。 +1. 由于计算机内部使用二进制表示,浮点数通常以近似值存储(受限于浮点精度,例如某些分数无法精确表示)。 -2. 浮点数在某些操作上是反直觉的,这也是精度问题导致的,来看个例子: +2. 浮点数在某些操作上可能反直觉,这同样是精度问题导致的。例如: ```zig const std = @import("std"); @@ -173,24 +169,24 @@ pub fn main() void { } ``` -你一定以为这个可以通过断言,0.1 + 0.2 很明显就应该是 0.3 嘛,但实际上在运行时会直接崩溃! +你可能会认为这个断言会通过,因为 0.1 + 0.2 显然等于 0.3。但实际上,这段代码在运行时会直接崩溃! ::: ## 运算 -常规的运算有等于 (`==`),不等于 (`!=`),大于 (`>`),小于 (`<`),大于等于 (`>=`),小于等于 (`<=`),加减乘除(`+`, `-`, `*`, `/`),左移右移 (`<<`,`>>`),与或非 (`and`, `or`, `!`),按位与 (`&`),按位或 (`|`),按位异或 (`^`),按位非 (`~`), +常规运算包括:等于 (`==`),不等于 (`!=`),大于 (`>`),小于 (`<`),大于等于 (`>=`),小于等于 (`<=`),加减乘除(`+`, `-`, `*`, `/`),左移右移 (`<<`, `>>`),逻辑与或非 (`and`, `or`, `!`),按位与 (`&`),按位或 (`|`),按位异或 (`^`),按位非 (`~`)。 -> 常见的加减乘除我们就不聊了,聊点 zig 中独具特色的小玩意。 +> 常见的加减乘除运算在此不再赘述,我们来聊聊 Zig 中独具特色的一些操作符。 -- `+|`:饱和加法,这涉及到[对等类型解析](../../advanced/type_cast.md#对等类型转换),你现在只需要知道加法结果最多只是该类型的极限即可,例如 `u8` 类型的 255 + 1 后还是 255。 -- `-|`:饱和减法,和上面一样,减法结果最小为该类型的极限。 -- `*|`:饱和乘法,同上,乘法结果最大或最小为该类型的极限。 -- `<<|`:饱和左移,同之前,结果为该类型的极限。 -- `++`:矩阵(数组)串联,需要两个矩阵(数组)内元素类型相同。 -- `**`:矩阵乘(数组)法,需要在编译期已知矩阵(数组)的大小(长度)和乘的倍数。 +- `+|`:饱和加法。这涉及到[对等类型解析](../../advanced/type_cast.md#对等类型转换)。简单来说,加法结果不会超过该类型的最大值。例如,`u8` 类型的 255 加 1 后仍然是 255。 +- `-|`:饱和减法。与饱和加法类似,减法结果不会低于该类型的最小值。 +- `*|`:饱和乘法。乘法结果不会超过该类型的最大值或最小值。 +- `<<|`:饱和左移。左移结果不会超过该类型的最大值。 +- `++`:数组串联。要求两个数组的元素类型相同。 +- `**`:数组重复。在编译期已知数组的长度和重复次数。 运算的优先级: @@ -212,7 +208,7 @@ or ::: tip 🅿️ 提示 -如果你有使用复数的需求,可以使用标准库中的 [`std.math.Complex`](https://ziglang.org/documentation/master/std/#std.math.complex.Complex)。 +如果你需要使用复数,可以使用标准库中的 [`std.math.Complex`](https://ziglang.org/documentation/master/std/#std.math.complex.Complex)。 <<<@/code/release/number.zig#complex diff --git a/course/basic/define-variable.md b/course/basic/define-variable.md index 91cb9bc3..0cad1cf0 100644 --- a/course/basic/define-variable.md +++ b/course/basic/define-variable.md @@ -2,82 +2,82 @@ outline: deep --- -# 基本类型 +# 变量声明与定义 > 变量的声明和定义是编程语言中最基础且最常见的操作之一。 ## 变量声明 -> 变量是在内存中存储值的单元。 +> 变量是用于在内存中存储值的命名空间。 -在 zig 中,我们使用 `var` 来进行变量的声明,格式是 `var variable:type = value;`,以下是一个示例: +在 Zig 中,我们使用 `var` 关键字来声明变量,其格式为 `var variable_name: Type = initial_value;`。以下是一个示例: <<<@/code/release/define_variable.zig#define ::: info 🅿️ 提示 -目前 Zig 遵循非必要不使用变量原则!即尽可能使用常量。 +目前 Zig 遵循“非必要不使用变量”的原则,即尽可能使用常量。 -同时,zig 还要求所有的非顶层定义的变量(常量)均被使用,如果未被使用编译器会报告错误,但可通过将其分配给 `_` 来解决此问题。 +同时,Zig 要求所有非顶层定义的变量(包括常量)都必须被使用。如果变量未被使用,编译器会报告错误,但可以通过将其赋值给 `_` 来解决此问题。 ::: ### 标识符命名 -在 zig 中,**_禁止变量覆盖外部作用域_**! +在 Zig 中,**_禁止变量遮蔽(shadowing)外部作用域的同名变量_**! -命名须以 **_字母_** 或者 **_下划线_** 开头,后跟任意字母数字或下划线,并且不得与关键字重叠。 +标识符必须以**字母**或**下划线**开头,后跟任意字母、数字或下划线,并且不得与关键字重叠。 -如果一定要使用不符合这些规定的名称(例如与外部库的链接),那么请使用 `@""` 语法。 +如果需要使用不符合这些规定的名称(例如与外部库的链接),可以使用 `@""` 语法。 <<<@/code/release/define_variable.zig#identifier ::: info 🅿️ 提示 -注意,上方代码 `const color: Color = .@"really red";` 后面的 `.@"really red"` 是一个枚举推断,这是由编译器完成的,更多内容见 [_枚举_](/basic/advanced_type/enum) 部分! +注意,上方代码 `const color: Color = .@"really red";` 后面的 `.@"really red"` 是一个枚举推断,这是由编译器完成的。更多内容请参见[_枚举_](/basic/advanced_type/enum)章节! ::: ### 常量 -zig 使用 `const` 作为关键字来声明常量,它无法再被更改,只有初次声明时可以赋值。 +Zig 使用 `const` 关键字来声明常量。常量一旦声明并赋值后,其值便不可更改,只能在初次声明时进行赋值。 <<<@/code/release/define_variable.zig#const ### `undefined` -我们可以使用 `undefined` 使变量保持未初始化状态。 +我们可以使用 `undefined` 关键字使变量保持未初始化状态。 <<<@/code/release/define_variable.zig#undefined -使用 `undefined` 初始化的变量不会执行任何初始化操作,我们无法预知它的初始值。 +使用 `undefined` 初始化的变量不会执行任何初始化操作,因此我们无法预知其初始值。 -因此,`undefined` 常用于初始值不重要的场合。例如,用作缓冲区的变量通常会被提交给另一个函数进行写操作,覆盖原有内容。由于不会对其进行读操作,所以没有必要将这个变量初始化为某个特定的值。 +`undefined` 常用于初始值不重要的场合。例如,用作缓冲区的变量通常会被传递给另一个函数进行写入操作,覆盖原有内容。由于不会对其进行读取操作,因此没有必要将这个变量初始化为某个特定值。 <<<@/code/release/define_variable.zig#use-undefined ::: warning ⚠️ 警告 -慎重使用 `undefined`:如果一个变量的值是未定义的,那么对这个变量进行读操作时,读取到任何值都是可能的,甚至可能读取到对于这个变量所使用的类型而言完全没有意义的值。 +慎重使用 `undefined`:如果一个变量的值是未定义的,那么对其进行读取操作时,读取到任何值都是可能的,甚至可能读取到对于该变量所使用的类型而言完全没有意义的值。 -在 `Debug` 模式下,Zig 将 `0xaa` 字节写入未定义的内存。这是为了尽早发现错误,并帮助检测调试器中未定义内存的使用。但是,此行为只是一种实现功能,而不是语言语义,因此不能保证代码可以观察到它。 +在 `Debug` 模式下,Zig 会将 `0xaa` 字节写入未定义的内存。这是为了尽早发现错误,并帮助检测调试器中未定义内存的使用。然而,此行为仅是一种实现细节,而非语言语义,因此不能保证代码可以观察到它。 ::: ## 解构赋值 -解构赋值(Destructuring Assignment)是于 `0.12` 新引入的语法,允许对可索引的聚合结构(如元组、向量和数组)进行解构。 +解构赋值(Destructuring Assignment)是 Zig `0.12` 版本引入的新语法,允许对可索引的聚合结构(如元组、向量和数组)进行解构。 <<<@/code/release/define_variable.zig#deconstruct -解构表达式只能出现在块内(不在容器范围内),赋值的左侧必须由逗号分隔的列表组成,其中每个元素可以是左值(例如`var`)或变量声明: +解构表达式只能出现在块内(不在容器范围内)。赋值的左侧必须由逗号分隔的列表组成,其中每个元素可以是左值(例如 `var` 变量)或变量声明: <<<@/code/release/define_variable.zig#deconstruct_2 -解构可以以 `comptime` 关键字作为前缀,在这种情况下,整个解构表达式在 `comptime` 处求值。所有声明的 `var` 都将是 `comptime var`,并且所有表达式(左值和右值)都在 `comptime` 处求值。 +解构表达式可以以 `comptime` 关键字作为前缀。在这种情况下,整个解构表达式在编译期(`comptime`)求值。所有声明的 `var` 都将是 `comptime var`,并且所有表达式(左值和右值)都在编译期求值。 ## 块 -块(block)用于限制变量声明的范围,例如以下代码是非法的: +块(block)用于限制变量声明的作用域。例如,以下代码是非法的: ```zig { @@ -87,20 +87,20 @@ zig 使用 `const` 作为关键字来声明常量,它无法再被更改,只 x += 1; ``` -块也可以是一个表达式,当它有标签时,`break` 会从块中返回一个值出来。 +块也可以是一个表达式。当块带有标签时,`break` 语句可以从块中返回一个值。 <<<@/code/release/define_variable.zig#block -上方的 `blk` 是标签名字,它可以是你设置的任何名字。 +上方的 `blk` 是标签名称,你可以设置任何你喜欢的名字。 -### shadow +### Shadow(遮蔽) -Shadow(遮蔽)指的是在内部作用域中声明一个与外部作用域中同名的变量,导致外部作用域的变量被"遮蔽"的现象。 +Shadow(遮蔽)指的是在内部作用域中声明一个与外部作用域中同名的变量,导致外部作用域的变量被“遮蔽”的现象。 -但是该行为在 Zig 中禁止的! -这样做的好处是会强制标识符始终具有在其周期内的一致性,并且可以防止意外使用错误的变量。 +然而,这种行为在 Zig 中是被禁止的! +这样做的好处是强制标识符在其生命周期内保持一致性,并防止意外使用错误的变量。 -注意:如果两个块中的变量如果不交叉,那么它们是可以同名的。 +注意:如果两个块中的变量作用域不交叉,那么它们可以同名。 ### 空的块 @@ -108,44 +108,44 @@ Shadow(遮蔽)指的是在内部作用域中声明一个与外部作用域 ## 容器 -在 Zig 中,**容器** 是充当保存变量和函数声明的命名空间的任何语法结构。容器也是可以实例化的类型定义。结构体、枚举、联合、不透明,甚至 Zig 源文件本身都是容器,但容器并不能包含语句(语句是描述程序运行操作的一个单位)。 +在 Zig 中,**容器** 是充当命名空间的任何语法结构,用于保存变量和函数声明。容器也可以是可实例化的类型定义。结构体、枚举、联合、不透明类型,甚至 Zig 源文件本身都是容器。然而,容器不能包含语句(语句是描述程序运行操作的一个单位)。 -当然,你也可以这样理解,容器是一个只包含变量或常量定义以及函数定义的命名空间。 +当然,你也可以这样理解:容器是一个只包含变量或常量定义以及函数定义的命名空间。 -注意:容器和块(block)不同! +注意:容器和块(block)是不同的概念! > [!IMPORTANT] -> 初次阅读此处困惑是正常的,后面的概念学习完成后此处自通。 +> 初次阅读此处感到困惑是正常的。在学习完后续概念后,此处内容将自然理解。 ## 注释 -先来看一下在 zig 中如何正确的书写注释,zig 本身支持三种注释方式,分别是普通注释、文档注释、顶层文档注释。 +接下来我们了解如何在 Zig 中正确书写注释。Zig 支持三种注释方式:普通注释、文档注释和顶层文档注释。 -`//` 就是普通的注释,就只是和其他编程语言中 `//` 起到的注释效果相同。 +`//` 是普通注释,其作用与其他编程语言中的 `//` 相同。 ::: details 小细节 -值得一提的是,zig 本身并未提供类似`/* */` 这种多行注释,这意味着多行注释的最佳实践形式就是多行的`//`了。 +值得一提的是,Zig 本身并未提供类似 `/* */` 这种多行注释。这意味着多行注释的最佳实践形式是使用多行的 `//`。 -PS:说实话,我认为这个设计并不太好。 +PS: 说实话,我认为这个设计并不太好。 ::: -`///` 就是文档注释,用于给函数、类型、变量等这些提供注释,文档注释记录了紧随其后的内容。 +`///` 是文档注释,用于为函数、类型、变量等提供说明。文档注释记录了紧随其后的代码元素。 <<<@/code/release/define_variable.zig#doc-comment -`//!` 是顶层文档注释,通常用于记录一个文件的作用,**必须放在作用域的顶层,否则会编译错误** +`//!` 是顶层文档注释,通常用于记录文件的作用。**它必须放在作用域的顶层,否则会导致编译错误**。 <<<@/code/release/define_variable.zig#top-level ::: details 小细节 -为什么是作用域顶层呢?实际上,zig 将一个源码文件看作是一个容器。 +为什么是作用域顶层呢?实际上,Zig 将一个源码文件看作是一个容器。 ::: ## `usingnamespace` 关键字 `usingnamespace` 可以将一个容器中的所有 `pub` 声明混入到当前的容器中。 -例如,可以使用将 `usingnamespace` 将 std 标准库混入到 `main.zig` 这个容器中: +例如,可以使用 `usingnamespace` 将 `std` 标准库混入到 `main.zig` 这个容器中: ```zig const T = struct { @@ -158,7 +158,7 @@ pub fn main() !void { 注意:无法在结构体 `T` 内部直接使用混入的声明,需要使用 `T.debug` 这种方式才可以! -`usingnamespace` 还可以使用 `pub` 关键字进行修饰,用于转发声明,这常用于组织 API 文件和 C import。 +`usingnamespace` 还可以使用 `pub` 关键字进行修饰,用于转发声明,这常用于组织 API 文件和 C 语言的 `import`。 ```zig pub usingnamespace @cImport({ @@ -179,13 +179,13 @@ pub usingnamespace @cImport({ }); ``` -针对以上的引入的头文件,我们可以这样使用 `@This().xcb_generic_event_t` +针对以上引入的头文件,我们可以这样使用 `@This().xcb_generic_event_t`。 > [!IMPORTANT] -> 初次阅读此处困惑是正常的,后面的概念学习完成后此处自通。 +> 初次阅读此处感到困惑是正常的。在学习完后续概念后,此处内容将自然理解。 ## `threadlocal` -变量可以使用 `threadlocal` 修饰,来使得该变量在不同线程中是不同的示例: +变量可以使用 `threadlocal` 修饰符,使得该变量在不同线程中拥有不同的实例: <<<@/code/release/define_variable.zig#threadlocal diff --git a/course/basic/error_handle.md b/course/basic/error_handle.md index f4859778..254734b4 100644 --- a/course/basic/error_handle.md +++ b/course/basic/error_handle.md @@ -4,29 +4,29 @@ outline: deep # 错误处理 -程序会在某些时候因为某些已知或未知的原因出错,有的可以正确处理,有的则很棘手,但我们可以捕获它们并有效地报告给用户。 +程序在某些时候会因为已知或未知的原因出错。有些错误可以正确处理,有些则非常棘手。Zig 提供了机制来捕获这些错误并有效地报告给用户。 :::info 🅿️ 提示 -事实上,目前 zig 的错误处理方案笔者认为是比较简陋的,在 zig 中错误类型很像 `enum`,这导致错误类型无法携带有效的 `payload`,你只能通过错误的名称来获取有效的信息。 +目前 Zig 的错误处理方案相对简洁。错误类型类似于 `enum`,这意味着错误类型无法携带有效的 `payload`(额外数据),你只能通过错误的名称来获取信息。 ::: ## 错误集 -以下代码使用 `error` 关键字定义了两个错误集,分别是 `FileOpenError` 和 `AllocationError`。 +以下代码使用 `error` 关键字定义了两个错误集:`FileOpenError` 和 `AllocationError`。 <<<@/code/release/error_handle.zig#BasicUse -错误集中包含了若干错误,一个错误也可以同时被包含在多个不同的错误集中:例子中的 `OutOfMemory` 错误就同时被包含在了 `FileOpenError` 和 `AllocationError` 里。 +错误集中包含了若干错误。一个错误也可以同时被包含在多个不同的错误集中:例如,示例中的 `OutOfMemory` 错误就同时被包含在 `FileOpenError` 和 `AllocationError` 中。 -错误可以从子集隐式转换到超集,例子中就是将子集 `AllocationError` 通过函数 `foo` 转换到了超集 `FileOpenError`。 +错误可以从子集隐式转换为超集。例如,示例中通过函数 `foo` 将子集 `AllocationError` 转换到了超集 `FileOpenError`。 :::info 🅿️ 提示 -在编译时,zig 会为每个错误名称分配一个大于 0 的整数值,这个值可以通过 `@intFromError` 获取。但不建议过分依赖具体的数值,因为错误与数值的映射关系可能会随源码变动。 +在编译时,Zig 会为每个错误名称分配一个大于 0 的整数值,这个值可以通过 `@intFromError` 获取。但不建议过分依赖具体的数值,因为错误与数值的映射关系可能会随源码变动。 -错误对应的整数值默认使用 `u16` 类型,即最多允许存在 65534 种不同的错误。从 `0.12` 开始,编译时可以通过 `--error-limit [num]` 指定错误的最大数量,这样就会使用能够表示所有错误值的最少位数的整数类型。 +错误对应的整数值默认使用 `u16` 类型,即最多允许存在 65534 种不同的错误。从 `0.12` 版本开始,编译时可以通过 `--error-limit [num]` 指定错误的最大数量,这样就会使用能够表示所有错误值的最少位数的整数类型。 ::: @@ -36,71 +36,71 @@ outline: deep <<<@/code/release/error_handle.zig#JustOneError2 -不过使用这种定义方法未免过于啰嗦,所以 zig 提供了一种简短的写法,两者是等价的: +不过使用这种定义方法未免过于啰嗦,所以 Zig 提供了一种简短的写法,两者是等价的: <<<@/code/release/error_handle.zig#JustOneError1 -[自动推断](#合并和推断错误) 函数返回的错误集时,会经常用到这种写法。 +在[自动推断](#合并和推断错误)函数返回的错误集时,会经常用到这种写法。 ## 全局错误集 -`anyerror` 指的是全局的错误集,它包含编译单元中的所有错误,是所有其他错误集的超集。 +`anyerror` 指的是全局错误集,它包含编译单元中的所有错误,是所有其他错误集的超集。 -任何错误集都可以隐式转换到全局错误集,但反之则不然,从 `anyerror` 到其他错误集的转换需要显式进行,此时就会增加一个语言级断言(language-level assert)要求该错误一定在目标错误集中存在。 +任何错误集都可以隐式转换为全局错误集,但反之则不然。从 `anyerror` 到其他错误集的转换需要显式进行,此时会增加一个语言级断言(language-level assert),要求该错误一定在目标错误集中存在。 ::: warning ⚠️ 警告 -应尽量避免使用 `anyerror`,它相当于放大了错误的范围,因为它会阻止编译器在编译期就检测出可能存在的错误,增加代码出错 debug 的负担。 +应尽量避免使用 `anyerror`,因为它相当于放大了错误的范围,会阻止编译器在编译期就检测出可能存在的错误,从而增加代码调试的负担。 ::: ## 错误联合类型 > [!TIP] -> !!!以上所说的错误类型实际上用的大概不多,但错误联合类型大概是经常使用的。 +> !!!虽然上面介绍的错误类型在实际使用中可能不那么频繁,但错误联合类型却是非常常用的。 -只需要在普通类型的前面增加一个 `!` 就是代表这个类型变成错误联合类型,我们来看一个比较简单的函数: +只需要在普通类型的前面增加一个 `!`,就代表这个类型变成了错误联合类型。我们来看一个比较简单的函数: 以下是一个将英文字符串解析为数字的示例: > [!NOTE] -> 这里代码看不懂没关系,此处代码仅仅用于演示一下联合类型的使用。 +> 这里代码看不懂没关系,此处代码仅用于演示错误联合类型的使用。 <<<@/code/release/error_handle.zig#ConvertEnglishToInteger -注意函数的返回值:`!u64`,这意味着函数返回的是一个 `u64` 或者是一个错误。我们没有在 `!` 左侧指定错误集,因此编译器会自动推导。 +注意函数的返回值:`!u64`。这意味着函数返回的是一个 `u64` 类型的值,或者是一个错误。我们没有在 `!` 左侧指定错误集,因此编译器会自动推导。 -该函数的具体效果取决于我们如何对待返回的错误: +该函数的具体行为取决于我们如何处理返回的错误: -1. 返回错误时我们准备一个默认值 -2. 返回错误时我们想将它向上传递 -3. 确信本次函数执行后肯定不会发生错误,想要无条件的解构它 -4. 针对返回错误集中不同的错误采取不同的处理方式 +1. 返回错误时提供一个默认值。 +2. 返回错误时将其向上传递。 +3. 确信本次函数执行后不会发生错误,并无条件地解构它。 +4. 针对返回错误集中不同的错误采取不同的处理方式。 ### `catch` -`catch` 用于发生错误时提供一个默认值,来看一个例子: +`catch` 用于在发生错误时提供一个默认值。来看一个例子: <<<@/code/release/error_handle.zig#CatchBasic -`number` 必定是一个类型为 `u64` 的值,当发生错误时,会提供默认值 13 给 `number`。 +`number` 必定是一个类型为 `u64` 的值。当发生错误时,`number` 将被赋予默认值 13。 :::info 🅿️ 提示 -`catch` 运算符右侧必须是一个与其左侧函数返回的错误联合类型展开后的类型 (也就是除了错误类型外的类型) 一致,或者是一个 `noreturn`(例如 `panic`) 的语句。 +`catch` 运算符右侧的表达式必须与左侧函数返回的错误联合类型展开后的类型(即除了错误类型外的类型)一致,或者是一个 `noreturn` 语句(例如 `panic`)。 ::: -当然进阶点我们还可以和命名块(named Blocks)功能结合起来,以此来提供进入 `catch` 后执行某些复杂操作以返回值: +当然,更进阶的用法是结合命名块(named Blocks)功能,以便在进入 `catch` 分支后执行某些复杂操作以返回所需的值: <<<@/code/release/error_handle.zig#CatchAdvanced -### try +### `try` -`try` 用于在出现错误时直接向上层返回错误,没错误就正常执行: +`try` 用于在出现错误时直接向上层返回错误;如果没有错误,则正常执行。 > [!TIP] -> 当然,try 也可以等价使用 catch 实现! +> `try` 也可以等价地使用 `catch` 实现! ::: code-group @@ -110,13 +110,13 @@ outline: deep ::: -`try` 会尝试评估联合类型表达式,如果是错误则直接从当前函数返回,否则解构它。 +`try` 会尝试评估错误联合类型表达式。如果表达式结果是错误,则直接从当前函数返回该错误;否则,解构并使用其值。 ::: info 🅿️ 提示 -那么如何假定函数不会返回错误呢? +那么如何断言函数不会返回错误呢? -使用 `unreachable`,这会告诉编译器此次函数执行不会返回错误,`unreachable` 在 `Debug` 和 `ReleaseSafe` 模式下会产生恐慌,在 `ReleaseFast` 和 `ReleaseSmall` 模式下会产生未定义的行为。所以当调试应用程序时,如果函数执行到了这里,那就会发生 `panic`。 +使用 `unreachable`。这会告诉编译器此次函数执行不会返回错误。`unreachable` 在 `Debug` 和 `ReleaseSafe` 模式下会触发恐慌(panic),而在 `ReleaseFast` 和 `ReleaseSmall` 模式下会产生未定义行为。因此,当调试应用程序时,如果函数执行到这里,就会发生 `panic`。 <<<@/code/release/error_handle.zig#AssertNoError @@ -140,7 +140,7 @@ outline: deep ::: -### errdefer +### `errdefer` `errdefer` 可以看作是 `defer` 的一个特殊变体,它用于处理错误,仅在函数作用域返回错误时,才会执行 `errdefer`。 @@ -158,28 +158,28 @@ outline: deep ::: warning -关于 `defer` 和 `errdefer`,需要注意的是如果他们在 `for` 循环的作用域中,那么它们会在 `for` 循环结束前执行 `defer` 和 `errdefer`,在使用该特性时需要谨慎对待。 +关于 `defer` 和 `errdefer`,需要注意的是,如果它们在 `for` 循环的作用域中,那么它们会在 `for` 循环结束前执行。在使用该特性时需要谨慎对待。 ::: ### 合并和推断错误 -你可以通过使用 `||` 运算符将两个错误合并到一起,注意,文档注释会优先使用运算符左侧的注释。 +你可以通过使用 `||` 运算符将两个错误集合并到一起。注意,文档注释会优先使用运算符左侧的注释。 -推断错误集,事实上我们使用的 `!T` 就是推断错误集,我们并未显式声明返回的错误,返回的错误类型将会由 zig 编译器自行推导而出,这是一个很好的特性,但有时也会给我们带来困扰。 +推断错误集:事实上,我们使用的 `!T` 就是推断错误集。我们并未显式声明返回的错误,返回的错误类型将会由 Zig 编译器自行推导而出。这是一个很好的特性,但有时也会给我们带来困扰。 <<<@/code/release/error_handle.zig#ReferError ::: info 🅿️ 提示 -> _当函数返回自动推导的错误集时,相对的这个函数会变成一个类泛型函数(因为这需要编译器在编译时进行推导),因此在执行某些操作时会变得有些不方便,例如获取函数指针或者在不同构建目标之间保持相同的错误集合。_ +> _当函数返回自动推导的错误集时,该函数会变成一个类似泛型函数(因为这需要编译器在编译时进行推导),因此在执行某些操作时会变得有些不方便,例如获取函数指针或者在不同构建目标之间保持相同的错误集合。_ > > _注意,错误集推导和递归并不兼容。_ 上述内容可能不够清晰,用两个例子作为说明: -1. 不同构建目标之间可能存在着专属于架构的错误定义,这使得在不同架构上构建出来的代码的实际错误集并不相同,函数也同理(与 cpu 指令集实现有关)。 -2. 当我们使用自动推导时,推导出的错误集是最小错误集,故可能一个函数被推导出 `ErrorSetOne!type` 和 `ErrorSetTwo!type` 两个错误集,这会使得在递归上出现不兼容。 +1. 不同构建目标之间可能存在专属于架构的错误定义,这使得在不同架构上构建出来的代码的实际错误集并不相同,函数也同理(与 CPU 指令集实现有关)。 +2. 当我们使用自动推导时,推导出的错误集是最小错误集。因此,一个函数可能被推导出 `ErrorSetOne!type` 和 `ErrorSetTwo!type` 两个错误集,这会导致在递归上出现不兼容。 对于上述问题,解决办法是显式声明一个错误集,这会明确告诉编译器返回的错误集合都包含哪些错误。 @@ -189,17 +189,17 @@ outline: deep ## 错误返回跟踪 -zig 本身有着良好的错误返回跟踪(Error Return Trace),能够显示错误到达调用函数途中所经过的所有代码节点。这使得在任何地方使用 `try` 都很实用,并且如果错误一路返回,最终离开了应用程序,我们也很容易能够知道发生了什么。 +Zig 本身具有良好的错误返回跟踪(Error Return Trace)机制,能够显示错误到达调用函数途中所经过的所有代码节点。这使得在任何地方使用 `try` 都很实用,并且如果错误一路返回,最终离开了应用程序,我们也很容易能够知道发生了什么。 > [!WARNING] -> 但需要注意的是,当链接 libc 时,错误返回跟踪不一定是完整的,因为 zig 有时会无法跟踪链接库的堆栈,这点在 windows 平台尤为明显,因为 windows 平台有很多私有的库。 +> 但需要注意的是,当链接 libc 时,错误返回跟踪不一定是完整的,因为 Zig 有时无法跟踪链接库的堆栈。这点在 Windows 平台尤为明显,因为 Windows 平台有很多私有的库。 -请注意,错误返回跟踪并不是堆栈跟踪(Stack Trace),堆栈跟踪中并不会提供控制流相关的信息,无法精确表示错误的传递过程。具体的信息可见 _[这里](https://ziglang.org/documentation/master/#Error-Return-Traces)_。 +请注意,错误返回跟踪并不是堆栈跟踪(Stack Trace)。堆栈跟踪中并不会提供控制流相关的信息,无法精确表示错误的传递过程。具体的信息可见 _[这里](https://ziglang.org/documentation/master/#Error-Return-Traces)_。 -错误返回跟踪生效的几个方式: +错误返回跟踪生效的几种方式: -- 从 `main` 返回一个错误 -- 错误抵达 `catch unreachable`(并且没有覆盖默认的 `panic` 处理函数) +- 从 `main` 函数返回一个错误。 +- 错误抵达 `catch unreachable`(并且没有覆盖默认的 `panic` 处理函数)。 - 使用 `@errorReturnTrace` 函数显式访问。构建时如果没有启用错误返回跟踪功能,则该函数会返回 `null`。 具体的堆栈跟踪实现细节可以看 _[这里](https://ziglang.org/documentation/master/#Implementation-Details)_。 diff --git a/course/basic/optional_type.md b/course/basic/optional_type.md index aa1f37b9..d0a043c9 100644 --- a/course/basic/optional_type.md +++ b/course/basic/optional_type.md @@ -6,21 +6,21 @@ outline: deep ## Overview -在 Zig 中,要在不损害效率的前提下,尽量提高代码安全性,其中一个方案就是可选类型,他的标志是 `?`,`?T`表示它的值是它的值是 `null` 或`T`。 +在 Zig 中,为了在不损害效率的前提下提高代码安全性,可选类型是一个重要的解决方案。它的标志是 `?`,`?T` 表示该类型的值可以是 `null` 或 `T` 类型。 <<<@/code/release/optional_type.zig#basic_type -当然,它一般在指针上发挥作用,而不是整数。 +通常,可选类型在指针上发挥作用,而非整数。 `null`(空引用)是许多运行时异常的根源,甚至被指责为[计算机科学中最严重的错误](https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/)。 -我们可以通过可选类型来规避它。这其实是一种比较保守的做法,它同时兼顾了代码的可读性和运行效率。目前最为激进的应该是 _Rust_,它真的是非常的激进,这增加了程序员在写代码时的心智负担(因为你经常需要和编译期斗智斗勇,但好处大大是减少了你在运行时 _Debug_ 的负担)。相对地,zig 采取了一种折中方案,编译期进行简单的检测,而且检测出来的错误一般很容易纠正;这样的缺点是并不能保证你的运行时是绝对安全的(可选类型仅仅能避免空指针问题,却不能避免悬空指针、迷途指针和野指针等问题)。 +我们可以通过可选类型来规避空引用问题。这是一种相对保守的做法,它同时兼顾了代码的可读性和运行效率。目前,Rust 在这方面更为激进,这增加了程序员在编写代码时的心智负担(因为你需要经常与编译期“斗智斗勇”,但好处是大大减少了运行时调试的负担)。相对而言,Zig 采取了一种折中方案:在编译期进行简单的检测,且检测出的错误通常很容易纠正。然而,这种方案的缺点是不能保证运行时绝对安全(可选类型仅能避免空指针问题,但不能避免悬空指针、迷途指针和野指针等问题)。 -zig 将 `null` 特殊看待,并且保证 `null` 不会被赋值给一个非可选类型变量。 +Zig 对 `null` 有特殊处理,并保证 `null` 不会被赋值给一个非可选类型变量。 ## 和 C 对比 -看看下面代码中两者在处理 `null` 上的区别。(尝试调用 `malloc` 申请一块内存。) +看看下面代码中 Zig 和 C 在处理 `null` 上的区别。(尝试调用 `malloc` 申请一块内存。) ::: code-group @@ -39,7 +39,7 @@ struct Foo *do_a_thing(void) { ::: -在这里,我们通过使用 `orelse` 解构了可选类型,保证 `ptr` 是一个合法可用的指针,否则直接返回 `null`。(这看起来比 C 更加明了且易用) +在这里,我们通过使用 `orelse` 解构了可选类型,确保 `ptr` 是一个合法可用的指针;如果 `malloc` 返回 `null`,则直接返回 `null`。(这看起来比 C 更加明了且易用。) 再看下例: @@ -59,19 +59,19 @@ void do_a_thing(struct Foo *foo) { ::: -看起来区别不大,只是在 `if` 语法上有点不同,`if` 块中保证 `foo` 不为 `null`。 +看起来区别不大,只是在 `if` 语法上有所不同。`if` 块中保证 `foo` 不为 `null`。 -当然,在 C 中,你可以用 `__attribute__((nonnull))` 来告诉 C 编译器这里不不可能是 `null`,但其使用成本明显比 Zig 高。 +当然,在 C 中,你可以使用 `__attribute__((nonnull))` 来告诉 C 编译器这里不可能为 `null`,但其使用成本明显比 Zig 高。 ## 编译期反射访问可选类型 > [!WARNING] > 该部分内容需要编译期反射的知识,可以选择暂时跳过! -我们也可以通过编译期函数来实现反射进而访问可选类型: +我们也可以通过编译期函数来实现反射,进而访问可选类型: <<<@/code/release/optional_type.zig#comptime_access_optional_type ## 可选指针 -可选指针会保证和指针有一样的大小,`null` 会被视作地址 0 考虑! +可选指针会保证与普通指针具有相同的大小,`null` 会被视为地址 0。 diff --git a/course/basic/process_control/decision.md b/course/basic/process_control/decision.md index 03d12d18..a891378b 100644 --- a/course/basic/process_control/decision.md +++ b/course/basic/process_control/decision.md @@ -2,11 +2,11 @@ outline: deep --- -# 条件 +# 条件控制 -> 在 zig 中,`if` 这个语法的作用可就大了! +> 在 Zig 中,`if` 语句的功能非常强大! -像基础的 `if`,`if else`,`else if` 我们就不说了,直接看例子: +基本的 `if`、`if else` 和 `else if` 语句在此不再赘述,直接看示例: ::: code-group @@ -18,7 +18,7 @@ outline: deep ## 匹配枚举类型 -`if` 可以用于枚举类型的匹配,判断是否相等: +`if` 语句可以用于枚举类型的匹配,判断是否相等: ::: code-group @@ -30,7 +30,7 @@ outline: deep ## 三元表达式 -zig 中的三元表达式是通过 `if else` 来实现的: +Zig 中的三元表达式是通过 `if else` 语句来实现的: ::: code-group @@ -42,21 +42,21 @@ zig 中的三元表达式是通过 `if else` 来实现的: ## 高级用法 -以下内容涉及到了[联合类型](/basic/union)和[可选类型](/basic/optional_type),你可以在阅读完这两章节后再回来学习。 +以下内容涉及[联合类型](/basic/union)和[可选类型](/basic/optional_type)。你可以在阅读完这两章节后再回来学习。 ### 解构可选类型 -事实上,解构可选类型操作很简单: +解构可选类型操作非常简单: <<<@/code/release/decision.zig#destruct_optional -以上代码的 `else` 分支并非必要,我们解构后获得 `real_b` 就是 `u32` 类型,但是注意我们获得的捕获是只读的! +以上代码中的 `else` 分支并非必需。解构后,我们获得的 `real_b` 是 `u32` 类型,但请注意,我们捕获的值是只读的! -如果我们想操纵值的内容,可以选择捕获对应的指针: +如果我们想修改值的内容,可以选择捕获对应的指针: <<<@/code/release/decision.zig#capture_optional_pointer -`*` 运算符就表示我们选择捕获这个值对应的指针,因此我们可以通过操控指针来修改其值。 +`*` 运算符表示我们选择捕获这个值对应的指针,因此我们可以通过操作指针来修改其值。 ### 解构错误联合类型 @@ -64,7 +64,7 @@ zig 中的三元表达式是通过 `if else` 来实现的: <<<@/code/release/decision.zig#destruct_error_union -以上代码中 `value` 类型为 `u32`,else 分支捕获的是错误,即 `err` 的类型将会是 `anyerror`,这是由我们之前显式声明的,否则将会是由编译器推导的。 +以上代码中 `value` 的类型为 `u32`。`else` 分支捕获的是错误,即 `err` 的类型将是 `anyerror`(这是由我们之前显式声明的,否则将由编译器推导)。 为了仅检测错误,我们可以这样做: @@ -76,18 +76,18 @@ zig 中的三元表达式是通过 `if else` 来实现的: ::: warning -那么 if 是如何解构 **错误联合可选类型** 的呢? +那么 `if` 是如何解构**错误联合可选类型**的呢? -答案是 if 会先尝试解构**错误联合类型**,再解构**可选类型**: +答案是 `if` 会先尝试解构**错误联合类型**,再解构**可选类型**: <<<@/code/release/decision.zig#destruct_error_optional_union -以上代码中的 `optional_value` 就是可选类型 `?u32`,我们可以在内部继续使用 if 来解构它。 +以上代码中的 `optional_value` 是可选类型 `?u32`。我们可以在内部继续使用 `if` 来解构它。 在错误联合可选类型上也可以使用指针捕获: <<<@/code/release/decision.zig#destruct_error_optional_union_pointer -以上代码中,`*optional_value` 捕获的是可选类型的指针,我们在内部尝试解引用后再一次捕获指针来进行操作。 +以上代码中,`*optional_value` 捕获的是可选类型的指针。我们在内部尝试解引用后,再次捕获指针来进行操作。 ::: diff --git a/course/basic/process_control/loop.md b/course/basic/process_control/loop.md index 747e2967..aa098c97 100644 --- a/course/basic/process_control/loop.md +++ b/course/basic/process_control/loop.md @@ -42,23 +42,23 @@ for 循环是另一种循环处理方式,主要用于迭代数组和切片。 ### 多目标迭代 -当然,你也可以同时迭代多个目标(数组或者切片),当然这两个迭代的目标要长度一致防止出现未定义的行为。 +当然,你也可以同时迭代多个目标(数组或切片)。需要注意的是,这些迭代目标的长度必须一致,以避免出现未定义行为。 <<<@/code/release/loop.zig#multi_for ### 作为表达式使用 -当然,for 也可以作为表达式来使用,它的行为和 [while](#作为表达式使用) 一模一样。 +`for` 循环也可以作为表达式使用,其行为与 [while](#作为表达式使用) 类似。 <<<@/code/release/loop.zig#for_as_expression ### 标记 -`continue` 的效果类似于 `goto`,并不推荐使用,因为它和 `goto` 一样难以把控,以下示例中,outer 就是标记。 +`continue` 的效果类似于 `goto`,不推荐过度使用,因为它和 `goto` 一样难以控制。在以下示例中,`outer` 是一个标签。 -`break` 的效果就是在标记处的 while 执行 break 操作,当然,同样不推荐使用。 +`break` 的效果是在带有标签的循环处执行 `break` 操作。同样,不推荐过度使用。 -它们只会增加你的代码复杂性,非必要不使用! +它们会增加代码的复杂性,非必要不使用。 ::: code-group @@ -70,9 +70,9 @@ for 循环是另一种循环处理方式,主要用于迭代数组和切片。 ### 内联 `inline` -`inline` 关键字会将 for 循环展开,这允许代码执行一些一些仅在编译时有效的操作。 +`inline` 关键字会将 `for` 循环展开,这使得代码能够执行一些仅在编译时有效的操作。 -需要注意,内联 for 循环要求迭代的值和捕获的值均是编译期已知的。 +需要注意的是,内联 `for` 循环要求迭代的值和捕获的值都必须在编译期已知。 :::code-group @@ -84,7 +84,7 @@ for 循环是另一种循环处理方式,主要用于迭代数组和切片。 ## `while` -while 循环用于重复执行表达式,直到某些条件不再成立。 +`while` 循环用于重复执行代码块,直到某个条件不再满足。 基本使用: @@ -98,7 +98,7 @@ while 循环用于重复执行表达式,直到某些条件不再成立。 ### `continue` 表达式 -while 还支持一个被称为 continue 表达式的方法来便于我们控制循环,其内部可以是一个语句或者是一个作用域(`{}` 包裹) +`while` 循环还支持一个被称为 `continue` 表达式的机制,以便于我们控制循环。其内部可以是一个语句或一个代码块(由 `{}` 包裹)。 :::code-group @@ -110,23 +110,19 @@ while 还支持一个被称为 continue 表达式的方法来便于我们控制 ### 作为表达式使用 -zig 还允许我们将 while 作为表达式来使用,此时需要搭配 `else` 和 `break`。 +Zig 还允许我们将 `while` 循环作为表达式使用,此时需要搭配 `else` 和 `break` 语句。 -这里的 `else` 是当 while 循环结束并且没有经过 `break` 返回值时触发,而 `break` 则类似于 return,可以在 while 内部返回值。 +这里的 `else` 分支会在 `while` 循环正常结束(即没有通过 `break` 语句退出)时触发。而 `break` 语句则类似于 `return`,可以在 `while` 循环内部返回值。 <<<@/code/release/loop.zig#while_as_expression ### 标记 -`continue` 的效果类似于 `goto`,并不推荐使用,因为它和 `goto` 一样难以把控,以下示例中,outer 就是标记。 +`continue` 的效果类似于 `goto`,不推荐过度使用,因为它和 `goto` 一样难以控制。在以下示例中,`outer` 是一个标签。 -`break` 的效果就是在标记处的 while 执行 break 操作,当然,同样不推荐使用。 +`break` 的效果是在带有标签的 `while` 循环处执行 `break` 操作。同样,不推荐过度使用。 -::: info 🅿️ 提示 - -它们只会增加你的代码复杂性,非必要不使用! - -::: +它们会增加代码的复杂性,非必要不使用。 :::code-group @@ -138,7 +134,7 @@ zig 还允许我们将 while 作为表达式来使用,此时需要搭配 `else ### 内联 `inline` -`inline` 关键字会将 while 循环展开,这允许代码执行一些一些仅在编译时有效的操作。 +`inline` 关键字会将 `while` 循环展开,这使得代码能够执行一些仅在编译时有效的操作。 :::code-group @@ -150,16 +146,16 @@ zig 还允许我们将 while 作为表达式来使用,此时需要搭配 `else :::info 🅿️ 提示 -建议以下情况使用内联 while: +建议在以下情况使用内联 `while`: -- 需要在编译期执行循环 -- 你确定展开后会代码效率会更高 +- 需要在编译期执行循环。 +- 你确定展开后代码效率会更高。 ::: ### 解构可选类型 -像 `if` 一样,`while` 也会尝试解构可选类型,并在遇到 `null` 时终止循环。 +与 `if` 类似,`while` 循环也会尝试解构可选类型,并在遇到 `null` 时终止循环。 :::code-group @@ -169,11 +165,11 @@ zig 还允许我们将 while 作为表达式来使用,此时需要搭配 `else ::: -当 `|x|` 语法出现在 `while` 表达式上,`while` 条件必须是可选类型。 +当 `|x|` 语法出现在 `while` 表达式中时,`while` 的条件必须是可选类型。 ### 解构错误联合类型 -和上面类似,同样可以解构错误联合类型,`while` 分别会捕获错误和有效负载,当错误发生时,转到 `else` 分支执行,并退出: +与上述类似,`while` 循环也可以解构错误联合类型。`while` 会分别捕获错误和有效负载。当错误发生时,程序会转到 `else` 分支执行,并退出循环: :::code-group @@ -183,4 +179,4 @@ zig 还允许我们将 while 作为表达式来使用,此时需要搭配 `else ::: -当 `else |x|` 时语法出现在 `while` 表达式上,`while` 条件必须是错误联合类型。 +当 `else |x|` 语法出现在 `while` 表达式中时,`while` 的条件必须是错误联合类型。 diff --git a/course/basic/process_control/switch.md b/course/basic/process_control/switch.md index 36449540..1bcbf94d 100644 --- a/course/basic/process_control/switch.md +++ b/course/basic/process_control/switch.md @@ -2,9 +2,9 @@ outline: deep --- -# Switch +# Switch 语句 -switch 语句可以进行匹配,并且 switch 匹配不能出现遗漏匹配的情况。 +`switch` 语句用于进行多分支匹配,并且要求覆盖所有可能的匹配情况。 ## 基本使用 @@ -18,19 +18,19 @@ switch 语句可以进行匹配,并且 switch 匹配不能出现遗漏匹配 :::info 🅿️ 提示 -switch 的匹配必须要穷尽所有,或者具有 `else` 分支! +`switch` 语句的匹配必须穷尽所有可能的分支,或者包含一个 `else` 分支来处理未匹配的情况! ::: ## 进阶使用 -switch 还支持用 `,` 分割的多匹配、`...` 的范围选择符,类似循环中的 `tag` 语法、编译期表达式,以下是演示: +`switch` 还支持使用 `,` 分割的多匹配、`...` 范围选择符、类似循环中的 `tag` 语法以及编译期表达式。以下是演示: <<<@/code/release/switch.zig#advanced [default] ### 作为表达式使用 -在 zig 中,还可以将 `switch` 作为表达式来使用: +在 Zig 中,`switch` 语句还可以作为表达式使用: ::: code-group @@ -42,45 +42,45 @@ switch 还支持用 `,` 分割的多匹配、`...` 的范围选择符,类似 ### 捕获 `Tag Union` -我们还可以使用 switch 对标记联合类型进行捕获操作,对字段值的修改可以通过在捕获变量名称之前放置 `*` 并将其转换为指针来完成: +我们还可以使用 `switch` 对标记联合类型进行捕获操作。要修改字段值,可以在捕获变量名称之前放置 `*` 并将其转换为指针: <<<@/code/release/switch.zig#catch_tag_union ### 匹配和推断枚举 -在使用 switch 匹配时,也可以继续对枚举类型进行自动推断: +在使用 `switch` 匹配时,也可以继续对枚举类型进行自动推断: <<<@/code/release/switch.zig#auto_refer -### 内联 switch +### 内联 `switch` -switch 的分支可以标记为 `inline` 来要求编译器生成该分支对应的所有可能分支: +`switch` 的分支可以标记为 `inline`,以要求编译器生成该分支对应的所有可能情况: <<<@/code/release/switch.zig#isFieldOptional -`inline else` 可以展开所有的 else 分支,这样做的好处是,允许编译器在编译时显式生成所有分支,这样在编译时可以检查分支是否均能被正确地处理: +`inline else` 可以展开所有的 `else` 分支。这样做的好处是,允许编译器在编译时显式生成所有分支,从而在编译时检查分支是否都能被正确处理: <<<@/code/release/switch.zig#withSwitch -当使用 `inline else` 捕获 tag union 时,可以额外捕获 tag 和对应的 value: +当使用 `inline else` 捕获 `tag union` 时,可以额外捕获标签(tag)和对应的值(value): <<<@/code/release/switch.zig#catch_tag_union_value -### labeled switch +### 标签化 `switch`(Labeled Switch) -这是 `0.14.0` 引入的新特性,当 `switch` 语句带有标签时,它可以被 `break` 或 `continue` 语句引用。`break` 将从 `switch` 语句返回一个值。 +这是 `0.14.0` 版本引入的新特性。当 `switch` 语句带有标签时,它可以被 `break` 或 `continue` 语句引用。`break` 将从 `switch` 语句返回一个值。 针对 `switch` 的 `continue` 语句必须带有一个操作数。当执行时,它会跳转到匹配的分支,就像用 `continue` 的操作数替换了初始 `switch` 值后重新执行 `switch` 一样。 -例如以下两段代码的写法是一样的: +例如,以下两段代码的写法是等价的: <<<@/code/release/switch.zig#labeled_switch_1 <<<@/code/release/switch.zig#labeled_switch_2 -这可以提高(例如)状态机的清晰度,其中 `continue :sw .next_state` 这样的语法是明确的、清楚的,并且可以立即理解。 +这可以提高(例如)状态机的清晰度,其中 `continue :sw .next_state` 这样的语法是明确、清晰且易于理解的。 -不过,这个设计的目的是处理对数组中每个元素进行 `switch` 判断的情况,在这种情况下,使用单个 `switch` 语句可以提高代码的清晰度和性能: +这个设计的目的是处理对数组中每个元素进行 `switch` 判断的情况。在这种情况下,使用单个 `switch` 语句可以提高代码的清晰度和性能: <<<@/code/release/switch.zig#vm diff --git a/course/basic/process_control/unreachable.md b/course/basic/process_control/unreachable.md index f8b52252..9bf6a22c 100644 --- a/course/basic/process_control/unreachable.md +++ b/course/basic/process_control/unreachable.md @@ -2,10 +2,10 @@ outline: deep --- -# unreachable +# `unreachable` 关键字 -在 `Debug` 和 `ReleaseSafe` 模式下,`unreachable` 会调用 `panic` ,并显示消息达到 unreachable code。 +在 `Debug` 和 `ReleaseSafe` 模式下,`unreachable` 会触发 `panic`,并报告“不可达代码”错误。 -在 `ReleaseFast` 和 `ReleaseSmall` 模式下,编译器假设永远不会执行到 `unreachable` 来对代码进行优化。 +在 `ReleaseFast` 和 `ReleaseSmall` 模式下,编译器会假定永远不会执行到 `unreachable` 处,从而对代码进行优化。 <<<@/code/release/unreachable.zig#unreachable diff --git a/course/basic/union.md b/course/basic/union.md index 1d843973..87d44342 100644 --- a/course/basic/union.md +++ b/course/basic/union.md @@ -4,11 +4,11 @@ outline: deep # 联合类型 -联合类型 (union),它实际上是用户定义的一种特殊的类型,划分出一块内存空间用来存储多种类型,但同一时间只能存储一个类型。 +联合类型(union)是一种特殊的类型,它在内存中划分出一块空间,用于存储多种不同类型的值,但在任何给定时间点,只能存储其中一种类型的值。 ## 基本使用 -联合类型的基本使用: +联合类型的基本使用示例如下: ::: code-group @@ -20,11 +20,11 @@ outline: deep :::info 🅿️ 提示 -需要注意的是,zig 不保证普通联合类型在内存中的表现形式!如果有需要,可以使用 `extern union` 或者 `packed union` 来保证它遵守 c 的规则。 +需要注意的是,Zig 不保证普通联合类型在内存中的具体表现形式。如果需要确保其内存布局与 C 兼容,可以使用 `extern union` 或 `packed union`。 ::: -如果要初始化一个在编译期已知的字段名的联合类型,可以使用 [`@unionInit`](https://ziglang.org/documentation/master/#unionInit): +如果要初始化一个在编译期已知字段名的联合类型,可以使用 [`@unionInit`](https://ziglang.org/documentation/master/#unionInit) 内建函数: ```zig @unionInit( @@ -36,17 +36,17 @@ outline: deep <<<@/code/release/union.zig#union_init -## 标记联合 +## 标记联合(Tagged Union) -联合类型可以在定义时使用枚举进行标记,并且可以通过 `@as` 函数将联合类型直接看作声明的枚举来使用(或比较)。 +联合类型在定义时可以使用枚举进行标记,并且可以通过 `@as` 函数将联合类型直接视为声明的枚举来使用或比较。 -换种说法,`union` 是普通的联合类型,它可以存储多种值,但它无法跟踪当前值的类型。而`tag union` 则在 `union` 的基础上可以跟踪当前值的类型,更加安全。 +换句话说,普通的 `union` 可以存储多种值,但无法跟踪当前存储的是哪种类型。而**标记联合**(`tag union`)则在 `union` 的基础上增加了类型跟踪能力,使其更加安全和易用。 ::: info 🅿️ 提示 -简单来说,就是标记联合可以辨别当前存储的类型,易于使用。 +简单来说,标记联合可以明确辨别当前存储的类型,使用起来更方便。 -而普通的联合类型在 `ReleaseSmall` 和 `ReleaseFast` 的构建模式下,将无法检测出普通的联合类型的读取错误,例如将一个 `u64` 存储在一个 `union` 中,然后尝试将其读取为一个 `f64`,在程序员的角度看是非法的,但运行确是正常的! +而普通联合类型在 `ReleaseSmall` 和 `ReleaseFast` 构建模式下,将无法检测出错误的读取行为。例如,将一个 `u64` 存储在一个 `union` 中,然后尝试将其读取为一个 `f64`,这在程序员看来是非法的,但在这些构建模式下运行时却可能不会报错! ::: @@ -58,7 +58,7 @@ outline: deep ::: -如果要修改实际的载荷(即标记联合中的值),你可以使用 `*` 语法捕获指针类型: +如果要修改实际的载荷(即标记联合中的值),可以使用 `*` 语法捕获指针类型: ::: code-group @@ -68,7 +68,7 @@ outline: deep ::: -还支持使用 [`@tagName`](https://ziglang.org/documentation/master/#tagName) 来获取到对应的 name(返回的是一个 comptime 的 `[:0]const u8`,也就是字符串): +还支持使用 [`@tagName`](https://ziglang.org/documentation/master/#tagName) 来获取当前活跃字段的名称(返回一个编译期常量 `[:0]const u8`,即字符串): <<<@/code/release/union.zig#tag_name @@ -80,16 +80,16 @@ outline: deep ## 自动推断 -zig 也支持自动推断联合类型: +Zig 也支持自动推断联合类型: <<<@/code/release/union.zig#auto_infer ## `extern union` -`extern union` 保证内存布局与目标 C ABI 兼容。 +`extern union` 保证其内存布局与目标 C ABI 兼容。 -具体可以见 [`extern struct`](advanced_type/struct.md#extern)。 +具体用法请参见 [`extern struct`](advanced_type/struct.md#extern) 部分。 ## `packed union` -`packed union` 保证内存布局和声明顺序相同并且尽量紧凑,具体见 [`extern struct`](advanced_type/struct.md#packed)。 +`packed union` 保证其内存布局与声明顺序相同,并且尽可能紧凑。具体用法请参见 [`packed struct`](advanced_type/struct.md#packed) 部分。 diff --git a/course/basic/zero-type.md b/course/basic/zero-type.md index b2e1b509..556210e8 100644 --- a/course/basic/zero-type.md +++ b/course/basic/zero-type.md @@ -2,44 +2,46 @@ outline: deep --- -# 零位类型 +# 零大小类型 -在 zig 中,有一些类型是特殊的零位类型(**Zero Type**),它们的大小是 0 bit。 +在 Zig 中,有一些特殊的类型被称为**零大小类型**(Zero-Sized Types),它们在内存中不占用任何空间(0 bit)。 -它们的特点是,涉及到它们的值不会出现在构建结果中(0 bit 不占任何空间)。 +这些类型的特点是,它们的值不会出现在最终的构建结果中,因为它们不占用任何内存空间。 ## `void` -`void` 是很明显的**零位类型**,常用于函数无返回值。 +`void` 是一个典型的**零大小类型**,常用于表示函数没有返回值。 -但它不止这一种用法,还可以用来初始化泛型实例,例如 `std.AutoHashMap`: +除了作为函数返回值,`void` 还可以用于初始化泛型实例,例如 `std.AutoHashMap`: ```zig var map = std.AutoHashMap(i32, void).init(std.testing.allocator); ``` -这样就会获得一个 `i32` 的 set,尽管可以使用其他方式来实现集合功能,但这样子实现效果内存占用会更少(因为相当于不存在 value)。 +这样可以得到一个 `i32` 类型的集合(set)。尽管可以使用其他方式实现集合功能,但这种方法可以显著减少内存占用,因为它不需要存储实际的值。 ## 整数 -[整数](../basic/basic_type/number.md) 声明可以使用 `u0` 和 `i0` 来声明**零位整数类型**,它们的大小也是 0 bit。 +[整数](../basic/basic_type/number.md) 声明可以使用 `u0` 和 `i0` 来声明**零大小整数类型**,它们的大小也是 0 bit。 ## 数组和切片 -[数组](../basic/advanced_type/array.md) 和 [切片](../basic/advanced_type/slice.md) 的长度为 0 时,就是**零位类型**。 +[数组](../basic/advanced_type/array.md) 和 [切片](../basic/advanced_type/slice.md) 当其长度为 0 时,被视为**零大小类型**。 -另外,如果它们的元素类型是零位类型,则它们必定是**零位类型**,此时与数组(切片)长度无关。 +此外,如果数组或切片的元素类型本身就是零大小类型,那么无论其长度如何,该数组或切片都将是**零大小类型**。 ## 枚举 -只有一个值的 [枚举](../basic/advanced_type/enum.md),也是**零位类型**。 +只有一个成员的 [枚举](../basic/advanced_type/enum.md) 也是**零大小类型**。 ## 结构体 -[结构体](../basic/advanced_type/struct.md) 为空或者字段均为零位类型时,此时结构体也是**零位类型**。 +[结构体](../basic/advanced_type/struct.md) 当其为空,或者所有字段都是零大小类型时,该结构体也是**零大小类型**。 -例如,`const zero = struct {};` 就是一个零位类型,它的大小为 0。 +例如,`const zero = struct {};` 就是一个零大小类型,其大小为 0。 ## 联合类型 -仅具有一种可能类型(且该类型是零位类型)的 [联合类型](../basic/union.md) 也是零位类型。 +## 联合类型 + +仅包含一种可能类型(且该类型是零大小类型)的 [联合类型](../basic/union.md) 也是零大小类型。 diff --git a/course/engineering/build-system.md b/course/engineering/build-system.md index f0522a4d..f17c6689 100644 --- a/course/engineering/build-system.md +++ b/course/engineering/build-system.md @@ -19,7 +19,7 @@ Zig 使用 `build.zig` 文件来描述一个项目的构建步骤。 这样的好处很明显,表达能力更强,开发者只需要使用同一门语言即可进行项目构建,减轻了用户心智。 -一个典型的构建文件如下: +一个典型的构建文件如下。 <<<@/code/release/build_system/basic/build.zig @@ -31,7 +31,7 @@ Zig 使用 `build.zig` 文件来描述一个项目的构建步骤。 > [!TIP] > 第一次接触 Zig 的构建流程,可能会觉得复杂,尤其是构建 Step 的依赖关系,但这是为了后续并发编译作基础。 > -> 如果没有 `build_runner.zig` ,让开发者自己去处理并发编译,将会是件繁琐且容易出错的事情。 +> 如果没有 `build_runner.zig` ,让开发者自己去处理并发编译,将会非常繁琐且容易出错。 `Step` 会在下一小节中会重点讲述,这里介绍一下上面这个构建文件的其他部分: @@ -101,7 +101,7 @@ zig 提供了四种构建模式(**Build Mode**): :::details 关于 Debug 不可复现的原因 -关于为什么 Debug 是不可复现的,zig 官方手册并未给出具体说明,以下内容为询问社区获得: +关于为什么 Debug 是不可复现的,zig 官方手册并未给出具体说明,根据社区的讨论: 在 Debug 构建模式下,编译器会添加一些随机因素进入到程序中(例如内存结构不同),所以任何没有明确说明内存布局的容器在 Debug 构建下可能会有所不同,这便于我们在 Debug 模式下快速暴露某些错误。 @@ -234,7 +234,7 @@ zig 本身提供了一个实验性的文档生成器,它支持搜索查询, ### 执行外部命令 -zig 的构建系统还允许我们执行一些额外的命令,录入根据 json 生成某些特定的文件(例如 zig 源代码),构建其他的编程语言(不只是 C / C++),如 Golang、Rust、前端项目构建等等! +zig 的构建系统还允许我们执行一些额外的命令,例如根据 json 生成某些特定的文件(例如 zig 源代码),构建其他的编程语言(不只是 C / C++),如 Golang、Rust、前端项目构建等等! 例如我们可以让 zig 在构建时调用系统的 sh 来输出 hello 并使用 `@embedFile` 传递给包: diff --git a/course/engineering/package_management.md b/course/engineering/package_management.md index 485e05c5..61033983 100644 --- a/course/engineering/package_management.md +++ b/course/engineering/package_management.md @@ -4,7 +4,7 @@ outline: deep # 包管理 -随着 `0.11` 的发布,zig 终于迎来了一个正式的官方包管理器,此前已知是通过第三方包管理器下载并处理包。 +随着 `0.11` 的发布,zig 终于迎来了一个正式的官方包管理器,此前需要通过第三方包管理器下载并处理包。 zig 当前并没有一个中心化存储库,包可以来自任何来源,无论是本地还是网络上。 @@ -20,7 +20,7 @@ zig 当前并没有一个中心化存储库,包可以来自任何来源,无 - `name`:当前你所开发的包的名字 - `version`:包的版本,使用 [Semantic Version](https://semver.org/)。 -- `fingerprint`: 该值为效验和,它与包的名字有关,使用 `zig build` 时会告诉你应该填什么。 +- `fingerprint`: 该值为校验和,它与包的名字有关,使用 `zig build` 时会告诉你应该填什么。 - `dependencies`:依赖项,内部是一连串的匿名结构体,字段 `dep_name` 是依赖包的名字, `url` 是源代码地址, @@ -43,7 +43,7 @@ zig 当前并没有一个中心化存储库,包可以来自任何来源,无 而若是想要离线使用本地包时则是先下载源码包并直接使用绝对或相对路径导入,例如在下载完包之后放在项目的 deps 目录下,那么使用本地包的格式为: -`./deps/tunk.tar.gz` +`./deps/trunk.tar.gz` ::: diff --git a/course/engineering/style_guide.md b/course/engineering/style_guide.md index bef0155e..e2534175 100644 --- a/course/engineering/style_guide.md +++ b/course/engineering/style_guide.md @@ -20,7 +20,7 @@ outline: deep ## 命名 -简单来说,使用 **驼峰命名法**、**TitleCase 命名法**、**蛇形命名法** +简单来说,分别使用 **驼峰命名法**、**TitleCase 命名法**、**蛇形命名法**。 - 类型声明使用 _TitleCase 命名法_(除非是一个 0 字段的 `struct`,此时它被视为一个命名空间,应使用 _蛇形命名法_) - 如果 `x` 是可以被调用的,并且它返回一个类型,那么使用 _TitleCase 命名法_ diff --git a/course/engineering/unit-test.md b/course/engineering/unit-test.md index ffa1bbc6..d45c7daf 100644 --- a/course/engineering/unit-test.md +++ b/course/engineering/unit-test.md @@ -4,11 +4,13 @@ outline: deep # 单元测试 -> 在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试来源请求,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。 +> 在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。 +> +> ——维基百科 ## 基本使用 -在 zig 中,单元测试的是实现非常简单,只需要使用 `test` 关键字 + 字符串(测试名字,一般填测试的用途)+ 块即可。 +在 zig 中,单元测试的实现非常简单,只需要使用 `test` 关键字 + 字符串(测试名字,一般填测试的用途)+ 块即可。 <<<@/code/release/unit_test.zig#Basic diff --git a/course/environment/editor.md b/course/environment/editor.md index ee735d4a..12a3d981 100644 --- a/course/environment/editor.md +++ b/course/environment/editor.md @@ -10,9 +10,9 @@ outline: deep 官网地址:[https://code.visualstudio.com/](https://code.visualstudio.com/) -> Visual Studio Code 是一款由微软开发且跨平台的免费源代码编辑器。该软件以扩展的方式支持语法高亮、代码自动补全、代码重构功能,并且内置了命令行工具和 Git 版本控制系统。用户可以更改主题和键盘快捷方式实现个性化设置,也可以通过内置的扩展程序商店安装其他扩展以拓展软件功能。 +> Visual Studio Code 是一款由微软开发且跨平台的免费源代码编辑器。该软件通过扩展支持语法高亮、代码自动补全、代码重构等功能,并内置了命令行工具和 Git 版本控制系统。用户可以更改主题和键盘快捷方式实现个性化设置,也可以通过内置的扩展程序商店安装其他扩展以拓展软件功能。 -目前最轻量且生态丰富的编辑器,微软出品,zig 官方为其开发了插件,仅需要安装 [`Zig Language`](https://marketplace.visualstudio.com/items?itemName=ziglang.vscode-zig)这个插件即可,在初次初始化时会推荐安装 _language server_,确认即可! +作为目前最轻量且生态最丰富的编辑器之一,由微软出品。Zig 官方为其开发了 [`Zig Language`](https://marketplace.visualstudio.com/items?itemName=ziglang.vscode-zig) 插件,安装即可使用。初次使用时,插件会推荐安装 language server,确认即可。 ![vscode-zig](/picture/basic/vscode-zig.png){data-zoomable} @@ -20,11 +20,11 @@ outline: deep 官网地址:[`https://zed.dev/`](https://zed.dev/) -> ZED 是新一代的代码编辑器,使用 AI 增强人的开发速度。 +> ZED 是一款新一代代码编辑器,旨在通过 AI 提升开发效率。 -这是近年来比较热门的编辑器,使用 rust 编写,并支持插件系统,颜值还很不错,同时其提出了一种 zed AI 的体系,用于接入各种 AI 模型,提供高效的使用体验。 +这款近年来备受关注的编辑器使用 Rust 编写,拥有不错的颜值和插件系统。它还引入了 Zed AI 体系,可以接入各种 AI 模型,提供更高效的编码体验。 -Zig 扩展安装方式:在主界面按下 `Ctrl + Shift + p`,在呼出的命令栏中输入 extension,选择 `zed: extensions`,进入后搜索 zig,点击右侧的 `Install` 即可! +Zig 扩展安装方式:在主界面按下 `Ctrl + Shift + P`,在弹出的命令栏中输入 `extension`,选择 `zed: extensions`,然后搜索 `zig` 并点击右侧的 `Install` 即可完成安装。 ![zed-zig](/picture/basic/zed-zig.png){data-zoomable} @@ -34,13 +34,13 @@ Vim:[https://github.com/vim/vim](https://github.com/vim/vim) Neovim:[https://github.com/neovim/neovim](https://github.com/neovim/neovim) -古老的编辑器之一,被誉为“编辑器之神”! +历史悠久的编辑器,被誉为“编辑器之神”。 -推荐安装插件 [zig.vim](https://github.com/ziglang/zig.vim),由官方维护,提供了基本的语法解析 +推荐安装由官方维护的 [zig.vim](https://github.com/ziglang/zig.vim) 插件,它提供了基础的语法解析功能。 ::: details zig.vim 配置小细节 -推荐关闭 vim / neovim 的保存自动格式化功能(默认开启): +建议关闭 vim / neovim 保存时自动格式化的功能(默认开启): ```sh # for vim @@ -52,19 +52,19 @@ vim.g.zig_fmt_autosave = false ::: -Neovim 使用内置的 lsp(大多数用户选择)支持的话,推荐使用插件 [zig-lamp](https://github.com/jinzhongjia/zig-lamp),该插件支持自动安装和配置 zls,并且支持可视化管理 `build.zig.zon` 文件! +如果使用 Neovim 内置的 LSP(大多数用户的选择),推荐使用 [zig-lamp](https://github.com/jinzhongjia/zig-lamp) 插件。该插件支持自动安装和配置 zls,并提供了可视化管理 `build.zig.zon` 文件的功能。 -Neovim 使用 `coc.nvim` 作为 _language server_,则推荐使用 [**coc-zig**](https://github.com/UltiRequiem/coc-zig),会自动下载最新的 zls 并配置好。 +如果使用 `coc.nvim` 作为 language server,则推荐 [**coc-zig**](https://github.com/UltiRequiem/coc-zig) 插件,它会自动下载并配置好最新的 zls。 ![nvim-zig](/picture/basic/nvim-zig.png) ## Emacs -如果说 Vim 是编辑器之神,那么 Emacs 就是神的编辑器! +如果说 Vim 是“编辑器之神”,那么 Emacs 就是“神的编辑器”。 -Zig 官方维护了 Emacs 的插件 [zig-mode](https://github.com/ziglang/zig-mode),参照页面配置即可。 +Zig 官方维护了 Emacs 的 [zig-mode](https://github.com/ziglang/zig-mode) 插件,参照其说明页面配置即可。 -推荐使用 Emacs 28 版本新引入的 [eglot](https://www.gnu.org/software/emacs/manual/html_mono/eglot.html) 作为 LSP 客户端。 +推荐使用 Emacs 28 新引入的 [eglot](https://www.gnu.org/software/emacs/manual/html_mono/eglot.html) 作为 LSP 客户端。 ![emacs-zig](/picture/basic/emacs-zig.png){data-zoomable} @@ -74,29 +74,29 @@ Zig 官方维护了 Emacs 的插件 [zig-mode](https://github.com/ziglang/zig-mo > Microsoft Visual Studio 是微软公司的开发工具包系列产品。VS 是一个基本完整的开发工具集,它包括了整个软件生命周期中所需要的大部分工具,如 UML 工具、代码管控工具、集成开发环境等等。 -windows 上最棒的开发 IDE,存在第三方插件:[ZigVS](https://marketplace.visualstudio.com/items?itemName=LuckystarStudio.ZigVS)。 +Windows 上最强大的 IDE 之一,可以通过第三方插件 [ZigVS](https://marketplace.visualstudio.com/items?itemName=LuckystarStudio.ZigVS) 提供 Zig 支持。 ## CLion > CLion 是一款专为开发 C 及 C++ 所设计的跨平台 IDE。它是以 IntelliJ 为基础设计的,包含了许多智能功能来提高开发人员的生产力。CLion 帮助开发人员使用智能编辑器来提高代码质量、自动代码重构并且深度整合 CMake 编译系统,从而提高开发人员的工作效率。 -原本 CLion 仅仅是 C/C++ 的开发 IDE,但在安装插件后可以作为 zig 的 IDE 使用。 +CLion 最初是为 C/C++ 开发设计的 IDE,但通过安装插件,现在也可以作为强大的 Zig IDE 使用。 -目前插件市场活跃的两个 zig 插件(均为第三方作者维护)分别是 [ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) 和 [Zig Support](https://plugins.jetbrains.com/plugin/18062-zig-support),均支持 zig 的 `latest release` 版本。 +目前插件市场有两个活跃的 Zig 插件(均为第三方作者维护):[ZigBrains](https://plugins.jetbrains.com/plugin/22456-zigbrains) 和 [Zig Support](https://plugins.jetbrains.com/plugin/18062-zig-support)。两者均支持 Zig 的 `latest release` 版本。 ## Sublime Text -经典的编辑器,插件也是由 zig 官方维护:[sublime-zig-language](https://github.com/ziglang/sublime-zig-language),安装即可。 +经典的编辑器,其 Zig 插件 [sublime-zig-language](https://github.com/ziglang/sublime-zig-language) 由官方维护,安装即可使用。 ::: danger ⛔ 危险 -值得注意的是,该插件已经有两年无人维护! +值得注意的是,该插件已超过两年未获更新,可能无法支持最新的 Zig 功能。 ::: ## zls 体验优化 -当前的 zls 已经支持保存时自动检查代码,但默认关闭。 +zls 已支持保存时自动检查代码的功能,但此功能默认关闭。 -仅仅需要在 zls 的配置文件(可以通过 `zls --show-config-path`)中加入以下内容即可: +只需在 zls 的配置文件(可通过 `zls --show-config-path` 命令找到路径)中加入以下内容即可开启: ```json { @@ -105,7 +105,7 @@ windows 上最棒的开发 IDE,存在第三方插件:[ZigVS](https://marketp } ``` -同时对应的项目的 `build.zig` 也需要进行部分调整: +同时,对应项目的 `build.zig` 也需要进行如下调整: ```zig const exe_check = b.addExecutable(.{ @@ -119,11 +119,11 @@ const check = b.step("check", "Check if foo compiles"); check.dependOn(&exe_check.step); ``` -这意味着要求编译器在编译时会添加 `-fno-emit-bin`,然后 Zig 将仅仅分析代码,但它不会调用 LLVM,所以并不会生成实际的文件 +这意味着编译器在编译时会添加 `-fno-emit-bin` 标志,Zig 将只分析代码而不会调用 LLVM 后端,因此不会生成任何可执行文件。 -但需要注意的是,我们对 `zls` 的设置是全局的,也就意味着我们需要给所有项目添加上述 `build.zig` 的内容,否则诊断功能将会失效。 +需要注意的是,zls 的此项设置是全局性的。这意味着你需要为所有希望使用此功能的项目添加上述 `build.zig` 配置,否则诊断功能将无法在这些项目中生效。 更多资料: - [Improving Your Zig Language Server Experience](https://kristoff.it/blog/improving-your-zls-experience/) -- [Local ZLS config per project](https://github.com/zigtools/zls/issues/1687) (针对本地项目的 zls 配置) +- [Local ZLS config per project](https://github.com/zigtools/zls/issues/1687) (了解如何为单个项目配置 zls) diff --git a/course/environment/install-environment.md b/course/environment/install-environment.md index da07dde4..e6ee5b64 100644 --- a/course/environment/install-environment.md +++ b/course/environment/install-environment.md @@ -5,28 +5,28 @@ outline: deep # 环境安装 ::: tip 🅿️ 提示 -当前 Zig 还没有发布 1.0 版本,发布周期与 LLVM 的新版本关联,其发布周期约为 6 个月。 -因此,Zig 的发布往往要间隔很久,以目前的开发速度,稳定版最终会变得过时(即便此时还没有新的稳定版),所以官方鼓励用户使用 `nightly` 版本。 +当前 Zig 尚未发布 1.0 版本,其发布周期与 LLVM 的新版本发布(约每 6 个月一次)相关联。 +因此,Zig 的稳定版发布间隔较长。鉴于 Zig 的快速发展,稳定版可能很快会落后于最新功能,所以官方鼓励用户使用更新更频繁的 `nightly` 版本。 ::: ## Windows ::: details windows 输出中文乱码问题 -如果你是中文简体用户,那么建议将 windows 的编码修改为 UTF-8 编码,由于 zig 的源代码编码格式是 UTF-8,导致在 windows 下向控制台打印输出中文会发生乱码的现象。 +如果你是简体中文用户,建议将 Windows 的系统区域设置修改为 UTF-8。由于 Zig 源代码及默认输出使用 UTF-8 编码,此举可以避免在控制台输出中文时出现乱码。 -修改方法为: +修改步骤如下: -1. 打开 windows 设置中的 **时间和语言**,进入 **语言和区域**。 +1. 打开 Windows 设置中的 **时间和语言**,进入 **语言和区域**。 2. 点击下方的管理语言设置,在新打开的窗口中点击 **管理**。 -3. 点击下方的 **更改系统区域设置**,勾选下方的“使用 unicode UTF-8 提供全球语言支持” +3. 点击下方的 **更改系统区域设置**,勾选下方的“Beta: 使用 Unicode UTF-8 提供全球语言支持”。 4. 重启计算机。 ::: ### Scoop -推荐使用 [Scoop](https://scoop.sh/#/) 工具进行安装,Scoop 的 **main** 仓库和 **version** 仓库分别有着最新的 `release` 和 `nightly` 版本。 +推荐使用 [Scoop](https://scoop.sh/#/) 工具进行安装。Scoop 的 **main** 仓库提供最新的 `release` 版本,而 **versions** 仓库提供 `nightly` 版本。 安装方式如下: @@ -46,13 +46,13 @@ scoop install versions/zig-dev ::: info 🅿️ 提示 -- 使用 `scoop reset zig-dev` 或者 `scoop reset zig` 可以从 nightly 和 release 版本相互切换 -- 使用 `scoop install zig@0.11.0` 可以安装指定版本的 zig,同理 `scoop reset zig@0.11.0` 也能切换到指定版本! +- 使用 `scoop reset zig-dev` 或 `scoop reset zig` 可以在 `nightly` 和 `release` 版本之间切换。 +- 使用 `scoop install zig@0.11.0` 可以安装特定版本,同理,`scoop reset zig@0.11.0` 可以切换到该指定版本。 ::: ### 其他的包管理器 -也可以使用诸如 [WinGet](https://github.com/microsoft/winget-cli),[Chocolatey](https://chocolatey.org/) +也可以使用诸如 [WinGet](https://github.com/microsoft/winget-cli) 或 [Chocolatey](https://chocolatey.org/) 等包管理器。 ::: code-group @@ -68,9 +68,9 @@ choco install zig ### 手动安装 -通过官方的 [发布页面](https://ziglang.org/zh/download/) 下载对应的 Zig 版本,普通用户选择 `zig-windows-x86_64` 即可。 +从官方 [发布页面](https://ziglang.org/zh/download/) 下载对应的 Zig 版本,大多数用户应选择 `zig-windows-x86_64`。 -执行以下命令: +解压后,将包含 `zig.exe` 的目录路径添加到系统的 `Path` 环境变量中。可以通过以下 PowerShell 命令完成: ::: code-group @@ -95,14 +95,14 @@ choco install zig ::: ::: info 🅿️ 提示 -以上的 **_System_** 对应的系统全局的环境变量, **_User_** 对应的是用户的环境变量。如果是个人电脑,使用任意一个没有区别。 +**_System_** 对应系统全局环境变量,**_User_** 对应当前用户环境变量。如果是个人电脑,两者通常没有太大区别。 -首先确保你的路径是正确的,其次你可能注意到路径前面还有一个 `;` ,此处并不是拼写错误! +请确保将 `C:\your-path\zig-windows-x86_64-your-version` 替换为你的实际解压路径。路径前的分号 `;` 是必需的,并非拼写错误,它用于在 `Path` 变量中分隔多个路径。 ::: ## Mac -Mac 安装 zig 就很方便,但是如果要使用 `nightly` ,还是需要自行下载并添加环境变量 +在 macOS 上安装 Zig 非常方便。但若要使用 `nightly` 版本,仍需手动下载并设置环境变量。 ::: code-group @@ -118,7 +118,7 @@ port install zig ## Linux -Linux 安装的话,由于发行版的不同,安装的方式五花八门,先列出通过包管理器安装 Zig 的方法,再说明如何手动安装 Zig 并设置环境变量。 +由于 Linux 发行版众多,安装方式各异。下面将先列出通过包管理器安装的方法,然后说明手动安装的步骤。 ### 包管理器安装 @@ -136,28 +136,28 @@ Linux 安装的话,由于发行版的不同,安装的方式五花八门, ### 手动安装 -通过官方的[发布页面](https://ziglang.org/zh/download/)下载对应的 Zig 版本,之后将包含 Zig 二进制的目录加入到 PATH 环境变量即可。 +从官方[发布页面](https://ziglang.org/zh/download/)下载对应的 Zig 版本,解压后将包含 Zig 二进制文件的目录加入到 `PATH` 环境变量即可。 ## 多版本管理 -由于 Zig 还在快速开发迭代中,因此在使用社区已有类库时,有可能出现新版本 Zig 无法编译的情况,这时候一方面可以跟踪上游进展,看看是否有解决方案;另一个就是使用固定的版本来编译这个项目,显然这种方式更靠谱一些。 +由于 Zig 仍在快速迭代,使用新版 Zig 编译器时,可能会遇到无法编译旧有社区库的问题。此时,除了向上游社区寻求解决方案,更可靠的方式是使用特定版本的 Zig 来编译特定项目。这就需要版本管理工具。 -目前为止,Zig 的版本管理工具主要有如下几个: +目前,Zig 的版本管理工具主要有以下几个: - [marler8997/zigup](https://github.com/marler8997/zigup): Download and manage zig compilers - [tristanisham/zvm](https://github.com/tristanisham/zvm): Lets you easily install/upgrade between different versions of Zig - [hendriknielaender/zvm](https://github.com/hendriknielaender/zvm): Fast and simple zig version manager -读者可根据自身需求选择,这里介绍一个通用的版本管理工具:[asdf](https://asdf-vm.com/)。 +读者可根据需求自行选择。本文将介绍一个通用的多语言版本管理工具:[asdf](https://asdf-vm.com/)。 -1. 参考 [Getting Started](https://asdf-vm.com/guide/getting-started.html) 下载 asdf,一般而言,常见的系统管理器,如 brew、apt 均可直接安装 -2. 安装 asdf [Zig 插件](https://github.com/asdf-community/asdf-zig) +1. 请参考官方文档 [Getting Started](https://asdf-vm.com/guide/getting-started.html) 安装 asdf。通常,可以通过 Homebrew (macOS) 或 apt (Debian/Ubuntu) 等包管理器直接安装。 +2. 安装 asdf [Zig 插件](https://github.com/asdf-community/asdf-zig): ```bash asdf plugin-add zig https://github.com/asdf-community/asdf-zig.git ``` -3. 之后就可以用 asdf 管理 Zig 版本。这里列举一些 asdf 常用命令: +3. 安装完成后,便可使用 asdf 管理 Zig 版本。以下是一些常用命令: ```bash # 列举所有可安装的版本 diff --git a/course/environment/zig-command.md b/course/environment/zig-command.md index 0be4f3a0..9182f277 100644 --- a/course/environment/zig-command.md +++ b/course/environment/zig-command.md @@ -4,70 +4,71 @@ outline: deep # `zig` 命令 -现在,我们已经安装了 zig,也安装了对应的编辑器,接下来就了解一下基本的 `zig` 命令。 +安装好 Zig 和编辑器后,我们来了解一下 `zig` 命令行的基本用法。 -这单单一个命令可神了,它囊括了项目建立、构建、测试、运行,甚至你可以用它来部署你的项目,也可以用来给 C/C++ 作为编译或者依赖管理工具,非常的全面,这一切都是得益于 zig 本身的编译器。 +这个命令非常强大,它不仅涵盖了项目的创建、构建、测试和运行,甚至还能用于部署,或作为 C/C++ 的编译器及依赖管理工具。这一切都得益于 Zig 强大的自举编译器。 -以下仅列出常用的命令! +以下列出一些常用命令: ## `zig build` -构建项目,会自动搜索当前目录及父目录的 `build.zig` 进行构建。 +构建项目。该命令会自动在当前及父目录中查找 `build.zig` 文件并执行构建流程。 ## `zig build-obj` -编译一个 Zig 源文件为一个对象文件(`.o` 文件)。 +将指定的 Zig 源文件编译成对象文件(`.o` 文件)。 -## `zig build-test` +## `zig build test` -编译并执行 Zig 文件中的所有测试用例。 +编译并执行 `build.zig` 中定义的 "test" 步骤。通常用于运行整个项目的测试。 -## `zig init` +## `zig init-exe` -这个命令用于初始化项目,在当前路径下创建 `src/main.zig`、`src/root.zig` 、`build.zig` 和 `build.zig.zon` 四个文件。 - -关于 `build.zig` 这个文件的内容涉及到了 zig 的构建系统,我们将会单独讲述。 +此命令用于初始化一个新的可执行文件(application)项目,会在当前目录下创建 `build.zig`、`build.zig.zon` 和 `src` 目录(包含 `main.zig`)。 ```sh . # 项目根目录 ├── build.zig # Zig 构建脚本:定义如何编译、测试和打包项目 ├── build.zig.zon # 项目清单文件 (zon 是 Zig Object Notation):声明项目元数据和依赖项 └── src # 源代码目录 - ├── main.zig # 程序主入口文件 - └── root.zig # 核心逻辑模块:存放应用或库的主要代码和功能 + └── main.zig # 程序主入口文件 ``` +## `zig init-lib` + +与 `zig init-exe` 类似,但用于初始化一个库项目。它会创建一个结构相似的模板,但 `build.zig` 中的配置是面向库的。 + ## `zig ast-check` -对指定文件进行 AST 语法检查,支持指定文件和标准输入。 +对指定的源文件或从标准输入读取的代码进行 AST (抽象语法树) 级别的语法检查。 ## `zig fmt` -用于格式化代码源文件,支持`stdin`和指定路径。 +格式化 Zig 源代码文件。支持指定文件路径,也支持从标准输入(`stdin`)读取内容。 ## `zig test` -对指定的源文件运行 test,适用于单元测试。 +编译并运行指定源文件中的测试用例。非常适用于单元测试。 ## `zig run` -编译并立即运行一个 Zig 程序。这对于快速测试片段代码非常有用。 +编译并立即运行一个 Zig 程序。该命令对于快速测试代码片段非常有用。 ## `zig cc` -使用 Zig 的内置 C 编译器来编译 C 代码。 +使用 Zig 的内置 C 编译器来编译 C 代码。可以看作是 `gcc` 或 `clang` 的直接替代品。 ## `zig c++` -使用 Zig 的内置 C++ 编译器来编译 C++ 代码。 +使用 Zig 的内置 C++ 编译器来编译 C++ 代码。可以看作是 `g++` 或 `clang++` 的直接替代品。 ## `zig translate-c` -将 C 代码转换为 Zig 代码。这是 Zig 提供的一个强大功能,可以帮助你将现有的 C 代码库迁移到 Zig。 +将 C 代码自动转换为 Zig 代码。这是一个强大的功能,可以极大地帮助开发者将现有的 C 代码库迁移到 Zig。 ## `zig targets` -显示 Zig 编译器支持的所有目标架构、操作系统和 ABI。 +列出 Zig 编译器支持的所有目标架构、操作系统和 ABI (应用程序二进制接口)。 ## `zig version` @@ -79,21 +80,21 @@ outline: deep ## `zig fetch` -该命令用于获取包的 hash 或者添加包到 `build.zig.zon` 中! +此命令用于获取包的哈希值,或直接将包添加为项目的依赖项并记录在 `build.zig.zon` 文件中。 ```sh +# 仅获取包的哈希值 $ zig fetch https://github.com/webui-dev/zig-webui/archive/main.tar.gz 12202809180bab2c7ae3382781b2fc65395e74b49d99ff2595f3fea9f7cf66cfa963 ``` -当然如果你想将包直接添加到 `zon` 中,你可以附加 `--save` 参数来实现效果: +如果你希望将包直接添加为依赖项,可以附加 `--save` 参数: -```zig +```sh +# 获取哈希值,并将其作为依赖项保存到 build.zig.zon zig fetch --save https://github.com/webui-dev/zig-webui/archive/main.tar.gz -// 当包提供 name 时,会自动使用包的 name -// 当然,你也可以指定包的 name,使用 --save=webuizig ``` -除了上述命令之外,还有一些其他的命令和选项可以在 Zig 的官方文档中找到。随着 Zig 语言的不断发展,可能会有新的命令和功能加入,所以建议定期查看官方文档来获取最新信息。 +当包在其 `build.zig.zon` 中定义了 `name` 字段时,`zig fetch` 会自动使用该名称。你也可以使用 `--save=` 来指定一个自定义的依赖名称,例如 `--save=webuizig`。 -希望这些补充能够帮助完善你的文档。如果你需要更详细的信息,可以参考 Zig 的官方文档。 +除了以上介绍的命令,`zig` 还提供了许多其他命令和选项。随着 Zig 语言的不断发展,新的功能和命令也会持续加入,建议您定期查阅 [Zig 官方文档](https://ziglang.org/documentation/master/) 以获取最新信息。 diff --git a/course/examples/echo_tcp_server.md b/course/examples/echo_tcp_server.md index de60b4e4..9a403d37 100644 --- a/course/examples/echo_tcp_server.md +++ b/course/examples/echo_tcp_server.md @@ -4,7 +4,7 @@ outline: deep # Echo TCP Server -我们来进行编写一个小小的示例———— Echo TCP Server(TCP 回显 server),帮助我们理解更多的内容。 +我们来编写一个小小的示例———— Echo TCP Server(TCP 回显 server),帮助我们理解更多的内容。 > 代码一共也就一百行左右,简洁但不简单! diff --git a/course/hello-world.md b/course/hello-world.md index c0ae8b66..4281905b 100644 --- a/course/hello-world.md +++ b/course/hello-world.md @@ -4,79 +4,87 @@ outline: deep # Hello World -我相信你一定是从 `Hello, World!` 开始学习其他语言的,在这里我们也不例外,我们来通过一个简单的程序,来向 zig 的世界打一声招呼! +与学习其他编程语言一样,我们也将从经典的 `Hello, World!` 程序开始,以此向 Zig 的世界打一声招呼。 -先使用 `zig init` 命令初始化一个项目,再将以下内容覆写到 `src/main.zig` 中。 +首先,使用 `zig init-exe` 命令初始化一个可执行项目,然后将以下内容覆盖写入到 `src/main.zig` 文件中。 <<<@/code/release/hello_world.zig#one -然后只需要运行命令:`zig build run`,你就可以看到熟悉的 `Hello, World!` 了! +接着运行 `zig build run` 命令,你就可以在终端看到熟悉的 `Hello, World!` 了。 _很简单,不是吗?_ -## 简单说明 +## 代码解析 -以上程序中,我们先通过 `@import` 这个内建函数(在 zig 中有很多的内置函数,它们都以`@`开头,并且遵循 [小驼峰命名法](#))引入了 zig 的标准库。 +上述程序通过内建函数 `@import` 导入了 Zig 的标准库 `std`。 +在 Zig 中,所有内建函数都以 `@` 符号开头,并遵循小驼峰命名法(lowerCamelCase)。 ::: info 🅿️ 提示 -`@import` 函数查找对应名称的模块或者 zig 源文件(源文件带后缀)并导入它。 +`@import` 函数用于查找并导入相应名称的模块或 Zig 源文件(以 `.zig` 为后缀)。 -zig 始终支持导入三个模块,`std`、`builtin` 和 `root`,分别代表标准库、构建目标相关信息、根文件(编译时的 root 文件,通常为 `src/main.zig`)。 +Zig 默认可导入三个核心模块: -::: - -通过在 `main` 函数(程序默认是从此处开始执行,这是规定)中使用在标准库 **debug** 包中定义的 `print` 函数来输出语句 _Hello, World!_ +- `std`:标准库。 +- `builtin`:与构建目标相关的信息。 +- `root`:项目的根文件(编译时指定的入口文件,通常是 `src/main.zig`)。 -`print` 函数接受两个参数,类似于 C 的 `printf` 函数,第一个参数是要格式化的字符串,第二个是参量表,这里我们需要说的是,格式化字符串使用的是`{}`, -zig 会自动为我们根据后面的参量表推导出对应的类型,当 zig 无法推导时,我们需要显式声明要格式化的参量类型,例如字符串就是 `{s}`,整数就是 `{d}`, -更多的类型我们在后面会详细说明。我们传入的第二个参数是一个元组(**tuple**),它是一个元组(你可以把它看作是一个匿名结构体,这里你只需要知道一下就行)。 +::: -::: info 🅿️ 提示 +程序的入口点是 `main` 函数。我们在 `main` 函数中调用了标准库 `debug` 包内的 `print` 函数,输出了 "Hello, World!"。 -好了,上面的内容你应该已经看完了,现在我要告诉你,正常使用 zig 打印字符串是不能这样子做的! +`print` 函数的用法类似于 C 语言的 `printf`,它接受两个参数:第一个是格式化字符串,第二个是包含替换值的元组(tuple)。 -你是不是觉得自己被耍了?别担心,上面仅仅只是一个示例而已,来为你演示一下 zig 的使用! +- **格式化字符串**:使用 `{}` 作为占位符。Zig 会自动推断值的类型。如果无法推断,则需要显式指定,例如 `{s}` 代表字符串,`{d}` 代表整数。 +- **参数**:第二个参数是一个元组,你可以将其理解为一个匿名结构体。 +::: warning ⚠️ 注意 +`std.debug.print` 主要用于调试,不推荐在生产环境中使用。因为它会将信息打印到 `stderr`,且在某些构建模式下可能会被编译器优化掉。 +这只是一个入门示例,接下来我们将探讨更“正确”的打印方式。 ::: -下面的内容可能有点难,你可以*暂时跳过这里*,后面再来学习! +下面的内容涉及更底层的概念,你可以*暂时跳过*,待熟悉 Zig 后再来回顾。 -## 换一种方式? +## 更标准的输出方式 -接下来,让我们换一种方式来讲述如何“正确”地使用 zig 打印出“Hello, World!”,不要认为这是一个简单的问题,这涉及到计算机相当底层的设计哲学。 +“打印 Hello, World”看似简单,但在 Zig 中,它能引导我们思考一些底层设计。 -首先,我要告诉你,zig 并没有一个内置的打印功能,包含“输出”功能的包只有 `log` 包和 `debug` 包,zig 并没有内置类似与 `@print()` 这种函数。再来一个简单的例子告诉你,如何打印东西(**_但是请记住,以下示例代码不应应用于生产环境中_**)。 +Zig 本身没有内置的 `@print()` 函数,输出功能通常由标准库的 `log` 和 `io` 包提供。`std.debug.print` 是一个特例,主要用于调试。 + +让我们看一个更规范的例子(**但请注意,此代码同样不建议直接用于生产环境**): <<<@/code/release/hello_world.zig#two :::info 🅿️ 提示 +`main` 函数的返回类型 `!void` 是一个错误联合类型。它表示该函数要么成功执行并返回 `void`(即无返回值),要么返回一个错误。 +::: -`main` 函数的返回值是`!void`,这是联合错误类型,该语法将会告诉 zig 编译器会返回错误或者值,此处的意思是如果有返回值,一定是一个错误。 +这段代码分别向 `stdout` 和 `stderr` 输出了信息。 -::: +- `stdout` (标准输出):用于输出程序的正常信息。写入 `stdout` 的操作可能会失败。 +- `stderr` (标准错误):用于输出错误信息。我们通常假定写入 `stderr` 的操作不会失败(由操作系统保证)。 -这段代码将会分别输出 `Hello out!` 和 `Hello err!`,这里我需要向你讲述一下 `stdout` 和 `stderr` ,它们均是抽象的 io(input and output)流句柄(关于流这个概念可能不好解释,你暂时就当作像水流一样的数据的就行)。`stdout` 用于正常的输出,它可能会出现错误导致写入失败。`stderr` 用于错误输出,我们假定 `stderr` 一定不会失败(这个是操作系统负责保证的),这就是它们的区别。 +我们通过 `std.io` 模块获取了标准输出和标准错误的 `writer`,它们提供了 `print` 方法,可以将格式化的字符串写入对应的 I/O 流。 -通过 `io` 模块获取到了标准输出和错误输出的 `writer` 句柄,这个句柄实现流`print`函数,我们只需要正常打印即可! +### 考虑性能:使用缓冲区 -接下来加深一点难度,你有没有想过,这些`print`函数是如何实现的? +`print` 函数的每次调用都可能触发一次系统调用(System Call),这会带来内核态与用户态之间上下文切换的开销,影响性能。为了解决这个问题,我们可以引入缓冲区(Buffer),将多次输出的内容攒在一起,然后通过一次写入操作完成,从而减少系统调用的次数。 -它们都是依靠系统调用来实现输出效果,但是这就面临着性能问题,我们知道系统调用会造成内核上下文切换的开销(系统调用的流程:执行系统调用,此时控制权会切换回内核,由内核执行完成进程需要的系统调用函数后再将控制权返回给进程),所以我们如何解决这个问题呢?可以增加一个缓冲区,等到要打印的内容都到一定程度后再一次性全部 `print`,那么此时的解决方式就如下: +实现方式如下: <<<@/code/release/hello_world.zig#three -此时我们就分别得到了使用缓冲区的 `stdout` 和 `stderr`,性能更高了! +通过 `std.io.bufferedWriter`,我们为 `stdout` 和 `stderr` 的 `writer` 添加了缓冲功能,从而提高了性能。 -## 更进一步? +## 更进一步:线程安全 -上面我们已经完成了带有缓冲区的“打印”,这很棒! +以上代码在单线程环境下工作良好,但在多线程环境中,多个线程同时调用 `print` 可能会导致输出内容交错混乱。为了保证线程安全,我们需要为 `writer` 添加锁。 -但是,它还没有多线程支持,所以我们可能需要添加一个**锁**来保证打印函数的先后执行顺序,你可以使用 `std.Thread.Mutex`,它的文档在[_这里_](https://ziglang.org/documentation/master/std/#std.Thread.Mutex),但我更推荐你结合标准库的源码来了解它。 +你可以使用 `std.Thread.Mutex` 来实现一个线程安全的 `writer`。我们鼓励你阅读[标准库源码](https://ziglang.org/documentation/master/std/#std.Thread.Mutex)来深入了解其工作原理。 -## 了解更多? +## 了解更多 -如果你想了解更多内容,可以看一看这个视频 [Advanced Hello World in Zig - Loris Cro](https://youtu.be/iZFXAN8kpPo?si=WNpp3t42LPp1TkFI) +如果你想深入探索这个话题,可以观看此视频:[Advanced Hello World in Zig - Loris Cro](https://youtu.be/iZFXAN8kpPo?si=WNpp3t42LPp1TkFI) diff --git a/course/index.md b/course/index.md index 5fbdb6f6..c1aacba8 100644 --- a/course/index.md +++ b/course/index.md @@ -24,7 +24,7 @@ showVersion: false ## 为何使用 Zig -从本质上看,Zig 是一门 `low level` 的高级语言,它和 C 很像,但改善旧问题并提供了完善的工具链,并且它可选支持 `libc`。 +从本质上看,Zig 是一门 `low level` 的高级语言,它和 C 很像,但解决了 C 的一些历史遗留问题,并提供了完善的工具链,而且它可选支持 `libc`。 一切都是强制显式声明式,这使得代码阅读很轻松! diff --git a/course/prologue.md b/course/prologue.md index 49c61566..08e40473 100644 --- a/course/prologue.md +++ b/course/prologue.md @@ -15,7 +15,7 @@ Zig 这门语言并不适合计算机初学者,如果你已经对计算机有 有过多营销号说 zig 已经是 C 的替代品了,这是完全不正确的。将来或许会是,但现在肯定不是,目前 zig 只是一个有那么一丁点热度的高级语言。 -你可能会疑惑,为什么要再学习一门如此 `low level` 的语言,C 难道不好吗? +你可能会疑惑,为什么要学习一门如此 `low level` 的语言,C 难道不好吗? C 很好,非常好,它非常成功,以至于 C 现在已经不再是一门语言,而是一个标准。 你可能会注意到,现在跨语言调用,基本都是遵循的 C ABI,包括编译出来的各种库供其他语言使用,也都是 C 可以使用的动态链接库。 diff --git a/course/update/0.12.0-description.md b/course/update/0.12.0-description.md index aefd4548..8057bc8b 100644 --- a/course/update/0.12.0-description.md +++ b/course/update/0.12.0-description.md @@ -8,7 +8,7 @@ showVersion: false 2024/4/20,`0.12.0` 终于发布了,历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交! -> 过去的发行说明都异常的长,因为试图记录所有在开发周期中所有的变化,本次更新则进行了适当地删减,便于读者和维护者阅读! +> 过去的发行说明都非常长,因为试图记录开发周期中的所有变化。本次更新则进行了适当删减,以便于读者和维护者阅读。 ::: info @@ -67,7 +67,7 @@ showVersion: false 总输出大小为 **47M**,经过 gzip 处理后为 **5.7M**! -- `src/Autodoc.zig` 用于处理 ZIR 代码(zig 编译时产生的中间代码),输出 json 以便 js 使用,这就导致很多代码无法通过某些数据(这些数据很可能是无效的),重建 AST 语法树 +- `src/Autodoc.zig` 用于处理 ZIR 代码(zig 编译时产生的中间代码),输出 json 以便 js 使用,这就导致很多代码无法通过某些数据重建 AST 语法树 - `lib/docs/commonmark.js` 是一个第三方的 markdown 实现,但它的特性有点太多了,例如,我们并不希望在文档注释中使用 HTML 标签,因为这样会让源代码的注释无比丑陋,应当只渲染源代码和 markdown。 @@ -123,7 +123,7 @@ HTTP 服务器会动态创建请求的文件,包括 main.wasm 重建(如果 这意味着贡献者可以通过在浏览器窗口中按刷新来测试对 Zig 标准库文档以及 autodocs 功能的更改,只需要 Zig 的二进制发行版。 -总之,这使的 zig 的安装大小从 317M 减少到了 268M(-15%)。 +总之,这使得 zig 的安装大小从 317M 减少到了 268M(-15%)。 编译器的 ReleaseSmall 版本从 10M 缩小到 9.8M(-1%)。 @@ -262,7 +262,7 @@ Benchmark 2 (189 runs): cache-dedup/zig build-exe hello.c -target native-native- ## Comptime 指针访问 -Zig 在编译期访问指针有几个长期存在的错误。当试图以特殊的方式的方式访问指针,例如加载数组的切片或重新解引用内存时,你有时会遇到一个错误的编译错误,编译器会要求 comptime 解引用需要具有明确定义的布局的类型。 +Zig 在编译期访问指针有几个长期存在的错误。当试图以特殊的方式访问指针,例如加载数组的切片或重新解引用内存时,你有时会遇到一个错误的编译错误,编译器会要求 comptime 解引用需要具有明确定义的布局的类型。 [#19630](https://github.com/ziglang/zig/pull/19630) 的合并解决了这个问题。在 `0.12.0` 中,编译器在进行与编译期内存有关的复杂操作时,不再报告错误的编译错误。这个改变也包括对编译期 `@bitCast` 逻辑的一些修复;特别是,包含指针的位转换聚合不再错误地强制在运行时进行操作。 diff --git a/course/update/0.13.0-description.md b/course/update/0.13.0-description.md index c2524986..6844e0b7 100644 --- a/course/update/0.13.0-description.md +++ b/course/update/0.13.0-description.md @@ -28,7 +28,7 @@ showVersion: false > > 如果子进程崩溃或返回非零退出代码,步骤将失败。 -过去缺少该锁的实现,目前已实现,确保 stdout/stderr 只会被一个进程占用。 +过去缺少该锁的实现,现已添加,以确保 stdout/stderr 只会被一个进程占用。 ### 默认情况下将 Windows DLL 安装到 `/bin/` @@ -88,7 +88,7 @@ Windows 不支持 `RPATH`,默认情况下仅在少量预定路径中搜索 DLL - [https://bixense.com/clicolors/](https://bixense.com/clicolors/) 尝试标准化 `CLICOLOR_FORCE`,创建于 2015 年。`FORCE_COLOR` 对应的 [https://force-color.org/](https://force-color.org/) 创建于 2023 年。 - `CLICOLOR_FORCE` 似乎是由 `ls` 于 2000 年在 FreeBSD `4.1.1` 中引入的。`FORCE_COLOR` 似乎是由 chalk JavaScript 库于 2015 年引入的。 - `CLICOLOR_FORCE` 受 CMake 和 Ninja 支持。 -- 虽然搜索 `/(?-i)\bCLICOLOR_FORCE\b/` 返回 28.9k 文件并且搜索 `/(?-i)\bFORCE_COLOR\b/` 返回 39k 文件,但前者似乎在用 C/C++ 编写的软件中更常见,而后者似乎更常见于 Node.js 和 Python 相关生态系统。 +- 虽然搜索 `/(?-i)\bCLICOLOR_FORCE\b/` 返回 28.9k 文件且搜索 `/(?-i)\bFORCE_COLOR\b/` 返回 39k 文件,但前者似乎在用 C/C++ 编写的软件中更常见,而后者似乎更常见于 Node.js 和 Python 相关生态系统。 `CLICOLOR_FORCE` 也会添加到 `zig env` 的输出中。 diff --git a/course/update/0.14.0-description.md b/course/update/0.14.0-description.md index 96324968..61235e74 100644 --- a/course/update/0.14.0-description.md +++ b/course/update/0.14.0-description.md @@ -15,11 +15,11 @@ showVersion: false 此版本的 Zig 的一个重要特性是改进目标支持状况,Zig 可以正确交叉编译和运行的目标列表已增加很多。 Zig 可以为其构建程序的目标见下表,[zig-bootstrap README](https://github.com/ziglang/zig-bootstrap/blob/master/README.md#supported-triples) 涵盖了 Zig 编译器本身可以轻松交叉编译以运行的目标。 -针对特性目标修复的完整列表太长,不在列出,但简言之,如果过去尝试针对 `arm/thumb`、`mips/mips64`、`powerpc/powerpc64`、`riscv32/riscv64` 或 `s390x` 并遇到工具链问题、缺少标准库支持或看似无意义的崩溃,那么现在大概率 Zig `0.14.0` 可以正常工作。 +针对特性目标修复的完整列表太长,不再列出,但简言之,如果过去尝试针对 `arm/thumb`、`mips/mips64`、`powerpc/powerpc64`、`riscv32/riscv64` 或 `s390x` 并遇到工具链问题、缺少标准库支持或看似无意义的崩溃,那么现在大概率 Zig `0.14.0` 可以正常工作。 -### 目标三重变化 +### 目标三元组变化 -Zig 对理解的目标三重做了一些更改: +Zig 对理解的目标三元组做了一些更改: - `arm-windows-gnu` 已被替换为 `thumb-windows-gnu`,以反映 Windows 仅支持 Thumb-2 模式的事实。 - `armeb-windows-gnu` 和 `aarch64_be-windows-gnu` 已被移除,因为 Windows 不支持大端。 @@ -195,7 +195,7 @@ const foo: Foo = @import("foo.zon"); ### 标记器:简化和规范一致性 -我们用模糊测试器测试了 tokenizer,结果它立刻就崩溃了。检查后,对这个实现不满意。 +我们用模糊测试器测试了标记器,结果它立刻就崩溃了。检查后,对这个实现不满意。 此提交移除了几个机制: @@ -300,7 +300,7 @@ info: hint: pass --port 38239 to use this same port next time [asciinema demo](https://asciinema.org/a/asgN7rIr6LFeMhQMOXI825wGe) -[vedio demo and screenshots](https://github.com/ziglang/zig/pull/20958) +[video demo and screenshots](https://github.com/ziglang/zig/pull/20958) ## Bug 修复 diff --git a/course/update/upgrade-0.12.0.md b/course/update/upgrade-0.12.0.md index 0fcce062..547de7c7 100644 --- a/course/update/upgrade-0.12.0.md +++ b/course/update/upgrade-0.12.0.md @@ -129,7 +129,7 @@ const B = MakeOpaque(1); 对编译器的编译时内存(comptime memory)的内部表示,特别是编译时可变内存(即 `comptime var`)进行了全面改革。这次改革带来了一些面向用户的变化,以新的限制的形式出现,限制了你可以对 `comptime var` 做什么。 -第一个也是最重要的新规则是,永远不允许指向 a comptime var 的指针成为运行时已知的。例如: +第一个也是最重要的新规则是,永远不允许指向 `comptime var` 的指针成为运行时已知的。例如: ```zig test "runtime-known comptime var pointer" { @@ -392,7 +392,7 @@ test "@abs on int" { [BadBatBut 的缓解措施](https://github.com/ziglang/zig/pull/19698)没有在 0.12.0 版本的发布截止日期之前完成。 -### 不在允许覆盖 POSIX API +### 不再允许覆盖 POSIX API Zig 的历史版本允许应用程序覆盖标准库的 POSIX API 层。该版本故意移除了这个能力,没有提供迁移方案。 @@ -575,7 +575,7 @@ pub fn serve( ### deflate 的重实现 -> deflat e 是一种无损数据压缩算法和相关的文件格式。它通常用于 gzip 和 zip 文件格式中,也是 HTTP 协议中的一种常见的内容编码方式。 +> deflate 是一种无损数据压缩算法和相关的文件格式。它通常用于 gzip 和 zip 文件格式中,也是 HTTP 协议中的一种常见的内容编码方式。 > > inflate 是一种数据解压缩算法,它是 deflate 压缩算法的反向操作。在网络传输或数据存储中,通常先使用 deflate 算法将数据压缩,然后在需要使用数据时,再使用 inflate 算法将数据解压缩回原始形式。 @@ -697,7 +697,7 @@ pub fn oldGzip(allocator: std.mem.Allocator) !void { // Remove allocator var cmp = try gzip.compress(allocator, buf.writer(), .{}); _ = try cmp.write(data); - try cmp.close(); // Rename to finisho + try cmp.close(); // Rename to finish cmp.deinit(); // Remove var fbs = std.io.fixedBufferStream(buf.items); @@ -1299,7 +1299,7 @@ error: the following build command crashed: ### Header 安装 -`Compile.installHeader `和其它相关函数的目的一直是将头文件与产物一起打包,让它们与产物一起安装,并自动添加到与产物链接的模块的包含搜索路径中。 +`Compile.installHeader`和其它相关函数的目的一直是将头文件与产物一起打包,让它们与产物一起安装,并自动添加到与产物链接的模块的包含搜索路径中。 然而,在 `0.11.0` 中,这些函数修改了构建器的默认 `install` 顶级步骤,导致了一些意想不到的结果,比如根据调用的顶级构建步骤的不同,可能会安装或不安装头文件。 diff --git a/course/update/upgrade-0.14.0.md b/course/update/upgrade-0.14.0.md index c2504301..d5f16e8e 100644 --- a/course/update/upgrade-0.14.0.md +++ b/course/update/upgrade-0.14.0.md @@ -1416,5 +1416,5 @@ const dep_foo_bar = b.dependency("foo_bar", .{ .linkage = .dynamic // or leave for default static }); -mod.linkLibrary(dep_foor_bar.artifact("foo_bar")); +mod.linkLibrary(dep_foo_bar.artifact("foo_bar")); ``` diff --git a/package.json b/package.json index 38264c67..3a81676b 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "export-pdf": "press-export-pdf export course" }, "devDependencies": { - "@types/node": "^24.0.7", - "prettier": "^3.6.0", + "@types/node": "^24.0.10", + "prettier": "^3.6.2", "vitepress": "^1.6.3", "vitepress-export-pdf": "^1.0.0" },