读文件直到最后
逐行读取文本文件
从 ifstream
文档中通常不清楚逐行读取文本文件直到结尾的正确方法。让我们考虑初学者 C++程序员所做的一些常见错误,以及读取文件的正确方法。
没有空格字符的行
为简单起见,我们假设文件中的每一行都不包含空格符号。
ifstream
有 operator 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 个以上整数的文件,它将尝试在数组外写入并运行到未定义的行为。