Skip to content

Chore changes #212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion course/basic/advanced_type/array.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@ outline: deep

在以上的示例中,我们使用了 [for](/basic/process_control/loop) 循环,来进行矩阵的打印,关于循环我们放在后面再聊。

## 哨兵数组
## 哨兵数组(标记终止数组)

> 很抱歉,这里的名字是根据官方的文档直接翻译过来的,原文档应该是 ([Sentinel-Terminated Arrays](https://ziglang.org/documentation/master/#toc-Sentinel-Terminated-Arrays)) 。

:::info

本质上来说,这是为了兼容 C 中的规定的字符串结尾字符`\0`

:::

我们使用语法 `[N:x]T` 来描述一个元素为类型 `T`,长度为 `N` 的数组,在它对应 `N` 的索引处的值应该是 `x`。前面的说法可能比较复杂,换种说法,就是这个语法表示数组的长度索引处的元素应该是 `x`,具体可以看下面的示例:

<<<@/code/release/array.zig#terminated_array
Expand All @@ -48,6 +54,12 @@ outline: deep

## 操作

:::info

以下操作都是编译期 (comptime) 的,如果你需要运行时地处理数组操作,请使用 `std.mem`。

:::

### 乘法

可以使用 `**` 对数组做乘法操作,运算符左侧是数组,右侧是倍数,进行矩阵的叠加。
Expand Down
18 changes: 15 additions & 3 deletions course/basic/advanced_type/pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最

:::

### 哨兵指针
### 哨兵指针(标记终止指针)

:::info

本质上来说,这是为了兼容 C 中的规定的字符串结尾字符`\0`

:::

哨兵指针就和哨兵数组类似,我们使用语法 `[*:x]T`,这个指针标记了边界的值,故称为“哨兵”。

Expand Down Expand Up @@ -118,11 +124,11 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最

对指针的操作应假定为没有副作用。如果存在副作用,例如使用内存映射输入输出(Memory Mapped Input/Output),则需要使用 `volatile` 关键字来修饰。

在以下代码中,保证使用 `mmio_ptr` 的值进行操作(这里你看起来可能会感到迷惑,在编译代码时,编译器可以能会让值在实际运行过程中进行缓存,这里保证每次都使用 `mmio_ptr` 的值,以避免无法正确触发“副作用”),并保证了代码执行的顺序。
在以下代码中,保证使用 `mmio_ptr` 的值进行操作(这里你看起来可能会感到迷惑,在编译代码时,编译器可以能会让值在实际运行过程中进行缓存,这里保证每次都使用 `mmio_ptr` 的值,以确保正确触发“副作用”),并保证了代码执行的顺序。

<<<@/code/release/pointer.zig#volatile

该节内容,仅仅讲述的少量内容,如果要了解更多,你可能需要查看[官方文档](https://ziglang.org/documentation/master/#toc-volatile)!
该节内容,此处仅仅讲述了少量内容,如果要了解更多,你可能需要查看[官方文档](https://ziglang.org/documentation/master/#toc-volatile)!

### 对齐

Expand All @@ -131,6 +137,12 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
每种类型都有一个对齐方式——数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。

内存对齐大小取决于 CPU 架构,但始终是 2 的幂,并且小于 1 << 29。
:::info

`align(0)` 表意是:无需对齐,这在高性能、内存紧迫场景下用处很大。
参考:[allow align(0) on struct fields](https://github.com/ziglang/zig/issues/3802)

:::

在 Zig 中,指针类型具有对齐值。如果该值等于基础类型的对齐方式,则可以从类型中省略它:

Expand Down
2 changes: 1 addition & 1 deletion course/basic/advanced_type/slice.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ slice.ptr 类型为[*]i32
slice 的索引 0 取地址,得到指针类型为*i32
```

## 哨兵切片
## 哨兵切片(标记终止切片)

语法 `[:x]T` 是一个切片,它具有运行时已知的长度,并且还保证由该长度索引的元素的标记值。该类型不保证在此之前不存在哨兵元素,哨兵终止的切片允许元素访问 len 索引。

Expand Down
7 changes: 7 additions & 0 deletions course/basic/advanced_type/string.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ Unicode 码点字面量类型是 `comptime_int`,所有的转义字符均可以

如果要使用多行字符串,可以使用 `\\`,多行字符串没有转义,最后一行行尾的换行符号不会包含在字符串中。示例如下:

:::info

从 `0.14.0` 开始,字符串中不能出现`<Tab>`(在 Zig 中任何`<Tab>`都是不被允许的),但是可以用`\t`或者`@embedFile`实现平行功能。
参考:[enum-backed address spaces](https://github.com/ziglang/zig-spec/issues/38)

:::

<<<@/code/release/string.zig#multiline_string

### 常见错误
Expand Down
7 changes: 4 additions & 3 deletions course/basic/advanced_type/struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ zig 在使用结构体的时候还支持省略结构体类型,只要能让 zig

:::info 🅿️ 提示

当然这种操作不局限于声明变量,你在函数中也可以使用(当编译器无法完成推断时,它会给出一个完整的堆栈跟踪)!
当然这种操作不局限于声明变量,你在函数中也可以使用(当编译器无法完成推断时,它会给出一个包含完整堆栈跟踪的报错)!

:::

Expand All @@ -125,11 +125,12 @@ zig 在使用结构体的时候还支持省略结构体类型,只要能让 zig

:::info 🅿️ 提示

使用 Go 的朋友对这个可能很熟悉,在 Go 中经常用空结构体做实体在 chan 中传递,它的内存大小为 0!
使用 Go 的朋友对这个可能很熟悉,在 Go 中经常用空结构体做实体在 chan 中传递,它的内存大小为 0!
而在 C++ 中,这样的空结构体的内存大小则是 1。

:::

## 通过字段获取基指针
## 通过字段获取基指针(基于字段的指针)

为了获得最佳的性能,结构体字段的顺序是由编译器决定的,但是,我们可以仍然可以通过结构体字段的指针来获取到基指针!

Expand Down
28 changes: 14 additions & 14 deletions course/basic/optional_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ outline: deep

# 可选类型

zig 在不损害效率和可读性的前提下提高代码安全性的一个方案就是可选类型,`?` 是可选类型的标志,你可以将它放在类型的前面,代表它的值是 null 或者这个类型。
## Overview

在 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/)。
当然,它一般在指针上发挥作用,而不是整数。

`null`(空引用)是许多运行时异常的根源,甚至被指责为[计算机科学中最严重的错误](https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/)。

当然这在 zig 中不存在,通过可选类型我们可以解决这个问题,zig 在解决空指针上采取的方式比较保守,它兼顾了代码的可读性和效率问题
我们可以通过可选类型来规避它。这其实是一种比较保守的做法,它同时兼顾了代码的可读性和运行效率。目前最为激进的应该是 _Rust_,它真的是非常的激进,这增加了程序员在写代码时的心智负担(因为你经常需要和编译期斗智斗勇,但好处大大是减少了你在运行时 _Debug_ 的负担)。相对地,zig 采取了一种折中方案,编译期进行简单的检测,而且检测出来的错误一般很容易纠正;这样的缺点是并不能保证你的运行时是绝对安全的(可选类型仅仅能避免空指针问题,却不能避免悬空指针、迷途指针和野指针等问题)

其中目前最为激进的应该是 _Rust_,它真的是非常的激进,这增加了程序员在写代码时的心智负担(因为你经常需要和编译期斗智斗勇,但好处大大是减少了你在运行时 _Debug_ 的负担)。相对来说,zig 采取的是一种折中的方案,编译期仍然会给你检测,并且这种检测不是很深奥,而且纠正起来很简单,缺点是并不能保证你的运行时是绝对安全的(可选类型仅仅能保证你不使用空指针,却不能保证你出现悬空指针【迷途指针、野指针】等情况的出现)
zig 将 `null` 特殊看待,并且保证 `null` 不会被赋值给一个非可选类型变量

zig 会将 `null` 特殊看待,并且保证你不会将一个可能为 `null` 的值赋值给一个不可能是 `null` 的变量。
## 和 C 对比

首先我们和 zig 的目标:C 对比一下,看一下两者在处理 `null` 上的区别,在接下来的代码中,我们尝试调用 `malloc`,并且申请一块内存:
看看下面代码中两者在处理 `null` 上的区别。(尝试调用 `malloc` 申请一块内存。)

::: code-group

Expand All @@ -35,9 +39,9 @@ struct Foo *do_a_thing(void) {

:::

在这里,至少 zig 看起来要比 c 好用,我们通过使用 `orelse` 关键字保证解构了可选类型,保证我们这里的 `ptr` 一定是一个可用的指针,否则的话我们直接会返回 `null`。
在这里,我们通过使用 `orelse` 解构了可选类型,保证 `ptr` 是一个合法可用的指针,否则直接返回 `null`。(这看起来比 C 更加明了且易用)

我们再来对比一下 _C_ 和 _Zig_ 处理 `null` 的方式
再看下例

:::code-group

Expand All @@ -55,9 +59,9 @@ void do_a_thing(struct Foo *foo) {

:::

看起来区别不大,zig 只是在 if 语法有点不同,这个块中保证了 `foo` 不是一个可选类型的指针,而是一个指针
看起来区别不大,只是在 `if` 语法上有点不同,`if` 块中保证 `foo` 不为 `null`

当然在 c 中你可以使用 `__attribute__((nonnull))` 来告诉 GCC 编译器这里不能是一个 null,但使用成本明显要比 zig 高。
当然,在 C 中,你可以用 `__attribute__((nonnull))` 来告诉 C 编译器这里不不可能是 `null`,但其使用成本明显比 Zig 高。

## 编译期反射访问可选类型

Expand All @@ -68,10 +72,6 @@ void do_a_thing(struct Foo *foo) {

<<<@/code/release/optional_type.zig#comptime_access_optional_type

## `null`

`null` 是一个独特的类型,类似 `undefined`,它的使用方式就是赋值给可选类型!

## 可选指针

可选指针会保证和指针有一样的大小,`null` 会被视作地址 0 考虑!
2 changes: 2 additions & 0 deletions course/code/12/error_handle.zig
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ const NotHandleError = struct {
} else |_| {
// 你也可以在这里做点额外的事情
}
// 或者你也可以这样:
parseU64(str, 10) catch {};
}
// #endregion NotHandleError
};
Expand Down
2 changes: 2 additions & 0 deletions course/code/14/error_handle.zig
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ const NotHandleError = struct {
} else |_| {
// 你也可以在这里做点额外的事情
}
// 或者你也可以这样:
parseU64(str, 10) catch {};
}
// #endregion NotHandleError
};
Expand Down