GitHub

核心概念

流程控制

Conditions

在 zig 中,if 这个语法的作用可就大了!

像基础的 ifif elseelse if 我们就不说了,直接看例子:

const num: u8 = 1;
if (num == 1) {
print("num is 1\n", .{});
} else if (num == 2) {
print("num is 2\n", .{});
} else {
print("num is other\n", .{});
}

Enum Type Matching

if 可以用于枚举类型的匹配,判断是否相等:

const Small = enum {
one,
two,
three,
four,
};
const demo = Small.one;
if (demo == Small.one) {
std.debug.print("{}\n", .{demo});
}

Ternary Expression

Zig 中的三元表达式是通过 if else 来实现的:

const a: u32 = 5;
const b: u32 = 4;
// 下方 result 的值应该是47
const result = if (a != b) 47 else 3089;
print("result is {}\n", .{result});

Destructuring Types

以下内容涉及到了联合类型和可选类型,你可以在阅读完这两章节后再回来学习。

Destructuring Option Type

事实上,解构可选类型操作很简单:

const val: ?u32 = null;
if (val) |real_b| {
_ = real_b;
} else {
try expect(true);
}

以上代码的 else 分支并非必要,我们结构后获得 real_b 就是 u32 类型,但是注意我们获得的捕获是只读的!

如果我们想操纵值的内容,可以选择捕获对应的指针:

var c: ?u32 = 3;
if (c) |*value| {
value.* = 2;
}
  • 运算符就表示我们选择捕获这个值对应的指针,因此我们可以通过操控指针来修改其值。

Destructuring Error Union Type

解构错误联合类型类似于解构可选类型:

const val: anyerror!u32 = 0;
i (val) |value| {
try expect(value == 0);
} else |err| {
_ = err;
unreachable;
}

以上代码中 value 类型为 u32else 分支捕获的是错误,即 err 的类型将会是 anyerror,这是由我们之前显式声明的,否则将会是由编译器推导的。

为了仅检测错误,我们可以这样做:

if (val) |_| {} else |err| {
try expect(err == error.BadValue);
}

同样支持捕获指针来操作值:

var val: anyerror!u32 = 3;
if (val) |*value| {
value.* = 9;
} else |_| {
unreachable;
}

提示

那么 if 是如何解构 错误联合可选类型 的呢?

答案是 if 会先尝试解构错误联合类型,再解构可选类型:

const a: anyerror!?u32 = 0;
if (a) |optional_value| {
try expect(optional_value.? == 0);
} else |err| {
_ = err;
}

以上代码中的 optional_value 就是可选类型 ?u32,我们可以在内部继续使用 if 来解构它。

在错误联合可选类型上也可以使用指针捕获:

var d: anyerror!?u32 = 3;
if (d) |*optional_value| {
if (optional_value.*) |*value| {
value.* = 9;
}
} else |_| {
// nothing
}

以上代码中,*optional_value 捕获的是可选类型的指针,我们在内部尝试解引用后再一次捕获指针来进行操作。

Loops

在 zig 中,循环分为两种,一种是 while,一种是 for

for

for 循环是另一种循环处理方式,主要用于迭代数组和切片。

它支持 continuebreak

迭代数组和切片:

const items = [_]i32{ 4, 5, 3, 4, 0 };
var sum: i32 = 0;
for (items) |value| {
if (value == 0) {
continue;
}
sum += value;
}

以上代码中的 value,我们称之为对 数组(切片)迭代的值捕获,注意它是只读的。

在迭代时操作数组(切片):

var items = [_]i32{ 3, 4, 2 };
for (&items) |*value| {
value.* += 1;
}

以上代码中的 value 是一个指针,我们称之为对 数组(切片)迭代的指针捕获,注意它也是只读的,不过我们可以通过借引用指针来操作数组(切片)的值。

Iterate on Numbers

迭代连续的整数很简单,以下是示例:

for (0..5) |i| {
_ = i;
// do something
}

Iterate on Indices

如果你想在迭代数组(切片)时,也可以访问索引,可以这样做:

const items = [_]i32{ 4, 5, 3, 4, 0 };
for (items, 0..) |value, i| {
_ = value;
_ = i;
// do something
}

以上代码中,其中 value 是值,而 i 是索引。

Multi-objective Iteration

当然,你也可以同时迭代多个目标(数组或者切片),当然这两个迭代的目标要长度一致防止出现未定义的行为。

const items = [_]usize{ 1, 2, 3 };
const items2 = [_]usize{ 4, 5, 6 };
for (items, items2) |i, j| {
_ = i;
_ = j;
// do something
}

Use as an Expression

当然,for 也可以作为表达式来使用,它的行为和 while 一模一样。

const items = [_]?i32{ 3, 4, null, 5 };
const result = for (items) |value| {
if (value == 5) {
break value;
}
} else 0;

Mark

continue 的效果类似于 goto,并不推荐使用,因为它和 goto 一样难以把控,以下示例中,outer 就是标记。

break 的效果就是在标记处的 while 执行 break 操作,当然,同样不推荐使用。

它们只会增加你的代码复杂性,非必要不使用!

var count: usize = 0;
outer: for (1..6) |_| {
for (1..6) |_| {
count += 1;
break :outer;
}
}

inline

inline 关键字会将 for 循环展开,这允许代码执行一些一些仅在编译时有效的操作。

需要注意,内联 for 循环要求迭代的值和捕获的值均是编译期已知的。

pub fn main() !void {
const nums = [_]i32{ 2, 4, 6 };
var sum: usize = 0;
inline for (nums) |i| {
const T = switch (i) {
2 => f32,
4 => i8,
6 => bool,
else => unreachable,
};
sum += typeNameLength(T);
}
try expect(sum == 9);
}
fn typeNameLength(comptime T: type) usize {
return @typeName(T).len;
}

While

while 循环用于重复执行表达式,直到某些条件不再成立.

基本使用:

var i: usize = 0;
while (i < 10) {
if (i == 5) {
continue;
}
std.debug.print("i is {}\n", .{i});
i += 1;
}

continue Expression

while 还支持一个被称为 continue 表达式的方法来便于我们控制循环,其内部可以是一个语句或者是一个作用域({} 包裹)

单语句:

var i: usize = 0;
while (i < 10) : (i += 1) {}

多语句:

var i: usize = 1;
var j: usize = 1;
while (i * j < 2000) : ({
i *= 2;
j *= 3;
}) {}

Use as an Expression

Zig 还允许我们将 while 作为表达式来使用,此时需要搭配 elsebreak

这里的 else 是当 while 循环结束并且没有经过 break 返回值时触发,而 break 则类似于return,可以在 while 内部返回值。

fn rangeHasNumber(begin: usize, end: usize, number: usize) bool {
var i = begin;
return while (i < end) : (i += 1) {
if (i == number) {
break true;
}
} else false;
}

Mark

continue 的效果类似于 goto,并不推荐使用,因为它和 goto 一样难以把控,以下示例中,outer 就是标记。

break 的效果就是在标记处的 while 执行 break 操作,当然,同样不推荐使用。

对于continue:

var i: usize = 0;
outer: while (i < 10) : (i += 1) {
while (true) {
continue :outer;
}
}

对于break:

outer: while (true) {
while (true) {
break :outer;
}
}

inline

inline 关键字会将 while 循环展开,这允许代码执行一些一些仅在编译时有效的操作。

pub fn main() !void {
comptime var i = 0;
var sum: usize = 0;
inline while (i < 3) : (i += 1) {
const T = switch (i) {
0 => f32,
1 => i8,
2 => bool,
else => unreachable,
};
sum += typeNameLength(T);
}
try expect(sum == 9);
}
fn typeNameLength(comptime T: type) usize {
return @typeName(T).len;
}

提示

建议以下情况使用内联 while

  • 需要在编译期执行循环
  • 你确定展开后会代码效率会更高

Destructuring Optional Type

if 一样,while 也会尝试解构可选类型,并在遇到 null 时终止循环。

while (eventuallyNullSequence()) |value| {
sum2 += value;
} else {
std.debug.print("meet a null\n", .{});
}
// 还可以使用else分支,碰到第一个 null 时触发并退出循环

|x| 语法出现在 while 表达式上,while 条件必须是可选类型。

Destructuring Error Union Type

和上面类似,同样可以解构错误联合类型,while 分别会捕获错误和有效负载,当错误发生时,转到 else 分支执行,并退出:

while (eventuallyErrorSequence()) |value| {
sum1 += value;
} else |err| {
std.debug.print("meet a err: {}\n", .{err});
}

else |x| 时语法出现在 while 表达式上,while 条件必须是错误联合类型。

上一篇
Union类型