Reading

Sections 6.1-6.2 of Problem Solving with C++.

An oddity of cin

The now-familiar cin object has type istream. It is, in fact, the only object of that type we've seen up 'til now. Other than the >>-operator, we haven't used it like we've used other types - we never try any other operations. Oddly enough, objects of type istream can be cast as type bool, which means for example that they can be used in the test condition of a loop or an if-statement. But what does that mean?!?!? Lets look at an example:
int j, k;
k = 0;
cin >> j
while(cin)
{
  k = k + j;
  cin >> j;
}
cout << k << endl;
Now what does this do? Well, if I run this program and enter
1 2 3 4;
... it'll print out 10. What it's done is to compute the sum of the numbers entered, stopping at the ';' ... but why?

Well, when cin (or any other object of type istream) fails to read in an object of the type you've requested, it goes into a state that as a bool is false. Otherwise, it translates into the bool true. So, in our loop, each time we read in a new int successfully with cin >> j the evaluated expression "cin" is of type istream and is cast to the bool value true - meaning the loop continues. However, after adding 4 to the sum we try to read another int but encounter a ';' instead, meaning that the read fails. So the loop condition for the next iteration takes cin and casts it to the bool value false, and the loop terminates. In fact, in this state any subsequent attempts to read will fail. This is a cool trick.

More often than not, we use the the fact that the expression cin >> j is an expression that evaluates to cin (with the side effect of reading into the variable j), to write the above more compactly as

int j, k;
k = 0;
while(cin >> j)
{
  k = k + j;  
}
cout << k << endl;
We think of this as "while cin >> j succeeds ...".

Reading from a file

Sometimes we want to read our input from a file rather than from standard-in. In this case, cin won't help us. However, we can create an istream object that acts just like cin except that it gets its character stream from a file rather than a keyboard. So, let's do the same program as above, but first let's create a file temp with the input for our program.
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
  // Create istream object that reads from file temp
  ifstream fin("temp");

  // Loop and add as long as more int's can be read
  int j, k;
  k = 0;
  while(fin >> j)
  {
    k = k + j;
  }

  // Print out sum of all int's read
  cout << k << endl;

  return 0;
}
A few comments: When I say ifstream fin; it looks like I'm declaring an object of type ifstream (and indeed I am!), and yet I claimed to be creating an object of type istream, which is it? Well ... it's both. As you get into more advanced programming, the idea of subtypes will be important. For now I'll just say this: cin and the fin we created are both of type istream in the same way that "square" and "trapazoid" are both in the category "quadrilateral" - they are subtypes of the type quadrilateral.

Now, if you run this, it's only going to successfully open and read from the file temp if temp is in the current directory, i.e. the directory you were in when you launched the program.

So what happens when your program doesn't find the file it's looking for? Well, when you create fin to read from temp, and temp does not exist, then fin is in a state which, when cast as a bool, is false. Thus, if we want to improve the program above so that it prints an error message if the file temp cannot be found, we would do the following:

For a lot of small programs, you simply want to print an error and exit your program when you can't find the given file. Otherwise, just carry-on. We usually do this as shown below. Note that "return 1;" in main has the effect of exiting the program immediately, and returning a non-zero value is the traditional way of indicating that there was some kind of error.
ifstream fin("temp");
if (!fin)
{
  cout << "File \"temp\" not found!" << endl;
  return 1;					
}
...
// Create istream object that reads from file temp
ifstream fin("temp");

if (fin)
{
  // Found the file, read input & do work!
}
else
{
  // Couldn't find the file!  Report error!
}

When a file ends

When reading data from a file, we often want to simply read until a file ends. Since there isn't really an obvious analogue to that when reading from the keyboard, we haven't had to deal with anything like this. The idea is this, if you try to read an object, say an int for example, and the file ends before istream object can read an int, the istream object goes into an error state again - i.e. casting it to a bool would result in the value false. So, if you had a file infile that simply consisted of a bunch of numbers, nothing else, you could sum these numbers with the following code:
  // Open ifstream to "infile"
  ifstream fin("infile");

  // Read & sum each number in file
  int k, sum;
  sum = 0;
  fin >> k;
  while (fin)
  {
    sum = sum + k;
    fin >> k;
  }

  // Print out result
  cout << sum << endl;
If you prefer, we can use some of our shortcuts, namely that fin >> k has the side effect of reading a new value into k, as well as being an expression that has the value of the object fin after the new value is read into k.
  // Open ifstream to "infile"
  ifstream fin("infile");

  // Read & sum each number in file
  int k, sum;
  sum = 0;
  while (fin >> k)
    sum = sum + k;

  // Print out result
  cout << sum << endl;
Important Unix note: If you're entering data into the terminal, you can send an "end of file" to the program by pressing ctrl-d. Thus, when running something like
int count = 0;
char c;
while(cin >> c)
{
  if (c == ' ')
    count = count + 1;
}
you can signal the end of the input, and make the program break out of the while loop, by pressing ctrl-d.

Closing input file streams

We know how to create an ifstream object that is open for reading to a file. How are input file streams closed? Well, when the ifstream object goes out of scope that variable dies, and this closes the connection to the file. If another ifstream object is created to read from the same file, it starts over at the beginning.
Note: you can close a filestream named fin explicitly at any time with: fin.close()

Writing to a file

Just as reading from a file is not much different from reading from cin, writing to a file is not much different from writing to cout. First, you need to create an object of type ostream like cout, but which sends its output to a file. To do this, you declare a variable of type ofstream.
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
  // Create ostream object that writes to file outfile
  ofstream fout("outfile");

  // Write 1 through 10 on separate lines
  int i;
  i = 1;
  while(i <= 10)
  {
    fout << i << endl;
    i = i + 1;
  }

  return 0;
}
You don't really have the issue of checking to see if the file is found, since you're trying to create a new file. If there is file of that name already there, it gets obliterated ... oops! A fully featured program might warn the user of this. Can you think of how you might check whether the file outfile already exists?

Opening files when the user provides the filename:

If name is a string object with the name of the file you'd like to open, then
ifstream fin(name.c_str());
    
opens an input stream to that file, not ifstream fin(name), like you'd hope. The following also works:
ifstream fin;
fin.open(name.c_str());
    
The deal is this: the iostream stuff expects a C-style string, not a C++ string object. when you have a C++ string object s, then s.c_str() evaluates to the C-style version of the same string.

Problems

  1. A simple data-conversion problem. You'd be amazed how often you need to write programs that do nothing more than convert data from one format to another. Write a program that reads in a file (name given by user) that contains points in ordered pair notation, and writes the same points to a file (name also given by user) in gnuplot notation, i.e. one point per line, each point given by x-coordinate tab ('\t') y-coordinate. For a nice small file to test with, we have testin.txt. For a nice big challenge file, we have in.txt.
    a solution.
  2. Census Statistics - The census keeps tables of populations and population densities for all of our states. Each state has its own file giving the names of all cities, towns, and CDP's ("census designated place" - this appears to be census-eese for "other") in that state. For example, take a look at Maryland's geographic census data.