能否在Zig中直接向ArrayList执行类似sprintf的输出操作?
在Zig中如何直接向ArrayList输出格式化字符串?
Zig标准库提供了std.fmt.allocPrint函数,它接收分配器,将格式化内容输出到分配器分配的缓冲区并返回结果,功能类似动态版的sprintf。
但在需要拼接多个格式化字符串的场景下,目前用allocPrint的方式是先分配内存生成单个格式化字符串,再复制到ArrayList里,这显得有些冗余。我希望能有直接向ArrayList输出格式化内容的函数,示例代码如下(注:示例中的allocPrintToArrayList是虚构函数,实际不存在):
pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); var list = std.ArrayList(u8).init(allocator); defer list.deinit(); const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 }; for (values) |value| { try std.fmt.allocPrintToArrayList(list, "{e} ", .{value}); // 此函数不存在 } _ = list.pop(); // 期望list.items最终包含用空格分隔的values内容 }
请问在Zig中有没有实现该需求的方法?比如,能不能把ArrayList当作分配器传给allocPrint API?
解决方案
方法1:使用std.fmt.format直接写入ArrayList的Writer
Zig的ArrayList实现了std.io.Writer接口,直接用std.fmt.format就能把格式化内容写入它的writer,这是最简洁高效的方式:
pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); var list = std.ArrayList(u8).init(allocator); defer list.deinit(); const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 }; for (values) |value| { try std.fmt.format(list.writer(), "{e} ", .{value}); } if (list.items.len > 0) { _ = list.pop(); } std.debug.print("{s}\n", .{list.items}); }
方法2:使用Zig 0.11+新增的appendPrint方法
从Zig 0.11版本开始,std.ArrayList原生支持appendPrint方法,完全匹配你的需求,代码最简洁:
pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); var list = std.ArrayList(u8).init(allocator); defer list.deinit(); const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 }; for (values) |value| { try list.appendPrint("{e} ", .{value}); } if (list.items.len > 0) { _ = list.pop(); } std.debug.print("{s}\n", .{list.items}); }
方法3:自定义适配Allocator接口的ArrayList包装器(不推荐)
如果确实要通过allocPrint实现,可以包装ArrayList让它满足Allocator接口,但这种方式冗余且没必要,仅作参考:
const ArrayListAllocator = struct { list: *std.ArrayList(u8), const Allocator = std.mem.Allocator; fn alloc(self: *@This(), len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) ?[*]u8 { self.list.ensureTotalCapacity(len) catch return null; const slice = self.list.addManyAsSlice(len) catch return null; return slice.ptr; } fn resize(self: *@This(), buf: []u8, new_len: usize, ret_addr: usize) bool { if (new_len > buf.len) return false; self.list.items.len = self.list.items.len - buf.len + new_len; return true; } fn free(self: *@This(), buf: []u8, ret_addr: usize) void { _ = self; _ = buf; _ = ret_addr; } fn allocator(self: *@This()) Allocator { return .{ .ptr = self, .vtable = &.{ .alloc = alloc, .resize = resize, .free = free, }, }; } }; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer std.debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); var list = std.ArrayList(u8).init(allocator); defer list.deinit(); var list_alloc = ArrayListAllocator{ .list = &list }; const alloc = list_alloc.allocator(); const values = [_]f64{ 5.6, 123.4, 19.6, 18.4 }; for (values) |value| { _ = try std.fmt.allocPrint(alloc, "{e} ", .{value}); } if (list.items.len > 0) { _ = list.pop(); } std.debug.print("{s}\n", .{list.items}); }
总结
优先使用方法1或方法2:方法2是Zig新版本提供的原生能力,代码最简洁;方法1兼容性更好,适配旧版本Zig。方法3属于hack性质,除非特殊场景不推荐使用。
内容的提问来源于stack exchange,提问作者William Ryman
相关产品推荐
相关产品推荐

