【问题标题】:What's preferred pattern for reading lines from a file in C++?从 C++ 文件中读取行的首选模式是什么?
【发布时间】:2011-08-28 04:36:46
【问题描述】:

我在 C++ 教程中看到了至少两种从文件中读取行的方法:

std::ifstream fs("myfile.txt");
if (fs.is_open()) {
  while (fs.good()) {
    std::string line;
    std::getline(fs, line);
    // ...

和:

std::ifstream fs("myfile.txt");
std::string line;
while (std::getline(fs, line)) {
  // ...

当然,我可以添加一些检查以确保文件存在并且已打开。除了异常处理之外,是否有理由更喜欢更详细的第一种模式?你的标准做法是什么?

【问题讨论】:

  • 这不是相当完全相同的副本,但previous question 的答案是相关的。

标签: c++ file-io


【解决方案1】:
while (std::getline(fs, line))
{}

这不仅正确,而且更可取 因为它是惯用的。

我假设在第一种情况下,您不会在 std::getline() 之后检查 fsif(!fs) break; 或类似的东西。因为如果你不这样做,那么第一种情况是完全错误的。或者如果你这样做,那么第二个仍然是更可取的,因为它在逻辑上更简洁明了。

函数good() 应该在您尝试从流中读取之后使用;它用于检查尝试是否成功。在你的第一种情况下,你不这样做。在std::getline() 之后,您假设读取成功,甚至没有检查fs.good() 返回的内容。此外,您似乎假设如果fs.good() 返回true,std::getline 将成功地从流中读取一行。你正朝着相反的方向前进:事实是,如果std::getline 成功地从流中读取了一行,那么fs.good() 将返回true

cplusplus 的文档中提到了 good()

如果没有设置流的错误标志(eofbit、failbit 和 badbit),则该函数返回 true。

也就是说,当您尝试从输入流中读取数据时,如果尝试失败,则只会设置失败标志并且good() 返回false 作为失败的指示。

如果您想将line 变量的范围限制在循环内部,那么您可以将for 循环编写为:

for(std::string line; std::getline(fs, line); )
{
   //use 'line'
}

注意:在阅读@john 的解决方案后,我想到了这个解决方案,但我认为它比他的版本更好。


在此处阅读详细说明,为什么第二个更可取且惯用:

或阅读@Jerry Coffin 撰写的这篇写得很好的博客:

【讨论】:

  • 哎呀,对我来说最热门的谷歌搜索是cplusplus.com/doc/tutorial/files 方法名称 good() 到底是如何成为标准的? :)
  • @jda:函数good()应该在你尝试从流中读取之后使用;它用于检查尝试是否成功。在你的第一种情况下,你不这样做。在std::getline() 之后,您假设读取成功,甚至没有检查fs.good() 返回的内容。此外,您似乎假设如果fs.good() 返回true,std::getline 将成功地从流中读取一行。你正朝着相反的方向前进:事实是,如果std::getline 成功地从流中读取了一行,那么fs.good() 将返回true
  • @jda:我在回答中添加了更多解释。请阅读。
  • @Nawaz:限制范围且仍保留使用标准习语的另一种方法是将声明和循环放入一个块中:{ std::line line; while (getline(fs, line)) {...} }。并不是说我会使用它;我只是使用标准的成语。顺便说一句,非常好的帖子。
  • 函数good()应该永远被使用。如果前面的输入成功,它可能返回也可能不返回true;你不知道也不能指望它。如果前面的输入失败,它会返回false,所以调用它没有意义。
【解决方案2】:

将此视为对 Nawaz 已经非常出色的答案的扩展评论。

关于您的第一个选项,

while (fs.good()) {
  std::string line;
  std::getline(fs, line);
  ...

这有多个问题。第一个问题是while 条件在错误的位置并且是多余的。它在错误的位置,因为fs.good() 指示最近对文件执行的操作是否正常。一段时间条件应该是关于即将到来的动作,而不是之前的动作。无法知道即将对文件执行的操作是否正常。即将采取什么行动? fs.good() 不会读取您的代码来查看即将执行的操作。

第二个问题是您忽略了来自std::getline() 的返回状态。如果您立即使用fs.good() 检查状态,那没关系。所以,稍微修正一下,

while (true) {
  std::string line;
  if (std::getline(fs, line)) {
    ...
  }
  else {
     break;
  }
}

或者,您可以使用if (! std::getline(fs, line)) { break; },但现在您在循环中间有一个break。叶赫。如果可能的话,让退出条件成为循环语句本身的一部分会好得多。

比较一下

std::string line;
while (std::getline(fs, line)) {
  ...
}

这是从文件中读取行的标准习语。 C 中存在一个非常相似的习语。这个习语非常古老,使用非常广泛,并且被广泛认为是从文件中读取行的正确方法。

如果您来自一家禁止带有副作用的条件句的商店怎么办? (有很多很多的编程标准都可以做到这一点。)有一种方法可以解决这个问题,而无需使用循环中间的中断方法:

std::string line;
for (std::getline(fs, line); fs.good(); std::getline(fs, line)) {
  ...
}

不像 break 方法那么难看,但大多数人都会同意这不像标准习语那样好看。

我的建议是使用标准成语,除非某些标准白痴禁止使用它。

附录
关于for (std::getline(fs, line); fs.good(); std::getline(fs, line)):这很丑,有两个原因。一个是明显的重复代码块。

不太明显的是调用getline 然后good 会破坏原子性。如果其他线程也在从文件中读取怎么办?现在这不是很重要,因为 C++ I/O 当前不是线程安全的。它将在即将到来的 C++11 中。仅仅为了让标准的执行者高兴而破坏原子性是灾难的根源。

【讨论】:

  • 如果有人认为你的最后一个例子比倒数第二个更好,那他们真的是一个“标准白痴”。代码重复很糟糕。
  • @john:不幸的是,有很多人确实认为我的最后一个例子更可取。少量的复制代码并没有像原子性的损失那样困扰我。相同的概念适用于应该在多线程环境中执行复杂事务的函数(但有时事务会失败)。有很多地方不仅可以接受带有副作用的编程,而且是唯一可行的方法。
  • idium 继承自旧语言,实际上是通用的
  • @Martin:我说过:“C 中存在一个非常相似的习语。这个习语非常古老,使用非常广泛,并且被广泛认为是 的正确方法从文件中读取行。”
【解决方案3】:

其实我更喜欢另一种方式

for (;;)
{
  std::string line;
  if (!getline(myFile, line))
    break;
  ...
}

对我来说它读起来更好,并且字符串的范围正确(即在使用它的循环内,而不是循环外)

但是在你写的两个中,第二个是正确的。

【讨论】:

  • 对我来说,中间有一个中断的无限循环是一种反模式。
  • 如果你关心字符串变量的作用域,那你为什么不直接写for(string line; getline(fs,line); ) {}
  • @David Hammen:我想你会在顶部打破。范围界定问题才是真正的收获。但是你去吧,风格问题,人们永远不会同意。
  • @Nawaz:我想是因为当我开始编写 C++ 编程时,这是不可能的。但好点。
  • @David Hammen,我曾经对 Pascal 进行编程,Pascal 没有中断,而您永远都看到过这种循环 code; while (condition) { ...; code; } 现在这是一种反模式,因为重复code。现在你可以用while (function(...)) { ... } 清理它,其中函数包含code;condition,或者你可以按照我的方式for (;;) { code; if (!condition) break; ... }。对我来说,两者都是合理的,取决于哪个产生更清晰的结果,这当然是主观的。
【解决方案4】:

第一个在每个循环中释放并重新分配字符串,浪费时间。
第二次将字符串写入已经存在的空间,删除了释放和重新分配,使其实际上比第一次更快(更好)。

【讨论】:

  • 谢谢。不过,我对此并不担心。我总是可以将声明移到循环之外,但我被告知(我可能错了)C++ 编译器在分配方面很聪明,最佳做法是将声明移到尽可能接近用法的位置。
  • @jda,我不确定编译器是否足够聪明来优化分配(我可能是错的),但你的第二点很重要。除非您也有明确的需求,否则不要进行微优化,首先编写干净的代码。
  • 一个甚至还不错的优化编译器将摆脱重复的分配和释放。除非您在关闭优化的情况下进行编译,否则这不是问题。当您在关闭优化的情况下编译 C++ 时,您会遇到很多性能问题,这只是这些问题的一小部分。
  • @David Hammen:实际上没有。使用 LLVM 编译器,我使用 -O3 优化测试了编译器程序集输出,发现当字符串在循环中时,new 在循环中被调用,而当字符串不在循环中时,只调用一个。供参考,代码为pastebin.com/6ErddNSS
【解决方案5】:

试试这个 =>

// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {
  string line;
  ifstream myfile ("example.txt");
  if (myfile.is_open())
  {
    while ( myfile.good() )
    {
      getline (myfile,line);
      cout << line << endl;
    }
    myfile.close();
  }

  else cout << "Unable to open file"; 

  return 0;
}

【讨论】:

  • 让我猜猜。你去 cplusplus.com 复制了这个。那个网站有很多错误,包括这个。
猜你喜欢
  • 2014-06-09
  • 1970-01-01
  • 1970-01-01
  • 2013-08-23
  • 1970-01-01
  • 1970-01-01
  • 2021-08-28
  • 2011-05-19
  • 2011-05-12
相关资源
最近更新 更多