Ziglings 笔记 80: 匿名结构体 (Anonymous Structs)
类型工厂
在 Ziglings 的第 80 个练习中,我们终于揭开了 Zig 泛型数据结构的神秘面纱。
在其他语言中,你可能习惯了 class List<T> { ... }。
在 Zig 中,泛型不是一种特殊的语法,而是普通的代码逻辑。既然函数可以返回整数、字符串,为什么不能返回一个 struct 类型呢?
挑战:制造圆圈
我们需要定义两个圆:
circle1:存储i32类型的坐标和半径。circle2:存储f32类型的坐标和半径。
我们有一个通用的生成器函数 Circle(T)。
解决方案
我们需要调用这个函数来获取类型,并立即初始化它:
const print = @import("std").debug.print;
// 这是一个“类型工厂”函数
// 输入:一个类型 T (comptime)
// 输出:一个具体的 struct 类型
fn Circle(comptime T: type) type {
return struct {
center_x: T,
center_y: T,
radius: T,
};
}
pub fn main() void {
// 1. 创建 i32 版本的圆
// Circle(i32) 返回了一个类型。
// 我们紧接着用 { ... } 初始化了这个类型的一个实例。
const circle1 = Circle(i32){
.center_x = 25,
.center_y = 70,
.radius = 15,
};
// 2. 创建 f32 版本的圆
const circle2 = Circle(f32){
.center_x = 25.234,
.center_y = 70.999,
.radius = 15.714,
};
// 打印类型名称
// 编译器会自动为这些匿名结构体生成名字,通常格式为文件名.函数名(参数)
// 例如: main.Circle(i32)
print("[{s}: {},{},{}] ", .{
stripFname(@typeName(@TypeOf(circle1))),
circle1.center_x,
circle1.center_y,
circle1.radius,
});
print("[{s}: {d:.1},{d:.1},{d:.1}]\n", .{
stripFname(@typeName(@TypeOf(circle2))),
circle2.center_x,
circle2.center_y,
circle2.radius,
});
}
fn stripFname(mytype: []const u8) []const u8 {
// 硬编码切片,仅用于演示,实际项目中不要这样做
return mytype[22..];
}
核心知识点总结
1. 结构体即表达式
在 Zig 中,struct { ... } 就像 1 + 1 一样是一个表达式。
你可以把它赋值给变量:
const Point = struct { x: u8, y: u8 };
你也可以直接返回它:
return struct { x: u8, y: u8 };
2. 泛型的本质
Zig 的泛型本质上就是 Type Function (类型函数)。
当我们调用 Circle(i32) 时,编译器在编译期执行这个函数,生成了一个包含 i32 字段的新结构体。因为是编译期执行,所以没有运行时开销。
3. 类型名称的奥秘
如果你打印 @typeName(Circle(i32)),你会发现 Zig 给它起了一个很有意义的名字,比如 exercises.080_anonymous_structs.Circle(i32)。这对于调试非常有帮助,让你一眼就能看出这是一个由哪个函数生成的泛型类型。
后续预告:我们已经掌握了匿名结构体。接下来的练习将进入 匿名列表 (Anonymous Lists),也就是我们常说的元组 (Tuples)。如果你想一次性返回多个值,或者创建一个包含不同类型元素的数组,匿名列表是你的好帮手。