Code:
cout << "Please enter input file name: ";
string iname;
cin >> iname;
ifstream ist {iname}; // ist is an input stream for the file named name
if (!ist) error("can't open input file ",iname);
Defining an ifstream with a name string opens the file of that name for reading. The test of !ist checks if the file was properly opened. After that, we can read from the file exactly as we would from any other istream. For example, assuming that the input operator, >>, was defined for a type Point, we could write
Code:
vector<Point> points;
for (Point p; ist>>p; )
points.push_back(p);
Output to files is handled in a similar fashion by ofstreams. For example:
Click here to view code image
Code:
cout << "Please enter name of output file: ";
string oname;
cin >> oname;
ofstream ost {oname}; // ost is an output stream for a file named oname
if (!ost) error("can't open output file ",oname);
Defining an ofstream with a name string opens the file with that name for writing. The test of !ost checks if the file was properly opened. After that, we can write to the file exactly as we would to any other ostream. For example:
Click here to view code image
Code:
for (int p : points)
ost << '(' << p.x << ',' << p.y << ")\n";
When a file stream goes out of scope its associated file is closed. When a file is closed its associated buffer is “flushed”; that is, the characters from the buffer are written to the file.
It is usually best to open files early in a program before any serious computation has taken place. After all, it is a waste to do a lot of work just to find that we can’t complete it because we don’t have anywhere to write our results.
Opening the file implicitly as part of the creation of an ostream or an istream and relying on the scope of the stream to take care of closing the file is the ideal. For example:
Click here to view code image
Code:
void fill_from_file(vector<Point>& points, string& name)
{
ifstream ist {name}; // open file for reading
if (!ist) error("can't open input file ",name);
// . . . use ist . . .
// the file is implicitly closed when we leave the function
}
You can also perform explicit open() and close() operations (B.7.1). However, relying on scope minimizes the chances of someone trying to use a file stream before it has been attached to a stream or after it was closed. For example:
Click here to view code image
Code:
ifstream ifs;
// . . .
ifs >> foo; // won’t succeed: no file opened for ifs
// . . .
ifs.open(name,ios_base::in); // open file named name for reading
// . . .
ifs.close(); // close file
// . . .
ifs >> bar; // won’t succeed: ifs’ file was closed
// . . .
In real-world code the problems would typically be much harder to spot. Fortunately, you can’t open a file stream a second time without first closing it. For example:
Click here to view code image
Code:
fstream fs;
fs.open("foo", ios_base::in) ; // open for input
// close() missing
fs.open("foo", ios_base::out); // won’t succeed: fs is already open
if (!fs) error("impossible");
Don’t forget to test a stream after opening it.
Why would you use open() or close() explicitly? Well, occasionally the lifetime of a connection to a file isn’t conveniently limited by a scope so you have to. But that’s rare enough for us not to have to worry about it here. More to the point, you’ll find such use in code written by people using styles from languages and libraries that don’t have the scoped idiom used by iostreams (and the rest of the C++ standard library).
As we’ll see in Chapter 11, there is much more to files, but for now we know enough to use them as a data source and a destination for data. That’ll allow us to write programs that would be unrealistic if we assumed that a user had to directly type in all the input. From a programmer’s point of view, a great advantage of a file is that you can repeatedly read it during debugging until your program works correctly.