Module 5 Complete
Module 5 Complete
MODULE 5
CONSOLE I/O OPERATIONS
C++ supports two complete I/O systems: the one inherited from C and the
object-oriented I/O system defined by C++ (hereafter called simply the C++ I/O
system). Like C-based I/O, C++'s I/O system is fully integrated. The different aspects
of C++'s I/O system, such as console I/O and disk I/O, are actually just different
perspectives on the same mechanism.
a list of the mapping of template class names to their character and wide-character
versions.
Since these functions are members of the input/output stream classes,we must
invoke them using an appropriate object.
Example:
char c;
cin.get(c );//get a character fron keyboard and assign it to c
while(c!=‘\n‘)
{
cout<<c;//display the character on screen
cin.get(c);//get another character
}
This code reads and displays a line of text.Remember, the operator >> can also
be used to read a character but it will skip the white spaces and newline character. The
above while loop will not work properly if the statement
cin>.c;
is used in place of
cin.get(c);
The get(void) version is used as follows:
………..
char c;
c=cin.get();//cin.get(c); replaced
………..
………..
The value returned by the function get() is assigned to the variable c.
The function put(), a member of ostream class, can be used to output a line of
text,character by character . For example,
cout.put(‗x‘);
displays the character x and
cout.put(ch);
displays the value of variable ch.
The variable ch must contain a character value. We can also use a number as an
argument to the function put(). For example,
cout.put(68);
Displays the character D. This statement will convert the int value 68 to a char
value and display the character whose ASCII value is 68.
The following segment of a program reads a line of text from the keyboard and
displays it on the screen.
char c;
cin.get(c);//read a character
while(c!=‘\n‘)
{
cout.put(c);
cin.get(c);
}
encountered. If the size is greater than the length of line, then it displays beyond the
bounds of line.
#include<iostream.h>
int main()
{
char *string1=‖C++‖;
char *string2=‖Program‖;
int m=strlen(string1);
int n=strlen(string2);
for(int i=1;i<n;i++)
{
cout.write(string2,i);
cout<<‖\n‖;
}
for(int i=n;i>0;i--){
cout.write(string2,i);
cout<<‖\n‖;}
cout.write(string1,m).write(string2,n);//concatenating strings
cout<<‖\n‖;
//crossing the boundary
cout.write(string1,10);
return 0;
}
Output of program:
p
pr
pro
prog
progr
progra
program
progra
progr
prog
pro
pr
p
C++ program
C++ progra
Function Task
Width() To specify the required field size for displaying an output value.
precision() To specify the number of digits to be displayed after the decimal point
of a float value.
fill() To specify a character that is used to fill the unused portion of a field.
setf() To specify format flags that can control the form of output
display(such as left-justification and right-justification)
unsetf() To clear the flags specified.
Manipulators are special functions that can be included in the I/O statements to
alter the format parameters of a stream. Table shows some important manipulator
functions that are frequently used. To access these manipulators, the file iomanip
should be included in the program.
In addition to these functions supported by the C++ library, we can create our
own manipulator functions to provide any special output formats.
Setting Precision:precision()
By default, the floating numbers are printed with six digits after the decimal
point. However, we can specify the number of digits to be displayed after the decimal
point while printing the floating-point numbers. This can be done by using the
precision() member function as follows:
cout.precision(d);
Where d is the number of digits to the right of the decimal point. For example
,the statements
cout.precision(3);
cout<<sqrt(2)<<‖\n‖;
cout<<3.14159<<‖\n‖;
cout<<2.50032<<‖\n‖;
will produce the following output:
1.141(truncated)
3.142(rounded to the nearest cent)
2.5(no trailing zeros)
Not that, unlike the function width(),precision() retains the setting in effect
until it is reset. That is why we have declared only one statement for the precision
setting which is used by all the three outputs.
We can set different values to different precision as follows:
cout.precision(3);
cout<<sqrt(2)<<‖\n‖;
cout.precision(5);//reset the precision
cout<<3.14159<<‖\n‖;
We can also combine the field specification with the precision setting.
Example:
cout.precision(2);
cout.width(5);
cout<<1.2345;
The first two statements instruct:‖ print two digits after the decimal point in a
field of five character width‖. Thus, the output will be:
1 2 3
Where ch represents the character which is used for filling the unused
positions. Example:
cout.fill(‗*‘);
cout.width(10);
cout<<5250<<‖\n‖;
The output would be:
* * * * * * 5 2 5 0
Financial institutions and banks use this kind of padding while printing cheques
so that no one can change the amount easily.
(Table 5.3 The bit fields, flags and their format actions)
1 0 . 7 5
2 5
1 5 . 5
Note that the trailing zeros in the second and third items have been truncated.
Certain situations, such as a list of prices of items or the salary statement of
employees, require trailing zeros to be shown. The above output would look better if
they are printed as follows:
10.75
25.00
15.50
The setf() can be used with the flag ios::showpoint as a single argument to
achieve this form of output. For example,
cout.setf(ios::showpoint);//display trailing zeros
Would cause cout to display trailing zeros and trailing decimal point. Under
default precision, the value 3.25 will be displayed as 3.250000. Remember, the default
precision assumes a precision of six digits.
Similarly, a plus sign can be printed before a positive number using the
following statement:
cout.setf(ios::showpos);//show + sign
For example, the statements
cout.setf(ios::showpoint);
cout.setf(ios::showpos);
cout.precision(3);
cout.setf(ios::fixed,ios::floatfield);
cout.setf(ios::internal,ios::adjustfield);
cout.width(10);
cout<<275.5<<‖\n‖;
Will produce the following output:
+ 2 7 5 . 5 0 0
The flags such as showpoint and showpos do not have any bit fields and
therefore are used as single arguments in setf().This is possible because the setf() has
been declared as an overloaded function in the class ios. Table 5.4 lists the flags that
do not possess a named bit field. These flags are not mutually exclusive and therefore
can be set or cleared independently.
Flag Meaning
ios::showbase Use base indicator on output
ios::showpos Print + before positive numbers
ios::showpoint Show trailing decimal point and zeroes
ios::uppercase Use uppercase letters for hex output
ios::skipus skip white space on input
ios::unitbuf Flush all streams after insertion
ios::stdio Flush stdout and stderr after insertion
(Table 5.4 Lists the flags that do not possess a named bit field)
cout<<manip1<<item1<<manip2<<item2;
This kind of concatenation is useful when we want to display several
columns of output.
The most commonly used manipulators are shown in Table 5.5 .The table
also gives their meaning and equivalents. To access these manipulators, we must
include the file iomanip in the program.
cout.precision(p)//p=2
{
output<<‖Rs‖;
return output;
}
ostream& form(ostream & output)
{
output.setf(ios::showpos);
output.setf(ios::showpoint);
output.fill(‗*‘);
output.precision(2);
output<<setiosflags(ios::fixed)<<setw(10);
return output;
}
int main()
{
cout<<currency <<form<<7864.5;
return 0;
}
Notice that the function returns a reference to a stream of type ostream., which
is an output stream (ostream is a class derived from ios that supports output.)
Creating Our Own Extractors:
Extractors are the complement of inserters. The general form of an extractor
function is
istream &operator>>(istream &stream, class_type &obj)
{
// body of extractor
return stream;
}
Extractors return a reference to a stream of type istream, which is an input
stream. The first parameter must also be a reference to a stream of type istream.
Example:
Program to overloading << and >> as a non-member function
#include<conio.h>
#include<iostream.h>
class vector
{
int v[10];
public:
vector();
vector(int *x);
friend istream & operator >> (istream &input,vector &b);
friend ostream & operator << (ostream &output,vector &b);
};
vector::vector()
{
for(int i=0;i<5;i++)
v[i]=0;
}
vector::vector(int* x)
{
for(int i=0;i<5;i++)
v[i]=x[i];
}
istream & operator >> (istream &input,vector &b)
{
for(int i=0;i<5;i++)
input>>b.v[i];
return input;
}
ostream & operator<<(ostream &output,vector &b)
{
output<<"("<<b.v[0];
for(int i=1;i<5;i++)
output<<";"<<b.v[i];
output<<")";
return(output);
}
void main()
{
clrscr();
vector vec;
cout<<"\nEnter the elements for vector: ";
cin>>vec;
cout<<"\nElements are";
cout<<vec;
getch();
}
Output:
Enter the elements for vector: 1 2 3 4 5
Elements are (1;2;3;4;5)
The I/O system of C++ handles file operations which are very much similar to
the console input and output operations. It uses file streams as an interface between
the programs and the files. The stream that supplies data to the program is known as
input stream and the one that receives data from the program is known as output
stream. In other words, the input stream extracts (or reads) data from the file and the
output stream inserts (or writes) data to the file.
This is illustrated in Fig:5.1
Read data Data input
Input stream
Write data
Output stream
Data output
Class Contents
filebuf Its purpose is to set the file buffers to read and write. Contain close() and
open() functions
fstreambase Provides operations common to the file streams. Serves as a base for
fstream,ifstream and ofstream class.Contains open() and close() functions
ifstream Provides input operations.Contains open() with default input mode.
Inherits the functions get(), getline(), read(), seekg() and tellg() functions
from istream.
ofstream Provides output operations. Contains open() with default output mode.
Inherits the functions put(), seekp(), write() and tellp() functions from
ostream.
fstream Provides support for simultaneous input and output operations. Contains
open() with default input mode. Inherits all the functions from istream
and ostream classes through iostream
Fig5.2.
Disk
Output stream
Results
file
outfile
Program
Data
Input stream
file
infile
fout<<‖USA\n‖;
fout<<‖UNITED KINGDOM\n‖;
fout<<‖SOUTH KOREA\n‖;
fout.close(); //disconnect ―country‖ and
fout.open(―capital‖);//connect ―capital‖
fout<<‖WASHINGTON\n‖;
fout<<‖LONDON\n‖;
fout<<‖SEOUL\n‖;
fout.close();//disconnect ―capital‖
const int N=80;//size of line
char line[N];
ifstream fin;//create input stream
fin.open(―country‖);//connect ―country‖ to it
cout<<‖contents of country file\n‖;
while(fin)//check end-of-file
{
fin.getline(line,N);//read a line
cout<<line;//display it
}
fin.close();//disconnect ―country‖ and
fin.open(―capital‖);//connect ―capital‖
cout<<‖contents of capital file\n‖;
while(fin)
{
fin.getline(line,N);
cout<<line;
}
fin.close();
return 0;
}
connect one
file to fin
#include<iostream.h>
#include<fstream.h>
int main()
{
cons tint SIZE=80;
char line[SIZE];
ifstream fin1,fin2;//create two input streams
fin1.open(―country‖);
fin2.open(―capital‖);
for(int i=1;i<=10;i++)
{
if(fin1.eof()!=0)
{
cout<<‖Exit from country\n‖;
exit(1);
}
fin1.getline(line,SIZE);
cout<<‖Capital of‖<<line;
if(fin2.eof()!=0)
{
cout<<‖Exit from capital\n‖;
exit(1);
}
fin2.getline(line,SIZE);
cout<<line<<‖\n‖;
}
return 0;
}
The output of Program would be:
Capita of USA
WASHINGTON
Capital of UNITED KINGDOM
LONDON
Capital of SOUTH KOREA
SEOUL
The prototype of these class member functions contains default values for
the second argument and therefore they use the default values in the absence of the
actual values. The default values are as follows:
ios::in for ifstream functions meaning open for reading only.
ios::out for ofstream functions meaning open for writing only.
The file mode parameter can take one (or more) of such constrants defined
in the class ios.
Table lists the file mode parameters and their meanings
Parameter Meaning
ios::app Append to end-of-file
ios::ate Go to end-of-file on opening
ios::binary Binary file
ios::in Open file for reading only
ios::nocreate Open fails if the file does not exit
ios::noreplace Open fails if the file already exits
ios::out Open file for writing only
ios::trunc Delete the contents of the file if it exists
1. Opening a file in ios::out mode also opens it in the ios::trunk mode by default.
2. Both ios::app and ios::ate take us to the end of the file when it is opened. The
difference between the two parameters is that the ios::app allows us to add data to
the end of the file only, while ios::ate mode permits us to add data or to modify the
existing data anywhere in the file. In both the cases, a file is created by the
specified name, if it does not exit.
3. The parameter ios::app can be used only with the files capable of output
4. Creating a stream using ifstream implies input and creating a stream using
ofstream implies output. So in these cases it is not necessary to provide the mode
parameters.
5. The fstream class does not provide a mode by default and therefore, we must
provide the mode explicitly when using an object of fstream class.
6. The mode can combine two or more parameters using the bitwise OR operator
(symbol |) shown as follows:
fout.open(“data”,ios::app|ios::nocreate)
This opens the file in the append mode but fails to open the file if it does not exit.
seekg(offset,refposition);
seekp(offset,refposition);
Where the first argument represents an offset from a particular location in the
file, and the second specifies the location from which the offset is measured. There are
three possibilities for the second argument:
ios::beg is the beginning of the file,
ios::cur is the current pointer position, and
ios::end is the end of the file.
The statement
seekp(-10, ios::end);
for example, will set the put pointer to 10 bytes before the end of the file
Table lists some sample pointer offset calls and their actions.fout is an ofstream
object.
Seek call Action
fout.seekg(0,ios::beg); Go to start
fout.seekg(0,ios::cur); Stay at the current position
fout.seekg(0,ios::end); Go to the end of file
fout.seekg(m,ios::beg); Move to (m+1)th byte in the file
fout.seekg(m,ios::cur); Go forward by m byte from the current position
fout.seekg(-m,ios::cur); Go backward by m bytes from the current position
fout.seekg(-m,ios::end); Go backward by m bytes from the end
numbers are stored as they are in the computer‘s RAM memory, rather than as strings
of characters. In binary I/O an int is stored in 4 bytes, whereas its text version might
be ―12345‖, requiring 5 bytes. Similarly, a float is always stored in 4 bytes, while its
formatted version might be ―6.02314e13‖, requiring10 bytes.
Our next example shows how an array of integers is written to disk and then
read back into memory, using binary format. We use two new functions: write(), a
member of ofstream; and read(), a member of ifstream. These functions think about
data in terms of bytes (type char).
They don‘t care how the data is formatted, they simply transfer a buffer full of
bytes from and to a disk file. The parameters to write() and read() are the address of
the data buffer and its length. The address must be cast, using reinterpret_cast, to type
char*, and the length is the length in bytes (characters), not the number of data items
in the buffer. Here‘s the listing for BINIO:
// binio.cpp
// binary input and output with integers
#include <fstream> //for file streams
#include <iostream>
using namespace std;
const int MAX = 100; //size of buffer
int buff[MAX]; //buffer for integers
int main()
{
for(int j=0; j<MAX; j++) //fill buffer with data
buff[j] = j; //(0, 1, 2, ...)
//create output stream
ofstream os(―edata.dat‖, ios::binary);
//write to it
os.write( reinterpret_cast<char*>(buff), MAX*sizeof(int) );
os.close(); //must close it
for(j=0; j<MAX; j++) //erase buffer
buff[j] = 0;
//create input stream
ifstream is(―edata.dat‖, ios::binary);
//read from it
is.read( (char*)buff, MAX*sizeof(int) );
for(j=0; j<MAX; j++) //check data
if( buff[j] != j )
{ cerr << ―Data is incorrect\n‖; return 1; }
cout << ―Data is correct\n‖;
return 0;}
You must use the ios::binary argument in the second parameter to write() and
read() when working with binary data. This is because the default, text mode, takes
some liberties with the data. For example, in text mode the ‗\n‘ character is expanded
into two bytes—a carriagereturn and a linefeed—before being stored to disk. This
makes a formatted text file more readable by DOS-based utilities such as TYPE, but it
causes confusion when it is applied to binary data, since every byte that happens to
have the ASCII value 10 is translated into 2 bytes. The ios::binary argument is an
example of a mode bit.
return 0;
}
The output from IPERS reflects whatever data the OPERS program placed in
the PERSON.DAT file:
Name: Coleridge
Age: 62
1. A file which we are attempting to open for reading does not exist.
2. The file name used for a new file may already exist.
3. We may attempt an invalid operation such as reading past the end-of-file.
4. There may not be any space in the disk for sorting more data.
5. We may use an invalid file name.
6. We may attempt to perform an operation when the file is not opened for that
purpose.
The C++ file stream inherits a ‗stream-state‘ member from the class ios.This
member records information on the status of a file that is being currently used.The
stream state member uses bit fields to store the status of the error conditions stated
above.
The class ios supports several member functions that can be used to read the
status recorded in a file stream. These functions along with their meanings are listed in
Table.
Function Return value and meaning
Returns true(non-zero value)if end-of-file is encountered
eof() while reading;
Otherwise returns false(zero)
fail() Returns true when an input or output operation has failed
Returns true if an invalid operation is attempted or any
unrecoverable error has occurred. However, if it is false, it
bad()
may be possible to recover from any other error reported,
and continue operation.
Returns true if no error has occurred. This means, all the
good() above functions are false. For instance, if file.good() is
true, all is well with the stream file and we can proceed to
…………
…………
ifstream infile;
infile.open(―ABC‖);
while(!infile.fail())
{
………
………(process the file)
………
}
if(infile.eof())
{
……….(terminate program normally)
}
else
if(infile.bad())
{
…….(report fatal error)
}
else
{
infile.clear();//clear error state
………
………
}
………
The function clear() resets the error state so that further operations can be
attempted.
Remember that we have already used statements such as
while(infile)
{
………
………
}
and
while(infile.read(……))
{
…….
…….
}
Here, infile becomes false (zero)when end of the file is reached (and eof()
becomes true).
………….
………….
outfile.open(argv[2]);//open results file for writing
………….
………….
Program illustrates the use of the command-line arguments for supplying the
file names. The command line is
test ODD EVEN
The program creates two files called ODD and EVEN using the command-line
arguments, and a set of numbers stored in an array are written to these files. Note that
the odd numbers are written to the file ODD and the even numbers are written to the
file EVEN. The program then displays the contents of the files.
#include<iostream.h>
#include<fstream.h>
int main(int argc,char *argv[])
{
int number[9]={11,22,33,44,55,66,77,88,99};
if(argc!=3)
{
cout<<‖argc=‖<<argc<<‖\n‖;
cout<<‖Error in arguments\n‖;
exit(1);
}
ofstream fout1,fout2;
fout1.open(argv[1]);
if(fout1.fail())
{
cout<<‖could not open the file‖<<argv[1]<<‖\n‖;
exit(1);
}
fout2.open(argv[2]);
if(fout2.fail())
{
cout<<‖could not open the file‖<<argv[2]<<‖\n‖;
exit(1);
}
for(int i=0;i<9;i++)
{
if(number[i]%2==0)
fout2<<number[i]<<‖ ‖;//write to EVEN file
else
fout1<<number[i]<<‖ ‖;//write to ODD file
}
fout1.close();
fout2.close();
ifstream fin;
char ch;
for(i=1;i<argc;i++)
{
fin.open(argv[i]);
cout<<‖Contents of ―<<argv[i]<<‖\n‖;
do
{
fin.get(ch);//read a value
cout<<ch;//display it
}
while(fin);
cout<<‖\n\n‖;
fin.close();
}
return 0;
}
The output of program would be
contents of ODD
11 33 55 77 99
contents of EVEN
22 44 66 88
5.15 TEMPLATES
The template is one of C++'s most sophisticated and high-powered features.
Although not part of the original specification for C++, it was added several years ago
and is supported by all modern C++ compilers. Using templates, it is possible to
create generic functions and classes. In a generic function or class, the type of data
upon which the function or class operates is specified as a parameter. Thus, you can
use one function or class with several different types of data without having to
explicitly recode specific versions for each data type.
int main()
{
int i=10, j=20;
double x=10.1, y=23.3;
char a='x', b='z';
cout << "Original i, j: " << i << ' ' << j << '\n';
cout << "Original x, y: " << x << ' ' << y << '\n';
cout << "Original a, b: " << a << ' ' << b << '\n';
swapargs(i, j); // swap integers
swapargs(x, y); // swap floats
swapargs(a, b); // swap chars
cout << "Swapped i, j: " << i << ' ' << j << '\n';
cout << "Swapped x, y: " << x << ' ' << y << '\n';
cout << "Swapped a, b: " << a << ' ' << b << '\n';
return 0;
}
Let's look closely at this program. The line: template <class X> void
swapargs(X &a, X &b) tells the compiler two things: that a template is being created
and that a generic definition is beginning. Here, X is a generic type that is used as a
placeholder. After the template portion, the function swapargs() is declared, using X
as the data type of the values that will be swapped. In main() , the swapargs()
function is called using three different types of data: ints, doubles, and chars.
Because swapargs() is a generic function, the compiler automatically creates three
versions of swapargs() : one that will exchange integer values, one that will exchange
floating-point values, and one that will swap characters. Here are some important
terms related to templates. First, a generic function (that is, a function definition
preceded by a template statement) is also called a template function. When the
compiler creates a specific version of this function, it is said to have created a
specialization. This is also called a generated function. The act of generating a
function is referred to as instantiating it. Put differently, a generated function is a
specific instance of a template function. Since C++ does not recognize end-of-line as a
statement terminator, the template clause of a generic function definition does not
have to be on the same line as the function's name. The following example shows
another common way to format the swapargs() function.
template <class X>
void swapargs(X &a, X &b)
{
X temp;
temp = a;
a = b;
b = temp;
}
If you use this form, it is important to understand that no other statements can
occur between the template statement and the start of the generic function definition.
For example, the fragment shown next will not compile.
// This will not compile.
template <class X>
int i; // this is an error
void swapargs(X &a, X &b)
{
X temp;
temp = a;
a = b;
b = temp;
}
As the comments imply, the template specification must directly precede the
function definition.
When you create a template function, you are, in essence, allowing the
compiler to generate as many different versions of that function as are necessary for
handling the various ways that your program calls the function.
void myfunc(double d)
{
double intpart;
double fracpart;
fracpart = modf(d, &intpart);
cout << "Fractional part: " << fracpart;
cout << "\n";
cout << "Integer part: " << intpart;
}
int main()
{
myfunc(1);
myfunc(12.2);
return 0;
}
overloaded to accommodate the two different types of data. You might want to try
using bubble() to sort other types of data, including classes that you create. In each
case, the compiler will create the right version of the function for you.
{
StackType stck[SIZE]; // holds the stack
int tos; // index of top-of-stack
public:
stack() { tos = 0; } // initialize stack
void push(StackType ob); // push object on stack
StackType pop(); // pop object from stack
};
// Push an object.
template <class StackType> void stack<StackType>::push(StackType ob)
{
if(tos==SIZE)
{
cout << "Stack is full.\n";
return;
}
stck[tos] = ob;
tos++;
}
// Pop an object.
template <class StackType> StackType stack<StackType>::pop()
{
if(tos==0)
{
cout << "Stack is empty.\n";
return 0; // return null on empty stack
}
tos--;
return stck[tos];
}
int main()
{
// Demonstrate character stacks.
stack<char> s1, s2; // create two character stacks
int i;
s1.push('a');
s2.push('x');
s1.push('b');
s2.push('y');
s1.push('c');
s2.push('z');
for(i=0; i<3; i++) cout << "Pop s1: " << s1.pop() << "\n";
for(i=0; i<3; i++) cout << "Pop s2: " << s2.pop() << "\n";
// demonstrate double stacks
stack<double> ds1, ds2; // create two double stacks
ds1.push(1.1);
ds2.push(2.2);
ds1.push(3.3);
ds2.push(4.4);
ds1.push(5.5);
ds2.push(6.6);
for(i=0; i<3; i++) cout << "Pop ds1: " << ds1.pop() << "\n";
for(i=0; i<3; i++) cout << "Pop ds2: " << ds2.pop() << "\n";
return 0;
}
As you can see, the declaration of a generic class is similar to that of a generic
function. The actual type of data stored by the stack is generic in the class declaration.
It is not until an object of the stack is declared that the actual data type is determined.
When a specific instance of stack is declared, the compiler automatically generates all
the functions and variables necessary for handling the actual data. In this example,
two different types of stacks are declared. Two are integer stacks. Two are stacks of
doubles. Pay special attention to these declarations:
stack<char> s1, s2; // create two character stacks
stack<double> ds1, ds2; // create two double stacks
Notice how the desired data type is passed inside the angle brackets. By
changing the type of data specified when stack objects are created, you can change
the type of data stored in that stack. For example, by using the following declaration,
you can create another stack that stores character pointers.
stack<char *> chrptrQ;
You can also create stacks to store data types that you create. For example, if
you want to use the following structure to store address information,
struct addr
{
char name[40];
char street[40];
char city[30];
char state[3];
char zip[12];
};
then to use stack to generate a stack that will store objects of type addr, use a
declaration like this:
stack<addr> obj;
As the stack class illustrates, generic functions and classes are powerful tools
that you can use to maximize your programming efforts, because they allow you to
define the general form of an object that can then be used with any type of data. You
are saved from the tedium of creating separate implementations for each data type
with which you want the algorithm to work. The compiler automatically creates the
specific versions of the class for you.
The program declares two types of objects. ob1 uses int and double data. ob2
uses a character and a character pointer. For both cases, the compiler automatically
generates the appropriate data and functions to accommodate the way the objects are
created.
variety of different situations. You are saved from the tedium of creating separate
implementations for each data type with which you want the class to work. While it is
true that the template syntax can seem a bit intimidating at first, the rewards are well
worth the time it takes to become comfortable with it. Template functions and classes
are already becoming commonplace in programming, and this trend is expected to
continue. For example, the STL (Standard Template Library) defined by C++ is, as its
name implies, built upon templates. One last point: although templates add a layer of
abstraction, they still ultimately compile down to the same, high-performance object
code that you have come to expect from C++.
that you want to monitor for exceptions must have been executed from within a try
block. (Functions called from within a try block may also throw an exception.)
Exceptions that can be thrown by the monitored code are caught by a catch statement,
which immediately follows the try statement in which the exception was thrown. The
general form of try and catch are shown here.
try {
// try block
}
catch (type1 arg) {
// catch block
}
catch (type2 arg) {
// catch block
}
catch (type3 arg) {
// catch block
}
..
.
catch (typeN arg) {
// catch block
}
The try can be as short as a few statements within one function or as all-
encompassing as enclosing the main() function code within a try block (which
effectively causes the entire program to be monitored). When an exception is thrown,
it is caught by its corresponding catch statement, which processes the exception.
There can be more than one catch statement associated with a try. Which catch
statement is used is determined by the type of the exception.
That is, if the data type specified by a catch matches that of the exception, then
that catch statement is executed (and all others are bypassed). When an exception is
caught, arg will receive its value. Any type of data may be caught, including classes
that you create. If no exception is thrown (that is, no error occurs within the try
block), then no catch statement is executed.
The general form of the throw statement is shown here:
throw exception;
throw generates the exception specified by exception. If this exception is to be
caught, then throw must be executed either from within a try block itself, or from any
function called from within the try block (directly or indirectly).
If you throw an exception for which there is no applicable catch statement, an
abnormal program termination may occur. Throwing an unhandled exception causes
in the preceding example, if you change the type in the catch statement to double, the
exception will not be caught and abnormal termination will occur. This change is
shown here.
// This example will not work.
#include <iostream>
using namespace std;
int main()
{
cout << "Start\n";
try
{ // start a try block
cout << "Inside try block\n";
throw 100; // throw an error
cout << "This will not execute";
}
catch (double i)
{ // won't work for an int exception
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
return 0;
}
This program produces the following output because the integer exception will
not be caught by the catch(double i) statement.
Start
Inside try block
Abnormal program termination
An exception can be thrown from outside the try block as long as it is thrown
by a function that is called from within try block. For example, this is a valid
program.
/* Throwing an exception from a function outside the
try block.
*/
#include <iostream>
using namespace std;
void Xtest(int test)
{
cout << "Inside Xtest, test is: " << test << "\n";
if(test) throw test;
}
int main()
{
cout << "Start\n";
try
{ // start a try block
cout << "Inside try block\n";
Xtest(0);
Xtest(1);
Xtest(2);
}
catch (int i)
{ // catch an error
cout << "Caught an exception -- value is: ";
cout << i << "\n";
}
cout << "End";
return 0;
}
This program produces the following output:
Start
Inside try block
Inside Xtest, test is: 0
Inside Xtest, test is: 1
Caught an exception -- value is: 1
End
A try block can be localized to a function. When this is the case, each time the
function is entered, the exception handling relative to that function is reset. For
example, examine this program.
#include <iostream>
using namespace std;
// Localize a try/catch to a function.
void Xhandler(int test)
{
try
{
if(test) throw test;
}
catch(int i)
{
}
cout << "End";
return 0;
}
The preceding program produces the following output.
Start
Inside try block
Still inside try block
End
As you see, the catch statement is bypassed by the flow of execution.
cin >> i;
if(i<0)
throw MyException("Not Positive", i);
}
catch (MyException e)
{ // catch an error
cout << e.str_what << ": ";
cout << e.what << "\n";
}
return 0;
}
Here is a sample run:
Enter a positive number: -4
Not Positive: -4
The program prompts the user for a positive number. If a negative number is
entered, an object of the class MyException is created that describes the error. Thus,
MyException encapsulates information about the error. This information is then used
by the exception handler. In general, you will want to create exception classes that
will encapsulate information about an error to enable the exception handler to respond
effectively.
}
catch(const char *str)
{
cout << "Caught a string: ";
cout << str << '\n';
}
}
int main()
{
cout << "Start\n";
Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout << "End";
return 0;
}
This program produces the following output:
Start
Caught Exception #: 1
Caught Exception #: 2
Caught a string: Value is zero
Caught Exception #: 3
End
As you can see, each catch statement responds only to its own type. In general,
catch expressions are checked in the order in which they occur in a program. Only a
matching statement is executed. All other catch blocks are ignored.
Here, the ellipsis matches any type of data. The following program illustrates
catch(...).
// This example catches all exceptions.
#include <iostream>
using namespace std;
void Xhandler(int test)
{
try
{
if(test==0) throw test; // throw int
if(test==1) throw 'a'; // throw char
if(test==2) throw 123.23; // throw double
}
catch(...)
{ // catch all exceptions
cout << "Caught One!\n";
}
}
int main()
{
cout << "Start\n";
Xhandler(0);
Xhandler(1);
Xhandler(2);
cout << "End";
return 0;
}
This program displays the following output.
Start
Caught One!
Caught One!
Caught One!
End
As you can see, all three throws were caught using the one catch statement.
One very good use for catch(...) is as the last catch of a cluster of catches. In this
capacity it provides a useful default or "catch all" statement. For example, this slightly
different version of the preceding program explicity catches integer exceptions but
relies upon catch(...) to catch all others.
// This example uses catch(...) as a default.
#include <iostream>
{
cout << "Caught double\n";
}
cout << "end";
return 0;
}
In this program, the function Xhandler() may only throw integer, character,
and double exceptions. If it attempts to throw any other type of exception, an
abnormal program termination will occur. (That is, unexpected() will be called.) To
see an example of this, remove int from the list and retry the program. It is important
to understand that a function can be restricted only in what types of exceptions it
throws back to the try block that called it. That is, a try block within a function may
throw any type of exception so long as it is caught within that function. The restriction
applies only when throwing an exception outside of the function.
The following change to Xhandler() prevents it from throwing any exceptions.
// This function can throw NO exceptions!
void Xhandler(int test) throw()
{
/* The following statements no longer work. Instead,
they will cause an abnormal program termination. */
if(test==0) throw test;
if(test==1) throw 'a';
if(test==2) throw 123.23;
}
At the time of this writing, Microsoft's Visual C++ does not support the throw(
) clause for functions.