Reading and writing files
In C, the process of opening and
manipulating files requires a lot of language background to prepare you for the
complexity of the operations. However, the C++ iostream library provides a
simple way to manipulate files, and so this functionality can be introduced much
earlier than it would be in C.
To open files for reading and writing,
you must include <fstream>. Although this
will automatically include <iostream>, it’s generally prudent
to explicitly include <iostream> if you’re planning to use
cin, cout, etc.
To open a file for reading, you create an
ifstream object, which then behaves like
cin. To open a file for writing, you create an
ofstream object, which then behaves like
cout. Once you’ve opened the file, you can read from it or write to
it just as you would with any other iostream object. It’s that simple
(which is, of course, the whole point).
One of the most useful functions in the
iostream library is
getline( ), which
allows you to read one line (terminated by a newline) into a string
object[28]. The
first argument is the ifstream object you’re reading from and the
second argument is the string object. When the function call is finished,
the string object will contain the line.
Here’s a simple example, which
copies the contents of one file into another:
//: C02:Scopy.cpp
// Copy one file to another, a line at a time
#include <string>
#include <fstream>
using namespace std;
int main() {
ifstream in("Scopy.cpp"); // Open for reading
ofstream out("Scopy2.cpp"); // Open for writing
string s;
while(getline(in, s)) // Discards newline char
out << s << "\n"; // ... must add it back
} ///:~
To open the files, you just hand the
ifstream and ofstream objects the file names you want to create,
as seen above.
There is a new concept introduced here,
which is the
while
loop. Although this will be explained in detail in the next chapter, the basic
idea is that the expression in parentheses following the while controls
the execution of the subsequent statement (which can also be multiple
statements, wrapped inside curly braces). As long as the expression in
parentheses (in this case, getline(in, s)) produces a “true”
result, then the statement controlled by the while will continue to
execute. It turns out that getline( ) will return a value that can
be interpreted as “true” if another line has been read successfully,
and “false” upon reaching the end of the input. Thus, the above
while loop reads every line in the input file and sends each line to the
output file.
getline( ) reads in the
characters of each line until it discovers a newline (the termination character
can be changed, but that won’t be an issue until the iostreams chapter in
Volume 2). However, it discards the newline and doesn’t store it in the
resulting string object. Thus, if we want the copied file to look just
like the source file, we must add the newline back in, as
shown.
Another interesting example is to copy
the entire file into a single string
object:
//: C02:FillString.cpp
// Read an entire file into a single string
#include <string>
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream in("FillString.cpp");
string s, line;
while(getline(in, line))
s += line + "\n";
cout << s;
} ///:~
Because of the dynamic nature of
strings, you don’t have to worry about how much storage to allocate
for a string; you can just keep adding things and the string will
keep expanding to hold whatever you put into it.
One of the nice things about putting an
entire file into a string is that the string class has many
functions for searching and manipulation that would then allow you to modify the
file as a single string. However, this has its limitations. For one thing, it is
often convenient to treat a file as a collection of lines instead of just a big
blob of text. For example, if you want to add line numbering it’s much
easier if you have each line as a separate string object. To accomplish
this, we’ll need another
approach.