枚举
枚举常用于表示一个有限集合的成员,或对特定类型的对象进行分类。
枚举是一种相对简单但用途广泛的类型。
声明枚举
我们可以通过 enum 关键字轻松地声明和使用枚举:
const Type = enum {
ok,
not_ok,
};
const c = Type.ok;同时,Zig 还允许我们访问和操作枚举的标记值:
// 指定枚举的标记类型
// 现在我们可以在 u2 和 Value 这个枚举类型之中任意切换了
const Value = enum(u2) {
zero,
one,
two,
};在此基础上,我们还可以覆盖枚举的标记值:
const Value2 = enum(u32) {
hundred = 100,
thousand = 1000,
million = 1000000,
};
// 覆盖部分值
const Value3 = enum(u4) {
a,
b = 8,
c,
d = 4,
e,
};🅿️ 提示
枚举类型支持使用 if 和 switch 进行匹配,具体细节请参见相应章节。
枚举方法
是的,枚举也可以拥有方法。实际上,枚举在 Zig 中是一种特殊的命名空间(可以看作一种特殊的 struct)。
const Suit = enum {
clubs,
spades,
diamonds,
hearts,
pub fn isClubs(self: Suit) bool {
return self == Suit.clubs;
}
};枚举大小
需要注意的是,Zig 编译器会严格计算枚举的大小。例如,前面示例中的 Type 枚举,其大小等效于 u1。
以下示例中,我们使用了内建函数 @typeInfo 和 @tagName 来获取枚举的大小和对应的标签名称(tag name):
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;
const Small = enum {
one,
two,
three,
four,
};
pub fn main() !void {
const info = @typeInfo(Small).@"enum";
try expect(info.tag_type == u2);
// 0.17 起类型信息采用“数组结构体”风格:字段名与字段值分别存放
try expect(info.field_names.len == 4);
try expect(mem.eql(u8, info.field_names[1], "two"));
try expect(info.field_values[1] == 1);
try expect(mem.eql(u8, @tagName(Small.three), "three"));
}枚举推断
枚举也支持类型推断(通过结果位置语义),即在已知枚举类型的情况下,可以仅使用字段名来指定枚举值:
const Color = enum {
auto,
off,
on,
};
pub fn main() !void {
const color1: Color = .auto; // 此处枚举进行了自动推断
const color2 = Color.auto;
_ = (color1 == color2); // 这里比较的结果是 true
}非详尽枚举
Zig 允许我们定义非详尽枚举,即在定义时无需列出所有可能的成员。未列出的成员可以使用 _ 来表示。由于存在未列出的成员,编译器无法自动推断枚举的大小,因此必须显式指定其底层类型。
const Number = enum(u8) {
one,
two,
three,
_,
};
const number = Number.one;
const result = switch (number) {
.one => true,
.two, .three => false,
_ => false,
};
// result 是 true
const is_one = switch (number) {
.one => true,
else => false,
};
// is_one 也是true🅿️ 提示
Zig 0.17 新增了 @fromBackingInt 和 @backingInt,分别取代了已被标记为 deprecated 的 @enumFromInt 与 @intFromEnum(zig fmt 会自动完成这一替换)。
@fromBackingInt 能够将整数转换为枚举值,它的结果类型通过结果位置推断,参数必须恰好是该枚举的标记类型(例如 u4),因此其他整数类型需要先用 @intCast 转换。需要注意,如果所选枚举类型中没有表示该整数的值,就会导致未定义行为。
如果目标枚举类型是非详尽枚举,那么 @fromBackingInt 始终能够得到有效的枚举值。反过来,@backingInt 返回值的类型就是枚举的标记类型。
const Color = enum(u4) {
red,
green,
blue,
_,
};
// 明确列出的枚举值
// 0.17 使用 @fromBackingInt 代替 @enumFromInt
const blue: Color = @fromBackingInt(2);
try expect(blue == .blue);
// 未列出的枚举值:8 在 u4 的范围内(0~15)
const yellow: Color = @fromBackingInt(8);
try expect(@TypeOf(yellow) == Color);
// 0.17 使用 @backingInt 代替 @intFromEnum,结果类型就是标记类型 u4
try expect(@backingInt(yellow) == 8);
try expect(@TypeOf(@backingInt(yellow)) == u4);
// @fromBackingInt 的参数必须恰好是标记类型 u4,42 超出了 u4 的范围,无法通过编译
// const ub: Color = @fromBackingInt(42);EnumLiteral
🅿️ 提示
此部分内容并非初学者需要掌握的内容,它涉及到 Zig 的类型系统和编译期反射,可以暂且跳过!
Zig 还包含一个特殊的类型 EnumLiteral,可以通过内建函数 @EnumLiteral() 得到。 我们可以称之为“枚举字面量”。它是一个与 enum 完全不同的类型,通常用于表示 .foo 这样的枚举字面量值。
它的具体用法如下:
// 使用内建函数 @EnumLiteral 构造出一个 EnumLiteral 类型
// Zig 0.16 起使用 @EnumLiteral() 替代 @Type(.enum_literal)
const EnumLiteralType: type = @EnumLiteral();
// 定义一个常量 enum_literal,它的类型为 EnumLiteral,并赋值为 ".kkk"
const enum_literal: EnumLiteralType = .kkk;
// 使用内建函数 @tagName 获取 enum_literal 的 tag name,并进行打印
std.debug.print("enum_literal is {s}", .{@tagName(enum_literal)});注意:此类型常用于函数参数。
extern
注意,我们通常不直接对枚举使用 extern 关键字。
默认情况下,Zig 不保证枚举与 C ABI 兼容,但我们可以通过指定其标记类型来确保兼容性:
const Foo = enum(c_int) { a, b, c };