我有一个包含一堆素数的文件。为此,我们可以说这个名为“primes”的文件的内容如下:
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,
我想读取这个文件的内容(稍后再做一些其他不相关的事情)。
我的问题是,读取文件的方式似乎有样板,而且也显得非常业余。这就是我拥有的
const std = @import("std");
const print = std.debug.print;
pub fn main() void {
print("This is the beginning of the program\n", .{});
const file: std.fs.File = std.fs.cwd().openFile(
"primes",
.{}) catch | err | {
print("En arror occured while opening file: {}\n", .{err});
std.os.exit(1);
};
defer file.close();
const reader = file.reader();
var buff: [1024]u8 = undefined;
while(reader.readUntilDelimiterOrEof(&buff, ',')) | number | { // THIS IS A PROBLEM LINE
if(number == null) break; // AND SO IS THIS
print("...running {s}\n", .{buff});
} else | err | {
print("En arror occured while opening file: {}\n", .{err});
}
print("Buffer: {s}\n", .{buff});
}
将值解压到“number”上似乎是多余且几乎愚蠢的,而缓冲区“buff”也将具有相同的内容,除非“number”为空(这将打破循环)。我想知道是否有更简化的方法来做到这一点..
我避免在主函数中使用 !void 的返回值,因为我正在尝试练习错误处理,并且想自己处理它们。
提前谢谢您
发布的代码中存在一些基本问题,我认为它没有像OP预期的那样工作。有一些与处理错误和潜在空值相关的“样板文件”,但这是使用系统编程语言所付出的代价之一。您可以在 C 代码中减少此类样板,但在编写稳健的 C 代码中则不然。
OP 代码打印
buff
的值,它是 u8
的数组,而不是打印 number
的值,这是 readUntilDelimiterOrEof
返回的值。当此函数确实读入缓冲区时,它会根据读取的字节数返回由缓冲区内容形成的切片。如果您打算将这些内容打印为字符串,则需要切片。
问题是,我怀疑这就是OP遇到问题的地方,
readUntilDelimiterOrEof
不返回一个简单的切片,而是返回一个带有可选切片的错误联合。返回值必须被解包(两次)才能使用它的值。
带有有效负载捕获的
while
循环首先解开错误联合,并且 OP 代码基本上正确地处理了这个问题,尽管错误消息似乎放错了地方。 while
循环捕获的有效负载是可选类型。 OP 代码正确地对照 null
进行检查,并在返回 null
时从循环中中断。但要打印可选值中包含的值,必须将可选值展开。您可以使用 .{number orelse unreachable}
代替打印代码中的 .{buff}
来完成此操作,或者您可以使用其简写版本:.{number.?}
。
OP 代码在程序的最后一行打印缓冲区
buff
的内容。这很糟糕,原因有两个。首先, buff
不是切片,不应作为字符串打印。其次,如果在 while
循环中遇到错误,则此行尝试打印可能未初始化的数组的内容。这条线的存在,加上 buff
的大尺寸,让我想知道 OP 是否认为 buff
在循环中读取结果时以某种方式 累积 。它不是; readUntilDelimiterOrEof
每次只是从头开始读入缓冲区。
OP 说他们想“稍后做一些其他不相关的事情”。目前尚不清楚这些不相关的东西是什么,但大概他们想对从文件中读取的数据进行一些其他工作。在编写时,OP 代码不会保留从文件中读取的任何数据。在下一节中,我建议解决此问题的一种可能方法。
这是对已发布代码的最简单的更正:
const std = @import("std");
const print = std.debug.print;
pub fn main() void {
print("This is the beginning of the program\n", .{});
const file: std.fs.File = std.fs.cwd().openFile("primes.dat", .{}) catch |err| {
print("An error occured while opening file: {}\n", .{err});
std.os.exit(1);
};
defer file.close();
const reader = file.reader();
var buff: [1024]u8 = undefined;
while (reader.readUntilDelimiterOrEof(&buff, ',')) |maybe_number| {
if (maybe_number == null) break;
print("...running {s}\n", .{maybe_number.?});
} else |err| {
print("Read error: {}\n", .{err});
}
}
您可以使用另一个有效负载捕获以稍微更详细的方式编写循环,以使双重展开更加明确:
while (reader.readUntilDelimiterOrEof(&buff, ',')) |maybe_number| {
if (maybe_number) |number| {
print("...running {s}\n", .{number});
} else break;
} else |err| {
print("Read error: {}\n", .{err});
}
将整个文件读入缓冲区并使用该缓冲区可能比一次从文件中获取一位更好。这可能是一种更稳健的方法,而且性能可能更高。
splitAny
提供的迭代器迭代这些内容。使用 splitAny
的一个优点是您可以为其提供一段分隔符;这些分隔符中的任何一个都指示缓冲区的内容应在何处分割。对于 OP 数据文件,最好不仅按逗号分割,还按空格和换行符分割。
const std = @import("std");
pub fn main() !void {
// Get an allocator.
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer {
_ = gpa.deinit();
}
// Open a file.
const primes_file = try std.fs.cwd().openFile("primes.dat", .{});
defer primes_file.close();
// Read the file into a buffer.
const stat = try primes_file.stat();
const buffer = try primes_file.readToEndAlloc(allocator, stat.size);
defer allocator.free(buffer);
// Iterate over the buffer.
var numbers = std.mem.splitAny(u8, buffer, " ,\r\n");
while (numbers.next()) |number| {
if (!std.mem.eql(u8, number, "")) {
std.debug.print("{s}\n", .{number});
}
}
}