结构体(struct)
基本定义与初始化
const Point = struct {
x: f32,
y: f32,
};
// 初始化:所有字段都必须指定(或有默认值)
const p1 = Point{ .x = 1.0, .y = 2.0 };
const p2: Point = .{ .x = 3.0, .y = 4.0 }; // 类型推断
// 带默认值的字段
const Config = struct {
host: []const u8 = "localhost",
port: u16 = 8080,
debug: bool = false,
};
const cfg = Config{}; // 使用所有默认值
const custom = Config{ .port = 3000, .debug = true };
_ = p1; _ = p2; _ = cfg; _ = custom;
方法与 Self
const Vec2 = struct {
x: f32,
y: f32,
// 方法:第一个参数通常是 self 或 *Self
pub fn length(self: Vec2) f32 {
return std.math.sqrt(self.x * self.x + self.y * self.y);
}
pub fn scale(self: *Vec2, factor: f32) void {
self.x *= factor;
self.y *= factor;
}
pub fn add(a: Vec2, b: Vec2) Vec2 {
return .{ .x = a.x + b.x, .y = a.y + b.y };
}
// 静态方法(无 self 参数)
pub fn zero() Vec2 {
return .{ .x = 0, .y = 0 };
}
};
var v = Vec2{ .x = 3.0, .y = 4.0 };
std.debug.print("length: {d}\n", .{v.length()}); // 5.0
v.scale(2.0);
std.debug.print("scaled: ({d}, {d})\n", .{ v.x, v.y });
元组结构体
// 没有字段名的结构体(元组)
const Pair = struct { i32, f64 };
const pair = Pair{ 42, 3.14 };
// 通过索引访问字段
std.debug.print("first: {d}, second: {d}\n", .{ pair[0], pair[1] });
枚举(enum)
基本枚举
const Direction = enum { north, south, east, west };
const dir = Direction.north;
const same = .north; // 类型推断
// 枚举的整数值
const Status = enum(u8) {
ok = 200,
not_found = 404,
error_ = 500,
};
// 枚举 → 整数
const code: u8 = @intFromEnum(Status.ok); // 200
// 整数 → 枚举(可能失败)
const status = try std.meta.intToEnum(Status, 404); // Status.not_found
_ = dir; _ = same; _ = code; _ = status;
// 枚举方法
const Color = enum {
red, green, blue,
pub fn hex(self: Color) u24 {
return switch (self) {
.red => 0xFF0000,
.green => 0x00FF00,
.blue => 0x0000FF,
};
}
};
std.debug.print("red hex: #{x:0>6}\n", .{Color.red.hex()});
联合类型(union)
普通 union(C 风格)
// 普通 union:字段共享内存,不追踪当前活跃字段(不安全)
const Num = union {
int: i64,
float: f64,
uint: u64,
};
var n = Num{ .int = 42 };
// 访问错误字段会导致未定义行为(debug 模式会 panic)
std.debug.print("int: {d}\n", .{n.int});
tagged union:安全的联合类型
// union(enum):带标签的联合,运行时追踪当前活跃字段
const Value = union(enum) {
int: i64,
float: f64,
string: []const u8,
boolean: bool,
nil, // void 变体,不携带数据
};
fn printValue(v: Value) void {
switch (v) {
.int => |i| std.debug.print("int: {d}\n", .{i}),
.float => |f| std.debug.print("float: {d}\n", .{f}),
.string => |s| std.debug.print("string: {s}\n", .{s}),
.boolean=> |b| std.debug.print("bool: {}\n", .{b}),
.nil => std.debug.print("nil\n", .{}),
}
}
const v1 = Value{ .int = 42 };
const v2 = Value{ .string = "hello" };
const v3 = Value.nil;
printValue(v1);
printValue(v2);
printValue(v3);
// 修改 union(必须整体替换活跃字段)
var val = Value{ .int = 10 };
val = .{ .float = 3.14 }; // 切换到 float 字段
extern struct 与 packed struct
extern struct:C ABI 兼容布局
// extern struct:字段按 C ABI 规则对齐,用于与 C 代码交互
const CPoint = extern struct {
x: c_float,
y: c_float,
};
// 可以直接传递给 C 函数
extern fn c_draw_point(p: CPoint) void;
// c_draw_point(.{ .x = 1.0, .y = 2.0 });
// 确认内存布局(@sizeOf 和 @offsetOf)
std.debug.print("size: {d}, x offset: {d}\n",
.{ @sizeOf(CPoint), @offsetOf(CPoint, "x") });
packed struct:位字段控制
// packed struct:字段紧密排列,无填充字节,支持位字段
const Flags = packed struct(u8) { // 整体是 1 字节
read: u1, // 1 位
write: u1, // 1 位
execute: u1, // 1 位
_pad: u5, // 5 位填充(对齐到字节)
};
const rwx = Flags{ .read = 1, .write = 1, .execute = 1, ._pad = 0 };
std.debug.print("flags size: {d} bytes\n", .{@sizeOf(Flags)}); // 1
std.debug.print("read={d} write={d} execute={d}\n",
.{ rwx.read, rwx.write, rwx.execute });
// packed struct 转整数(按位解释)
const byte: u8 = @bitCast(rwx);
std.debug.print("as byte: 0b{b:0>8}\n", .{byte});
packed struct 的限制
packed struct 中的字段必须是整数、布尔值、其他 packed struct/union 或枚举。不能包含指针(指针有对齐要求)或浮点数(某些平台限制)。packed struct 主要用于协议头、硬件寄存器映射等需要精确位控制的场景。
结构体与枚举的反射
// 在 comptime 检查类型信息
const TypeInfo = @typeInfo(Vec2); // 返回 std.builtin.Type
switch (TypeInfo) {
.Struct => |s| {
std.debug.print("字段数: {d}\n", .{s.fields.len});
for (s.fields) |field| {
std.debug.print("字段: {s}\n", .{field.name});
}
},
else => {},
}
泛型结构体:参数化类型
Zig 通过 comptime 参数实现泛型,而不是专门的泛型语法。函数可以返回类型,这是 Zig 泛型的核心机制:
// 泛型栈:返回不同元素类型的 Stack 结构体
pub fn Stack(comptime T: type) type {
return struct {
items: std.ArrayList(T),
const Self = @This(); // 引用当前类型
pub fn init(allocator: std.mem.Allocator) Self {
return .{ .items = std.ArrayList(T).init(allocator) };
}
pub fn deinit(self: *Self) void {
self.items.deinit();
}
pub fn push(self: *Self, value: T) !void {
try self.items.append(value);
}
pub fn pop(self: *Self) ?T {
if (self.items.items.len == 0) return null;
return self.items.pop();
}
pub fn len(self: Self) usize {
return self.items.items.len;
}
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// 使用泛型 Stack:实例化时传入类型参数
var int_stack = Stack(i32).init(gpa.allocator());
defer int_stack.deinit();
try int_stack.push(1);
try int_stack.push(2);
try int_stack.push(3);
while (int_stack.pop()) |val| {
std.debug.print("{d}\n", .{val}); // 3, 2, 1(后进先出)
}
// 字符串栈
var str_stack = Stack([]const u8).init(gpa.allocator());
defer str_stack.deinit();
try str_stack.push("hello");
try str_stack.push("world");
}
接口模拟:鸭子类型与 anytype
Zig 没有传统 OOP 的接口,但通过 anytype 参数和 comptime 可以实现类似效果:
// anytype:接受任何实现了 write 方法的类型
fn writeLine(writer: anytype, line: []const u8) !void {
try writer.writeAll(line);
try writer.writeByte('\n');
}
// 同一函数可用于 stdout、文件、内存缓冲区等任何 Writer
pub fn main() !void {
// 写入标准输出
const stdout = std.io.getStdOut().writer();
try writeLine(stdout, "Hello, stdout!");
// 写入内存缓冲区
var buf: [256]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
try writeLine(fbs.writer(), "Hello, buffer!");
std.debug.print("Buffer content: {s}\n", .{fbs.getWritten()});
}
// 编译期检查:如果类型没有 writeAll 方法,编译报错(而非运行时错误)
// 这比动态类型的"运行时找不到方法"更安全
完整实战:JSON-like 值类型
用 tagged union 实现一个能表示任意嵌套值的动态类型:
const std = @import("std");
const Value = union(enum) {
null_val,
bool_val: bool,
int_val: i64,
float_val: f64,
string_val: []const u8,
array_val: []const Value, // 嵌套数组
pub fn isNull(self: Value) bool {
return self == .null_val;
}
pub fn asInt(self: Value) ?i64 {
return switch (self) {
.int_val => |i| i,
.float_val => |f| @intFromFloat(f),
else => null,
};
}
pub fn print(self: Value, depth: usize) void {
switch (self) {
.null_val => std.debug.print("null", .{}),
.bool_val => |b| std.debug.print("{}", .{b}),
.int_val => |i| std.debug.print("{d}", .{i}),
.float_val => |f| std.debug.print("{d}", .{f}),
.string_val => |s| std.debug.print("\"{s}\"", .{s}),
.array_val => |arr| {
std.debug.print("[", .{});
for (arr, 0..) |v, i| {
if (i > 0) std.debug.print(", ", .{});
v.print(depth + 1);
}
std.debug.print("]", .{});
},
}
}
};
pub fn main() void {
const values = [_]Value{
.{ .int_val = 42 },
.{ .string_val = "hello" },
.null_val,
.{ .bool_val = true },
};
const array = Value{ .array_val = &values };
array.print(0); // 输出: [42, "hello", null, true]
}
本章小结
本章核心要点
- struct 是 Zig 的主要数据组织单元:方法直接定义在结构体内部;
self是普通参数,*Self用于可变方法;@This()引用当前类型——是泛型结构体的关键。 - enum 是安全的整数集合:可以指定底层类型(如
enum(u8));用@intFromEnum/std.meta.intToEnum与整数转换;支持定义方法。 - union(enum) 是 Zig 的 sum type:比 C union 安全,运行时追踪活跃字段;switch 必须穷举所有变体(编译器检查);比 OOP 多态更高效(无虚表)。
- extern struct / packed struct 控制内存布局:extern 用于 C 互操作(C ABI 对齐);packed 用于位字段(硬件寄存器、协议头);普通 struct 布局由编译器优化。
- 泛型通过函数返回 type 实现:
fn MyGeneric(comptime T: type) type { return struct {...}; };比模板或泛型语法更统一——类型是第一等值。 - anytype 实现鸭子类型:函数接受 anytype 参数,调用其方法;类型在编译期推断,没有运行时开销;不满足接口的类型在编译期报错。