📘第05课:结构体与枚举

📋本课目录结构体方法与泛型枚举标签联合体packed struct练习

结构体

Zig的结构体是值类型,支持默认值、方法和泛型:

const std = @import("std");

const Point = struct {
    x: f64,
    y: f64,
    z: f64 = 0.0,

    pub fn distance(self: Point) f64 {
        return std.math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z);
    }

    pub fn origin() Point {
        return .{ .x=0, .y=0, .z=0 };
    }

    pub fn translate(self: *Point, dx: f64, dy: f64) void {
        self.x += dx;
        self.y += dy;
    }
};

pub fn main() !void {
    var p = Point{ .x=3.0, .y=4.0 };
    std.debug.print("距离: {d:.2}\n", .{p.distance()});

    var p2 = Point.origin();
    p2.translate(1.0, 2.0);
    std.debug.print("p2=({d:.1},{d:.1})\n", .{p2.x, p2.y});
}

结构体特性

方法与泛型

// 泛型结构体——comptime返回类型
fn Stack(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,

        pub fn push(self: *@This(), item: T) !void {
            if (self.len >= self.items.len) return error.Overflow;
            self.items[self.len] = item;
            self.len += 1;
        }

        pub fn pop(self: *@This()) ?T {
            if (self.len == 0) return null;
            self.len -= 1;
            return self.items[self.len];
        }

        pub fn peek(self: *const @This()) ?T {
            if (self.len == 0) return null;
            return self.items[self.len-1];
        }
    };
}

// 自定义format输出
const Vec3 = struct {
    x:f32, y:f32, z:f32,
    pub fn add(a:Vec3, b:Vec3) Vec3 {
        return .{.x=a.x+b.x, .y=a.y+b.y, .z=a.z+b.z};
    }
    pub fn dot(a:Vec3, b:Vec3) f32 {
        return a.x*b.x + a.y*b.y + a.z*b.z;
    }
    pub fn length(self:Vec3) f32 {
        return std.math.sqrt(self.dot(self));
    }
    pub fn format(self:Vec3, comptime fmt:[]const u8,
        options:std.fmt.FormatOptions, writer:anytype) !void {
        _=fmt; _=options;
        try writer.print("Vec3({d:.2},{d:.2},{d:.2})", .{self.x,self.y,self.z});
    }
};

pub fn main) !void {
    var buf: [10]i32 = undefined;
    var stack = Stack(i32){ .items=&buf, .len=0 };
    try stack.push(42);
    try stack.push(99);
    std.debug.print("pop: {}\n", .{stack.pop().?});

    const a = Vec3{.x=1,.y=2,.z=3};
    const b = Vec3{.x=4,.y=5,.z=6};
    std.debug.print("a+b={} |a|={d:.2}\n", .{Vec3.add(a,b), a.length()});
}

性能分析

泛型通过comptime单态化,每个类型实例化一份代码,零虚调开销。@This()在编译期替换为实际类型。方法编译为普通函数,self按值或指针传递。

枚举

const Color = enum { red, green, blue };

// 指定标签类型
const Opcode = enum(u8) {
    NOP=0x00, LOAD=0x01, STORE=0x02,
    ADD=0x03, SUB=0x04, HALT=0xFF,
};

// 带方法的枚举
const HttpStatus = enum(u16) {
    ok=200, not_found=404, server_error=500,
    pub fn isSuccess(self:HttpStatus) bool {
        const code = @intFromEnum(self);
        return code >= 200 and code < 300;
    }
};

// 非穷举枚举——接收任意底层值
const Token = enum(u8) { Ident, Number, String, _, };

pub fn main() !void {
    const c = Color.red;
    std.debug.print("HALT=0x{X:0>2}\n", .{@intFromEnum(Opcode.HALT)});
    const op = std.meta.intToEnum(Opcode, 0x03) catch unreachable;
    std.debug.print("名称: {s}\n", .{@tagName(c)});
    inline for (std.meta.fields(Color)) |field| {
        std.debug.print("Color.{s}\n", .{field.name});
    }
}

枚举类型对比

类型底层值穷举用途
enum自动一般枚举
enum(u8)指定协议/指令
enum(u8){..., _}指定外部数据

标签联合体

// 标签联合体——类型安全变体
const Value = union(enum) {
    int_val: i64,
    float_val: f64,
    string_val: []const u8,
    bool_val: bool,
};

pub fn main() !void {
    var val = Value{ .int_val=42 };
    switch (val) {
        .int_val => |v| std.debug.print("整数: {}\n", .{v}),
        .float_val => |v| std.debug.print("浮点: {d}\n", .{v}),
        .string_val => |v| std.debug.print("字符串: {s}\n", .{v}),
        .bool_val => |v| std.debug.print("布尔: {}\n", .{v}),
    }
    // 指针捕获修改
    switch (val) {
        .int_val => |*v| v.* += 1,
        else => {},
    }
}
标签联合体是Rust的enum+data等价物,但无借用检查。switch必须穷举,编译器保证类型安全。访问错误字段会在Debug模式panic。

packed struct

const Flags = packed struct {
    carry: bool, zero: bool, interrupt: bool,
    decimal: bool, overflow: bool, _pad: u3=0,
};

const CtrlReg = packed struct {
    enable: u1, mode: u2, prescaler: u3, _reserved: u2,
};

pub fn main() !void {
    var flags = Flags{.carry=true,.zero=false,.interrupt=true,.decimal=false,.overflow=false};
    flags.carry = false;
    const bits: u8 = @bitCast(flags);
    std.debug.print("标志: 0b{b:0>8}\n", .{bits});

    const reg = CtrlReg{.enable=1,.mode=2,.prescaler=5,._reserved=0};
    std.debug.print("寄存器: 0x{X:0>2}\n", .{@as(u8, @bitCast(reg))});
}

性能分析

packed struct无填充,与C布局兼容。位字段编译为掩码+移位,与手动位操作等价。适合寄存器映射和协议头解析。

练习

📝练习1:链表

泛型双向链表push/pop/insert

📝练习2:JSON值

标签联合体JsonValue支持6种类型

📝练习3:CPU标志

packed struct模拟x86 EFLAGS

📝练习4:状态机

枚举+标签联合体词法分析器

📝练习5:IPv4头

packed struct解析20字节头部

🏆成就:数据结构工匠