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;
}
精彩评论