next up previous
Next: About this document ...

CSCI-1200 Computer Science II
Summer, 2002
Worksheet 12
I/O and Files

Reading: Deitel & Deitel, Chap. 11.1 - 11.4, 14.1 - 14.6

Input from the keyboard

The input stream operator >> is used to read data from the keyboard into your program. Note that the input stream is always characters, but this operator ``knows'' how to convert to the appropriate type. For example:
double f;
cin >> f;
The program waits until the user enters some characters, which hopefully represent a floating point number. If the user enters the five character string 34.56, the program converts this to the internal representation of a floating point number with the value 34.56.

The input stream operator has some features which can be helpful or annoying, depending on what you are trying to do. First, input stream operators ignore whitespace characters: space (ASCII 32), tab (ASCII 9), and newline (ASCII 10). If the user types four spaces, return, a tab, and then 34.56, the statement above will read and ignore everything before the 3.

When used to input integer or floating point numbers, input stream operators cannot ignore other non-numeric characters. If the first non-whitespace character read is not a digit (or, for floats and doubles, a decimal point), the statement will fail, and the value in f will be unchanged. (In this example, it remains uninitialized.) Once an input statement fails, all subsequent input statements for the same input stream will also fail. Thus, a program such as this one can enter an infinite loop if the user types a letter in place of a number for input:


   int x = 0, sum = 0;
   while (x != -1) {
     cout << "Enter a number or -1 to end: ";
     cin >> x;
     if (x != -1) sum += x;
   }

Once the cin statement inside the loop is made to fail when the input is bad, x retains its previous value. The next time through the loop, the cin statement will fail automatically, without even letting the user type anything else. Thus, x never gets the value -1 to end the loop.

As you know, the >> operator can also read strings (arrays of chars) properly, but it too ignores all whitespace characters. Any leading whitespace characters are read and ignored. Once it finds a non-whitespace character, it continues reading characters until the next whitespace character is read. A null character '\0' (ASCII 0) is automatically appended to the end of the string.

Note that you cannot read data into other types of arrays. The following code will generate a compiler error.


  int n[5]; // an array of 5 integers
  cin >> n; // doesn't work

Buffering input

When the user types input from the keyboard, not all of the characters typed are necessarily read by the input statement that requested the typing. The remaining characters are stored in an input buffer associated with cin, where they will be read by subsequent input statements.

Consider the following example:


   int i; float f; char c; char s[80];
   cin >> i;
   cin >> f;

Suppose the user types 231.4 at the first cin statement. What happens? The program reads the characters 2, 3, and 1, recognizing all of them as digits. When it reads the period, it stops, because a period character cannot be part of an integer. Thus, i gets the value 231, and the remaining characters typed (a period, a 4, and a newline character representing the return key) remain in the input buffer. When the next cin statement is executed, the characters period and 4 are read from the buffer, and it stops when it looks at the newline character. Thus, after the second cin, f has the value 0.4, and the input buffer contains only a newline character.

Now let's consider a different set of cin statements with the same user input, 231.4:


   cin >> c;
   cin >> i;
   cin >> s;

What happens this time? At the first statement, the user types in an input line. The character '2' (ASCII 50) is stored in c, and the input buffer contains 31.4\n. At the second statement, the characters 31 are read, the value 31 is stored in i, and the input buffer is left with .4\n. At the third statement, we read into a character array, so all characters up to the first whitespace character are read in: the first three characters in s become '.', '4', and '\0', and the input buffer contains only \n.

Here is an example involving only string input.


   char s[80];
   cin >> s;

The computer will wait for the user to type in a string and press the return key. If the user types The quick brown fox, the input stream operator will only read up to the first whitespace character. Thus the contents of s will be:


\begin{picture}(270,42)
\put(0,15){\framebox (27,27){'T'}}
\put(27,15){\framebox...
...{\makebox(0,0){5}}
\put(175,0){\makebox(0,0){6}}
\put(200,15){...}
\end{picture}

U means undefined.

The input buffer would then contain:
quick brown fox\n (with one space before quick)

If the program then executed another statement
cin >> s;
the next characters in the input buffer would be read. The space after The would be read and ignored, and the array s would now contain the word quick. The next time a cin statement is encountered, it would pick up with the space after quick.

It often happens that you need to read in an entire line of input including whitespace. The clever student might try to do this by reading a character at a time like this:


    // bad code to read an entire line including whitespaces
    char s[80];
    char c = ' ';       // initialize variables
    int i = 0;
    while (c != '\n') {
       cin >> c;
       s[i] = c;
       ++i;
    }
    s[i-1] = '\0';      // replace newline char with null char

but this does not work for two reasons. First, the statement cin >> c; skips whitespaces. Thus the words will all be bunched together with no spaces between them like this, Thequickbrownfox. More importantly, the program will never detect the newline character, so the loop will continue forever. (Remember that if a program is stuck, you can kill it with CTRL-C.)

Other ifstream class member functions

The input stream cin has a number of member functions. We will describe a few of the most commonly used ones in this section.1One way to solve the problem above is to use the member function int cin.get(char &c), which reads a single character at a time and does not skip whitespace characters. Thus, the following program would work as intended (as long as the line has fewer than 80 characters):


    char s[80];
    char c;
    int i = 0;
    cin.get(c);        // priming read before start of loop
    while (c != '\n') {
       s[i] = c;
       i++;
       cin.get(c);
    }
    s[i] = '\0';       // append a null char at the end

This code also has a better loop structure than the first example. Loops written for many different applications, but especially user input, benefit from placing the first read statement just before the loop test (called a priming read), and placing the second read statement at the end of the loop (which is just before the loop goes back to do its test again). In this way, the data read in is always tested by the while statement immediately after it is read.

Another member function of ifstream is int getline(char *s, int n), where s is a character array and n is the length of this array. This function will read a string (including whitespaces) until it either reads a newline character or until $n - 1$ characters have been read. If it reads a newline character, it discards it. It automatically appends a terminal null character to the end of s, which is why it reads only $n - 1$ characters rather than $n$ characters. (getline can also take a third argument, a delimiter or stopping character to search for instead of \n, which is the default.)

Here is the simple way to read a line of text from the terminal including white spaces.


  char s[80];
  cin.getline(s,80);

Mixing cin >> statements with getline statements can produce confusing results. For example:


  int day; char s[80];
  cout << "Enter day of month: "; cin >> day;
  cout << "Enter text string: "; cin.getline(s, 80);

When the first cin statement is executed, the user types in a number and presses the return key. The statement stores the number into day, and the input buffer still holds the newline character. The getline function call, which is supposed to read until either 79 characters have been read or it sees a newline, reads in this newline character and immediately stops. The result is the user does not get to type in a line of text, and s contains a null character in position 0.

To avoid this, we could add a second ``dummy'' call to getline to absorb that extra newline character. A more elegant way is to call the ignore member function in between the two input statements, like this:
cin.ignore(80, '\n');
This function will read and throw out characters until either 80 characters have been ignored or the newline character is seen. (By changing the arguments you can have it look for a different number of characters, or a stopping character other than newline.)

Exercise 1: Write a program that prompts the user to enter a line of text from the keyboard (possibly including whitespaces). Your program should read in the line, convert all of the lower case letters in the line to upper case, and then print the line to the screen. For example, if the user entered:
The TV cost $250.
Your program would write
THE TV COST $250.
Note that there were three spaces between TV and cost, so your program should write three spaces. Try it once using getline once using get. Which one is easier?

Output to the terminal

The output stream operator << is used to print data to the screen. Note that what is actually written to the screen is always characters. The operator << ``knows'' how to convert from data types like int and float to characters. For example, when this code fragment is executed:
float f = 34.56;
cout << f << endl;
five characters, the digit '3' (ASCII 51), the digit '4' (ASCII 52), a period (ASCII 46), the digit '5' (ASCII 53) and the digit '6' (ASCII 54) are written to the screen.

The endl causes a newline character (ASCII 10) to be written. In addition, it causes the output buffer to be flushed. To be more efficient, output statements first write data to a buffer, and only at certain times is this buffer emptied and the output actually written. One of these times is when an endl is encountered. Another is when a cin input statement follows a cout statement.

This is why you sometimes don't see the results of cout statements in your program if it is stuck in an infinite loop, or it crashes in the middle. In cases like these, you can either make sure you use endl frequently, or you can instead write to cerr, which is similar to cout but does not buffer its output.

Like the class to which cin belongs, there are some member functions associated with the ostream class, of which cout is an instance, that can be used to make your output neater.

The function cout.width(w) sets a minimum width for the next item to be written. If the number of characters to be printed is less than this value, additional spaces are printed. The items are right justified so this makes it easy to print a table of numbers with the columns aligned. Here is a simple example:

#include <iostream>
using namespace std;
//prints first 5 powers of 2, 3, 4
int main()
{
   int n2 = 2, n3 = 3, n4 = 4;
   int i;
   for(i = 1; i < 6; ++i) {
       cout.width(8);
       cout << n2;
       cout.width(8); 
       cout << n3;
       cout.width(8);
       cout << n4;
       cout << endl;
       n2 *= 2;
       n3 *= 3;
       n4 *= 4;
   }
   return 0;
}
The output of this program looks like this:


       2       3       4
       4       9      16
       8      27      64
      16      81     256
      32     243    1024

You can also set the precision of real numbers with the function cout.precision(p) where p is the number of digits to the right of the decimal point. The default is 6. Here is an example:


#include <iostream>
using namespace std;
int main()
{
   double d;
   d = 2.0 / 3.0;
   cout << d << endl; // default precision
   cout.precision(2);
   cout << d << endl;
   cout.precision(8);
   cout << d << endl;
   return 0;
}
Here is the output of this program.


0.666667
0.67
0.66666667

The output stream operator will print strings in the expected manner; that is, it will print an array of characters (including spaces and newline characters) up to the first null character.

However, you cannot print other types of arrays. If you create an array of integers like this:
int n[6];
and insert some values into it, and then try to print it like this:
cout << n;
the program will display the address of n in hexadecimal.

Exercise 2: The function sqrt takes a variable of type double and returns a value of type double which is the square root of its argument. Write a program which prints the square roots of 2, 3, and 5 to precisions of 0 to nine significant digits to the right of the decimal. Note that you must include the header file <math.h> to use sqrt. Here is what your output should look like.


 
Root of    2           3           5
------------------------------------
           1           2           2
         1.4         1.7         2.2
        1.41        1.73        2.24
       1.414       1.732       2.236
      1.4142      1.7321      2.2361
     1.41421     1.73205     2.23607
    1.414214    1.732051    2.236068
   1.4142136   1.7320508    2.236068
  1.41421356  1.73205081  2.23606798
Your program should have a nested loop and should not be more than about 25 lines.

Other output functions and manipulators

The library <iomanip> contains a number of other features to further customize input and output behavior. For example, you may already be familiar with the setw and setprecision manipulators in this library, which perform the same functions as the width and precision member functions described above. You can find a discussion of these advanced I/O options in sections 11.6 - 11.9 of the Deitel & Deitel text.

Files

Most computer applications have to read stored data from files and write data to files. There are two data types for files defined in the header file fstream.2An ifstream is an input file, that is, a file from which the program will read data, and an ofstream is an output file, that is, a file to which the program will write data.

Before a file can be used, it must be opened. Here is the C++ syntax to open an input file.

ifstream file ;
file.open(filename); 3

The variable filename is a null terminated array of characters which refers to a file on your system. In order for a file to be opened for input, it must already exist, and you must have read permission for it. If this is not the case, the open will fail. If the open fails, the value of the file variable will be NULL. You should always check for this and take appropriate action if the open has failed.

Here is an example of some code which opens a file called myfile for input.


  ifstream in;
  in.open("c:\\temp\\myfile.txt");
  if (in == NULL) {
     cerr << "Error, could not open the file myfile" << endl;
     exit(0);  
  }

The exit(0) function immediately terminates your program.

A filename can be just the name of a file, or it can include a full pathname, as in the example above. If it is only the name of a file, the program will look for the file in the working directory (normally the directory where the program is being run).

Alert: On a Windows system, subdirectory names in a path are separated with the backslash \ character. To represent a backslash character inside a string, you must type two backslashes in a row; a single backslash is used for special characters such as newline (\n), the null character (\0), etc. But, if you are reading in a file name from the keyboard, the user doesn't need to type double backslashes; the input functions take care of it.

Output files are opened in the same way as input files. However, they do not need to exist prior to being opened. If a file of that name does not exist, it will be automatically created, and if it does already exist, the contents will be erased and overwritten (without warning!)

Once a file has been opened for input, you can use the >> operator to read from the file, just as you did with cin. (In fact, cin is just an instance of an ifstream.) You can also use all of the other input functions mentioned above such as get and getline. Likewise, you can use the << operator to write to a file that has been opened for output, You can also use all of the other output functions such as width and precision. To use the functions, just replace cin or cout with the name of the ifstream or ofstream object.

When you are finished with a file, you should close it with the close() member function; although all open files are automatically closed when a program terminates.

Typically when you open a file for input, you do not know how many lines there are or how many characters there are, and you want to read the entire file. One easy way to test if you have reached the end of an input file is by testing the name of the ifstream object itself. When the end of file is reached, its value becomes NULL.

Alert: In C++ end-of-file is not sensed until after a read function call fails because the end of the file has been reached. Thus, you must always attempt to read from the file before testing for end-of-file. One good way to do this is to structure your reading loop with a priming read, as was described earlier:

input statement
while (ifstreamobject != NULL) {
all statements which process the most recent input
input statement
}

Here is a complete program which takes two file names typed in by the user. It opens the input file name and makes an exact duplicate of the file in the output file name. It shows the use of a priming read and testing the ifstream object for the reading loop. (It is in /dept/cs/cs2/download/copyfile.cpp)


#include <iostream>
#include <fstream>
using namespace std;
int main()
{
  ifstream in;                     // the input file
  ofstream out;                    // the output file
  char inname[80], outname[80];    // holds file names

  cout << "Input file? ";
  cin >> inname;
  in.open(inname);
  if (in == NULL) {
    cerr << "Error, could not open the file " << inname << endl;
    exit(0);
  }
  cout << "Output file? ";
  cin >> outname;
  out.open(outname);
  if (out == NULL) {
    cerr << "Error, could not open the file " << outname << endl;
    exit(0);
  }
  char c;
  in.get(c);           // can't use in >> c; -- don't want to skip whitespace
  while (in != NULL) {
    out << c;
    in.get(c);
  }
  return 0;
}

There is an easier way to test for end of file, if you can structure your reading loop to take advantage of it. Some input functions, including get and getline, return NULL when they attempt to read at the end of a file, so you can embed the function call inside a while statement and test this return value. If the read succeeds, the function returns a non-zero (true) value, and the statements inside the while loop are executed. If it fails, the loop ends. Here is an example. Pay particular attention to the structure of the while statement.


   ifstream infile;
   infile.open("c:\\temp\\myfile.txt");
   if (infile == NULL) {
     cerr << "Error, could not open myfile" << endl;
     exit(0);
   }
   char line[81];
   while (infile.getline(line,80) != NULL) {  
     // do whatever processing on the line
   }
   infile.close();

This program reads one line at a time from the file myfile, continuing to loop as long as there are more lines to be read, and then terminates. Note: if a line is encountered with more than 80 characters in it, the first 80 characters would be read and processed first, and the remaining characters on the line would be processed on the next pass through the loop.

Exercise 3: Rewrite the code that you wrote for Exercise 1 so that it prompts the user for two file names. The first is the input file and the second is the output file. The input file should consist of several lines of text including whitespaces. Your program should open this file, read it one line at a time, convert all lower case letters to upper case and write the line to the output file. Note that the number of characters written to the output file should be identical to the number of characters in the input file.



next up previous
Next: About this document ...
Paul Lalli 2002-05-17