开发者

Best way to read a whole file opened in text mode into a string variable

These are the things I cannot change:

  • The language is C++
  • However, the file is opened with the good old fopen()
  • The file is not opened in binary mode

This is what I have to do:

  • Write a function that loads the entire file into a std::string. Lines should be separated only by \n and not other variants.

This is what I did:

string ReadWhole()
{
    Seek(0);
    char *data = new char[GetS开发者_如何学编程ize()];

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

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

For reference, this is GetSize, but it simply returns the size of the file (cached):

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;
}

This is the problem

fread() fails because the file has \r\n line endings and they count as only 1 character instead of 2, so it tries to read more than the characters in the file.

I could fix it with fgets but I was wondering if there was a better way. Thanks.


After fread returns that it was unable to read the requested number of bytes, you should simply check ferror(mFile). If it's 0 (false) then fread just stopped at the end of the file and you should not tread this as an error. You should switch the two arguments though so you can get the number of bytes actually read:

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


There is a trivial, idiomatic way to perform this operation.

#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());
}

Note: this function does not use seeking to obtain the size (in bytes) of the input file. This has the down-side of (few) re-allocations to increase the buffer as more input is made available. It has the up-side of working with other std::istream implementations, which might not be able to provide the contents' size ahead of time (i.e. reading from a socket).

Edit: because your requirements state use of FILE*, which is already opened and you cannot change, you can implement a std::streambuf implementation that uses an existing FILE* to allow re-use of high-level std::istream and std::ostream operations.

A sample implementation is available right here, on StackOverflow.

P.S.: If you've never used non-standard-library stream buffer implementations, here's a quick overview of how to write the function given the implementation I pointed to.

#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());
}


You could allocate a fixed-size buffer, and repeatedly fread at most that much from the file and append that to the string with string::apeend(char*, size_type).


Just use fgetc() to read one character at a time. You can use a special case to convert '\r\n' endings to plain '\n's.

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;
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜