Skip to content
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
13 changes: 13 additions & 0 deletions course/basic/define-variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ x += 1;

上方的 `blk` 是标签名字,它可以是你设置的任何名字。

### shadow

Shadow(遮蔽)指的是在内部作用域中声明一个与外部作用域中同名的变量,导致外部作用域的变量被"遮蔽"的现象。

但是该行为在 Zig 中禁止的!
这样做的好处是会强制标识符始终具有在其周期内的一致性,并且可以防止意外使用错误的变量。

注意:如果两个块中的变量如果不交叉,那么它们是可以同名的。

### 空的块

空的块等效于 `void{}`,即一个空的函数体。

## 容器

在 Zig 中,**容器** 是充当保存变量和函数声明的命名空间的任何语法结构。容器也是可以实例化的类型定义。结构体、枚举、联合、不透明,甚至 Zig 源文件本身都是容器,但容器并不能包含语句(语句是描述程序运行操作的一个单位)。
Expand Down
22 changes: 22 additions & 0 deletions course/basic/process_control/switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,25 @@ switch 的分支可以标记为 `inline` 来要求编译器生成该分支对应
当使用 `inline else` 捕获 tag union 时,可以额外捕获 tag 和对应的 value:

<<<@/code/release/switch.zig#catch_tag_union_value

### labeled 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` 这样的语法是明确的、清楚的,并且可以立即理解。

不过,这个设计的目的是处理对数组中每个元素进行 `switch` 判断的情况,在这种情况下,使用单个 `switch` 语句可以提高代码的清晰度和性能:

<<<@/code/release/switch.zig#vm

如果 `continue` 的操作数在编译时就已知,那么它可以被简化为一个无条件分支指令,直接跳转到相关的 `case`。这种分支是完全可预测的,因此通常执行速度很快。

如果操作数在运行时才能确定,每个 `continue` 可以内联嵌入一个条件分支(理想情况下通过跳转表实现),这使得 CPU 可以独立于其他分支来预测其目标。相比之下,基于循环的简化实现会迫使所有分支都通过同一个分发点,这会妨碍分支预测。
90 changes: 90 additions & 0 deletions course/code/14/switch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pub fn main() !void {
Expression.main();
Catch_tagUnion.main();
AutoRefer.main();
try LabeledSwitch1.main();
try LabeledSwitch2.main();
}

const Basic = struct {
Expand Down Expand Up @@ -191,3 +193,91 @@ fn getNum(u: U) u32 {
}
}
// #endregion catch_tag_union_value

const LabeledSwitch1 = struct {
pub fn main() !void {
// #region labeled_switch_1
sw: switch (@as(i32, 5)) {
5 => continue :sw 4,

// `continue` can occur multiple times within a single switch prong.
2...4 => |v| {
if (v > 3) {
continue :sw 2;
} else if (v == 3) {

// `break` can target labeled loops.
break :sw;
}

continue :sw 1;
},

1 => return,

else => unreachable,
}
// #endregion labeled_switch_1
}
};

const LabeledSwitch2 = struct {
pub fn main() !void {
// #region labeled_switch_2
var sw: i32 = 5;
while (true) {
switch (sw) {
5 => {
sw = 4;
continue;
},
2...4 => |v| {
if (v > 3) {
sw = 2;
continue;
} else if (v == 3) {
break;
}

sw = 1;
continue;
},
1 => return,
else => unreachable,
}
}
// #endregion labeled_switch_2
}
};

// #region vm
const Instruction = enum {
add,
mul,
end,
};

fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
const std = @import("std");
var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);
var ip: usize = 0;

return vm: switch (code[ip]) {
// Because all code after `continue` is unreachable, this branch does
// not provide a result.
.add => {
try stack.append(stack.pop().? + stack.pop().?);

ip += 1;
continue :vm code[ip];
},
.mul => {
try stack.append(stack.pop().? * stack.pop().?);

ip += 1;
continue :vm code[ip];
},
.end => stack.pop().?,
};
}
// #endregion vm
Loading