【问题标题】:Best way to read a whole file opened in text mode into a string variable将以文本模式打开的整个文件读入字符串变量的最佳方法
【发布时间】:2010-12-12 16:09:59
【问题描述】:

这些是我无法改变的:

  • 语言是 C++
  • 但是,文件是用旧的fopen()打开的
  • 文件未以二进制模式打开

这是我必须做的:

  • 编写一个将整个文件加载到std::string 中的函数。行只能由\n 分隔,而不是其他变体。

这就是我所做的:

string ReadWhole()
{
    Seek(0);
    char *data = new char[GetSize()];

    if (1 != fread(data, GetSize(), 1, mFile))
        FATAL(Text::Format("Read error: {0}", strerror(errno)));

    string ret(data, GetSize());
    delete[] data;
    return ret;
}

作为参考,这是GetSize,但它只是返回文件的大小(缓存):

int GetSize()
{
    if (mFileSize)
        return mFileSize;

    const int current_position = ftell(mFile);
    fseek(mFile, 0, SEEK_END);
    mFileSize = ftell(mFile);
    fseek(mFile, current_position, SEEK_SET);

    return mFileSize;
}

这就是问题

fread() 失败,因为文件有 \r\n 行结尾,它们只计为 1 个字符而不是 2 个字符,因此它尝试读取的字符数超过文件中的字符数。

我可以用fgets 修复它,但我想知道是否有更好的方法。谢谢。

【问题讨论】:

  • 第一个问题很好。欢迎来到 SO!
  • C 中没有 std::string。标签已删除。
  • 看来您正在将 mFileSize 初始化为零,并使用它来指示该值尚未被缓存。但是,由于零是有效的文件大小,您应该使用不同的标记值,例如 -1。
  • 请注意,您的代码中存在潜在的内存泄漏。如果string ret(...); 抛出std::bad_alloc,您将泄漏data 中的缓冲区。

标签: c++ file


【解决方案1】:

fread 返回无法读取请求的字节数后,您只需检查ferror(mFile)。如果它为 0(假),那么 fread 就停在文件的末尾,您不应将此视为错误。不过,您应该切换两个参数,以便获得实际读取的字节数:

size_t number_of_bytes_read = fread(data, 1, GetSize(), mFile);

【讨论】:

    【解决方案2】:

    有一种简单的惯用方式来执行此操作。

    #include <string>
    #include <fstream>
    #include <sstream>
    std::string load_file ( const std::string& path ) 
    {
        std::ostringstream contents;
        std::ifstream file(path);
        if ( !file.is_open() ) {
            // process error.
        }
        contents << file.rdbuf();
        return (contents.str());
    }
    

    注意:此函数使用搜索来获取输入文件的大小(以字节为单位)。随着更多输入可用,这具有(很少)重新分配以增加缓冲区的缺点。它具有与其他 std::istream 实现一起工作的优势,这些实现可能无法提前提供内容的大小(即从套接字读取)。

    编辑:因为您的需求声明使用了FILE*,它已经打开并且您无法更改,您可以实现一个使用现有FILE*std::streambuf 实现以允许重新使用高级 std::istreamstd::ostream 操作。

    right here, on StackOverflow 提供了一个示例实现。

    P.S.:如果您从未使用过非标准库的流缓冲区实现,这里有一个关于如何根据我指出的实现编写函数的快速概述。

    #include <string>
    #include <istream>
    #include <sstream>
    #include "FILEbuf.h"
    std::string load_file ( ::FILE * opened_c_file ) 
    {
        FILEbuf buffer(opened_c_file);
        std::istream file(&buffer);
        std::ostringstream contents;
        contents << file.rdbuf();
        return (contents.str());
    }
    

    【讨论】:

    • 等一下,更新我的答案以使用FILE*
    • 为什么是 !file.is_open() 而不是 !file?当前标准的 ifstream 没有 std::string ctor。
    • 不需要在 &buffer 上构造 istream 来再次提取它:内容
    • @Fred。 1) operator! 检查其他内容,例如最后一次输出操作的状态。我发现!is_open() 使用构造函数后更清晰。 2) 我忘了打电话给.c_str()。 3) 在这种情况下,缓冲区不是必需的,但有人可能想将istream 对象直接用于其他目的。
    【解决方案3】:

    您可以分配一个固定大小的缓冲区,并从文件中重复fread,最多将其附加到带有string::apeend(char*, size_type) 的字符串中。

    【讨论】:

      【解决方案4】:

      只需使用fgetc() 一次读取一个字符。您可以使用特殊情况将 '\r\n' 结尾转换为普通的 '\n'。

      std::string ReadWhole() {
          std::string ret;
          char prev = 0, c;
          while ((c = fgetc(mFile)) != EOF) {
              if (prev == '\r' && c == '\n') {
                  ret.erase(ret.rend()); // erase the previous \r
              }
              ret += c;
              prev = c;
          }
          return ret;
      }
      

      【讨论】:

      • 文件以文本模式打开(问题中的“文件未以二进制模式打开”),因此 stdio 已经完成了换行符的转换。
      猜你喜欢
      • 2012-11-10
      • 2013-11-06
      • 2015-11-17
      • 2011-06-28
      • 1970-01-01
      • 1970-01-01
      • 2014-09-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多