坚持用C ++编写我的diff实用程序

时间:2012-11-11 14:55:43

标签: c++ boost diff file-comparison

我试图使用我在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这样的过度技术来获取和比较这两个文件的长度之前做真正的比较。

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

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

感谢。

3 个答案:

答案 0 :(得分:3)

像往常一样eof()是错误的。这工作

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

建议您阅读eof()确实做的事情。它不符合你的想法,但实际上它在这个程序中会很有用,因为你可以正确地使用它,告诉你哪两个文件到达了文件的末尾。见here

您可以在此程序中正确使用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()的使用方式。

答案 1 :(得分: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;
}

答案 2 :(得分: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&lt;&lt;前缀[line1 == line2]&lt;&lt; line1&lt;&lt; “\ n”&lt;&lt; line2&lt;&lt; “\ n” 个;

编辑:@ user1802174提出了一个很好的观点 - 实际上,循环实际上根本没有并行读取数据。由于它使用||进行短路评估,当第一个文件的读取成功时,它没有读取第二个文件中的任何内容。幸运的是,他错了一件事:它很容易修复。至少在这种情况下,+工作正常,但我们必须明确地将结果转换为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

虽然显然仍然不是一个完整的差异实用程序,但我认为这是在做预期的事情。

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

另外,请注意使用"\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 ++样式转换没什么好处。如果我想摆脱使用(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();
}

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