C++ Week 3

Loops, Reading from files, istringstreams

Pseudo-random numbers

The C++ random number generator is only pseudo-random. Successive calls to the random number generation function rand() will produce the same series of numbers. You can use the srand() function to seed the random number generator; seeding it with a different number will produce a different series, but always the same series for a given seed number. If you want to make the function less predictable, you need to specify a different seed each time you execute your program. You can do this by setting the seed using srand() and supplying an argument based on the time.

To use the random number generation functions you need to #include the cstdlib library. To access the current time, you need the time(NULL) function which is in the ctime library.

For example the following code implements a simple fruit machine:

#include<iostream>
#include<cstdlib>
#include<ctime>
#include<string>

using namespace std;

int main( )
{  srand(time(NULL));
   cout << "Do you want to play? (y or n)";
   string resp;
   cin >> resp;
   if (resp == "y")
   {  int x = rand() % 4 + 1;
      int y = rand() % 4 + 1;
      cout << x << " " << y << endl;
      if (x == y) 
         cout << "prize\n";
   }
}

Loops

The above program plays the fruit machine sequence only once before exiting. Ideally we would want it to keep on asking the question Do you want to play and repeating as long as the answer y is given. We can do this by using a while loop based on the condition that resp is equal to y:

...

cin >> resp;
while(resp == "y")
{  int x = rand() % 4 + 1;
   int y = rand() % 4 + 1;
   cout << x << " " << y << endl;
   if (x == y) 
      cout << "prize\n";
   cout << "Play again (y/n)?";
   cin >> resp;
}

If the while condition is true the statement after the while (it might be a block of statements enclosed in { } ) is executed. Then the condition is tested again; if it is still true, the statement is executed again, and so on. If and when the test is false, the loop is skipped and execution continues with the statement after the loop.

Another variation on this theme is to specify in advance how many times the loop is to be executed. You can do this as follows:

int i = 0;
while (i < 100)
{   cout << i << endl;
    i++;
} 

This code outputs the numbers 0 to 99.

Be careful not to put a semi-colon immediately after the condition of the while. An empty statement is a valid statement in C++. If we added a semi-colon to the above example, thus:

int i = 0;
while (i < 100);	// Note the semi-colon
{   cout << i << endl;
    i++;
} 
it would change the meaning completely. The body of the loop now consists of the empty statement between the (i < 100) and the semi-colon. In other words, that single line while (i < 100); is now the complete while loop. The line cout << i << endl; is just the start of the rest of the program.

So what would it do? It tests the condition and decides that 0 is less than 100. Then it executes the body of the loop, i.e. the empty statement, so it does nothing. Then it returns to the start of the loop and again tests whether 0 is less than 100. And so on. It's in an infinite loop, apparently doing nothing, but actually comparing the value of i (0) with 100 over and over again.

for loops

The for loop provides a more succinct way of writing the kind of while loop we've just seen. The for statement contains three items: some initialisation, the continuation condition and the action(s) performed at the end of each iteration. A typical for loop takes the following form:

for (n=0; n < 100; n++)
   iterated statement 

You can declare the loop driver in the for statement:

for (int i = 0; ... )

in which case the loop driver's scope is the loop itself - the variable is destroyed when the loop is exited. If you want to use the loop driver elsewhere you need to declare it before the loop.

For example:

for (int n = 100; n >= 0; n-=5)
   cout << n << endl;

This is the standard pattern of the three elements in the definition of a for loop, but the three parts do not have to have any obvious relationship to each other - from a programmer's point of view, it's better if they do, but the compiler doesn't care. It's also not essential to have all three parts:

Here are some typical for loops:

int nz = 0;
for (int i = 0; i < s.length(); i++)
   if (s.substr(i, 1) == "z") nz++;
cout << "There are " << nz << " zs in the string.\n";

or:

for (int i = s.length() -1; i >=0; i--)
   cout << s.substr(i, 1); 

There are a few patterns that you often need:

You might sometimes need a combination of elements from more than one of these patterns, such as for (i = 0; i <= max; i++), but if you write something unusual like this, make sure that it really is what you want.

You can initialize more than one variable and perform more than one action at the end of each loop. For example:

for (i = 1, j = 0; ...; i++, j++)  
for (i = 0, j = s.length() - 1; i < s.length() / 2; i++, j--)
   if (s.substr(i, 1) == s.substr(j, 1) ...;

You can have a for loop where all the work is done by the loop header and there is no need for a body at all. For instance, the following finds the first occurrence of the letter "z" in a string:

int i;
for (i = 0; i < s.length() and s.substr(i, 1) != "z"; i++)
   ; // loop terminates when i is the position of the first "z" in the string s
// if i is equal to s.length(), it means that there was no "z" in the string

Handling files

There are two ways to use a file, rather than the keyboard, as the source of the input stream - you can:

Redirecting I/O from the command line

To call a program from the command line you type the name of the executable file, e.g. prog (if your program file was called prog.cpp), and by default it reads input from the keyboard and sends output to the monitor. However, you can specify instead that the program reads its input data from a file and writes the output to another file. You do this by typing the following at the command prompt:

prog < input_file // instructs the program to read from input_file 

Now, any statements in the program that take input from cin will take input from this input_file instead of from the keyboard. Similarly you can redirect the standard output:

prog > output_file // instructs the program to write to output_file 

Now, any statements in the program that send output to cout will send output to this output_file instead of to the monitor. If you do this on Unix, then, if there is no file of this name, it will create one; if there is a file of this name, the new one will replace the old one.

You can combine these arguments to read from one file and write to another:

prog < infile > outfile 

Using a filestream

Filestreams allow you to hardcode the names of the files that will be read and written when the program is executed or to supply the filenames as strings when the program runs. To use filestreams you must #include the fstream library. Once a file has been opened for reading, you can use the name of the filestream in the same way as you would use cin to read characters and lines from the file rather than from the keyboard. For example:

#include <fstream>
#include <string>
using namespace std;

int main()
{  ifstream infile; // ifstream is the typename of an input file stream called "infile"
   infile.open("datafile.dat"); // opens datafile.dat for reading when stream infile is accessed
   if (infile.fail()) // detects failure in opening file (usually because it couldn't find it)
   {  cout << "Error: input file not opened" << endl;
      return 1;		// exit program
   }  
   else cout << "File successfully opened" << endl;  // This is just an example; you don't normally output such a message.
   int n;  string s;
   infile >> n;  getline(infile,s);   // use "infile" like "cin"
   infile.close();   // you can close an ifstream and reopen it to read a different file
   string fname;
   cout << endl << "What file should I open?" << endl;
   cin >> fname;
   infile.open(fname.c_str()); // c_str converts the filename into a form the open function can handle
   if (infile.fail())
   {  cout << "Error: file " << fname << " not opened." << endl;
      return 1;
   }
   else cout << "File successfully opened" << endl;
}

Reading until the end of a file

When you get to the end of a file and try to read more input, the input stream goes into a fail state. This means that to test for the end of a file you have to try to read beyond the end of the file and then test for failure. The test is always retrospective; you are asking, "Has the input failed?" Let's assume that we have successfully opened a file by opening an ifstream called input_stream, that we have a string variable called s and that we are reading the file one line at a time. It is tempting to try to read to the end of this file as follows:

while (not input_stream.fail())  // tempting but WRONG!!
{   getline(input_stream, s);
   ...  process s 
}

However, this approach won't work. Having read the last line of the file, the ifstream is not (yet) in a fail state. The answer to the question, "Has the input failed?" is No – we read the last line of the file successfully. So it enters the loop again and tries to read beyond the end of the file. The ifstream now does go into a fail state. s is unchanged, and we now try to process s as if we had just read a line, but we haven't; we will be processing the last line for a second time. Any of the following will work:

while (true)
{  getline(input_stream, s);
   if (input_stream.fail()) break;    // break terminates the current loop; this works, but exiting a loop from the middle is not good style
    ... process s 
} 
getline(input_stream, s);             // Read the first line before entering the loop
while (not input_stream.fail())
{   ...  process s 
  getline(input_stream, s);           // Read the next at the end of the loop
}
while (getline(input_stream, s))      // Looks a bit strange, but works.
{ .... process s 
}

The last works because, if getline(input_stream, s) succeeds, it returns a value which is treated as true; if it fails, it returns a value which is treated as false. So we read the last line of the file successfully; the condition is true; we enter the loop and process this final line. We then try to read beyond the end of the file; this fails and the condition is false, so we exit the loop. The evaluation of the while condition is both reading a line of input and returning a value indicating success or failure; this one line of program code is doing a lot of work, which is why this version is so succinct.

In all the above examples, I could have used the input operator >> instead of getline. Suppose we were reading a file of integers, one at a time. Any of these would work (assume we've declared an integer variable n):

while (true)
{  input_stream >> n;
   if (input_stream.fail()) break;    // break terminates the current loop; this works, but exiting a loop from the middle is not good style
    ... process n 
} 
input_stream >> n;                 // Read the first integer before entering the loop
while (not input_stream.fail())
{   ...  process n 
    input_stream >> n;           // Read the next at the end of the loop
}
while (input_stream >> n)
{ .... process n 
}

One of the advantages of using the first of the two methods described earlier for reading a file – the redirection of standard input – is that you can test your program with input from the keyboard and then run exactly the same program taking input from a file. When keying in data from the keyboard, you simulate the end of file by keying in CTRL+Z (for DOS) CTRL+D (for UNIX).

(Annoyingly, on Borland, this works properly only if the control character comes at the start of a new line and only if it is the first thing the program encounters when it attempts a read. Suppose you are keying in integers, one per line, and reading them with the input operator. The input stream could look like this: 45\n67\n^Z\n (where \n indicates where you hit RETURN, and ^Z is where you key in a CTRL-Z). And suppose you are reading it with while (cin >> x) One read will take in the 45 but stop at the newline (without consuming it). The next read will skip the newline (because that's what >> does) and take in the 67. Now, in order for the next read to detect the ^Z properly, you have to consume the newline that precedes it. So arrange that each cin >> x is followed immediately by a getline(cin, junk); or a cin.ignore();, so each newline gets consumed immediately after a read, so each read begins at the start of a line. This only applies to simulating EOF from the keyboard; it has no problem detecting EOF in a file.)

Istringstreams

The input stream goes into a fail state when you try to read beyond the end of a file, but it also goes into a fail state if you are reading with >> straight into an int or a double and it encounters unacceptable data; for example, it would fail if it were trying to read a value into an int and encountered a letter. Suppose the file contained the following:

123
-456
891
XYZ
543
876
Any of the methods recommended above, using input_stream >> n, will read only the first three lines; they will fail on XYZ and exit. But we would probably prefer that the program skip over XYZ, perhaps reporting that it had encountered some unaccceptable input, and carry on to the end. We can do this by using an istringstream. This is an object which is a cross between a string and an input stream. It's like a string but you can do the sort of things with it that you can do with an input stream. To use one, you have to #include <sstream>. You would use it like this:
string s;
while (getline(input_stream, s))
{   istringstream is(s);      // initialize a new istringstream with the string s you've just read
    int n;
    is >> n;                  // you can use >> with an istringstream
    if (is.fail())            // i.e. if the attempt to extract an int just failed
    {   cerr << "Bad input data: " << s << endl;        // Error message to cerr (or to cout)
        continue;             // go on to next line
    }
    process n                 // an int was successfully extracted from is
}

Since we have declared the istringstream local to the while loop, we get a new one each time round the loop. (This is not special to istringstreams; we also get a new int n each time round the loop.)

cerr is an output stream intended for error messages; it defaults to the monitor.

continue is a relative of break. It terminates the current iteration through the loop and goes back to the beginning, i.e. it begins the next iteration with the evaluation of the while loop condition. Some programmers disapprove of explicitly changing the behaviour of a loop with break or continue. I am using it here in order to deal briskly with the bad data and to focus on the processing of n, which is the important part. Having the processing of n hanging off an else can look a bit unwieldy, especially if it's long and complicated.

It is possible, though unlikely, that the getline function will fail before the end of the file, perhaps due to a hardware glitch. Once you have exited the loop, you can check whether you are indeed at the end of the file by using the input_stream.eof() boolean function. This returns true if you have attempted to read beyond the end of the file. By the way there is no advantage in using it as the loop condition:

while (not input_stream.eof())  // tempting but still WRONG!!
{   getline(input_stream, s);
   ...  process s 
}
This will not work properly for the same reason that while (not input_stream.fail()) does not work, as explained above.

do ... while loops

It is possible that a while loop will not execute even once if the termination condition is met on the first iteration (in other words a while loop executes zero or more times). This is generally what you want (consider trying to read from an empty file, for example). But sometimes you need to execute a loop at least once. You can force a while loop to behave in this way, but it is more elegant to use a construct specially provided for this – a do ... while loop:

string move;
do
{  cout << "Make your move or Q to quit.";
   cin >> move;
   if (move != "Q")
  	 process move  
}  while (move != "Q");

Notes on R. Mitton's lectures by S.P. Connolly, edited by R. Mitton, 2000-2010