the most common uses of the error flags are input checking and data termination.
input checking means realizing when those idiot users have typed non-numeric characters at a numeric prompt and fixing it somehow:
stream >> data; while (stream.fail()) { stream.clear(); // clear translate failure flag stream.ignore(); // toss offending character out stream >> data; // try to read again... }
data termination can be done with flag values, but this limits your potential data value set. the eof character (end of file) is defined by the operating system and can be used to terminate any sequence of data — even at the keyboard. on DOS/Windoze this character is ^Z (Control-Z). on *nix this character is ^D (Control-D). a typical data processing loop is:
pretend to read while (!stream.eof()) { read data process data pretend to read }
Note how we use a hacked while loop instead of plain-old, read-primed loop. This is because the eof flag is raised only when that eof character is encountered in the buffer during an input, ignore, or peek operation. Since data may or may not be followed in the file by whitespace before the eof marker, we must shift our focus from reading the data to reading the potential whitespace after the data. That is, reading the whitespace which potentially follows the data.
If we are reading data with the extraction operator (>>), then we pretend to read by extracting for whitespace (ws). But if we are reading data line-by-line (getline), then we merely want to peek ahead for the [potential] trailing whitespace. (If we were to extract it, we might rip out a blank line — or two or three... Sometimes blank lines are significant parts of our data stream; sometimes not. Use your best judgement as to which is appropriate for your application.)
Another way that works well is to use a more object-oriented style of reading. With this style, the input itself is placed in the while head as the test. All the reading tools we use (get, >>, getline) return the stream just read from. In a bool context, a stream evaluates to whether it was happy (good). So this test is actually a little different than an eof test as it includes not failing and not bad tests as well! (So caution should prevail when wanting to test just an eof condition.)
while (read data) { process data }
We don't use a do loop (hacked or otherwise) because, given the extreme of an empty data file, a do loop would process garbage:
do { read data (hack?) process data } while (!stream.eof());
To fix this, we'd have to add an extra if statement (increasing complexity and decreasing readability):
do { read data (hack?) if (!stream.eof()) { process data } } while (!stream.eof());
Of course that's just silly! (And it doesn't always work..!)
Here is a collection of source code and data files to help solidify your understanding of this situation. (There are some notes inside and at the end of the C++ program.)
first note that a disk file has a name — a name the user chose when they created the file (in nedit for example). they should get to use that name here, too — not have to change it to some literal string you chose.
file name variables are strings. if using C-strings, they are arrays. arrays need a size. how long should a file name variable be?
the ISO-9660 standard says 8.3 (name.ext) is enough. but modern operating systems allow up to 255-ish characters (and can even handle spaces and some punctuation in the names). plus there is the fact that to open the file, you need not only its name and extension, but also the path to that file (i.e. which folder/directory is it in?). then, factoring in the average user's laziness, we can probably limit ourselves to around 150-250 characters. (to be ultra-safe, you could give it 500-1000, but this is just CSC122, right?)
also we need to check for errors while the file is opening. the ! operator knows how to behave not only with bool expressions, but also with the stream classes. it returns true if a file failed to open correctly.
what sorts of errors? you say... well, we might not have permission to read/write the file (*nix/network). the file might not exist (input). the file might already exist (output). some physical device failure might occur.
By default, input files which don't exist were once created. it was decided that this was stupid, finally, and has been fixed in the final standard. But be wary as there may be a few compilers that have not yet caught up to this. (Not to worry here, g++ caught on...)
should you be unlucky enough to work on a behind the times compiler, you'll need to fix this problem. to do so we must tell the stream that not only should it be opened for input, but also that it should NOT create empty files when the requested file does not already exist.
so, what does this process look like? here it is for input files:
ifstream db_file; // declare an input file stream variable string fname; // file name variable cout << "Enter file's name: "; getline(cin, fname); // can use setw, but loses spaces db_file.open(fname); // open file with this name while (!db_file) // while the file did NOT open successfully { db_file.close(); // close the OS connection -- freeing resources db_file.clear(); // clear the open failure flag cout << "Invalid name...open failed!" << endl; cout << "Enter file's name: "; getline(cin, fname); db_file.open(fname); }
note the close/clear pair there. the close function severs the link to the file. but it didn't link ...? although the file did NOT link, the operating system still is using up what it terms 'resources' to allow the connection. if we don't close even the failed link, these resources may never be recovered. (anyone seen an 'insufficient system resources' dialog lately?) the clear will clear the flag which was set to say, "I couldn't open right!!" (you'd think close would have done this, but it didn't — it is defined to leave the stream in an indeterminate state, in fact!)
also by default, output files which do exist are truncated — all the old data is lost. this has NOT been fixed — and doesn't seem to be a likely candidate any day soon. *sigh*
But we can do some things about it. With one of those older, less-compliant library implementations, we can tell the stream that it should NOT replace existing files when opening for output, for instance. (If your library implementation is up to snuff, there are also other, fancier things we can do, too.)
for simple, unprotected opening of output files, things look very similar to opening an input file:
ofstream report; // declare an output file stream variable string fname; // file name variable cout << "Enter file's name: "; getline(cin, fname); // can use setw, but loses spaces report.open(fname); // open file with this name while (!report) // while the file did NOT open successfully { report.close(); // close the OS connection -- freeing resources report.clear(); // clear the open failure flag cout << "Invalid name...open failed!" << endl; cout << "Enter file's name: "; getline(cin, fname); report.open(fname); }
as mentioned before, standard processing features apply:
input: >>, get, getline, setw, ignore, peek, eof, good, fail, bad
So, for instance, we could extract a number from the db_file stream above like so:
db_file >> num_var;
Or read a string that might contain spacing like so:
getline(db_file, str_var);
Anything you can do to cin you can do to an ifstream. The only thing you don't need is prompting — who'd be in the file to read the prompt?!
output: <<, endl, flush, setw, precision, setf, unsetf, flags
To set the above report file's precision to 3 you can:
report.precision(3);
Or to write — store, save, etc. — some data to the file you could just:
report << data_var << ' ' << another_data_var;
Anything you can do to cout you can do to an ofstream. Spacing is important, but labeling is not necessary unless you go that far.
Here is the example code from lecture for your perusal and for playing around with. *smile*