读文件直到最后

逐行读取文本文件

ifstream 文档中通常不清楚逐行读取文本文件直到结尾的正确方法。让我们考虑初学者 C++程序员所做的一些常见错误,以及读取文件的正确方法。

没有空格字符的行

为简单起见,我们假设文件中的每一行都不包含空格符号。

ifstreamoperator bool(),当流没有错误并准备好读取时返回 true。此外,ifstream::operator >> 返回对流本身的引用,因此我们可以使用非常优雅的语法在一行中读取和检查 EOF(以及错误):

std::ifstream ifs("1.txt");
std::string s;
while(ifs >> s) {
    std::cout << s << std::endl;
}

带有空格字符的行

ifstream::operator >> 读取流直到出现任何空格字符,因此上面的代码将在单独的行上打印一行中的单词。要阅读所有内容直到行尾,请使用 std::getline 而不是 ifstream::operator >>getline 返回对其使用的线程的引用,因此可以使用相同的语法:

while(std::getline(ifs, s)) {
    std::cout << s << std::endl;
}

显然,std::getline 也应该用于读取单行文件直到结束。

一次将文件读入缓冲区

最后,让我们从开头到结尾读取文件而不停止任何字符,包括空格和换行符。如果我们知道确切的文件大小或长度的上限是可以接受的,我们可以调整字符串的大小,然后阅读:

s.resize(100);
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
    s.begin());

否则,我们需要将每个字符插入字符串的末尾,因此 std::back_inserter 是我们需要的:

std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
    std::back_inserter(s));

或者,可以使用带有迭代器范围参数的构造函数初始化具有流数据的集合:

std::vector v(std::istreambuf_iterator<char>(ifs),
    std::istreambuf_iterator<char>());

请注意,如果将 ifs 作为二进制文件打开,这些示例也适用:

std::ifstream ifs("1.txt", std::ios::binary);

复制流

可以使用流和迭代器将文件复制到另一个文件:

std::ofstream ofs("out.file");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
    std::ostream_iterator<char>(ofs));
ofs.close();

或使用兼容接口重定向到任何其他类型的流。例如 Boost.Asio 网络流:

boost::asio::ip::tcp::iostream stream;
stream.connect("example.com", "http");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
    std::ostream_iterator<char>(stream));
stream.close();

数组

由于迭代器可能被认为是指针的泛化,上面示例中的 STL 容器可能会被原生数组替换。以下是如何将数字解析为数组:

int arr[100];
std::copy(std::istream_iterator<char>(ifs), std::istream_iterator<char>(), arr);

请注意缓冲区溢出,因为阵列在分配后无法即时调整大小。例如,如果上面的代码将包含一个包含 100 个以上整数的文件,它将尝试在数组外写入并运行到未定义的行为。