Lab 6 -- I/O Streams

In this lab you will learn about the following: So let's get started. First, log in to the cs machine and create a lab6 subdirectory of your 1440 directory.

Streams

A stream is a flow of characters into your program (in which case it is called an input stream) or out of your program (in which case it is called an output stream). Thus far, you have been using two streams in your programs; cin which is connected to your keyboard and cout which is connected to your monitor. These streams have already been defined for you and you can use them as long as you have an include for iostream.h.

You can also define streams in your program to read input from a file instead of the keyboard and write output to a file instead of the monitor. Once a stream is connected to a file instead of the keyboard, it can read from the file in much the same way as it would from the keyboard. Likewise, once a stream is connected to a file instead of the monitor, it prints data to the file in much the same way as it would to the monitor. Using files instead of the keyboard or monitor allows you to store data permanently (or at least until you rm the input or output file). Okay, let's see an example.

The program below declares two streams, in_stream and out_stream. A stream is a special kind of variable known as an object. The variable in_stream is of type ifstream for input-file stream; it is a variable by which you will read input from a file. The variable out_stream is of type ofstream for output-file stream; you can use it to write output to a file.

Copy and paste program below into a file getbig1.C and compile and execute the program.

#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>

//Reads four numbers from the file numbers.dat and
//outputs to the file greatest.dat the greater of the four numbers
int main( )
{
   int num1, num2, num3, num4;  // the four numbers
   ifstream in_stream;          // input file stream
   ofstream out_stream;         // output file stream
   // connect stream to the input file
   in_stream.open("numbers.dat");
   if (in_stream.fail())
   {
      cout << "Input file opening failed.\n";
      exit(1);
   }   
   // connect stream to the output file
   out_stream.open("greatest.dat");
   if (out_stream.fail())
   {
      cout << "Output file opening failed.\n";
      exit(1);
   }
   in_stream >> num1 >> num2 >> num3 >> num4;
   if (num1 > num2 && num1 > num3 && num1 > num4)
      out_stream << "The greatest number is: " << num1;
   else if (num2 > num3 && num2 > num4)
      out_stream << "The greatest number is: " << num2;
   else if (num3 > num4)
      out_stream << "The greatest number is: " << num3;
   else
      out_stream << "The greatest number is: " << num4;
   in_stream.close();
   out_stream.close();
   return 0;
}
//*********************************************************************
What output did you get? You should have gotten "Input file opening failed." because we have not yet created a file to hold input for the program. Using pico, edit a file called numbers.dat and put four integer numbers 4 10 9 3 into the file. You can put them all on one line or four separate lines. Just like reading from the keyboard, when numbers are read from a file, whitespace is skipped. Save the file, exit pico and run the getbig1 program again.

What output did you get? You should have seen nothing on the monitor because the output produced by the program was sent to the file greatest.dat. Type the Unix ls command and you should see that there is now the file greatest.dat in your lab6 directory. See what is in that file by using the cat command (cat greatest.dat) and you'll see the line:

The greatest number is: 10
Next, edit the numbers.dat file again, change the 4 to a 12, run the getbig1 program again, and again look at greatest.dat using the cat command. You should see that the program caused the contents of the old greatest.dat file to be lost and now the greatest.dat file contains the output of the most recent run of the program.

Okay, let's look at the getbig1.C program more carefully. First notice the include directive for fstream.h. You'll need this include directive in order to declare variables of type ifstream or ofstream. The statement:

in_stream.open("numbers.dat");
opens the numbers.dat file. Since in_stream is declared to be of type ifstream the file is being opened as input; ie. the program will read from the file. The if (in_stream.fail()) statement that comes next checks to see if the open was successful. We saw above a case where the open would fail, ie. the file didn't exist. If the open wasn't successful, a message is printed. Notice that the message is displayed on the monitor, thus your program can both write to the monitor or a file, and read from a file or the keyboard. It simply needs to use the correct stream.

The statement

exit(1);
is a function call to a predefined function that causes the program to halt. The exit function takes as input a single integer argument. By convention, if the program needs to halt because of an error then the argument should be 1; otherwise the argument should be 0. In order to use exit you must have an include directive for stdlib.h.

The next statement opens the greatest.dat file to receive output. And the if (out_stream.fail()) checks to see if that open was successful.

Finally, if both opens were successful, the program begins the work of reading from the file and figuring out what number was the greatest. Notice the read is very much like how we read from a keyboard except that the cin is replaced by in_stream. Likewise, the write is very much like how we write to the monitor except instead of using cout, out_stream is used.

The final two statement of the program close the files, ie. it disconnects the file from the stream. It is a good idea to close files before a program exits. Also, if for some reason you wanted to begin reading the file from the top of the file again, you would close the file and then open the file again. You could also use the same stream variable to open a different file.

Notice that our files have two names. The name numbers.dat is the external name for the input file. After it is open, the program reads from the file using the internal name in_stream. There is nothing special about the name numbers.dat. We could have called the file numbers instead; the dat suffix makes it more apparent that the file is a data file instead of a program file, directory or executable.

Streams as Arguments to Functions and EOF

The program below demonstrates two important uses of streams. First, it shows how a stream can be passed to a function. Thus, after you open a file, you can pass the stream connected to the file to another function in order to read and process the data in another function. Notice that a stream must be passed as call-by-reference.

Second, the program demonstrates how to check for end of file, commonly called EOF. Often when reading from a file, we want the program to handle files where the length of the file is unknown. Or else, we want the program to work on two different input files with different length. Thus, we want the program to simply continue reading until there is no more data. The statement:

while (in_file >> num)
reads a number from in_file until EOF is detected. When there is a number to read, in_file >> num evaluates to true and puts the number read into num. When there are no more numbers, in_file >> num evaluates to false and nothing is put into num.
#include <iostream.h>
#include <fstream.h>
void read_data(ifstream& in_file);  // stream must be passed call by ref
int main ()
{
   ifstream fin;
   fin.open("numbers.dat");
   if (fin.fail())
   {
      cout << "Input file opening failed.\n";
      exit(1);
   }
   read_data(fin);
   return 0;
}

void read_data(ifstream& in_file)
{
   int num;
   while (in_file >> num)
   cout << "\nThe number read: " << num;
   cout << endl;
}
Look at the program above closely and be sure you understand how it works. You'll need to do some of the same things above in the homework assignment.

File Names as Input

Suppose you wanted to run a program on different data files. For example, suppose we have two input files each with four numbers in them on which we want to run the program getbig1. The input files are numbers.dat and numbers2.dat. We could run the program first on numbers.dat and then edit the program, change the numbers.dat to numbers2.dat and then compile and execute the program again. Or we change the program so that the name of the input file is input to the program, like in the following example.
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>

int main ()
{
   char in_file_name[16],   // variable to hold name of input file
   out_file_name[16];  // variable to hold name of output file
   int num;                 // variable to hold one number read from file
   ifstream in_stream;          // input file stream
   ofstream out_stream;         // output file stream
   cout << "Enter name of input file: ";
   cin >> in_file_name;
   cout << "Enter name of output file: ";
   cin >> out_file_name;
   // connect stream to the input file
   in_stream.open(in_file_name);
   if (in_stream.fail())
   {
      cout << "Input file opening failed.\n";
      exit(1);
   }
   // connect stream to the output file
   out_stream.open(out_file_name);
   if (out_stream.fail())
   {
      cout << "Output file opening failed.\n";
      exit(1);
   }

   // read one number and put it in the output file
   in_stream >> num;
   out_stream << "The number read is: " << num;
   // close the files
   in_stream.close();
   out_stream.close();
}
The external file names in our programs are represented as strings in our programs, so in order to have the external name as input to a program we need a variable capable of storing string. The variables in_file_name and out_file_name are variables which can store strings that are no more than 15 characters long. Notice the number 16 in the variable declaration. This tells the compiler to create space for 16 characters, but one of those characters is a special character indicating the end of the string, thus our file name can only be 15 characters long. If you have a file with a longer name, simply change the 16 to something bigger.

Copy the program getbig1.C into the file getbig2.C. Modify the program so that it takes the name of the input file as input to the program. (You can leave the output file as greatest.dat.) Next create another file called numbers2.dat and put 4 integer numbers in it. Compile and execute your getbig2 program, first giving it numbers.dat as input and then numbers2.dat. Be sure to check the contents of greatest.dat between runs to verify the correct file is being opened.
 

Character I/O

Sometimes you'll need your program to read data from a file not as numbers but instead as characters. Right now you'll be introduced to two C++ functions which are useful for reading character data. The first function get reads one character from a stream and puts it into its character argument. For example:
char letter;

cin.get(letter);
The statement above reads one character from the keyboard and stores it in the variable letter. Remember a character is a single character and if the user entered the number 10, the program would read the 1 as a character and leave the 0 and newline unread.

The second function is put which can be used to put a character onto an output stream. For example, the statement:

cout.put('a');
displays the single character a on the monitor. You can also use put to print a newline, by calling it like this:
cout.put('\n');
Note that '\n' is the newline character and is not the same as "\n". "\n" is the newline character within a string. Since the put function expects a character, you would get a compilation error if you called it like this:
cout.put("\n");
You can use get to read all of the characters on a line of input (no matter how many characters there are), by looking to see if the character your program read is a newline like in the following example:
char symbol;

cout << "Enter a line of input and I will echo it:\n"

do

{

   cin.get(symbol);

   cout.put(symbol);

} while (symbol != '\n');

Formatting Output

In the past, you formatted the output of double precision numbers by calling functions like these:
cout.setf(ios::fixed);

cout.setf(ios::showpoint);

cout.precision(2);
Using the same functions, but with a different stream, you can format the output of data going to a file. For example, if the output stream is named ot_stream then statements:
ot_stream.setf(ios::fixed);

ot_stream.setf(ios::showpoint);

ot_stream.precision(2);
cause the double precision numbers output to ot_stream to be formatted. Let's go over what these mean again. The first statement:
ot_stream.setf(ios::fixed);

causes the program to display the number in fixed point notation; that is what we consider to be the "normal" notation (not scientific notation). The second statement:
ot_stream.setf(ios::showpoint); 

causes the program to always show a decimal point even if the number has no non-zero digits after the decimal point. The final statement:
ot_stream.precision(2);

causes the program to display two numbers after the decimal point.
The last statement is a function call to a stream function called precision. Note that the effect of the call will only be to the indicated stream, in this case, ot_stream. The first two statements are function calls to a stream function called setf for set flags. A flag is an instruction to do something in one of two possible ways. You turn on the flag by making a call to setf. You can turn off the flag by making a call to the function unsetf. For example, if printing some numbers as fixed, you decide you want to display the remaining numbers in scientific notation, you turn off the flags, like in the following example:
#include <iostream.h>
int main ()
{
   cout.setf(ios::fixed);
   cout.setf(ios::showpoint);
   cout.precision(2);
   cout << 99.3456 << endl<< 100.0 << endl<< 11.23 << endl;
   cout.unsetf(ios::fixed);
   cout.unsetf(ios::showpoint);
   cout << 99.3456 << endl << 100.0 << endl << 11.23 << endl;
   return 0;
}
Copy and paste the program above into a file called setftest.C and compile, execute the program and study the output. Now, edit the program changing the precision to 1 and compile and execute the program again. Again, edit the program changing the precision to 4 and compile and execute the program again. Notice that if the number is small enough to display in fixed point notation so that the desired number of digits are displayed as indicated by the argument to precision, then C++ does so without displaying the number in scientific notation.

Here are the other flags that can be passed to the setf and unsetf functions.
flag meaning default
ios::fixed fixed point notation not set
ios::scientific scientific notation set
ios::showpoint show decimal point and trailing zeros not set
ios::showpos show + for positive numbers not set
ios::right right justify; use with width set
ios::left left justify; use with width not set

The last two flags should be used in conjunction with the width function. The width function specifies the number of columns used to display an item of output. For example, the statements

cout.width(4);
cout.setf(ios::left);
cout << 8 << 22 << endl;
cause 8 and 22 to each be displayed left justified in 4 columns.

You can also use a special type of function called a manipulator to format output. Manipulators are placed following the insertion operator (<<) and specify how to format the number after the next insertion operator. Two manipulators that we will learn right now are setw and setprecision. The statement:
cout << setw(4) << 22 << endl;
causes the 22 to be displayed right justified in 4 columns. The statements:

cout.setf(ios::fixed);

cout.setf(ios::showpoint);

cout << setprecision(2) << setw(4) 

     << 10.3 << endl;
cause the number 10.30 to be right justified in 6 columns. In order to use manipulators, you must use the include directive to include the file iomanip.h.

Copy and paste program below into the file manip.C (manip for manipulators). Notice the include directive for iomanip.h. Compile and execute the program and notice how the numbers are squished together. Next, edit the file and make calls to the necessary stream functions and/or manipulators in order to display the output as two rows of numbers where each number is displayed in 10 columns and with two places displayed after the decimal point.

#include <iostream.h>

#include <iomanip.h>

int main ()

{
   cout << 99.3456 << 10000.3456 << endl;
   cout << 9.36 << 10.3456 << endl;
   cout << 299.3456 << 0.3456 << endl;
   cout << 33399.3456 << 1.56 << endl;
   return 0;
}
Desired Output
     99.35  10000.35
      9.36     10.35
    299.35      0.35
  33399.35      1.56

Homework (Due October 25)

Your homework is to write a C++ program called lab6.C that solves the quadratic equation.  You have already solved this in your assignment (4).  This time you will create a function called Qaud that takes a, b, a, x1, and x2 as the arguments, where a, b, and c are the coefficients of the quadratic equation: ax2 + bx +c = 0 and x1 and x2 are the roots.  Note that as we have seen before, depending on the value of a, b, and c, this equation may have two roots, no real root, or two similar roots (x1 = x2).   Also note that we found the answer to this equation when a was zero.  In such a case, our equation is no longer a quadratic and it is a linear equation.  We found the answer to that linear equation to be x1 = -c/b.  So your function Quad must be able to tell us how many answers (the return value) we had and what those answers are.  Return the number of roots through the functions itself, i.e., define:

int Quad(double a, double b, double c, double x1, double x2).

Every time a = 0 first print that the equation was not a quadratic equation and then print the output the way it is shown in the table below.

Your program should include the Instruction function that prints a description of the program and how it will work.  It has to explain what the three different types of answers are and also it has to mention that it will generate an answer when a = 0, although the equation is no longer a quadratic equation.

You will use an input file coeff.dat to to read a b and c for 10 equations.  You will define a function Read_coeff to that reads one line of the file at a time and will return a, b, c values. Then you will call Quad to solve the equation.  You also need to write the Write_roots function that prints the answers to the output file called quad.out in the format shown in the table below.

Each line contains the coefficients for one equation.  
Solving the quadratic equations complete.  Output file: quad.out

***************************************************************
After running your program the file quad.out should contain:
         a         b         c        x1        x2
       ---       ---       ---       ---       ---
       1.0       2.0      -3.0      1.00     -3.00
       1.0       2.0       3.0        --        --
a was 0, it was not a quadratic equation, for linear equation x1 = -c/b
       0.0       4.0       3.0     -0.75       
       2.0       0.0      -5.0      1.58     -1.58
       4.0       0.0      -1.0      0.50     -0.50
       1.0       0.0       4.0        --        --
       1.0       0.0      -4.0      2.00     -2.00
        ...
        ...
-- means the roots are imaginary
Program requirements: Turn in a printout of the lab6 program and the output file quadratic.dat. In addition, submit the labs program and the other programs required as part of this lab by typing:

~rt/bin/submit1440_10?  lab6 lab6.C

Depending on the section number, replace ? with 1 or 3.
You must be in your lab6 directory when you type this command and each of the files to be submitted must end with a .C