2

我试图使用我在 C++ 中学到的关于文件和资源处理的知识:我想编写一个类似diff的实用程序。

这是我的最新版本

#include <iostream>
#include <cstdlib>
#include <fstream>

int main(int argc, char* argv[])
{
  if(argc!=3)
  {
    std::cout << "error: 2 arguments required, now exiting ..." << std::endl;
    exit (EXIT_FAILURE);
  }

  std::ifstream file_1(argv[1]);
  std::ifstream file_2(argv[2]);

  if( file_1.fail() || file_2.fail() )
  {
    std::cout << "error: can't open files, now exiting ..." << std::endl;
    exit (EXIT_FAILURE);
  }

  std::string dummy_1;
  std::string dummy_2;

  while(!file_1.eof()) // dummy condition
  {
    std::getline(file_1,dummy_1);
    std::getline(file_2,dummy_2);
    std::cout << ((dummy_1==dummy_2) ? "= " : "# ") << dummy_1 << std::endl << "  " << dummy_2 << std::endl;
  }

  return(0);
}

这是我的指导方针:

  • 比较2个文件
  • 用户必须将这 2 个文件的名称直接传递给可执行文件,只有这 2 个参数
  • 尽可能多地涵盖C++ 中的错误处理
  • 尽量避免平台特定的步骤或不可移植的代码

我的实际问题是我不知道如何有效地改善我的假人状况。现在,while 迭代只遵循第一个传递文件的长度,我想显然在两个文件中一直向下走并解决这个问题,而不会引入额外的 cicle 之类的过度杀伤力来获取和比较这两个文件的长度做真正的比较。

我也想知道我的方法是否可以被认为是安全的。

最终,我也可以接受提出使用 boost 库的解决方案的答案,因为它们非常便携,而且我已经知道我会出于其他原因使用它们。

谢谢。

4

3 回答 3

3

像往常一样eof()是错误的做法。这有效

while (std::getline(file_1, dummy_1) && std::getline(file_2, dummy_2))
{
    ...
}

建议您阅读eof()真正的作用。它不是你想的那样,但实际上它在这个程序中很有用,因为你可以以正确的方式使用它,告诉你两个文件中的哪个文件已经到达文件末尾。看这里

您实际上可以eof()在该程序中正确使用来找出两个文件中的哪个文件到达文件末尾。我可能会像这样写你的循环

for (;;)
{
    getline(file_1, dummy_1);
    getline(file_2, dummy_2);
    if (file_1.eof() || file_2.eof())
        break;
    ...
}
if (file_1.eof() && file_2.eof())
{
    // both at end of file
}
else if (file_1.eof())
{
    // file 1 at end of file
}
else
{
    // file 2 at end of file
}

请注意,虽然eof()测试是在 之后getline(),而不是之前。这eof()就是应该如何使用的。

于 2012-11-11T15:00:29.857 回答
3

我首先对@Loki Astari 的答案写了相当长的评论,但它足够长(而且,IMO,足够干净的方式来完成这项工作),它可能作为一个独立的答案最有意义。在这种情况下,您想要一些接近标准循环的东西,除非您继续阅读,只要从其中一个文件中读取成功。既然如此,@john 是对的,最好避免将eof()其用作循环条件的一部分。

std::string line1, line2;
static const char *prefixes[] = {"#  ", "=  "};


while (std::getline(file_1, line1) || std::getline(file_2, line2)) std::cout << prefixes[line1==line2] << line1 << "\n " << line2 << "\n";

编辑:@user1802174 提出了一个很好的观点——事实上,循环实际上并没有并行读取数据。由于它使用||which 进行短路评估,因此当/如果从第一个文件读取成功时,它不会从第二个文件中读取任何内容。幸运的是,他在一件事上错了:修复起来相当容易。至少在这种情况下,+工作正常,尽管我们必须明确地将结果转换为bool. 我还添加了一个修复程序,即在失败时,getline字符串的先前内容保持不变,因此我们需要在循环的每次迭代中明确清除字符串以获得所需的行为。

while (line1.clear(), line2.clear(), 
      (bool)std::getline(file_1, line1) + (bool)std::getline(file_2, line2))
{
    std::cout << prefixes[line1==line2] << line1 << "\n   " << line2 << "\n";
}

这次我做了一个快速测试。文件 1:

line1
line 2

文件 2:

line 1
line 2
line 3

结果:

#  line1
   line 1
=  line 2
   line 2
#
   line 3

虽然显然仍然不是一个成熟的 diff 实用程序,但我认为这正在做预期的事情。

正如@Loki Astari 的回答一样,这基本上就像行数较少的文件在末尾填充了尽可能多的空行以匹配较长的文件。

顺便说一句,还要注意使用 of"\n"而不是std::endl。除了插入换行符外,std::endl还会刷新输出缓冲区,在这种情况下您几乎肯定不希望这样做。刷新缓冲区仍然会产生正确的结果,但在许多情况下可能会慢得多。

编辑:就编码风格而言,循环编写为for循环而不是while

for ( ; (bool)std::getline(file_1, line1) + (bool)std::getline(file_2, line2))
      ; line1.clear(), line2.clear())
{
    std::cout << prefixes[line1==line2] << line1 << "\n   " << line2 << "\n";
}

我个人认为在这里使用 C++ 风格转换几乎没有真正的收获。如果我想摆脱 using (bool),我可能会使用另一个著名的成语(诚然,许多人也不喜欢):

for ( ; !!std::getline(file_1, line1) + !!std::getline(file_2, line2))
      ; line1.clear(), line2.clear())
{
    std::cout << prefixes[line1==line2] << line1 << "\n   " << line2 << "\n";
}

如果有人真的反对使用逗号运算符,这很容易重写为:

while (!!std::getline(file_1, line1) + !!std::getline(file_2, line2))       
{
    std::cout << prefixes[line1==line2] << line1 << "\n   " << line2 << "\n";
    line1.clear();
    line2.clear();
}

就个人而言,我不认为这是一种改进,但其他人可能不同意。

于 2012-11-11T16:02:50.060 回答
3

正如约翰指出的那样。在条件中使用 eof() 通常是错误的。

但在这种情况下,我认为这是合适的。但因此,您需要添加一些额外的检查。

while(true)  // exit provided by break.
{
    std::string dummy_1;   // By declaring them here you force them to be 
    std::string dummy_2;   // reset each iteration.

    // Because you are doing the read inside the loop
    // You need to check if the reads work.
    if (!std::getline(file_1,dummy_1) && !std::getline(file_2,dummy_2))
    {
        // Only exit if both reads fail.
        break;
    }

    // Got here if at least one read worked.
    // A failed read will result in an empty line for comparison.    
    std::cout << ((dummy_1==dummy_2) ? "= " : "# ") << dummy_1 << std::endl << "  " << dummy_2 << std::endl;
}
于 2012-11-11T15:15:42.453 回答