【问题标题】:Stuck in writing my diff utility in C++坚持用 C++ 编写我的 diff 实用程序
【发布时间】:2012-10-31 04:49:40
【问题描述】:

我正在尝试使用我在 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 库的解决方案的答案,因为它们非常便携,而且我已经知道我会出于其他原因使用它们。

谢谢。

【问题讨论】:

  • 您所写的是一个很好的开始。您可能希望提供更详细的错误消息,例如通过分别检查每个文件的fail() 并告诉用户哪个文件无法打开,或者告诉用户哪个文件先结束。
  • @AdamLiss 我还注意到exit() 函数是C++ 的C 标准库的一部分,也就是cstdlib,这是一种真正的C++ 处理退出程序的方式吗?我的主要问题是确保应用程序能够工作并且它是可移植的,我很懒,我故意延迟了一堆额外的检查和std::cout:!
  • @user1802174: exit() 如果您想从任何点显式终止程序,则很好,并且还为您提供了使用数字代码退出的选项,该代码可以从 Windows 下的批处理文件中读取.
  • 两个文本文件的差异是一个不小的问题。 en.wikipedia.org/wiki/Diff
  • @LokiAstari 这就是为什么我想开始对此进行深入分析,我将不得不处理很多文件,我也很好奇为什么这看起来微不足道,但事实并非如此。跨度>

标签: c++ boost diff file-comparison


【解决方案1】:

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

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


而 (std::getline(file_1, line1) || std::getline(file_2, line2)) std::cout

编辑:@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

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

就像@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();
}

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

【讨论】:

  • 你的解决方案产生了这个问题stackoverflow.com/questions/13343387/… 并且看起来它不是“可修复的”
  • @user1802174:反正你说对了一半——这是错误的,但可以修复(见修改后的答案)。
  • 您的代码有效。但是从美学性质和代码维护的角度来看,我认为您在这种情况下投入了太多工作(从而使其可读性降低)。它的代码气味。一个可能的解决方案是将测试重构为一个单独的函数。请不要使用 C-casts,也不要发现逗号操作符有用(它只是一个填塞工具)。目前,如果您必须运行调试器(在单步模式下),您将很难验证它是否有效。
  • 我承认我并不特别关心代码的原样,但并不觉得逗号运算符特别令人反感。我一般不喜欢强制转换,但老实说,我不认为 C++ 风格强制转换比 C 风格有任何重大改进。
【解决方案2】:

正如约翰指出的那样。在条件中使用 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;
}

【讨论】:

    【解决方案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() 的方式。

    【讨论】:

    • @AdamLiss 有一天,其中一位错误地使用 eof() 的新手海报会向我解释他们为什么这样做。我试过很好地询问,有详细的解释,它不起作用。这么多人犯同样的基本错误真是不可思议,这一定是有原因的,我只是想知道它是什么。
    • getline 如何迭代?它有自己的隐式迭代器?
    • 在我的情况下,它可能只是听起来“eof = 文件结尾”:!
    • 在这种情况下我不同意。这是在测试中使用 eof() 可能更好的少数情况之一(因为我们需要循环两个文件并继续,即使一个文件先完成)。
    • eof() 测试流的当前状态。它会告诉您您在流中执行的 last 操作是否因文件结尾而失败。它不会告诉您在流上执行的 next 是否会因为文件结束而失败。这就是您(以及几乎所有其他 neebie)尝试使用它的方式。
    猜你喜欢
    • 2012-12-24
    • 1970-01-01
    • 2020-10-21
    • 2012-11-03
    • 1970-01-01
    • 2014-04-16
    • 2013-05-31
    • 2013-09-15
    • 1970-01-01
    相关资源
    最近更新 更多