Preface
I have been reviewing the message boards and posted articles on file input and output, and I just think it's that time again. File I/O is easier than baking cookies when used in conjunction with C++. In this article, I will be explaining to all of you how exactly to handle just about every aspect of file input and output using both ASCII and binary methods, and the best part is that it's all done using C++.
Getting started, ASCII output
To get anything to work using the methods here, you must first include [lessthan]fstream.h>. This is actually an extension onto [lessthan]iostream.h>, for those of you buffs out there that already like to use streams for console input and output. Actually, [lessthan]iostream.h> is already included with [lessthan]fstream.h>, so you really don't need to include both into your source files anymore, but if you prefer to just explicitly include both, feel free to do so. Let's begin by showing you the class designed for file handling, and I'll show you how to do ASCII I/O. If you guessed "fstream," you're exactly right! But for the methods of this article, we'll be using the two classes "ifstream" and "ofstream" for input and output, respectively.
If you have ever worked with the two console streams "cin" and "cout," then this will become a breeze for you. We'll work with just the output part for now, and start by declaring an object for it.
ofstream fout;
That's all for that, but to open the file, you must call ofstream::open() like this.
fout.open("output.txt");
You could have also just as well opened the file as you declared the stream by passing the filename as a parameter to the constructor of the object.
ofstream fout("output.txt");
This will be our selected method of declaring objects, since it's still rather simple to see how to create and open the file. By the way, if the file you are opening for output does not exist, it will be created for you, so there is no need to ever worry about creating the file. Now to output to the file, it works exactly like how you would output to "cout." For those of you that don't know "cout" for console output, then here's an example.
int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";
Now to save the file, you must close the file, or flush the buffer to the file. Closing the file will not let you access it anymore, so only call it when you are done using the file and it will automatically save the file for you. Flushing the buffer will write the buffer to the file and still keep the file open, so use this function when necessary. Flushing is done by another output looking call, and closing is done by a method call. Like this:
fout << flush;
fout.close();
Now the contents of the file when you open it in a text editor will read:
ofstream fout;
fout.open("output.txt");
ofstream fout("output.txt");
int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";
fout << flush;
fout.close();
[font=Courier New][color=#000080][bquote]Here is a number: 150
Now here is a string: John Doe[/bquote][/color][/font]
Now here is a string: John Doe[/bquote][/color][/font]
It's that simple! Now let's move on to file input, which can be a bit trickier, so make sure you have the simple streams down first. Become familiar with the "<<" and ">>" operators, because you'll need them again. So here we go...
Moving on, ASCII input
Input is, again, exactly the same as using the "cin" stream. It resembles the file output stream we've already discussed, but there are a few things you have to worry about. Before we go over the tricky stuff, let's just begin with a simple text file that contains:
[font=Courier New][color=#000080][bquote]12
GameDev
15.45
L
This is really awesome![/bquote][/color][/font]
GameDev
15.45
L
This is really awesome![/bquote][/color][/font]
To open this file, you must create the in-stream object, like so.
ifstream fin("input.txt");
Now let's read in the first four lines. Remember how you used the "<<" operator to insert variables and symbols into the stream? Well, to go along with the "<<" (insertion) operator, there's the ">>" (extraction) operator. And it works just the same. Look at this snippet below.
int number;
float real;
char letter, word[8];
fin >> number;
fin >> word;
fin >> real;
fin >> letter;
It's also possible to put these four lines reading the file into one, simpler line.
fin >> number >> word >> real >> letter;
How does this work? After each white space in the file, the ">>" operator will stop reading in the contents, until another >> operator is encountered. Since each of the four lines we were reading in was separated by a new line (which is a white space character), the ">>" operator puts only that line into the separate variables. That's how both of the code's samples would work.
Let's not forget about that last line in our file, though.
[font=Courier New][color=#000080][bquote]This is really awesome![/bquote][/color][/font]
If we want this whole line in one char array, we cannot do it with the ">>" operator because the spaces (white space) between each word would stop the file extraction. For instance, if we had said:
char sentence[101];
fin >> sentence;
We'd want [font=Courier New][color=#000080]sentence[/color][/font] to now contain, "This is really awesome!" But because of the white space, it now only contains "This". Obviously, there is a way to read in the whole line, and it is the method getline(). This is how we would do it.
fin.getline(sentence, 100);
Here are the parameters to the function. The first parameter is obviously the char array we want to read in to. The second is the maximum number of characters we will read in until we encounter a new line. So now [font=Courier New][color=#000080]sentence[/color][/font] contains "This is really awesome!" just like we wanted.
You should now know how to do input and output with ASCII files. But we can't stop there, because binary files now await us.
Continuing on, binary input and output
Binary files are a little bit more complicated, but they are still rather simple. The first thing you should note is that we do not use the insertion and extraction operators anymore. You can, but it will not write as binary. You must use the methods read() and write() for binary. To create a binary file, look at the next line.
ofstream fout("file.dat", ios::binary);
This will open the file as binary, instead of the default ASCII mode. Let's start first by writing to the file. The write() method takes two parameters. The first is a char pointer to the object you wish to write, and the second is the number of bytes the object is. To illustrate this, let's show an example.
int number = 30;
fout.write((char *)(&number), sizeof(number));
The first parameter is written as "(char *)(&number)". This just type casts the integer variable to a char pointer. If you don't understand how that works, then you can leaf through a C++ book on pointers if necessary. The second parameter was written as "sizeof(number)". The sizeof() function just returns an integer of the number of bytes a variable is. And that's it!
The best part of using binary files is that you can write an entire structure to the file in one line. Let's say you had a structure with 12 different items in it. Using an ASCII file would make you write each individual member of the structure one at a time, but binary files can do it all for you. Let's look at this.
struct OBJECT
{
int number;
char letter;
} obj;
obj.number = 15;
obj.letter = 'M';
fout.write((char *)(&obj), sizeof(obj));
That would write the entire structure for you! Let's move on to input now. Input will be a cinch now because the read() function takes exactly the same parameters as write(), and it works exactly the same.
ifstream fin("file.dat", ios::binary);
fin.read((char *)(&obj), sizeof(obj));
I don't even have to explain how that works, because it works just the same as write(). Binary files can be even easier that ASCII files, but the downside is that you cannot edit binary files with a simple text editor. But with that said, let me finish up by explaining some more methods to the ifstream and ofstream objects.
ofstream fout("file.dat", ios::binary);
int number = 30;
fout.write((char *)(&number), sizeof(number));
struct OBJECT
{
int number;
char letter;
} obj;
obj.number = 15;
obj.letter = 'M';
fout.write((char *)(&obj), sizeof(obj));
ifstream fin("file.dat", ios::binary);
fin.read((char *)(&obj), sizeof(obj));
Finishing up, More methods
I've already explained ASCII and binary files, so here's the low-down on all the functions that I didn't cover yet.
Checking files
You've already learned the open() and close() methods, but here's some you might want to use.
The method good() will return a boolean to whether or not the file opening was good.
Similarly, bad() will return a boolean to whether or not the file opening was bad. If it was bad, do not continue with the file operations.
The last checking method is fail(), which is somewhat like the method bad(), but not as severe.
Reading files
The method get() will return one character at a time.
The method ignore(int,char) will ignore a certain number of characters, but you must pass it two parameters. The first is the number of characters to ignore. The second is a character that, when encountered, will stop ignoring characters. For instance,
fin.ignore(100, '\n');
Will ignore 100 characters, or ignore all characters up to, and including the '\n' character.
The method peek() will return the next character in the file, but not actually take it. So if you used peek() to look at the next character, using get() right after peek() will give you the same character, and then move the file counter.
The method putback(char) will actually put characters, one at a time, into the stream. I haven't found a use for this one yet, but it's there.
fin.ignore(100, '\n');
Writing files
There's only one extra method here that may concern you. That's put(char), which just writes one character at a time to the out stream.
Opening files
When we opened binary files using this syntax:
ofstream fout("file.dat", ios::binary);
The "ios::binary" was an extra flag to give you some options on how to open the file. By default, the file is opened as ASCII, will create the file if it doesn't exist, and it will overwrite the file if it does exist. Here are some more flags you can use to change things up.
[color=#000080]
ofstream fout("file.dat", ios::binary);
[font=courier new,courier,monospace]ios::app[/font]
[/color]Append to the end of a file[color=#000080][font=courier new,courier,monospace]ios::ate
[/font][/color]Places the file marker at the end of the file, instead of at the beginning[color=#000080][font=courier new,courier,monospace]ios::trunc[/font]
[/color]Default. This will truncate an existing file and overwrite it.[color=#000080][font=courier new,courier,monospace]ios::nocreate[/font]
[/color]If the file does not exist, it will not create it for you.[color=#000080][font=courier new,courier,monospace]ios::noreplace[/font]
[/color] If the file does already exist, the open will fail.File status
The only method I have ever used for the file status is eof(), which will return true if the file marker is at the end of the file. The major use I have had with this is within loops. For an example, this code snippet will count the number of occurrences of the lower case 'e' in the file.
ifstream fin("file.txt");
char ch;
int counter;
while (!fin.eof())
{
ch = fin.get();
if (ch == 'e')
counter++;
}
fin.close();
I've never used any other methods than those described here. There are many others, but they are rarely used. Consult a C++ book or the help files on file streams for these extra methods.
ifstream fin("file.txt");
char ch;
int counter;
while (!fin.eof())
{
ch = fin.get();
if (ch == 'e')
counter++;
}
fin.close();