Zig 结构体教程
1. 结构体基础
Section titled “1. 结构体基础”1.1 定义和实例化
Section titled “1.1 定义和实例化”// 声明结构体类型const Point = struct { x: f32, y: f32,};
// 创建实例(使用匿名结构体字面量)const p: Point = .{ .x = 0.12, .y = 0.34,};
// 或明确指定类型const q = Point{ .x = 1.0, .y = 2.0 };1.2 重要特性
Section titled “1.2 重要特性”- 字段顺序:编译器决定,不保证与定义一致
- 大小和对齐:字段保证 ABI 对齐
- 零字段结构体:允许且大小为 0
2. 结构体方法
Section titled “2. 结构体方法”2.1 命名空间函数
Section titled “2.1 命名空间函数”const Vec3 = struct { x: f32, y: f32, z: f32,
// 构造方法 pub fn init(x: f32, y: f32, z: f32) Vec3 { return Vec3{ .x = x, .y = y, .z = z, }; }
// 实例方法(显式 self 参数) pub fn dot(self: Vec3, other: Vec3) f32 { return self.x * other.x + self.y * other.y + self.z * other.z; }
// 静态方法 pub fn zero() Vec3 { return Vec3.init(0, 0, 0); }};
test "methods" { const v1 = Vec3.init(1.0, 0.0, 0.0); const v2 = Vec3.init(0.0, 1.0, 0.0);
// 点语法调用 try expect(v1.dot(v2) == 0.0);
// 或作为命名空间函数调用 try expect(Vec3.dot(v1, v2) == 0.0);}2.2 结构体中的声明
Section titled “2.2 结构体中的声明”结构体可以包含常量、变量、类型等声明:
const MathConstants = struct { pub const PI = 3.141592653589793; pub const E = 2.718281828459045;
var callCount: u32 = 0; // 容器级变量
pub fn increment() void { callCount += 1; }};
test "struct declarations" { try expect(MathConstants.PI > 3.0); MathConstants.increment();}3. 字段父指针
Section titled “3. 字段父指针”3.1 从字段指针获取结构体指针
Section titled “3.1 从字段指针获取结构体指针”fn setYBasedOnX(x: *f32, y: f32) void { const point: *Point = @fieldParentPtr("x", x); point.y = y;}
test "field parent pointer" { var point = Point{ .x = 0.1, .y = 0.2 }; setYBasedOnX(&point.x, 0.3); try expect(point.y == 0.3);}3.2 使用场景
Section titled “3.2 使用场景”- 当只有字段指针时需要访问整个结构体
- 回调函数中获取上下文
- 底层系统编程
4. 泛型结构体
Section titled “4. 泛型结构体”4.1 返回结构体类型的函数
Section titled “4.1 返回结构体类型的函数”fn LinkedList(comptime T: type) type { return struct { pub const Node = struct { prev: ?*Node, next: ?*Node, data: T, };
first: ?*Node, last: ?*Node, len: usize,
pub fn init() @This() { return @This(){ .first = null, .last = null, .len = 0, }; } };}
test "generic struct" { const IntList = LinkedList(i32); const list = IntList.init(); try expect(list.len == 0);
// 编译期函数会被记忆化 try expect(LinkedList(i32) == LinkedList(i32));}4.2 类型作为一等公民
Section titled “4.2 类型作为一等公民”// 可以将类型赋值给变量const ListType = LinkedList(f64);const list: ListType = .{ .first = null, .last = null, .len = 0,};5. 默认字段值
Section titled “5. 默认字段值”5.1 基本用法
Section titled “5.1 基本用法”const Config = struct { timeout: u32 = 5000, // 默认值 5000 retries: u8 = 3, // 默认值 3 debug: bool = false, // 默认值 false};
test "default values" { // 可以省略有默认值的字段 const config = Config{ .debug = true, // 只覆盖这个字段 }; try expect(config.timeout == 5000); try expect(config.retries == 3); try expect(config.debug == true);}5.2 危险的反模式
Section titled “5.2 危险的反模式”不要在不满足数据不变量的结构体中使用默认值:
// ❌ 危险的例子const Threshold = struct { minimum: f32 = 0.25, maximum: f32 = 0.75, // 必须 >= minimum
fn isValid(self: Threshold) bool { return self.maximum >= self.minimum; }};
// 用户可能创建无效的实例var bad = Threshold{ .maximum = 0.1 }; // 违反不变量!5.3 正确的做法
Section titled “5.3 正确的做法”const Threshold = struct { minimum: f32, maximum: f32, // 必须 >= minimum
// 提供命名默认值 pub const default: Threshold = .{ .minimum = 0.25, .maximum = 0.75, };
// 或提供构造函数 pub fn init(min: f32, max: f32) !Threshold { if (max < min) return error.InvalidThreshold; return Threshold{ .minimum = min, .maximum = max, }; }};6. 外部结构体 (extern struct)
Section titled “6. 外部结构体 (extern struct)”6.1 使用场景
Section titled “6.1 使用场景”// 匹配 C ABI 的内存布局const CPoint = extern struct { x: f32, y: f32,};
// 用于 FFI(外部函数接口)extern "c" fn get_point() CPoint;
// 普通结构体更适合 Zig 内部使用const ZigPoint = struct { x: f32, y: f32,};建议: 只在需要明确的内存布局时使用 extern struct。
7. 压缩结构体 (packed struct)
Section titled “7. 压缩结构体 (packed struct)”7.1 基本概念
Section titled “7.1 基本概念”压缩结构体基于整数位解释,有明确的内存布局:
const Color = packed struct { r: u5, // 5位 g: u6, // 6位 b: u5, // 5位 a: u1, // 1位(alpha) // 总共 17位,但实际使用 32位(下一个对齐边界)};
const FlagSet = packed struct(u8) { // 显式指定 8位 flag1: bool, // 1位 flag2: bool, // 1位 flag3: bool, // 1位 _reserved: u5 = 0, // 5位保留};7.2 支持的字段类型
Section titled “7.2 支持的字段类型”- 整数:使用指定位宽
- bool:使用 1 位
- 枚举:使用其标签类型的位宽
- 压缩联合体:使用最大字段的位宽
- 压缩结构体:使用其基础整数
7.3 位转换
Section titled “7.3 位转换”const Full = packed struct { number: u16,};
const Divided = packed struct { half1: u8, quarter3: u4, quarter4: u4,};
test "bitCast between packed structs" { const full = Full{ .number = 0x1234 }; const divided: Divided = @bitCast(full);
// 小端序:0x1234 存储为 [0x34, 0x12] try expect(divided.half1 == 0x34); try expect(divided.quarter3 == 0x2); // 高4位中的 0x2 try expect(divided.quarter4 == 0x1); // 高4位中的 0x1}7.4 非字节对齐字段的指针
Section titled “7.4 非字节对齐字段的指针”const BitField = packed struct { a: u3, b: u3, c: u2,};
test "non-byte-aligned field pointers" { var field = BitField{ .a = 1, .b = 2, .c = 3 }; const ptr = &field.b; // ✅ 允许取地址
// 所有字段共享相同地址(都在同一字节内) try expect(@intFromPtr(&field.a) == @intFromPtr(&field.b));
// 但类型不同(包含位偏移信息) // &field.b 的类型是 *align(1:3:1) u3}限制: 非字节对齐指针不能传递给期望普通指针的函数。
7.5 偏移量函数
Section titled “7.5 偏移量函数”test "offsets" { comptime { // 位偏移 try expect(@bitOffsetOf(BitField, "a") == 0); try expect(@bitOffsetOf(BitField, "b") == 3); try expect(@bitOffsetOf(BitField, "c") == 6);
// 字节偏移(都在第0字节) try expect(@offsetOf(BitField, "a") == 0); try expect(@offsetOf(BitField, "b") == 0); try expect(@offsetOf(BitField, "c") == 0); }}7.6 对齐控制
Section titled “7.6 对齐控制”// 结构体整体对齐const S = packed struct { a: u32, b: u32,};var foo: S align(4) = .{ .a = 1, .b = 2 };
// 字段单独对齐const AlignedFields = struct { a: u32 align(2), // 2字节对齐 b: u32 align(64), // 64字节对齐};7.7 MMIO 注意事项
Section titled “7.7 MMIO 注意事项”// ✅ 正确做法:一次性写入整个寄存器pub const GpioRegister = packed struct(u8) { GPIO0: bool, GPIO1: bool, GPIO2: bool, GPIO3: bool, reserved: u4 = 0,};
const gpio: *volatile GpioRegister = @ptrFromInt(0x0123);
pub fn setGpio(new_state: GpioRegister) void { gpio.* = new_state; // 原子写入整个寄存器}
// ❌ 错误做法:逐个字段设置(非原子)fn badSetGpio() void { gpio.GPIO0 = true; // 可能与其他字段写入冲突 gpio.GPIO1 = false;}8. 结构体命名规则
Section titled “8. 结构体命名规则”8.1 自动命名
Section titled “8.1 自动命名”Zig 为匿名结构体自动生成名称:
- 变量初始化:
variable: struct_name.main.Foo - 函数返回:
function: struct_name.List(i32) - 匿名:
anonymous: struct_name.main__struct_22691 - 嵌套结构体:
parent.child
pub fn main() void { const Foo = struct {}; // 命名为 "struct_name.main.Foo" const Bar = struct { // 外层结构体 const Inner = struct {}; // 命名为 "struct_name.main.Bar.Inner" };}9. 匿名结构体字面量
Section titled “9. 匿名结构体字面量”9.1 省略类型
Section titled “9.1 省略类型”const Point = struct { x: i32, y: i32 };
// 类型推断const pt: Point = .{ .x = 13, .y = 67 };
// 完全匿名结构体const result = .{ .success = true, .value = 42, .message = "OK",};9.2 直接实例化
Section titled “9.2 直接实例化”没有中间副本,直接实例化到目标位置:
fn createPoint() Point { return .{ // 直接构造返回位置 .x = 10, .y = 20, };}10. 元组 (Tuples)
Section titled “10. 元组 (Tuples)”10.1 无字段名的匿名结构体
Section titled “10.1 无字段名的匿名结构体”// 元组:字段隐式命名为 "0", "1", "2"...const tuple = .{ 1234, 12.34, true, "hi" };
test "tuple operations" { // 访问(使用字符串化的数字) try expect(tuple.@"0" == 1234); try expect(tuple.@"1" == 12.34);
// 索引(编译期已知) try expect(tuple[0] == 1234);
// 长度 try expect(tuple.len == 4);
// 连接和重复 const combined = tuple ++ .{false, true}; const repeated = .{"hello"} ** 3;}10.2 解构元组
Section titled “10.2 解构元组”// 从块中返回多个值const min, const max = blk: { var min_val: i32 = std.math.maxInt(i32); var max_val: i32 = std.math.minInt(i32); // ... 计算 break :blk .{ min_val, max_val };};
// 函数返回元组fn divmod(a: i32, b: i32) struct { i32, i32 } { return .{ a / b, a % b };}
const quotient, const remainder = divmod(10, 3);10.3 内联迭代
Section titled “10.3 内联迭代”test "inline for with tuples" { const values = .{ 1, 2, 3, 4, 5 }; var sum: i32 = 0;
inline for (values) |value| { sum += value; } try expect(sum == 15);}11. 实用模式
Section titled “11. 实用模式”11.1 建造者模式
Section titled “11.1 建造者模式”const Query = struct { select: []const u8 = "*", from: []const u8, where: ?[]const u8 = null, limit: ?u32 = null,
pub fn build(self: Query) []const u8 { // 构建 SQL 查询... }};
// 使用默认值和命名参数const query = Query{ .from = "users", .where = "age > 18", .limit = 100,};11.2 类型状态模式
Section titled “11.2 类型状态模式”const Connection = struct { const Closed = struct { addr: []const u8 }; const Opening = struct { addr: []const u8 }; const Open = struct { fd: std.os.fd_t };
state: union(enum) { closed: Closed, opening: Opening, open: Open, },
pub fn connect(self: *Connection) void { switch (self.state) { .closed => |*closed| { self.state = .{ .opening = .{ .addr = closed.addr } }; // 开始连接... }, else => unreachable, } }};11.3 标记联合包装
Section titled “11.3 标记联合包装”const Result = union(enum) { success: struct { value: i32 }, failure: struct { error: []const u8, code: u32 },
pub fn unwrap(self: Result) i32 { return switch (self) { .success => |s| s.value, .failure => |f| { std.debug.print("Error: {s} (code: {})\n", .{f.error, f.code}); unreachable; }, }; }};12. 最佳实践总结
Section titled “12. 最佳实践总结”- 默认使用普通结构体,只在需要时用
extern或packed - 避免危险的默认值,使用构造函数或命名默认值
- 合理使用方法,提高代码组织性
- 利用元组进行多值返回
- 注意压缩结构体的指针语义
- MMIO 使用一次性写入,避免字段级访问
- 结构体组合优于继承(Zig 没有继承)
- 使用
@This()引用当前结构体类型
通过掌握 Zig 结构体的各种特性,你可以创建高效、类型安全的数据结构,充分利用编译器的优化能力。