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"; } }
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.
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.
int
; it can be a double
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:
for (i = 0; i < max; i++)
for (i = max - 1; i >= 0; i--)
for (i = 1; i <= max; i++)
for (i = max; i > 0; i--)
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
There are two ways to use a file, rather than the keyboard, as the source of the input stream - you can:
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
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; }
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.)
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 876Any 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.
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