I am implementing a very simple file database. I have 2 basic operations:
void Insert(const std::string & i_record)
{
//create or append to the file
m_fileStream.open(m_fileName.c_str(), std::ios::out | std::ios::app);
if (m_fileStream.is_open())
{
m_fileStream << i_record << "\n";
}
m_fileStream.flush();
m_fileStream.close();
}
/*
* Returns a list with all the items in the file.
*/
std::vector<std::string> SelectAll()
{
std::vector<std::string> results;
m_fileStream.open(m_fileName.c_str(), std::ios::in);
std::string line;
if (m_fileStream.is_open())
{
while (!m_fileStream.eof())
{
getline (m_fileStream, line);
results.push_back(line);
}
}
m_fileStream.close();
return results;
}
the class has m_fileStream and m_fileName as private members.
OK - here's the problem:
If I do something like:
db->Insert("a");
db->SelectAll();
db->Insert("b");
The end result is that the file will contain only "a"; WHY?
NOTE: it seems that getline() will set the fail bit. but why?
Change
while (!m_fileStream.eof())
{
getline (m_fileStream, line);
results.push_back(line);
}
to
while (getline (m_fileStream, line))
{
results.push_back(line);
}
Otherwise you will get one additional empty line at the end. eof() will return true only once you tried to read past the end of the file, and not if only the next read would be past the end of the file.
It sets the failbit because getline tries to extract characters from the stream. If there are no characters left (and no '\n' has been seen yet), stream.get(c) to a character will set the failbit. Then getline will set the eofbit and then .eof() will return true, and your loop exits.
If you don't want failbit set, then change your condition from !stream.eof() to stream.peek() != EOF (and make sure there is a trailing newline in your file).
This now is also the solution to your problem: .close() doesn't .clear() your stream, so the failbit still is set if you reopen your file. call stream.clear() after reading your stuff in, and then it works.
I think litb pretty much nailed it. But just to add my $0.02:
1) I always favored:
while ( stream && (stream.peek() != EOF) ) {...}
As [bad] events other than EOF can occur.
(And, as mentioned by litb, peek()!=EOF gets around the problem of stream not setting EOF until we try to read past the end.)
.
2) Since "m_fileStream" is open'ed, read/written/flushed, and closed in both these methods...
Why not declare it locally on the stack? Doing so assures that no previous state issues remain behind to mess you up. And yer accessing the disk, so efficiency may not be the largest concern...
Besides, you can be lazy:
ifstream stream ( m_fileName.c_str() );
ASSERT( stream, !=, NULL ); // Uses my own ASSERT macro && stream.operator().
while ( stream && (stream.peek() != EOF) ) {...}
Related
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Why is iostream::eof inside a loop condition considered wrong?
I have the following piece of code:
ifstream f("x.txt");
string line;
while (f.good()) {
getline(f, line);
// Use line here.
}
But this reads the last line twice. Why does this happen and how do I fix it?
Something very similar happens with:
ifstream f("x.txt");
string line;
while (!f.eof()) {
getline(f, line);
// Use line here.
}
You very, very rarely want to check bad, eof, and good. In particular for eof (as !stream.eof() is a common mistake), the stream currently being at EOF does not necessarily mean the last input operation failed; conversely, not being at EOF does not mean the last input was successful.
All of the stream state functions – fail, bad, eof, and good – tell you the current state of the stream rather than predicting the success of a future operation. Check the stream itself (which is equivalent to an inverted fail check) after the desired operation:
if (getline(stream, line)) {
use(line);
}
else {
handle_error();
}
if (stream >> foo >> bar) {
use(foo, bar);
}
else {
handle_error();
}
if (!(stream >> foo)) { // operator! is overloaded for streams
throw SomeException();
}
use(foo);
To read and process all lines:
for (std::string line; getline(stream, line);) {
process(line);
}
Pointedly, good() is misnamed and is not equivalent to testing the stream itself (which the above examples do).
Just use
ifstream f("x.txt");
while (getline(f, line)) {
// whatever
}
This is the idiomatic way to write such a loop. I've not been able to reproduce the error (on a Linux machine).
It didn't read the last line twice but because it failed to read when it reached eof, your string line has the value it had previously.
That is because f is no longer "good" when it has read EOF, not when it is about to read it.
First of all, there's an data inside my file...
1|Malaysia|UK|One-Way|20|6|20|6|2000|12|12|12|12|12|
The above data is the data inside my file
But when I cout the data, there's an duplicate data...
1|Malaysia|UK|One-Way|20|6|20|6|2000|12|12|12|12|12|
1|Malaysia|UK|One-Way|20|6|20|6|2000|12|12|12|12|12|
So what's the problem when I cout the data???
Here's the code....
void Flight::displayFlight(vector <Flight> &flightProfile)
{
string readFlightID, readPrice, readBusinessList, readBusinessWaitingList, readEconomicList, readEconomicWaitingList;
flightProfile.erase(flightProfile.begin(),flightProfile.end());
ifstream inFlight("Flight.txt");
if(inFlight.fail()) return;
while(!(inFlight.eof()))
{
getline(inFlight,readFlightID,'|');
istringstream(readFlightID)>>flightID;
getline(inFlight,departure,'|');
getline(inFlight,destination,'|');
getline(inFlight,flightType,'|');
getline(inFlight,readBusinessList,'|');
istringstream(readBusinessList)>>businessList;
getline(inFlight,readBusinessWaitingList,'|');
istringstream(readBusinessWaitingList)>>businessWaitingList;
getline(inFlight,readEconomicList,'|');
istringstream(readEconomicList)>>economicList;
getline(inFlight,readEconomicWaitingList,'|');
istringstream(readEconomicWaitingList)>>economicWaitingList;
getline(inFlight,readPrice,'|');
istringstream(readPrice)>>price;
getline(inFlight, day,'|');
getline(inFlight, month,'|');
getline(inFlight, year,'|');
getline(inFlight, hour,'|');
getline(inFlight, min,'|');
inFlight.ignore(numeric_limits<streamsize>::max(), '\n');
cout<<flightID<<departure<<destination<<flightType<<businessList<<businessWaitingList<<economicList<<economicWaitingList<<price<<day<<month<<year<<hour<<min<<endl;
}
inFlight.close();
}
Your (and others) common mistake is that the eof-bit is only set on input operations (RTFM).
The correct way to read a file line by line would be to do:
if (myfile.is_open())
{
while ( getline (myfile,line) )
{
cout << line << endl;
}
myfile.close();
}
You always need to check after you read from a stream if the stream is in a good state! If reading of the data failed, e.g., because the end of the stream was reached, the stream will be in a fail state. For example
while (std::getline(inFlight, readFlightID)
&& std::istringstream(readFlightId) >> flightID
&& std::getline(inFlight, departure)
...
) {
// now process the read data
}
BTW, note that the trick using a temporary stream only works like this if the target type is a member of std::istream. If it is not, you'll need to extra a reference from the stream, e.g. using
std::istringstream("some text") >> std::skipws >> value
That's because you're not checking that your getline succeeds.
The last time through the loop, it probably fails (because
you've read all of the data), so you pick up the old values.
This is not the way to read line based input. For starters,
line based input should be read line by line:
std::string line;
while ( std::getline( inFlight, line ) ) {
// Parse line here...
}
There are many ways to parse the line. One of the most common
solutions is to put it into an std::istringstream and read
from that. That's probably overkill for what you're doing,
however; you need probably something like boost::split (which
you can knock up in less than an hour if you can't use Boost).
At any rate, while ( !someIStream.eof() ) is never correct.
Two other quick comments: you shouldn't define your variables
before you need them, and there's no real point in closing
inFlight if it's immediately going out of scope.
Why does ifstream set the failbit to 1 after reading the last line of the specified file? How do I know if the specified file has been read correctly?
bool read_csv_file(const char* filename, vector<string>& lines, bool adding = false)
{
if( !adding ) lines.clear();
ifstream csvfile;
csvfile.open(filename);
if( csvfile.is_open() )
{
string line;
while( csvfile.good() && getline(csvfile,line) )
{
lines.push_back(line);
cout << "fail: " << csvfile.fail() << endl;
}
cout << "fail: " << csvfile.fail() << endl;
csvfile.close();
return (!csvfile.fail());
}
return false;
}
The fail bit is set after you run off the end of the file. Once that happens, you mustn't attempt to interpret the result of your input operation. That's perfectly fine, though, and getline will not set the fail bit while there's still any data to be read. So the following standard loop extracts all the lines:
for (std::string line; std::getline(csvfile, line); )
{
// process "line"
}
// all done
The only reason failbit could be set after reading the last
line (or any line) is if there were an error in the library, and
I don't really believe it. If failbit is set, it means that
you didn't read anything. In your case, it will never be set
when you're in the loop; if it were set, getline would have
evaluated to false, and you wouldn't have entered the loop.
And of course, the loop terminates precisely because getline
fails (or would fail—normally, you would not test for
good before doing input, and if you do, consider that the
failbit was set, regardless, if the test fails).
The usual pattern for this sort of thing is:
while ( someInput ) {
// ...
}
if ( csvfile.bad() ) {
// Serious error (disk read error, etc.)...
} else if ( ! csvfile.eof() ) {
// Formatting error...
} else {
// Normal end of file...
}
When someInput is std::getline(), however, you will never
fail because of a formatting error, so the else if above will
never be true (and a lot of code treats hard disk errors as if
they were an end of file, and so ignores the if part as well).
Too check for erroneous reads, you must test badbit using stream.bad().
Failbit indicates failure in operation logic, and apparently getline sets it when reaching EOF (confirmed on my machine).
I'm implementing a custom lexer in C++ and when attempting to read in whitespace, the ifstream won't read it out. I'm reading character by character using >>, and all the whitespace is gone. Is there any way to make the ifstream keep all the whitespace and read it out to me? I know that when reading whole strings, the read will stop at whitespace, but I was hoping that by reading character by character, I would avoid this behaviour.
Attempted: .get(), recommended by many answers, but it has the same effect as std::noskipws, that is, I get all the spaces now, but not the new-line character that I need to lex some constructs.
Here's the offending code (extended comments truncated)
while(input >> current) {
always_next_struct val = always_next_struct(next);
if (current == L' ' || current == L'\n' || current == L'\t' || current == L'\r') {
continue;
}
if (current == L'/') {
input >> current;
if (current == L'/') {
// explicitly empty while loop
while(input.get(current) && current != L'\n');
continue;
}
I'm breaking on the while line and looking at every value of current as it comes in, and \r or \n are definitely not among them- the input just skips to the next line in the input file.
There is a manipulator to disable the whitespace skipping behavior:
stream >> std::noskipws;
The operator>> eats whitespace (space, tab, newline). Use yourstream.get() to read each character.
Edit:
Beware: Platforms (Windows, Un*x, Mac) differ in coding of newline. It can be '\n', '\r' or both. It also depends on how you open the file stream (text or binary).
Edit (analyzing code):
After
while(input.get(current) && current != L'\n');
continue;
there will be an \n in current, if not end of file is reached. After that you continue with the outmost while loop. There the first character on the next line is read into current. Is that not what you wanted?
I tried to reproduce your problem (using char and cin instead of wchar_t and wifstream):
//: get.cpp : compile, then run: get < get.cpp
#include <iostream>
int main()
{
char c;
while (std::cin.get(c))
{
if (c == '/')
{
char last = c;
if (std::cin.get(c) && c == '/')
{
// std::cout << "Read to EOL\n";
while(std::cin.get(c) && c != '\n'); // this comment will be skipped
// std::cout << "go to next line\n";
std::cin.putback(c);
continue;
}
else { std::cin.putback(c); c = last; }
}
std::cout << c;
}
return 0;
}
This program, applied to itself, eliminates all C++ line comments in its output. The inner while loop doesn't eat up all text to the end of file. Please note the putback(c) statement. Without that the newline would not appear.
If it doesn't work the same for wifstream, it would be very strange except for one reason: when the opened text file is not saved as 16bit char and the \n char ends up in the wrong byte...
You could open the stream in binary mode:
std::wifstream stream(filename, std::ios::binary);
You'll lose any formatting operations provided my the stream if you do this.
The other option is to read the entire stream into a string and then process the string:
std::wostringstream ss;
ss << filestream.rdbuf();
OF course, getting the string from the ostringstream rquires an additional copy of the string, so you could consider changing this at some point to use a custom stream if you feel adventurous.
EDIT: someone else mention istreambuf_iterator, which is probably a better way of doing it than reading the whole stream into a string.
Wrap the stream (or its buffer, specifically) in a std::streambuf_iterator? That should ignore all formatting, and also give you a nice iterator interface.
Alternatively, a much more efficient, and fool-proof, approach might to just use the Win32 API (or Boost) to memory-map the file. Then you can traverse it using plain pointers, and you're guaranteed that nothing will be skipped or converted by the runtime.
You could just Wrap the stream in a std::streambuf_iterator to get data with all whitespaces and newlines like this .
/*Open the stream in default mode.*/
std::ifstream myfile("myfile.txt");
if(myfile.good()) {
/*Read data using streambuffer iterators.*/
vector<char> buf((std::istreambuf_iterator<char>(myfile)), (std::istreambuf_iterator<char>()));
/*str_buf holds all the data including whitespaces and newline .*/
string str_buf(buf.begin(),buf.end());
myfile.close();
}
By default, this skipws flag is already set on the ifstream object, so we must disable it. The ifstream object has these default flags because of std::basic_ios::init, called on every new ios_base object (more details). Any of the following would work:
in_stream.unsetf(std::ios_base::skipws);
in_stream >> std::noskipws; // Using the extraction operator, same as below
std::noskipws(in_stream); // Explicitly calling noskipws instead of using operator>>
Other flags are listed on cpp reference.
The stream extractors behave the same and skip whitespace.
If you want to read every byte, you can use the unformatted input functions, like stream.get(c).
Why not simply use getline ?
You will get all the whitespaces, and while you won't get the end of lines characters, you will still know where they lie :)
Just Use getline.
while (getline(input,current))
{
cout<<current<<"\n";
}
I ended up just cracking open the Windows API and using it to read the whole file into a buffer first, and then reading that buffer character by character. Thanks guys.
I am reading a text file character by character using ifstream infile.get() in an infinite while loop.
This sits inside an infinite while loop, and should break out of it once the end of file condition is reached. (EOF). The while loop itself sits within a function of type void.
Here is the pseudo-code:
void function (...) {
while(true) {
...
if ( (ch = infile.get()) == EOF) {return;}
...
}
}
When I "cout" characters on the screen, it goes through all the character and then keeps running outputting what appears as blank space, i.e. it never breaks. I have no idea why. Any ideas?
In C++, you don't compare the return value with EOF. Instead, you can use a stream function such as good() to check if more data can be read. Something like this:
while (infile.good()) {
ch = infile.get();
// ...
}
One idiom that makes it relatively easy to read from a file and detect the end of the file correctly is to combine the reading and the testing into a single, atomic, event, such as:
while (infile >> ch)
or:
while (std::getline(infile, instring))
Of course, you should also consider using a standard algorithm, such as copy:
std::copy(std::istream_iterator<char>(infile),
std::istream_iterator<char>(),
std::ostream_itertror<char>(std::cout, "\n"));
One minor note: by default, reading with >> will skip white space. When you're doing character-by-character input/processing, you usually don't want that. Fortunately, disabling that is pretty easy:
infile.unsetf(std::ios_base::skipws);
try converting the function to an int one and return 1 when reaching EOF
The reason it is not working is that get() returns an int but you are using the input as a char.
When you assign the result of get() to a char it is fine as long as the last character read was a character. BUT if the last character read was a special character (such as EOF) then it will get truncated when assigned to a char and thus the subsequent comparison to EOF will always fail.
This should work:
void function (...)
{
while(true)
{
...
int value;
if ( (value = infile.get()) == EOF) {return;}
char ch = value;
...
}
}
But it should be noted that it is a lot easier to use the more standard pattern where the read is done as part of the condition. Unfortunately the get() does not give you that functionality. So we need to switch to a method that uses iterators.
Note the standard istream_iterator will not work as you expect (as it ignores white space). But you can use the istreambuf_iterator (notice the buf after istream) which does not ignore white space.
void function (...)
{
for(std::istreambuf_iterator<char> loop(infile);
loop != std::istreambuf_iterator<char>();
++loop)
{
char ch = *loop;
...
}
}