Lecture Notes CS1201 Part2
Lecture Notes CS1201 Part2
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 1 -
Declaring Pointers
A pointer is just the memory address of a variable, so that a pointer variable is
just a variable in which we can store different memory addresses. Pointer
variables are declared using a "*", and have data types like the other variables
we have seen. For example, the declaration
int *number_ptr;
states that "number_ptr" is a pointer variable that can store addresses of variables
of data type "int". A useful alternative way to declare pointers is using a "typedef"
construct. For example, if we include the statement:
typedef int *IntPtrType;
we can then go on to declare several pointer variables in one line, without the
need to prefix each with a "*":
IntPtrType number_ptr1, number_ptr2, number_ptr3;
Assignments with Pointers Using the Operators "*" and "&"
Given a particular data type, such as "int", we can write assignment statements
involving both ordinary variables and pointer variables of this data type using the
dereference operator "*" and the (complementary) address-of operator "&".
Roughly speaking, "*" means "the variable located at the address", and "&"
means "the address of the variable". We can illustrate the uses of these
operators with a simple example program:
#include <iostream>
using namespace std;
typedef int *IntPtrType;
int main()
{
IntPtrType ptr_a, ptr_b;
int num_c = 4, num_d = 7;
ptr_a = &num_c; /* LINE 10 */
ptr_b = ptr_a; /* LINE 11 */
cout << *ptr_a << " " << *ptr_b << "\n";
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 2 -
ptr_b = &num_d; /* LINE 15 */
cout << *ptr_a << " " << *ptr_b << "\n";
*ptr_a = *ptr_b; /* LINE 19 */
cout << *ptr_a << " " << *ptr_b << "\n";
cout << num_c << " " << *&*&*&num_c << "\n";
return 0;
}
The output of this program is:
4 4
4 7
7 7
7 7
Dynamic variables are "created" using the reserved word "new", and "destroyed"
(thus freeing-up memory for other uses) using the reserved word "delete". Below
is a program analogous to Program 7.1.1, which illustrates the use of these
operations:
#include <iostream>
using namespace std;
typedef int *IntPtrType;
int main()
{
IntPtrType ptr_a, ptr_b; /* LINE 7 */
ptr_a = new int; /* LINE 9 */
*ptr_a = 4;
ptr_b = ptr_a; /* LINE 11 */
cout << *ptr_a << " " << *ptr_b << "\n";
ptr_b = new int; /* LINE 15 */
*ptr_b = 7; /* LINE 16 */
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 3 -
cout << *ptr_a << " " << *ptr_b << "\n";
delete ptr_a;
ptr_a = ptr_b; /* LINE 21 */
cout << *ptr_a << " " << *ptr_b << "\n";
delete ptr_a; /* LINE 25 */
return 0;
}
The output of this program is:
4 4
4 7
7 7
Array Variables and Pointer Arithmetic
We saw how to declare groups of variables called arrays. By adding the
statement
int hours[6];
we could then use the identifiers
hours[0] hours[1] hours[2] hours[3] hours[4] hours[5]
as though each referred to a separate variable. In fact, C++ implements arrays
simply by regarding array identifiers such as "hours" as pointers. Thus if we add
the integer pointer declaration
int *ptr;
to the same program, it is now perfectly legal to follow this by the assignment
ptr = hours;
After the execution of this statement, both "ptr" and "hours" point to the integer
variable referred to as "hours[0]". Thus "hours[0]", "*hours", and "*ptr" are now all
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 4 -
different names for the same variable. The variables "hours[1]", "hours[2]", etc.
now also have alternative names. We can refer to them either as
*(hours + 1) *(hours + 2) ...
or as
*(ptr + 1) *(ptr + 2) ...
In this case, the "+ 2" is shorthand for "plus enough memory to store 2 integer
values". We refer to the addition and subtraction of numerical values to and from
pointer variables in this manner as pointer arithmetic. Multiplication and division
cannot be used in pointer arithmetic, but the increment and decrement operators
"++" and "--" can be used, and one pointer can be subtracted from another of the
same type.
Pointer arithmetic gives an alternative and sometimes more succinct method of
manipulating arrays. The following is a function to convert a string to upper case
letters:
void ChangeToUpperCase(char phrase[])
{
int index = 0;
while (phrase[index] != '\0')
{
if (LowerCase(phrase[index]))
ChangeToUpperCase(phrase[index]);
index++;
}
}
int LowerCase(char character)
{
return (character >= 'a' && character <= 'z');
}
void ChangeToUpperCase(char &character)
{
character += 'A' - 'a';
Note the use of polymorphism with the function "ChangeToUpperCase(...)" - the
compiler can distinguish the two versions because one takes an argument of
type "char", whereas the other takes an array argument. Since "phrase" is really a
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 5 -
pointer variable, the array argument version can be re-written using pointer
arithmetic:
void ChangeToUpperCase(char *phrase)
{
while (*phrase != '\0')
{
if (LowerCase(*phrase))
ChangeToUpperCase(*phrase);
phrase++;
}
}
This re-writing is transparent as far as the rest of the program is concerned -
either version can be called in the normal manner using a string argument:
char a_string[] = "Hello World";
...
...
ChangeToUpperCase(a_string);
Dynamic Arrays
The mechanisms described above to create and destroy dynamic variables of
type "int", "char", "float", etc. can also be applied to create and destroy dynamic
arrays. This can be especially useful since arrays sometimes require large
amounts of memory. A dynamic array of 10 integers can be declared as follows:
int *number_ptr;
number_ptr = new int[10];
As we have seen, array variables are really pointer variables, so we can now
refer to the 10 integer variables in the array either as
number_ptr[0] number_ptr[1] ... number_ptr[9]
or as
*number_ptr *(number_ptr + 1) ... *(number_ptr + 9)
To destroy the dynamic array, we write
delete [] number_ptr;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 6 -
The "[]" brackets are important. They signal the program to destroy all 10
variables, not just the first. To illustrate the use of dynamic arrays, here is a
program fragment that prompts the user for a list of integers, and then prints the
average on the screen:
...
...
int no_of_integers, *number_ptr;
cout << "Enter number of integers in the list: ";
cin >> no_of_integers;
number_ptr = new int[no_of_integers];
if (number_ptr == NULL)
{
cout << "Sorry, ran out of memory.\n";
exit(1);
}
cout << "type in " << no_of_integers;
cout << " integers separated by spaces:\n";
for (int count = 0 ; count < no_of_integers ; count++)
cin >> number_ptr[count];
cout << "Average: " << average(number_ptr,no_of_integers);
delete [] number_ptr;
...
...
Automatic and Dynamic Variables
Although dynamic variables can sometimes be a useful device, the need to use
them can often be minimized by designing a well structured program, and by the
use of functional abstraction. Most of the variables we have been using in the
previous lectures have been automatic variables. That is to say, they are
automatically created in the block or function in which they are declared, and
automatically destroyed at the end of the block, or when the call to the function
terminates. So, for a well structured program, much of the time we don't even
have to think about adding code to create and destroy variables.
(N.B. It is also possible to declare variables as being static, i.e. remaining in
existence throughout the subsequent execution of the program, but in a well
designed, non-object based program it should not be necessary to use any static
variables other than the constants declared at the beginning.)
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 7 -
Friend functions: an introduction
There will be time when you want a function to have access to the private
members of a class without that function actually being a member of that class.
Towards this, C++ supports friend functions. A friend function is not a member of
a class but still has access to its private elements.
Friend functions are useful with operator overloading and the creation of certain
types of I/O functions.
A friend function is defined as a regular, nonmember function. However, inside
the class declaration for which it will be a friend, its prototype is also included,
prefaced by the keyword friend. To understand how this works, here a short
example:
//Example of a friend function
// ...
class myclass {
int n, d;
public:
myclass(int i, int j) { n = i; d = j; }
//declare a friend of myclass
friend int isfactor(myclass ob);
};
/* Here is friend function definition. It returns true
if d is a factor of n. Notice that the keyword friend
is not used in the definition of isfactor( ).
*/
int isfactor(myclass ob) {
if ( !(ob.n % ob.d) ) return 1;
else return 0;
}
int main( ) {
myclass ob1(10, 2), ob2(13, 3);
if (isfactor(ob1)) cout << "2 is a factor of 10\n";
else cout << "2 is not a factor of 10\n";
if (isfactor(ob2)) cout << "3 is a factor of 13\n";
else cout << "3 is not a factor of 13\n";
return 0;
}
It is important to understand that a friend function is not a member of the class for
which it is a friend. Thus, it is not possible to call a friend function by using an
object name and a class member access operator (dot or arrow). For example,
what follows is wrong.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 8 -
ob1.isfactor( ); //wrong isfactor is not a member function
Instead friend functions are called just like regular functions.
Because friends are not members of a class, they will typically be passed one or
more objects of the class for which they are friends. This is the case with
isfactor( ). It is passed an object of myclass, called ob. However, because
isfactor( ) is a friend of myclass, it can access obs private members. If isfactor(
) had not been made a friend of myclass it would not have access to ob.d or
ob.n since n and d are private members of myclass.
A friend function is not inherited. That is, when a base class includes a friend
function, that friend function is not a friend function of the derived class.
A friend function can be friends with more than one class. For example,
class truck; //This is a forward declaration
class car {
int passengers;
int speed;
public:
car(int p, int s) { passengers = p; speed =s; }
friend int sp_greater(car c, truck t);
};
class truck {
int weight;
int speed;
public:
truck(int w, int s) { weight = w; speed = s; }
friend int sp_greater(car c, truck t);
};
int sp_greater(car c, truck t) {
return c.speed - t.speed;
}
int main( ) {
// ...
}
This program also illustrates one important element: the forward declaration (also
called a forward reference), to tell the compiler that an identifier is the name of a
class without actually declaring it.
A function can be a member of one class and a friend of another class. For
example,
// ...
class truck; // forward declaration
class car {
int passengers;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 9 -
int speed;
public:
car(int p, int s) { passengers = p; speed =s; }
int sp_greater( truck t);
};
class truck {
int weight;
int speed;
public:
truck(int w, int s) { weight = w; speed = s; }
//note new use of the scope resolution operator
friend int car::sp_greater( truck t);
};
int car::sp_greater( truck t) {
return speed - t.speed;
}
int main( ) {
// ...
}
One easy way to remember how to use the scope resolution operation it is never
wrong to fully specify its name as above in class truck,
friend int car::sp_greater( truck t);
However, when an object is used to call a member function or access a member
variable, the full name is redundant and seldom used. For example,
// ...
int main( ) {
int t;
car c1(6, 55);
truck t1(10000, 55);
t = c1.sp_greater(t1); //can be written using the
//redundant scope as
t = c1.car::sp_greater(t1);
//...
}
However, since c1 is an object of type car the compiler already knows that
sp_greater( ) is a member of the car class, making the full class specification
unnecessary.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 10 -
ARRAYS, POINTERS, AND REFERENCES
Arrays of objects
Objects are variables and have the same capabilities and attributes as any other
type of variables. Therefore, it is perfectly acceptable for objects to be arrayed.
The syntax for declaring an array of objects is exactly as that used to declare an
array of any other type of variable. Further, arrays of objects are accessed just
like arrays of other types of variables.
#include < iostream >
using namespace std;
class samp {
int a;
public:
void set_a(int n) {a = n;}
int get_a( ) { return a; }
};
int main( ) {
samp ob[4]; //array of 4 objects
int i;
for (i=0; i<4; i++) ob[i].set_a(i);
for (i=0; i<4; i++) cout << ob[i].get_a( ) << " ";
cout << "\n";
return 0;
}
If the class type include a constructor, an array of objects can be initialised,
// Initialise an array
#include < iostream >
using namespace std;
class samp {
int a;
public:
samp(int n) {a = n; }
int get_a( ) { return a; }
};
int main( ) {
samp ob[4] = {-1, -2, -3, -4};
int i;
for (i=0; i<4; i++) cout << ob[i].get_a( ) << " ";
cout << "\n"
return 0;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 11 -
}
You can also have multidimensional arrays of objects. Here an example,
// Create a two-dimensional array of objects
// ...
class samp {
int a;
public:
samp(int n) {a = n; }
int get_a( ) { return a; }
};
int main( ) {
samp ob[4][2] = {
1, 2,
3, 4,
5, 6,
7, 8 };
int i;
for (i=0; i<4; i++) {
cout << ob[i][0].get_a( ) << " ";
cout << ob[i][1].get_a( ) << "\n";
}
cout << "\n";
return 0;
}
This program displays,
1 2
3 4
5 6
7 8
When a constructor uses more than one argument, you must use the alternative
format,
// ...
class samp {
int a, b;
public:
samp(int n, int m) {a = n; b = m; }
int get_a( ) { return a; }
int get_b( ) { return b; }
};
int main( ) {
samp ob[4][2] = {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 12 -
samp(1, 2), samp(3, 4),
samp(9, 10), samp(11, 12),
samp(13, 14), samp(15, 16)
};
// ...
Note you can always the long form of initialization even if the object takes only
one argument. It is just that the short form is more convenient in this case.
Using pointers to objects
As you know, when a pointer is used, the objects members are referenced using
the arrow (- >) operator instead of the dot (.) operator.
Pointer arithmetic using an object pointer is the same as it is for any other data
type: it is performed relative to the type of the object. For example, when an
object pointer is incremented, it points to the next object. When an object pointer
is decremented, it points to the previous object.
// Pointer to objects
// ...
class samp {
int a, b;
public:
samp(int n, int m) {a = n; b = m; }
int get_a( ) { return a; }
int get_b( ) { return b; }
};
int main( ) {
samp ob[4] = {
samp(1, 2),
samp(3, 4),
samp(5, 6),
samp(7, 8)
};
int i;
samp *p;
p = ob; // get starting address of array
for (i=0; i<4; i++) {
cout << p->get_a( ) << " ";
cout << p->get_b( ) << "\n";
p++; // advance to next object
}
// ...
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 13 -
The THIS pointer
C++ contains a special pointer that is called this. this is a pointer that is
automatically passed to any member function when it is called, and it points to
the object that generates the call. For example, this statement,
ob.f1( ); // assume that ob is an object
the function f1( ) is automatically passed as a pointer to ob, which is the object
that invokes the call. This pointer is referred to as this.
It is important to understand that only member functions are passed a this
pointer. For example a friend does not have a this pointer.
// Demonstrate the this pointer
#include < iostream >
#include < cstring >
using namespace std;
class inventory {
char item[20];
double cost;
int on_hand;
public:
inventory(char *i, double c, int o) {
//access members through
//the this pointer
strcpy(this->item, i);
this->cost = c;
this->on_hand = o;
}
void show( );
};
void inventory::show( ) {
cout << this->item; //use this to access members
cout << ": " << this->cost;
cout << "On hand: " << this->on_hand <<"\n";
}
int main( ) {
// ...
}
Here the member variables are accessed explicitly through the this pointer.
Thus, within show( ), these two statements are equivalent:
cost = 123.23;
this->cost = 123.23;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 14 -
In fact the first form is a shorthand for the second. Though the second form is
usually not used for such simple case, it helps understand what the shorthand
implies.
The this pointer has several uses, including aiding in overloading operators (see
later).
By default, all member functions are automatically passed a pointer to the
invoking object.
Using NEW and DELETE
When memory needed to be allocated, you have been using malloc( ) and free()
for freeing the allocated memory. Of course the standard C dynamic allocation
functions are available in C++, however C++ provides a safer and more
convenient way to allocate and free memory. In C++, you can allocate memory
using new and release it using delete. These operators take the general form,
p-var = new type;
delete p-var;
Here type is the type of the object for which you want to allocate memory and p-
var is a pointer to that type. new is an operator that returns a pointer to
dynamically allocated memory that is large enough to hold an object of type type.
delete releases that memory when it is no longer needed. delete can be called
only with a pointer previously allocated with new. If you call delete with an invalid
pointer, the allocation system will be destroyed, possibly crashing your program.
If there is insufficient memory to fill an allocation request, one of two actions will
occur. Either new will return a null pointer or it will generate an exception. In
standard C++, the default behaviour of new is to generate an exception. If the
exception is not handle by your program, your program will be terminated. The
trouble is that your compiler may not implement new as in defined by Standard
C++.
Although new and delete perform action similar to malloc( ) and free( ), they
have several advantages. First, new automatically allocates enough memory to
hold an object of the specified type. You do not need to use sizeof. Second, new
automatically returns a pointer of the specified type. You do not need to use an
explicit type cast the way you did when you allocate memory using malloc( ).
Third, both new and delete can be overloaded, enabling you to easily implement
your own custom allocation system. Fourth, it is possible to initialise a
dynamically allocated object. Finally, you no longer need to include < cstdlib >
with your program.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 15 -
// A simple example of new and delete
#include < iostream >
using namespace std;
int main( ) {
int *p;
p = new int; //allocate room for an integer
if (!p) {
cout << "Allocation error\n";
return 1;
}
*p = 1000;
cout << "Here is integer at p: " << *p << "\n";
delete p; // release memory
return 0;
}
// Allocating dynamic objects
#include < iostream >
using namespace std;
class samp {
int i, j;
public:
void set_ij(int a, int b) { i=a; j=b; }
int get_product( ) { return i*j; }
};
int main( ) {
samp *p;
p = new samp; //allocate object
if (!p) {
cout << "Allocation error\n";
return 1;
}
p- >set_ij(4, 5);
cout<< "product is: "<< p- >get_product( ) << "\n";
delete p; // release memory
return 0;
}
More about new and delete
Dynamically allocated objects can be given initial values by using this form of
statement:
p-var = new type (initial-value);
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 16 -
To dynamically allocate a one-dimensional array, use
p-var = new type [size];
After execution of the statement, p-var will point to the start of an array of size
elements of the type specified.
Note, it is not possible to initialize an array that is dynamically allocated
To delete a dynamically allocated array, use
delete [ ] p-var;
This statement causes the compiler to call the destructor function for each
element in the array. It does not cause p-var to be freed multiple time. p-var is
still freed only once.
// Example of initialising a dynamic variable
#include < iostream >
using namespace std;
int main( ) {
int *p;
p = new int(9); //allocate and give initial value
if (!p) {
cout << "Allocation error\n";
return 1;
}
*p = 1000;
cout << "Here is integer at p: " << *p << "\n";
delete p; // release memory
return 0;
}
// Allocating dynamic objects
#include < iostream >
using namespace std;
class samp {
int i, j;
public:
samp(int a, int b) { i=a; j=b; }
int get_product( ) { return i*j; }
};
int main( ) {
samp *p;
p = new samp(6, 5); //allocate object
// with initialisation
if (!p) {
cout << "Allocation error\n";
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 17 -
return 1;
}
cout<< "product is: "<< p- >get_product( ) << "\n";
delete p; // release memory
return 0;
}
Example of array allocation
// Allocating dynamic objects
#include < iostream >
using namespace std;
class samp {
int i, j;
public:
void set_ij(int a, int b) { i=a; j=b; }
samp( ) { cout << "Destroying...\n"; }
int get_product( ) { return i*j; }
};
int main( ) {
samp *p;
int i;
p = new samp [10]; //allocate object array
if (!p) {
cout << "Allocation error\n";
return 1;
}
for (i=0; i<10; i++) p[i].set_ij(i, i);
for (i=0; i<10; i++) {
cout << "product [" << i << "] is: ";
cout << p[i].get_product( ) << "\n";
}
delete [ ] p; // release memory the destructor
// should be called 10 times
return 0;
}
References
C++ contains a feature that is related to pointer: the reference. A reference is an
implicit pointer that for all intents and purposes acts like another name for a
variable. There are three ways that a reference can be used: a reference can be
passed to a function; a reference can be return by a function, an independent
reference can be created.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 18 -
The most important use of a reference is as a parameter to a function.
To help you understand what a reference parameter is and how it works, let's first
start with a program the uses a pointer (not a reference) as parameter.
#include < iostream >
using namespace std;
void f(int *n); // use a pointer parameter
int main( ) {
int i=0;
f(&i);
cout << "Here is i's new value: " << i << "\n";
return 0;
}
// function definition
void f(int *n) {
*n = 100; // put 100 into the argument
// pointed to by n
}
Here f( ) loads the value 100 into the integer pointed to by n. In this program, f( )
is called with the address of i in main( ). Thus, after f( ) returns, i contains the
value 100.
This program demonstrates how pointer is used as a parameter to manually
create a call-by-reference parameter-passing mechanism.
In C++, you can completely automate this process by using a reference
parameter. To see how, let's rework the previous program,
#include < iostream >
using namespace std;
void f(int &n); // declare a reference parameter
int main( ) {
int i=0;
f(i);
cout << "Here is i's new value: " << i << "\n";
return 0;
}
// f( ) now use a reference parameter
void f(int &n) {
// note that no * is needed in the following
//statement
n = 100; // put 100 into the argument
// used to call f( )
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 19 -
}
First to declare a reference variable or parameter, you precede the variable's
name with the &.
This is how n is declared as a parameter to f( ). Now that n is a reference, it is no
longer necessary - even legal- to apply the * operator. Instead, n is automatically
treated as a pointer to the argument used to call f( ). This means that the
statement n=100 directly puts the value 100 in the variable i used as argument to
call f( ).
Further, as f( ) is declared as taking a reference parameter, the address of the
argument is automatically passed to the function (statement: f(i) ). There is no
need to manually generate the address of the argument by preceding it with an &
(in fact it is not allowed).
It is important to understand that you cannot change what a reference is pointing
to. For example, if the statement, n++, was put inside f( ), n would still be
pointing to i in the main. Instead, this statement increments the value of the
variable being reference, in this case i.
// Classic example of a swap function that exchanges the
// values of the two arguments with which it is called
#include < iostream >
using namespace std;
void swapargs(int &x, int &y); //function prototype
int main( ) {
int i, j;
i = 10;
j = 19;
cout << "i: " << i <<", ";
cout << "j: " << j << "\n";
swapargs(i, j);
cout << "After swapping: ";
cout << "i: " << i <<", ";
cout << "j: " << j << "\n";
return 0;
}
// function declaration
void swapargs(int &x, int &y) { // x, y reference
int t;
t = x;
x = y;
y = t;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 20 -
}
If swapargs( ) had been written using pointer instead of references, it would have
looked like this:
void swapargs(int *x, int *y) { // x, y pointer
int t;
t = *x;
*x = *y;
*y = t;
}
Passing references to objects
Remember that when an object is passed to a function by value (default
mechanism), a copy of that object is made. Although the parameter's constructor
function is not called, its destructor function is called when the function returns.
As you should recall, this can cause serious problems in some case when the
destructor frees dynamic memory.
One solution to this problem is to pass an object by reference (the other solution
involves the use of copy constructors, see later).
When you pass an object by reference, no copy is made, and therefore its
destructor function is not called when the function returns. Remember, however,
that changes made to the object inside the function affect the object used as
argument.
It is critical to understand that a reference is not a pointer. Therefore, when an
object is passed by reference, the member access operator remains the dot
operator.
The following example shows the usefulness of passing an object by reference.
First, here the version that passes an object of myclass by value to a function
called f():
#include < iostream >
using namespace std;
class myclass {
int who;
public:
myclass(int i) {
who = i;
cout << "Constructing " << who << "\n";
}
myclass( ) { cout<< "Destructing "<< who<< "\n";
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 21 -
int id( ) { return who; }
};
// o is passed by value
void f(myclass o) {
cout << "Received " << o.id( ) << "\n";
}
int main( ) {
myclass x(1);
f(x);
return 0;
}
This program displays the following:
Constructing 1
Received 1
Destructing 1
Destructing 1
The destructor function is called twice. First, when the copy of object 1 is
destroyed when f( ) terminates and again when the program finishes.
However, if the program is change so that f( ) uses a reference parameter, no
copy is made and, therefore, no destructor is called when f( ) returns:
// ...
class myclass {
int who;
public:
myclass(int i) {
who = i;
cout << "Constructing " << who << "\n";
}
myclass( ) { cout<< "Destructing "<< who<< "\n";
}
int id( ) { return who; }
};
// Now o is passed by reference
void f(myclass &o) {
// note that . operator is still used !!!
cout << "Received " << o.id( ) << "\n";
}
int main( ) {
myclass x(1);
f(x);
return 0;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 22 -
This version displays:
Constructing 1
Received 1
Destructing 1
Remember, when accessing members of an object by using a reference, use the
dot operator not the arrow.
Returning references
A function can return a reference. You will see later that returning a reference
can be very useful when you are overloading certain type of operators. However,
it also can be employed to allow a function to be used on the left hand side of an
assignment statement. Here, a very simple program that contains a function that
returns a reference:
// ...
int &f( ); // prototype of a function
// that returns a reference.
int x; // x is a global variable
int main( ) {
f( ) = 100; // assign 100 to the reference
// returned by f( ).
cout << x << "\n";
return 0;
}
// return an int reference
int &f( ) {
return x; // return a reference to x
}
Here, f( ) is declared as returning a reference to an integer. Inside the body of the
function, the statement
return x;
does not return the value of the global variable x, but rather, it automatically
returns address of x (in the form of a reference). Thus, inside main( ) the
statement
f( ) = 100;
put the value 100 into x because f( ) has returned a reference to it.
To review, function f( ) returns a reference. Thus, when f( ) is used on the left
side of the assignment statement, it is this reference, returned by f( ), that is
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 23 -
being assigned. Since f( ) returns a reference to x (in this example), it is x that
receives the value 100.
You must be careful when returning a reference that the object you refer to does
not go out of scope. For example,
// return an int reference
int &f( ) {
int x; // x is now a local variable
return x; // returns a reference to x
}
In this case, x is now local to f( ) and it will go out of scope when f( ) returns. This
means that the reference returned by f( ) is useless.
Some C++ compilers will not allow you to return a reference to a local variable.
However, this type of problem can manifest itself on other ways, such as when
objects are allocated dynamically.
Independent references and restrictions
The independent reference is another type of reference that is available in C++.
An independent reference is a reference variable that is simply another name for
another variable. Because references cannot be assigned new values, an
independent reference must be initialised when it is declared.
Further independent references exist in C++ largely because there was no
compelling reason to disallow them. But for most part their use should be
avoided.
// program that contains an independent reference
// ...
int main( ) {
int x;
int &ref = x; // create an independent reference
x = 10; // these two statements are
ref = 10; // functionally equivalent
ref = 100;
// this print the number 100 twice
cout << x << " " << ref << "\n";
return 0;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 24 -
There are a number of restrictions that apply to all types of references:
You cannot reference another reference.
You cannot obtain the address of a reference.
You cannot create arrays of reference.
You cannot reference a bit-field.
References must be initialised unless they are members of a class, or are
function parameters.
FUNCTION OVERLOADING
Overloading constructor functions
It is possible to overload a class's constructor function. However, it is not possible
to overload destructor functions. You will want to overload a constructor:
- to gain flexibility,
- to support arrays,
- to create copy constructors (see next section)
One thing to keep in mind, as you study the examples, is that there must be a
constructor function for each way that an object of a class will be created. If a
program attempts to create an object for which no matching constructor is found,
a compiler-time error occurs. This is why overloaded constructor functions are so
common to C++ program.
Perhaps the most frequent use of overloaded constructor functions is to provide
the option of either giving an object an initialisation or not giving it one. For
example, in the following program, o1 is given an initial value, but o2 is not. If
you remove the constructor that has the empty argument list, the program will not
compile because there is no constructor that matches the non-initialised object of
type myclass.
// ...
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 25 -
class myclass {
int x;
public:
// overload constructor two ways
myclass( ) { x = 0; } // no initialiser
myclass(int n ) { x = n; } // initialiser
int getx( ) { return x; }
};
int main( ) {
myclass o1(10); // declare with initial value
myclass o2; // declare without initialiser
cout << "o1: " << o1.getx( ) << "\n";
cout << "o2: " << o2.getx( ) << "\n";
return 0;
}
Another reason to overload constructor functions, is to allow both individual
objects and arrays of objects to occur with the program. For example, assuming
the class myclass from the previous example, both of the declarations are valid:
myclass ob(10);
myclass ob[10];
By providing both a parameterised and a parameterless constructor, your
program allows the creation of objects that are either initialised or not as needed.
Of course, once you have defined both types of constructor you can use them to
initialise or not arrays.
// ...
class myclass {
int x;
public:
// overload constructor two ways
myclass( ) { x = 0; } // no initialiser
myclass(int n ) { x = n; } // initialiser
int getx( ) { return x; }
};
int main( ) {
// declare array without initialisers
myclass o1[10];
// declare with initialisers
myclass o2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int i;
for (i=0; i<10; i++) {
cout<< "o1["<< i << "]: "<< o1[i].getx( )<< "\n";
cout<< "o2["<< i << "]: "<< o2[i].getx( )<< "\n";
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 26 -
}
return 0;
}
In this example, all elements of o1 are set to 0 by the constructor. The elements
of o2 are initialised as shown in the program.
Another situation is when you want to be allowed to select the most convenient
method of initialising an object:
#include < iostream >
#include < cstdio > // included for sscanf( )
using namespace std;
class date {
int day, month, year;
public:
date(char *str);//accept date as character string
date(int m, int d, int y) {// passed as three ints
day = d;
month = m;
year = y;
}
void show( ) {
cout << day << "/" << month << "/" << year;
cout << "\n";
}
};
date::date(char *str) {
sscanf(str,"%d%*c%d%*c%d", &day, &month, &year);
}
int main( ) {
// construct date object using string
date sdate("31/12/99");
// construct date object using integer
date idate(12, 31, 99);
sdate.show( );
idate.show( );
return 0;
}
Another situation in which you need to overload a class's constructor function is
when a dynamic array of that class will be allocated. As you should recall, a
dynamic array cannot be initialized. Thus, if the class contains a constructor that
takes an initialize, you must include an overloaded version that takes no initialize.
// ...
class myclass {
int x;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 27 -
public:
// overload constructor two ways
myclass( ) { x = 0; } // no initialiser
myclass(int n ) { x = n; } // initialiser
int getx( ) { return x; }
void setx(int x) { x = n; }
};
int main( ) {
myclass *p;
myclass ob(10); // initialise single variable
p = new myclass[10]; // can't use initialiser here
if (!p) {
cout << "Allocation error\n";
return 1;
}
int i;
// initialise all elements of ob
for (i=0; i<10; i++) p[i]= ob;
for (i=0; i<10; i++)
cout<< "p["<< i << "]: "<< p[i].getx( ) << "\n";
return 0;
}
Without the overloaded version of myclass( ) that has no initialiser, the new
statement would have generated a compile-time error and the program would not
have been compiled.
Creating and using a copy constructor
One of the more important forms of an overloaded constructor is the copy
constructor. Recall, problems can occur when an object is passed to or returned
from a function. One way to avoid these problems, is to define a copy
constructor.
Remember when an object is passed to a function, a bitwise copy of that object
is made and given to the function parameter that receives the object. However,
there are cases in which this identical copy is not desirable. For example, if the
object contains a pointer to allocated memory, the copy will point to the same
memory as does the original object. Therefore, if the copy makes a change to the
contents of this memory, it will be changed for the original object too! Also, when
the function terminates, the copy will be destroyed, causing its destructor to be
called. This might lead to undesired side effects that further affect the original
object (as the copy points to the same memory).
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 28 -
Similar situation occurs when an object is returned by a function. The compiler
will commonly generate a temporary object that holds a copy of the value
returned by the function (this is done automatically and is beyond your control).
This temporary object goes out of scope once the value is returned to the calling
routine, causing the temporary object's destructor to be called. However, if the
destructor destroys something needed by the calling routine (for example, if it
frees dynamically allocated memory), trouble will follow.
At the core of these problems is the fact that a bitwise copy of the object is made.
To prevent these problems, you, the programmer, need to define precisely what
occurs when a copy of an object is made so that you can avoid undesired side
effects. By defining a copy constructor, you can fully specify exactly what occurs
when a copy of an object is made.
It is important to understand that C++ defines two distinct types of situations in
which the value of an object is given to another. The first situation is assignment.
The second situation is initialization, which can occur three ways:
When an object is used to initialized another in a declaration statement,
When an object is passed as a parameter to a function, and
when a temporary object is created for use as a return value by a function.
A copy constructor only applies to initialisation. It does not apply to
assignments.
By default, when an initialisation occurs, the compiler will automatically provide a
bitwise copy (that is, C++ automatically provides a default copy constructor that
simply duplicates the object.) However, it is possible to specify precisely how one
object will initialise another by defining a copy constructor. Once defined, the
copy constructor is called whenever an object is used to initialise another.
The most common form of copy constructor is shown here:
class-name(const class-name &obj) {
// body of constructor
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 29 -
Here obj is a reference to an object that is being used to initialise another object.
For example, assuming a class called myclass, and that y is an object of type
myclass, the following statements would invoke the myclass copy constructor:
myclass x = y; // y explicitly initialising x
func1(y); // y passed as a parameter
y = func2( ); // y receiving a returned object
In the first two cases, a reference to y would be passed to the copy constructor.
In the third, a reference to the object returned by func2( ) is passed to the copy
constructor.
/* This program creates a 'safe' array class. Since space for the array is
dynamically allocated, a copy constructor is provided to allocate memory when
one array object is used to initialise another */
#include < iostream >
#include < cstdlib >
using namespace std;
class array {
int *p;
int size;
public:
array(int sz) { // constructor
p = new int[sz];
if (!p) exit(1);
size = sz;
cout << "Using normal constructor\n";
}
array( ) { delete [ ] p; } //destructor
// copy constructor
array(const array &a); //prototype
void put(int i, int j) {
if (i>=0 && i<size) p[i] = j;
}
int get(int i) { return p[i]; }
};
// Copy constructor:
// In the following, memory is allocated specifically
// for the copy, and the address of this memory is
// assigned to p.Therefore, p is not pointing to the
// same dynamically allocated memory as the original
// object
array::array(const array &a) {
int i;
size = a.size;
p = new int[a.size]; // allocate memory for copy
if (!p) exit(1);
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 30 -
// copy content
for(i=0; i<a.size; i++) p[i] = a.p[i];
cout << "Using copy constructor\n";
}
int main( ) {
array num(10); // this call normal constructor
int i;
// put some value into the array
for (i=0; i<10; i++) num.put(i, j);
// display num
for (i=9; i>=0; i--) cout << num.get(i);
cout << "\n";
// create another array and initialise with num
array x = num; // this invokes the copy constructor
// display x
for (i=0; i<10; i++) cout << x.get(i);
return 0;
}
When num is used to initialise x the copy constructor is called, memory for the
new array is allocated and store in x.p and the contents of num are copied to x's
array. In this way, x and num have arrays that have the same values, but each
array is separated and distinct. That is, num.p and x.p do not point to the same
piece of memory.
A copy constructor is only for initialisation. The following sequence does not call
the copy constructor defined in the preceding program.
array a(10);
array b(10);
b = a; // does not call the copy constructor. It performs/ the assignment operation.
A copy constructor also helps prevent some of the problems associated with
passed certain types of objects to function. Here, a copy constructor is defined
for the strtype class that allocates memory for the copy when the copy is
created.
// This program uses a copy constructor to allow strtype
// objects to be passed to functions
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
class strtype {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 31 -
char *p;
public:
strtype(char *s); // constructor
strtype(const strtype &o); // copy constructor
strtype( ) { delete [ ] p; } // destructor
char *get( ) { return p; }
};
// Constructor
strtype::strtype(char *s) {
int l;
l = strlen(s) + 1;
p = new char [l];
if (!p) {
cout << "Allocation error\n";
exit(1);
}
strcpy(p, s);
}
// Copy constructor
strtype::strtype(const strtype &o) {
int l;
l = strlen(o.p) + 1;
p = new char [l]; // allocate memory for new copy
if (!p) {
cout << "Allocation error\n";
exit(1);
}
strcpy(p, o.p); // copy string into copy
}
void show(strtype x) {
char *s;
s = x.get( );
cout << s << "\n";
}
int main( ) {
strtype a("Hello"), b("There");
show(a);
show(b);
return 0;
}
Here, when show( ) terminates and x goes out of scope, the memory pointed to
by x.p (which will be freed) is not the same as the memory still in use by the
object passed to the function.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 32 -
Using default arguments
There is a feature of C++ that is related to function overloading. This feature is
called default argument, and it allows you to give a parameter a default value
when no corresponding argument is specified when the function is called. Using
default arguments is essentially a shorthand form of function overloading.
To give a parameter a default argument, simply follow that parameter with an
equal sign and the value you want it to default to if no corresponding argument is
present when the function is called. For example, this function gives two
parameters default values of 0:
void f(nit a=0, nit b=0);
Notice that this syntax is similar to variable initialisation. This function can now be
called three different ways:
It can be called with both arguments specified.
It can be called with only the first argument specified (in this case b will default
to 0).
It can be called with no arguments (both a and b default to 0).
That is the following calls to the function f are valid,
f( ); // a and b default to 0
f(10); // a is 10 and b defaults to 0
f(10, 99); // a is 10 and b is 99
When you create a function that has one or more default arguments, those
arguments must be specified only once: either in the function's prototype or in
the function's definition if the definition precedes the function's first use. The
defaults cannot be specified in both the prototype and the definition. This rule
applies even if you simply duplicate the same defaults.
All default parameters must be to the right of any parameters that don't have
defaults. Further, once you begin define default parameters, you cannot specify
any parameters that have no defaults.
Default arguments must be constants or global variables. They cannot be local
variables or other parameters.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 33 -
Default arguments often provide a simple alternative to function overloading. Of
course there are many situations in which function overloading is required.
It is not only legal to give constructor functions default arguments, it is also
common. Many times a constructor is overloaded simply to allow both initialised
and uninitialised objects to be created. In many cases, you can avoid overloading
constructor by giving it one or more default arguments:
#include <iostream>
using namespace std;
class myclass {
int x;
public:
// Use default argument instead of overloading
// myclass constructor.
myclass(int n = 0) { x = n; }
int getx( ) { return x; }
};
int main( ) {
myclass o1(10); // declare with initial value
myclass o2; // declare without initialiser
cout << "o1: " << o1.getx( ) << "\n";
return 0;
}
Another good application for default argument is found when a parameter is used
to select an option. It is possible to give that parameter a default value that is
used as a flag that tells the function to continue to use a previously selected
option.
Copy constructors can take default arguments, as long as the additional
arguments have default value. The following is also an accepted form of a copy
constructor:
myclass(const myclass &obj, nit x = 0) {
// body of constructor
}
As long as the first argument is a reference to the object being copied, and all
other arguments default, the function qualifies as a copy constructor. This
flexibility allows you to create copy constructors that have other uses.
As with function overloading, part of becoming an excellent C++ programmer is
knowing when use a default argument and when not to.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 34 -
OPERATOR OVERLOADING
The basics of operator overloading
Operator overloading resembles function overloading. In fact, operator
overloading is really just a type of function overloading. However, some
additional rules apply. For example, an operator is always overloaded relatively
to a user defined type, such as a class. Other difference will be discussed as
needed.
When an operator is overloaded, that operator loses none of its original meaning.
Instead, it gains additional meaning relative to the class for which it is defined.
To overload an operator, you create an operator function. Most often an operator
function is a member or a friend of the class for which it is defined. However,
there is a slight difference between a member operator function and a friend
operator function.
The general form of a member operator function is shown here:
return-type class-name::operator#(arg-list)
{
// operation to be performed
}
The return type of an operator function is often the class for which it is defined
(however, operator function is free to return any type). The operator being
overloaded is substituted for #. For example, if the operator + is being
overloaded, the operator function name would be operator+. The contents of
arg-list vary depending upon how the operator function is implemented and the
type of operator being overloaded.
There are two important restrictions to remember when you are overloading an
operator:
The precedence of the operator cannot be change.
The number of operands that an operator takes cannot be altered.
Most C++ operators can be overloaded. The following operators cannot be
overload:
. :: .* ?
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 35 -
Also, you cannot overload the pre-processor operators (.* is highly specialised
and is beyond the scope of this course).
Remember that C++ defines operators very broadly, including such things as the
[ ] subscript operator, the ( ) function call operators, new and delete, and the dot
and arrow operator. However, we will concentrate on overloading the most
commonly used operators.
Except for the =, operator functions are inherited by any derived class. However,
a derived class is free to overload any operator it chooses (including those
overloaded by the base class) relative to itself.
Note, you have been using two overloaded operators: << and >>. These
operators have been overloaded to perform console I/O. As mentioned,
overloading these operators does not prevent them from performing their
traditional jobs of left shift and right shift.
While it is permissible for you to have an operator function perform any activity, it
is best to have an overloaded operator's actions stay within the spirit of the
operator's traditional use.
Overloading binary operators
When a member operator function overloads a binary operator, the function will
have only one parameter. This parameter will receive the object that is on the
right side of the operator. The object on the left side is the object that generates
the call to the operator function and is passed implicitly by this.
It important to understand that operator functions can be written with many
variations. The examples given illustrate several of the most common
techniques.
The following program overloads the + operator relative to the coord class. This
class is used to maintain X, Y co-ordinates.
// overload the + relative to coord class
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
coord operator+(coord ob2);
};
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 36 -
// Overload + relative to coord class.
coord coord::operator+(coord ob2) {
coord temp;
temp.x = x + ob2.x;
temp.y = y + ob2.y;
return temp;
}
int main( ) {
coord o1(10, 10), o2(5, 3), o3;
int x, y;
o3 = o1 + o2; //add to objects,
// this calls operator+()
o3.get_xy(x, y);
cout << "(o1+o2) X: " << x << ", Y: " << y << "\n";
return 0;
}
The reason the operator+ function returns an object of type coord is that it
allows the result of the addition of coord objects to be used in larger
expressions. For example,
o3 = o1 + o2;
o3 = o1 + o2 + o1 + o3;
(o1+o2).get_xy(x, y);
In the last statement the temporary object returned by operator+( ) is used
directly. Of course, after this statement has executed, the temporary object is
destroyed.
The following version of the preceding program overloads the - and the =
operators relative to the coord class.
// overload the +, - and = relative to coord class
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
coord operator+(coord ob2);
coord operator-(coord ob2);
coord operator=(coord ob2);
};
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 37 -
// Overload + relative to coord class.
coord coord::operator+(coord ob2) {
coord temp;
temp.x = x + ob2.x;
temp.y = y + ob2.y;
return temp;
}
// Overload - relative to coord class.
coord coord::operator-(coord ob2) {
coord temp;
temp.x = x - ob2.x;
temp.y = y - ob2.y;
return temp;
}
// Overload = relative to coord class.
coord coord::operator=(coord ob2) {
x = ob2.x;
y = ob2.y;
return *this; // return the object that is assigned
}
int main( ) {
coord o1(10, 10), o2(5, 3), o3;
int x, y;
o3 = o1 + o2; // add two objects,
// this calls operator+()
o3.get_xy(x, y);
cout << "(o1+o2) X: " << x << ", Y: " << y << "\n";
o3 = o1 - o2; //subtract two objects
o3.get_xy(x, y);
cout << "(o1-o2) X: " << x << ", Y: " << y << "\n";
o3 = o1; //assign an object
o3.get_xy(x, y);
cout << "(o3=o1) X: " << x << ", Y: " << y << "\n";
return 0;
}
Notice that to correctly overload the subtraction operator, it is necessary to
subtract the operand on the right from the operand on the left. The second thing
you should notice is that the function returns *this. That is, the operator= function
returns the object that is being assigned to. The reason for this is to allow a
series of assignment to be made. By returning *this the overloaded assignment
operator allows objects of type coord to be used in a series of assignment,
o3 = o2 = o1;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 38 -
Here another example where the + operator is overloaded to add an integer
value to a coord object.
// overload the + for obj+int and as well as obj+obj
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
coord operator+(coord ob2); // obj + obj
coord operator+(int i); // obj + int
};
// Overload + relative to coord class.
coord coord::operator+(coord ob2) {
coord temp;
temp.x = x + ob2.x;
temp.y = y + ob2.y;
return temp;
}
// Overload + for obj + int.
coord coord::operator+(int i) {
coord temp;
temp.x = x + i;
temp.y = y + i;
return temp;
}
int main( ) {
coord o1(10, 10), o2(5, 3), o3;
int x, y;
o3 = o1 + o2; // add two objects,
// calls operator+(coord)
o3.get_xy(x, y);
cout << "(o1+o2) X: " << x << ", Y: " << y << "\n";
o3 = o1 + 100; // add object + int
// calls operator+(int)
o3.get_xy(x, y);
cout<< "(o1+100) X: "<< x << ", Y: "<< y << "\n";
return 0;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 39 -
You can use a reference parameter in an operator function. For example,
// Overload + relative to coord class using reference.
coord coord::operator+(coord &ob2) {
coord temp;
temp.x = x + ob2.x;
temp.y = y + ob2.y;
return temp;
}
One reason for using a reference in an operator function is efficiency. Another
reason is to avoid the trouble caused when a copy of an operand is destroyed.
There are many other variations of operator function overloading.
Overloading the relational and logical operators
It is possible to overload the relational and logical operators. When you overload
the relational and logical operators so that they behave in their traditional
manner, you will not want the operator functions to return an object of the class
for which they are defined. Instead, they will return an integer that indicates either
true or false. This not only allows the operators to return a true/false value, it also
allows the operators to be integrated into larger relational and logical expressions
that involve other type of data.
Note if you are using a modern C++ compiler, you can also have an overloaded
relational or logical operator function return a value of type bool, although there
is no advantage to doing so.
The following program overloads the operators == and &&:
// overload the == and && relative to coord class
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
int operator==(coord ob2);
int operator&&(int i);
};
// Overload the operator == for coord
int coord::operator==(coord ob2) {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 40 -
return (x==ob2.x) && (y==ob2.y);
}
// Overload the operator && for coord
int coord::operator&&(coord ob2) {
return (x && ob2.x) && (y && ob2.y);
}
int main( ) {
coord o1(10, 10), o2(5, 3), o3(10, 10), o4(0, 0);
if (o1==o2) cout << "o1 same as o2\n";
else cout << "o1 and o2 differ\n";
if (o1==o3) cout << "o1 same as o3\n";
else cout << "o1 and o3 differ\n";
if (o1&&o2) cout << "o1 && o2 is true\n";
else cout << "o1 && o2 is false\n";
if (o1&&o4) cout << "o1 && o4 is true\n";
else cout << "o1 && o4 is false\n";
return 0;
}
Overloading a unary operator
Overloading a unary operator is similar to overloading a binary operator except
that there is one operand to deal with. When you overload a unary operator using
a member function, the function has no parameters. Since, there is only one
operand, it is this operand that generates the call to the operator function. There
is no need for another parameter.
The following program overloads the increment operator ++ relative to the class
coord.
// overload the ++ relative to coord class
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
coord operator++( );
};
// Overload ++ operator for coord class
coord coord::operator++( ) {
x++;
y++;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 41 -
return *this;
}
int main( ) {
coord o1(10, 10);
int x, y;
++o1; //increment an object
o1.get_xy(x, y);
cout << "(++o1) X: " << x << ", Y: " << y << "\n";
return 0;
}
In early versions of C++ when increment or decrement operator was overloaded,
there was no way to determine whether an overloaded ++ or -- preceded or
followed its operand (i.e. ++o1; or o1++; statements). However in modern C++, if
the difference between prefix and postfix increment or decrement is important for
you class objects, you will need to implement two versions of operator++( ). The
first is defined as in the preceding example. The second would be declared like
this:
coord coord::operator++(int notused);
If ++ precedes its operand the operator++( ) function is called. However, if ++
follows its operand the operator++(int notused) function is used. In this case,
notused will always be passed the value 0. Therefore, the difference between
prefix and postfix increment or decrement can be made.
In C++, the minus sign operator is both a binary and a unary operator. To
overload it so that it retains both of these uses relative to a class that you create:
simple overload it twice, once as binary operator and once as unary operator. For
example,
// overload the - relative to coord class
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
coord operator-(coord ob2); // binary minus
coord operator-( ); // unary minus
};
// Overload binary - relative to coord class.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 42 -
coord coord::operator-(coord ob2) {
coord temp;
temp.x = x - ob2.x;
temp.y = y - ob2.y;
return temp;
}
// Overload unary - for coord class.
coord coord::operator+( ) {
x = -x;
y = -y;
return *this;
}
int main( ) {
coord o1(10, 10), o2(5, 7);
int x, y;
o1 = o1 - o2; // subtraction
// call operator-(coord)
o1.get_xy(x, y);
cout << "(o1-o2) X: " << x << ", Y: " << y << "\n";
o1 = -o1; // negation
// call operator-(int notused)
o1.get_xy(x, y);
cout << "(-o1) X: " << x << ", Y: " << y << "\n";
return 0;
}
Using friend operator functions
As mentioned before, it is possible to overload an operator relative to a class by
using a friend rather than a member function. As you know, a friend function
does not have a this pointer. In the case of a binary operator, this means that a
friend operator function is passed both operands explicitly. For unary operators,
the single operand is passed. All other things being equal, there is no reason to
use a friend rather than a member operator function, with one important
exception, which is discussed in the examples.
Remember, you cannot use a friend to overload the assignment operator. The
assignment operator can be overloaded only by a member operator
function.
Here operator+( ) is overloaded for the coord class by using a friend function:
//Overload the + relative to coord class using a friend.
#include <iostream>
using namespace std;
class coord {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 43 -
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
friend coord operator+(coord ob1, coord ob2);
};
// Overload + using a friend.
coord operator+(coord ob1, coord ob2) {
coord temp;
temp.x = ob1.x + ob2.x;
temp.y = ob1.y + ob2.y;
return temp;
}
int main( ) {
coord o1(10, 10), o2(5, 3), o3;
int x, y;
o3 = o1 + o2; //add to objects
// this calls operator+( )
o3.get_xy(x, y);
cout << "(o1+o2) X: " << x << ", Y: " << y << "\n";
return 0;
}
Note that the left operand is passed to the first parameter and the right operand
is passed to the second parameter.
Overloading an operator by using a friend provides one very important feature
that member functions do not. Using a friend operator function, you can allow
objects to be used in operations involving build-in types in which the built-in type
is on the left side of the operator:
ob1 = ob2 + 10; // legal
ob1 = 10 + ob2; // illegal
The solution is to make the overloaded operator functions, friend and define both
possible situations.
As you know, a friend operator function is explicitly passed both operands. Thus,
it is possible to define one overloaded friend function so that the left operand is
an object and the right operand is the other type. Then you could overload the
operator again with the left operand being the built-in type and the right operand
being the object. For example,
// Use friend operator functions to add flexibility.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 44 -
#include <iostream>
using namespace std;
class coord {
int x, y; // coordinate values
public:
coord( ) { x = 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
void get_xy(int &i, int &j) { i = x; j = y; }
friend coord operator+(coord ob1, int i);
friend coord operator+(int i, coord ob1);
};
// Overload for obj + int.
coord operator+(coord ob1, int i) {
coord temp;
temp.x = ob1.x + i;
temp.y = ob1.y + i;
return temp;
}
// Overload for int + obj.
coord operator+(int i, coord ob1) {
coord temp;
temp.x = ob1.x + i;
temp.y = ob1.y + i;
return temp;
}
int main( ) {
coord o1(10, 10);
int x, y;
o1 = o1 + 10; // object + integer
o1.get_xy(x, y);
cout << "(o1+10) X: " << x << ", Y: " << y << "\n";
o1 = 99 + o1; // integer + object
o1.get_xy(x, y);
cout << "(99+o1) X: " << x << ", Y: " << y << "\n";
return 0;
}
As a result of overloading friend operator functions both of these statements are
now valid:
o1 = o1 + 10;
o1 = 99 + o1;
If you want to use friend operator function to overload either ++ or -- unary
operator, you must pass the operand to the function as a reference
parameter. This is because friend functions do not have this pointers.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 45 -
Remember that the increment or decrement operators imply that the operand will
be modified. If you pass the operand to the friend as a reference parameter,
changes that occur inside the friend function affect the object that generates the
call. Here an example,
// Overload the ++ relative to coord class using a
}
return ob;
int main( ) {
coord o1(10, 10);
int x, y;
++o1; //o1 is passed by reference
o1.get_xy(x, y);
cout << "(++o1) X: " << x << ", Y: " << y << "\n";
return 0;
}
With modern compiler, you can also distinguish between the prefix and the
postfix form of the increment or decrement operators when using a friend
operator function in much the same way you did when using member functions.
For example, here are the prototypes for both versions of the increment operator
relative to coord class:
coord operator++(coord &ob); // prefix
coord operator++(coord &ob, int notused); // postfix
A closer look at the assignment operator
As you have seen, it is possible to overload the assignment operator relative to a
class. By default, when the assignment operator is applied to an object, a bitwise
copy of the object on the right side is put into the object on the left. If this is what
you want there is no reason to provide your own operator=( ) function. However,
there are cases in which a strict bitwise copy is not desirable (e.g. cases in which
object allocates memory). In these types of situations, you will want to provide a
special assignment operator. Here is another version of strtype class that
overload the = operator so that the point p is not overwritten by the assignment
operation.
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 46 -
class strtype {
char *p;
int len;
public:
strtype(char *s); // constructor
strtype( ) { // destructor
cout << "Freeing " << (unsigned) p << "\n";
delete [ ] p;
}
char *get( ) { return p; }
strtype &operator=(strtype &ob);
};
// Constructor
strtype::strtype(char *s) {
int l;
l = strlen(s) + 1;
p = new char [l];
if (!p) {
cout << "Allocation error\n";
exit(1);
}
len = 1;
strcpy(p, s);
}
// Assign an object
strtype &strtype::operator=(strtype &ob) {
// see if more memory is needed
if (len < ob.len) {// need to allocate more memory
delete [ ] p;
p = new char [ob.len];
if (!p) {
cout << "Allocation error\n";
exit(1);
}
}
len = ob.len;
strcpy(p, ob.p);
return *this;
}
int main( ) {
strtype a("Hello"), b("there");
cout << a.get( ) << "\n";
cout << b.get( ) << "\n";
a = b; // now p is not overwritten
cout << a.get( ) << "\n";
cout << b.get( ) << "\n";
return 0;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 47 -
}
Notice two important features about the operator=( ) function:
It takes a reference parameter (prevent a copy of the object on the right side
from being made).
It returns a reference, not an object (prevent a temporary object from being
created).
Overloading the [ ] subscript operator
The last operator that we will overload is the [ ] array subscript operator. In C++,
the [ ] is considered a binary operator for the overloading purposes. The [ ] can
be overloaded only by a member function. Therefore the general form of a
member operator[ ]( ) function is as shown here
type class-name::operator[ ](int index)
{
// body ...
}
Technically, the parameter does not have to be of type int, but operator[ ]( )
function is typically used to provide array subscript and as such an integer value
is generally used.
To understand how the [ ] operator works, assume that an object colled O is
indexed as shown here:
O[9]
This index will translate into the following call to the operator[ ]( ) function:
O.operator[ ](9)
That is, the value of the expression within the subscript operator is passed to the
operator[ ]( ) function in its explicit parameter. The this pointer will point to O,
the object that generates the call.
In the following program, arraytype declares an array of five integers. Its
constructor function initialises each member of the array. The overloaded
operator[ ]( ) function returns the value of the element specified by its
parameter.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 48 -
#include <iostream>
using namespace std;
const int SIZE = 5;
class arraytype {
int a[SIZE];
public:
arraytype( ) {
int i;
for (i=0;i<SIZE; i++) a[i] = i;
}
int operator[ ] (int i) { return a[i]; }
};
int main( ) {
arraytype ob;
int i;
for (i=0; i<SIZE; i++) cout << ob[i] << " ";
return 0;
}
This program displays the following output:
0 1 2 3 4
It is possible to design the operator[ ]( ) function in such a way that the [ ] can be
used on both the left and right sides of an assignment statement. To do this
return a reference to the element being indexed,
#include <iostream>
using namespace std;
const int SIZE = 5;
class arraytype {
int a[SIZE];
public:
arraytype( ) {
int i;
for (i=0;i<SIZE; i++) a[i] = i;
}
int &operator[ ] (int i) { return a[i]; }
};
int main( ) {
arraytype ob;
int i;
for (i=0; i<SIZE; i++) cout << ob[i] << " ";
cout << "\n";
// add 10 to each element in the array
for (i=0; i<SIZE; i++)
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 49 -
ob[i] = ob[i] + 10; // [ ] on left of =
for (i=0; i<SIZE; i++) cout << ob[i] << " ";
return 0;
}
This program displays:
0 1 2 3 4
10 11 12 13 14
As you can see this makes objects of arraytype act like normal arrays.
VIRTUAL FUNCTIONS
Pointers to derived class
Although we have discussed pointers at some length, one special aspect relates
specifically to virtual functions. This feature is: a pointer declared as a pointer to
a base class can also be used to point to any derived from that base. For
example, assume two classes called base and derived, where derived inherits
base.
Given this situation, the following statements are correct:
base *p; // base class pointer
base base_ob; // object of type base
derived derived_ob; // object of type derived
// p can, of course, points to base objects
p = &base_ob; // p points to base object
// p can also points to derived objects without error
p = &derived_ob; // p points to derived object
Although you can use a base pointer to point to a derived object, you can access
only those members of the derived object that were inherited from the base. This
is because the base pointer has knowledge only of the base class. It knows
nothing about the members added by the derived class.
While it is permissible for a base pointer to point to a derived object, the reverse
is not true.
One final point: remember that pointer arithmetic is relative to the data type the
pointer is declared as pointing to. Thus, if you point a base pointer to a derived
object and then increment that pointer, it will not be pointing to the next derived
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 50 -
object. It will be pointing to (what it thinks is) the next base object. Be careful
about this.
// Demonstrate pointer to derived class
#include <iostream>
using namespace std;
class base {
int x;
public:
void setx(int i) { x = i; }
int getx( ) { return x; }
};
class derived : public base {
int y;
public:
void sety(int i) { y = i; }
int gety( ) { return y; }
};
int main( ) {
base *p; // pointer to base type
base b_ob; // object of base
derived d_ob; // object of derived
// use p to access base object
p = &b_ob;
p- >setx(10); // access base object
cout << "Base object x: " << p- >getx( ) << "\n";
// use p to access derived object
p = &d_ob; // point to derived object
p- >setx(99); // access derived object
// cannot use p to set y, so do it directly
d_ob.sety(88);
cout << "Derived object x: "<< p- >getx( ) << "\n";
cout << "Derived object y: "<< d_ob.gety( )<< "\n";
return 0;
}
Aside from illustrating pointers to derived classes, there is value in using a base
pointer in the way shown in this example. However, in the next section you will
see why base class pointers to derived objects are so important.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 51 -
Introduction to virtual functions
A virtual function is a member function that is declare within a base class and
redefined by a derived class. To create a virtual function, precedes the function
declaration with the keyword virtual. When a class containing a virtual function is
inherited, the derived class redefines the virtual function relative to the derived
class. In essence, virtual functions implement the 'one interface, multiple
methods' philosophy that underlies polymorphism. The virtual function within the
base class defines the form of the interface to that function. Each redefinition of
the virtual function by a derived class implements its operation as it relates
specifically to the derived class. That is, the redefinition creates a specific
method. When a virtual function is redefined by a derived class, the keyword
virtual is not needed.
A virtual function can be called just like any member function. However, what
makes a virtual function interesting, and capable of supporting run-time
polymorphism, is what happens when a virtual function is called through a
pointer. When a base pointer points to a derived object that contains a virtual
function and that virtual function is called through that pointer, C++ determines
which version of that function will be executed based upon the type of object
being pointed to by the pointer. And this determination is made at run time.
Therefore, of two or more different classes are derived from a base class that
contains a virtual function, then when different objects are pointed to by a base
pointer, different versions of the virtual function are executed. This process is that
way that run-time polymorphism is achieved. In fact, a class that contains a
virtual function is referred to as a polymorphic class.
// A simple example using a virtual function.
#include <iostream>
using namespace std;
class base {
public:
int i;
base(int x) { i = x; }
virtual void func( ) {
cout << "Using base version of func(): ";
cout << i << "\n";
}
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 52 -
class derived1 : public base {
public:
derived1(int x) : base(x) { }
void func( ) {
cout << "Using derived1's version of func(): ";
cout << i*i << "\n";
}
};
class derived2 : public base {
public:
derived2(int x) : base(x) { }
void func( ) {
cout << "Using derived2's version of func(): ";
cout << i+i << "\n";
}
};
int main( ) {
base *p;
base ob(10);
derived1 d_ob1(10);
derived2 d_ob2(10);
p = &ob;
p- >func( ); // use base's func( )
p = &d_ob1;
p- >func( ); // use derived1's func( )
p = &d_ob2;
p- >func( ); // use derived2's func( )
return 0;
}
This program displays the following output:
Using base version of func( ): 10
Using derived1's version of func( ): 100
Using derived2's version of func( ): 20
The redefinition of a virtual function inside a derived class might seem somewhat
similar to function overloading. However, the two processes are different. First, a
redefined virtual function must have precisely the same type and number of
parameters and the same return type. Second, virtual functions must be class
members. This is not the case for overloaded functions. Also, while destructor
functions can be virtual, constructors cannot. Because of these differences
between overloaded functions and redefined virtual functions, the term overriding
is used to describe virtual function redefinition.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 53 -
The key points to understand from the preceding example are that the type of the
object being pointed to, determines which version of an overridden virtual
function will be executed via a base class pointer, and that that this decision is
made at run time.
Virtual functions are hierarchical in order of inheritance. Further, when a derived
class does not override a virtual function, the function defined within its base
class is used. Here is a slightly different version of the previous example:
// Virtual function are hierarchical.
#include <iostream>
using namespace std;
class base {
public:
int i;
base(int x) { i = x; }
virtual void func( ) {
cout << "Using base version of func(): ";
cout << i << "\n";
}
}
class derived1 : public base {
public:
derived1(int x) : base(x) { }
void func( ) {
cout << "Using derived1's version of func(): ";
cout << i*i << "\n";
}
};
class derived2 : public base {
public:
derived2(int x) : base(x) { }
// derived2 does not override func( )
};
int main( ) {
base *p;
base ob(10);
derived1 d_ob1(10);
derived2 d_ob2(10);
p = &ob;
p- >func( ); // use base's func( )
p = &d_ob1;
p- >func( ); // use derived1's func( )
p = &d_ob2;
p- >func( ); // use base's func( )
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 54 -
return 0;
}
This program displays the following output:
Using base version of func( ): 10
Using derived1's version of func( ): 100
Using base version of func( ): 20
Here is a more practical example of how a virtual function can be used. This
program creates a generic base class called area that holds two dimensions of a
figure. It also declares a virtual function called getarea( ) that, when overridden
by derived classes, returns the area of the type of figure defined by the derived
class. In this example, the area of a triangle and rectangle are computed.
#include < iostream >
using namespace std;
class area {
double dim1, dim2; // dimensions of figure
public:
void setarea(double d1, double d2) {
dim1 = d1;
dim2 = d2;
}
void getdim(double &d1, double &d2) {
d1 = dim1;
d2 = dim2;
}
virtual double getarea( ) {
cout << "You must override this function\n";
return 0.0;
}
};
class rectangle : public area {
public:
double getarea( ) {
double d1, d2;
getdim(d1, d2);
return d1*d2,
}
};
class triangle : public area {
public:
double getarea( ) {
double d1, d2;
getdim(d1, d2);
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 55 -
return 0.5*d1*d2;
}
};
int main( ) {
area *p;
rectangle r;
triangle t;
r.setarea(3.3, 4.5);
t.setarea(4.0, 5.0);
p = &r;
cout << "Rectangle area: "<< p- >getarea( ) <<"\n";
p = &t;
cout << "Triangle area: "<< t- >getarea( ) << "\n";
return 0;
}
Notice that the definition of getarea( ) inside area is just a placeholder and
performs no real function.
More about virtual functions
As in the previous section, sometimes when a virtual function is declared in the
base class there is no meaningful operation for it to perform. This situation is
common because often a base class does not define a complete class by itself.
Instead, it simply supplies a core set of member functions and variables to which
the derived class supplies the remainder. When there is no meaningful action for
a base class virtual function to perform, the implication is that any derived class
must override this function. To ensure that this will occur, C++ supports pure
virtual functions.
A pure virtual function has no definition relative to the base class. Only the
function prototype is included. To make a pure virtual function, use this general
form:
virtual type func-name(parameter-list) = 0;
The key part of this declaration is the setting of the function equal to 0. This tells
the compiler that no body exists for this function relative to the base class. When
a virtual function is made pure, it forces any derived class to override it. If a
derived class does not, a compile-time error results. Thus, making a virtual
function pure is a way to guaranty that a derived class will provide its own
redefinition.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 56 -
When a class contains at least one pure virtual function, it is referred to as an
abstract class. Since, an abstract class contains at least one function for which
no body exists, it is, technically, an incomplete type, and no objects of that class
can be created. Thus, abstract classes exist only to be inherited. They are
neither intended nor able to stand alone. It is important to understand, however,
that you can still create a pointer to an abstract class, since it is through the use
of base class pointers that run-time polymorphism is achieved. (It is also possible
to have a reference to an abstract class.)
When a virtual function is inherited, so is its virtual nature. This means that when
a derived class inherits a virtual function from a base class and then the derived
class is used as a base for yet another derived class, the virtual function can be
overridden by the final derived class (as well as the first derived class). For
example, if base class B contains a virtual function called f( ), and D1 inherits B
and D2 inherits D1, both D1 and D2 can override f( ) relative to their respective
classes.
Here is an improve version of the area program:
#include < iostream >
using namespace std;
class area {
double dim1, dim2; // dimensions of figure
public:
void setarea(double d1, double d2) {
dim1 = d1;
dim2 = d2;
}
void getdim(double &d1, double &d2) {
d1 = dim1;
d2 = dim2;
}
virtual double getarea( ) = 0;
// pure virtual function
};
class rectangle : public area {
public:
double getarea( ) {
double d1, d2;
getdim(d1, d2);
return d1*d2,
}
};
class triangle : public area {
public:
double getarea( ) {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 57 -
double d1, d2;
getdim(d1, d2);
return 0.5*d1*d2;
}
};
int main( ) {
area *p;
rectangle r;
triangle t;
r.setarea(3.3, 4.5);
t.setarea(4.0, 5.0);
p = &r;
cout << "Rectangle area: "<< p- >getarea( ) <<"\n";
p = &t;
cout << "Triangle area: "<< t- >getarea( ) << "\n";
return 0;
}
Now that getarea( ) is pure, it ensures that each derived class will override it.
The following program illustrates how the virtual nature of a function is preserved
when it is inherited:
#include <iostream>
using namespace std;
class base {
public:
virtual void func( ) {
cout << "Using base version of func()\n";
}
}
class derived1 : public base {
public:
void func( ) {
cout << "Using derived1's version of func()\n";
}
};
// derived2 inherits derived1
class derived2 : public derived1 {
public:
void func( ) {
cout << "Using derived2's version of func()\n";
}
};
int main( ) {
base *p;
base ob;
derived1 d_ob1;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 58 -
derived2 d_ob2;
p = &ob;
p- >func( ); // use base's func( )
p = &d_ob1;
p- >func( ); // use derived1's func( )
p = &d_ob2;
p- >func( ); // use derived2's func( )
return 0;
}
Because virtual function are hierarchical, if derived2 did not override func(),
when d_ob2 was accessed, derived1's func() would have been used. it neither
derived1 nor derived2 had overridden func() all references to it would have
routed to the one defined in base.
Applying polymorphism
Now that you know how to use a virtual function to achieve run-time
polymorphism, it is time to consider how and why to use it. As state many times,
polymorphism is the process by which a common interface is applied to two or
more similar (but technically different) situations, thus implementing the 'one
interface, multiple methods' philosophy. Polymorphism is important because it
can greatly simplify complex systems. A single, well-defined interface is used to
access a number of different but related actions, and artificial complexity is
removed. In essence, polymorphism allows the logical relationship of similar
actions to become apparent; thus, the program is easier to understand and
maintain. When related actions are accessed through a common interface, you
have less to remember.
There are two terms that are often linked to OOP in general and to C++
specifically. They are early binding and late binding. It is important to know what
they mean. Early binding essentially refers to those function calls that can be
known at compile time. Specifically, it refers to those function calls that can be
resolved during compilation. Early bound entities include 'normal' functions,
overloaded functions and non virtual member and friend functions. When these
types of functions are compiled, all address information necessary to call them is
known at compile time. The main advantage of early binding (and the reason that
it is so widely used) is that it is efficient. Calls to functions bound at compile time
are the fastest types of function calls. The main disadvantage is lack of flexibility.
Late binding refers to events that must occur at run time. A late bound function
call is one in which the address of the function to be called is not known until the
program runs. In C++, a virtual function is a late bound object. When a virtual
function is accessed via a base class pointer, the program must determine at run
time what type of object is being pointed to, and then select which version of the
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 59 -
overridden function of execute. The main advantage of late binding is flexibility at
run time. Your program is free to respond to random events without having to
contain large amount of 'contingency code'. Its primary disadvantage is that there
is more overhead associated with a function call. This generally makes such calls
slower than those that occur with early binding.
Because of the potential efficiency trade-offs, you must decide when it is
appropriate to use early binding and when to use late binding.
Here is a program that illustrates 'one interface, multiple methods'. It defines an
abstract list class for integer values. The interface to the list is defined by the
pure virtual functions store( ) and retrieve( ). To store a value, call the store()
function. To retrieve a value from the list, call retrieve(). The base class list does
not define any default methods for these actions. Instead, each derived class
defines exactly what type of list will be maintained. In this program, two types of
lists are implemented: a queue and a stack. Although the two lists operate
completely differently, each is accessed using the same interface. You should
study this program carefully.
// Demonstrate virtual function.
#include < iostream >
#include < cstdlib >
#include < cctype >
using namespace std;
class list {
public:
list *head; // pointer to start of list
list *tail; // pointer to end of list
list *next; // pointer to next item
int num; // value to be stored
list( ) { head = tail = next = NULL }
virtual void store(int i) = 0;
virtual int retrieve( ) = 0;
};
// Create a queue-type list
class queue : public list
public:
void store(int i);
int retrieve( );
};
void queue::store(int i) {
list *item;
item = new queue;
if (!item) {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 60 -
cout << "Allocation error\n";
exit(1);
}
// put on end of list
if (tail) tail- >next = item;
tail = item;
item- >next = NULL;
if (!head) head = tail;
}
int queue::retrieve( ) {
int i;
list *p;
if (!head) {
cout << "List empty.\n";
return 0;
}
// remove from start if list
i = head- >num;
p = head;
head = head- >next;
delete p;
return i;
}
// Create a stack-type list.
class stack : public list {
public:
void store(int i);
int retrieve( );
};
void stack::store(int i) {
list *item;
item = new stack;
if (!item) {
cout << "Allocation error\n";
exit(1);
}
item- >num = i;
// put on front of list for stack-like operation
if (head) item- >next = head;
head = item;
if (!tail) tail = head;
}
int stack::retrieve( ) {
int i;
list *p;
if (!head) {
cout << "List empty.\n";
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 61 -
return 0;
}
// remove from start of list
i = head- >num;
p = head;
head = head- >next;
delete p;
return i;
}
int main( ) {
list *p;
// demonstrate queue
queue q_ob;
p = &q_ob; // point to queue
p- >store(1);
p- >store(2);
p- >store(3);
cout "Queue: ";
cout << p- >retrieve( );
cout << p- >retrieve( );
cout << p- >retrieve( );
cout << "\n";
// demonstrate stack
stack s_ob;
p = &s_ob; // point to stack
p- >store(1);
p- >store(2);
p- >store(3);
cout "Queue: ";
cout << p- >retrieve( );
cout << p- >retrieve( );
cout << p- >retrieve( );
cout << "\n";
return 0;
}
The above main function in the list program just illustrates that the list classes do
work. However, to begin to see why run-time polymorphism is so powerful, try
using this main function instead:
int main( ) {
list *p;
stack s_ob;
queue q_ob;
char ch;
int i;
for (i=0; i<10; i++) {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 62 -
cout << "Stack or Queue? (S/Q): ";
cin << ch;
ch = tolower(ch);
if (ch=='q') p = &q_ob;
else p = &s_ob;
p- >store(i);
}
cout << "Enter T to terminate\n";
for (;;) {
cout << "Remove from Stack or Queue? (S/Q): ";
cin << ch;
ch = tolower(ch);
if (ch=='t') break;
if (ch=='q') p = &q_ob;
else p = &s_ob;
cout << p- >retrieve( ) << "\n";
}
cout << "\n";
return 0;
}
This main function illustrate how random events that occur at run time can be
easily handled by the virtual functions and run-time polymorphism. The program
executes a for loop running from 0 to 9. Each iteration through the loop, you are
asked to choose into which type of list (stack or queue) you want to put a value.
According to your answer, the base pointer p is set to point to the correct object
and the current value of i is stored. Once the loop is finished, another loop begins
that prompts you to indicate from which list to remove a value. Once again, it is
your response that determines which list is selected.
While this example is trivial, you should be able to see how run-time
polymorphism can simplify a program that must respond to random events.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 63 -
C++ I/O SYSTEM
C++ still supports the entire C I/O system. However, C++ supplies a complete set
of object oriented I/O routines. The major advantage of the C++ I/O system is
that it can be overloaded relative to classes that you create.
Like the C I/O system, the C++ object oriented I/O system makes little distinction
between console and file I/O. File and console I/O are really just different
perspectives on the same mechanism. The examples in this chapter use console
I/O, but the information presented is applicable to file I/O as well. (File I/O is
examined in detail in chapter Advanced C++ I/O.)
Some C++ I/O basics
The C++ I/O system like the C I/O system, operates through streams. You should
already know know that a stream is logical device that either produces or
consumes information. A stream is linked to a physical device by the C++ I/O
system. All streams behave in the same manner, even if the actual physical
devices they are linked to differ. Because all streams act the same, the I/O
system presents the programmer with a consistent interface.
As you know, when a C program begins execution, three pre-defined streams are
automatically opened: stdin, stdout, and stderr. A similar thing happens when a
C++ program starts running. When a C++ program begins, these four streams
are automatically opened:
Stream Meaning Default Device
Cin Standard Input Keyboard
Cout Standard Output Screen
Cerr Standard error Screen
Clog Buffer version of cerr Screen
C++ provides supports for its I/O system in the header file < iostream >. In this
file, a rather complicated set of class hierarchies is defined that supports I/O
operations. The I/O classes begin with a system of template classes. Template
classes also called generic classes, will be discussed later.; briefly, a template
class defines the form of a class without fully specifying the data upon which it
will operate. Once a template class has been defined, specific instances of it can
be created. As it relates to the I/O library, Standard C++ creates two specific
versions of the I/O template classes: one for 8-bit characters and another for
wide characters (16-bit).
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 64 -
Creating your own inserters
The advantage of the C++ I/O system is that you can overload the I/O operators
for classes that you create. In this section you learn how to overload the C++
output operator <<.
In C++ language, the output operation is called an insertion and the << is called
the insertion operator. When you overload the << for output, you are creating an
inserter function, or inserter for short. The rationale for these terms comes from
the fact that an output operator inserts information into the stream.
All inserter functions have this general form:
ostream &operator<<(ostream &stream, class-name ob)
{
// body of inserter
return stream;
}
The first parameter is a reference to an object of type ostream. This means that
stream must be an output stream. The second parameter receives the object that
will be output (can also be a reference parameter, if that is more suitable to your
application). Notice that the inserter function returns a reference to stream that is
of type ostream. This is required if the overloaded << is going to be used in a
series of I/O expressions, such as
cout << ob1 << ob2 << ob3;
Within an inserter you can perform any type of procedure. What an inserter does
is up to you. However, for the inserter to be consistent with good programming
practices, you should limit its operations to outputting to a stream.
Though inserters cannot be members of the class on which it is designed to
operate, they can be friends of the class.
// Use a friend inserter for objects of type coord
#include <iostream>
using namespace std;
class coord {
int x, y;
public:
coord( ) { x= 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
friend ostream &operator<<(ostream &st, coord ob);
};
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 65 -
ostream &operator<<(ostream &st, coord ob) {
st << ob.x << ", " << ob.y << "\n";
return st;
}
int main( ) {
coord a(1, 1), b(10, 23);
cout << a << b;
return 0;
}
This program displays
1, 1
10, 23
Here is a revised version of the program where the inserter is not a friend of the
class coord. Because the inserter does not have access to the private parts of
coord, the variables x and y have to be made public.
// Use a non-friend inserter for objects of type coord
#include <iostream>
using namespace std;
class coord {
public:
int x, y; // must be public
coord( ) { x= 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
};
// an inserter for the coord class
ostream &operator<<(ostream &st, coord ob) {
st << ob.x << ", " << ob.y << "\n";
return st;
}
int main( ) {
coord a(1, 1), b(10, 23);
cout << a << b;
return 0;
}
An inserter is not limited to display only textual information. An inserter can
perform any operation or conversion necessary to output information in a form
needed by a particular device or situation. The following program create a class
triangle that stores the width and height of a right triangle. The inserter for this
class displays the triangle on the screen.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 66 -
// This program draw right triangle
#include <iostream>
using namespace std;
class triangle {
int height, base;
public:
triangle(int h, int b) { height = h; base = b; }
friend ostream &operator<<(ostream &st, triangle ob);
};
// Draw a triangle
ostream &operator<<(ostream &st, triangle ob) {
int i, j, h, k;
i = j = ob.base-1;
for (h=ob.height-1; h; h--) {
for (k=i; k; k--) st << " ";
st << "*";
if (j!=i) {
for (k=j-i-1; k; k--) st << " ";
st << "*";
}
i--;
st << "\n";
}
return st;
}
int main( ) {
triangle t1(5, 5), t2(10, 10), t3(12, 12);
cout t1;
cout << endl << t2 << endl << t3;
return 0;
}
Creating extractors
Just as you can overload the << output operator, you can overload the >> input
operator. In C++, the >> is referred to as the extraction operator and a function
that overloads it is called an extractor. The reason for this is that the act of
inputting information from a stream removes (that is, extracts) data from it.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 67 -
The general for of an extractor function is:
istream &operator>>(istream &stream, class-name &ob)
{
// body of extractor
return stream;
}
Extractors returns a reference to istream, which is an input stream. The first
parameter must be a reference to an input stream. The second parameter must
be a reference to the object that is receiving input.
An extractor cannot be a member function. Although, you can perform any
operation within an extractor, it is best to limit its activity to inputting information.
// Add a friend extractor for objects of type coord
#include <iostream>
using namespace std;
class coord {
int x, y;
public:
coord( ) { x= 0; y = 0; }
coord(int i, int j) { x = i; y = j; }
friend ostream &operator<<(ostream &st, coord ob);
friend istream &operator>>(istream &st, coord &ob);
};
ostream &operator<<(ostream &st, coord ob) {
st << ob.x << ", " << ob.y << "\n";
return st;
}
istream &operator>>(istream &st, coord &ob) {
cout << "Enter co-ordinates: ";
st >> ob.x >> ob.y;
return st;
}
int main( ) {
coord a(1, 1), b(10, 23);
cout << a << b;
cin >> a;
cout << a;
return 0;
}
Here an inventory class is created that stores the name of an item, the number
on hand and its cost. The program includes both an inserter and an extractor.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 68 -
#include <iostream>
#include <cstring>
using namespace std;
class inventory {
char item[40]; // name of item
int onhand; // number on hand
double cost; // cost of item
public:
inventory(char *i, int o, double c) {
strcpy(item, i);
onhand = o;
cost = c;
}
friend ostream &operator<<(ostream &st, inventory ob);
friend istream &operator>>(istream &st,inventory &ob);
};
ostream &operator<<(ostream &st, inventory ob) {
st << ob.item << ": " << ob.onhand;
st << "on hand at " << ob.cost << "\n";
}
istream &operator>>(istream &st, inventory &ob) {
cout << "Enter item name: ";
st >> ob.item;
cout << "Enter number on hand: ";
st >> ob.onhand;
cout << "Enter cost: ";
st >> ob.cost;
return st;
}
int main( ) {
inventory ob("hammer", 4, 12.55);
cout << ob;
cin >> ob;
cout << ob;
return 0;
}
More C++ I/O Basics
The C++ I/O system is built upon two related, but different, template class
hierarchies. The first derived from the low level input I/O class called
basic_streambuf. This class supplies the basic, low level input and output
operations and provides the underlying support for the entire C++ I/O system.
Unless you are doing advance I/O programming, you will not need to use the
basic_streambuf directly. The class hierarchy that you will most commonly
working with is derived from basic_ios. This is the high-level I/O class that
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 69 -
provides formatting, error checking and status information related to stream I/O.
basic_ios is used as a base for several derived classes, including
basic_istream, basic_ostream, and basic_iostream. These classes are used
to create streams capable of input, output and input/output, respectively.
Template Class 8-bit character base class
Basic_streambuf Streambuf
Basic_ios Ios
Basic_istream Istream
Basic_ostream Ostream
Basic_iostream Iostream
Basic_fstream Fstream
Basic_ifstream Ifstream
Basic_ofstream Ofstream
The character-based names will be used, since they are the names that you will
use in your programs.
The ios class contains many member functions and variables that control or
monitor the fundamental operation of a stream. Just remember that if you include
<iostream> in your program, you will have access to these important classes.
Formatted I/O
Until now, we have only used to displayed information to the screen, the C++
default formats.
Each stream has associated with it a set of format flags that control the way
information is formatted. The ios class declares a bitmask enumeration called
fmtflags, in which the following values are defined:
These values are used to set or clear the format flags and are defined in the ios.
skipws: if set, the leading whitespaces (spaces, tabs, newlines) are discarded
when input is being performed on a stream. If clear, whitespace characters are
not discarded.
left: if set, output is left justified. If clear output is right justified by default
right: if set, output right justified.
internal: if set, a numeric value is padded to fill a field by inserting spaces
between any sign or base character.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 70 -
oct: if set, numeric values are output in octal. To return to output decimal set
dec.
hex: if set, numeric values are output in hexadecimal. Set dec to return to
decimal.
showbase: if set, causes the base of numeric values to be shown (e.g. if the
conversion base is hexadecimal, the value 1F will be displayed as 0x1F).
showpos: if set, causes a leading plus sign to be displayed before positive
values.
showpoint: causes a decimal point and trailing zeros to be displayed for all
floating-point output.
scientific: if set, floating-point numeric values are displayed using scientific
notation.
fixed: if set, floating-point values are displayed using normal notation.
unitbuf: if set, the buffer is flushed after each insertion operation.
boolalpha: Booleans can be input and output using keyword true and false.
Since, it is common to refer to the oct, hex and dec fields, they can be
collectively referred to as basefield. Similarly, left, right and internal fields can
be referred to as adjustfield. Finally, the scientific and fixed can be referred as
floatfield.
To set a format flag, use the setf( ) function. This function is a member of ios. Its
most common form is:
fmtflags setf(fmtflags flags);
This function returns the previous settings of the format flags and turns on those
flags specified by flags (other flags are unaffected). For example, to turn on the
showpos flag you can use this statement:
stream.setf(ios::showpos);
Here stream is the stream you want to affect (e.g. cout, cin, ...).
It is possible to set more than one flag, e.g.
cout.setf(ios::showbase ios::hex);
Remember the format flags are defined within the ios class, you must access
their values by using ios and the scope resolution operator.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 71 -
The complement of setf( ) is unsetf( ). This member function of ios clears one or
more format flags. Its most common prototype is,
void unsetf(fmtflags flags);
Flags specified by flags are cleared.
The member function that only returns the current format settings is flags( ). Its
prototype is, fmtflags flags( );
Here is a simple example that shows how to set several of the format flags
#include <iostream>
using namespace std;
int main( ) {
// display the default settings
cout << 123.23 << " hello " << 100 << "\n";
cout << 10 << " " << -10 << "\n";
cout << 100.0 << "\n\n";
// now change formats
cout.unsetf(ios::dec);
// not required by all compilers
cout.setf(ios::hex ios::scientific);
cout << 123.23 << " hello " << 100 << "\n";
cout.setf(ios::showpos);
cout << 10 << " " << -10 << "\n";
cout.setf(ios::showpoint ios::fixed);
cout << 100.0;
return 0;
}
Using width( ), precision( ), and fill( )
In addition to the formatting flags, there are three member functions defined by
the ios class that set these format parameters: the field width, the precision and
the fill character, respectively.
By default, when a value is output, it occupies only as much space as the
number of characters it takes to display it. However, you can specify a minimum
field width by using the width( ) function. Its prototype is
streamsize width(streamsize w);
Here w becomes the field width, and the previous field width is returned. The
streamsize type is defined by <iostream> as some form of integer. In some
implementations, each time an output is performed, the field width returns to its
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 72 -
default setting, so it might be necessary to set the minimum field width before
each output statement.
After you set a minimum field width, when a value uses less than the specified
width, the field is padded with the current fill character (the space, by default) so
that the field width is reached. However, keep in mind that if the size of the output
value exceeds the minimum field width, the field will be overrun. No value is
truncated.
By default, six digits of precision are used. You can set this number by using the
precision( ) function. Its prototype is
streamsize precision(streamsize p);
Here the precision is set to p and the old value is returned.
By default, when a field needs to be filled, it is filled with spaces. You can specify
the fill character by using the fill( ) function. Its prototype is
char fill(char ch);
After a call to fill( ), ch becomes the new fill character, and the old one is
returned.
Here an example that illustrates the format functions
#include <iostream>
using namespace std;
int main( ) {
cout.width(10); // set minimum field width
cout << "hello "<<"\n"; // right justify be default
cout.fill('%'); // set fill character
cout.width(10); // set width
cout << "hello"<<"\n"; // right justify by default
cout.setf(ios::left); // left justify
cout.width(10); // set width
cout << "hello"<<"\n"; // output left justified
cout.width(10); // set width
cout.precision(10); //set 10 digits of precision
cout << 123.234567 << "\n";
cout.width(10); // set width
cout.precision(6); // set 6 digits of precision
cout << 123.234567 << "\n";
return 0;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 73 -
This program displays the following output:
hello
%%%%%hello
hello%%%%%
123.234567
123.235%%%
Using I/O manipulators
There is a second way that you can format information using C++ I/O system.
The method uses special functions called I/O manipulators. I/O manipulators are,
in some situations, easier to use than the ios format flags and functions.
I/O manipulators are special I/O format functions that can occur within an I/O
statement, instead of separate from it. The standard manipulators are shown in
the next table. Many manipulators parallel member functions of the ios class.
Many of the manipulators shown in the table were added recently to Standard
C++ and will be supported only by modern compiler.
To access manipulators that takes parameters, such as setw( ), you must
include <iomanip> in your program. This is not necessary when you are using
manipulator that does not require argument.
Keep in mind that an I/O manipulator affects only the stream of which the I/O
expression is a part. I/O manipulators do not affect all streams currently opened
for use.
The following program demonstrates several of the I/O manipulators:
#include <iostream>
#include <iomanip>
using namespace std;
int main( ) {
cout << hex << 100 << endl;
cout << oct << 10 << endl;
cout << setfill('X') << setw(10);
cout << 100 << " hi " << endl;
return 0;
}
This program displays the following:
64
13
XXXXXXX144 hi
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 74 -
ADVANCE C++ I/O
Creating your own manipulators
In addition to overloading inserters and extractors, you can further customise I/O
system by creating manipulator functions.
As you know there are two basic types of manipulators: those that operate on
input streams and those that operate on output streams. In addition to these two
broad categories, there is a secondary division: those manipulators that take an
argument and that that do not.
Writing you own parameterless manipulators is quite easy.
All parameterless manipulator output functions have this form:
ostream &manip-name(ostream &stream)
{
// your code
return stream;
}
Here manip-name is the name of the manipulator and stream is a reference to
the invoking stream. A reference to the stream is returned. This is necessary if a
manipulator is used as part of a larger I/O expression.
All parameterless input manipulator functions have the form:
istream &manip-name(istream &stream)
{
// your code
return stream;
}
Remember it is crucial that your manipulator receives a reference to the invoking
stream. If this is not done, your manipulators cannot be used in a sequence of
input or output operations.
// A simple example
#include <iostream>
using namespace std;
ostream &setup(ostream &stream) {
stream.width(10);
stream.precision(4);
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 75 -
stream.fill('*');
return stream;
}
int main( ) {
cout << setup << 123.123456;
return 0;
}
File I/O basics
File I/O and console I/O are closely related. In fact, the same class hierarchy that
supports console I/O also supports the file I/O.
To perform file I/O, you must include <fstream> in your program. It defines
several classes, including ifstream, ofstream and fstream. These classes are
derived from ios, so ifstream, ofstream and fstream have access to all
operations defined by ios.
In C++, a file is opened by linking it to a stream. There are three types of
streams: input, output and input/output. Before you can open a file, you must first
obtain a stream.
To create an input stream, declare an object of type ifstream.
To create an output stream, declare an object of type ofstream.
To create an input/output stream, declare an object of type fstream.
For example, this fragment creates one input stream, one output stream and one
stream capable of both input and output:
ifstream in: // input;
ofstream out; // output;
fstream io; // input ad output
Once you have created a stream, one way to associate it with a file is by using
the function open( ). This function is a member function of each of the three
stream classes. The prototype for each is shown here:
void ifstream::open(const char *filename,
openmode mode=ios::in);
void ofstream::open(const char *filename,
openmode mode=ios::out ios::trunc);
void fstream::open(const char *filename,
openmode mode=ios::in ios::out);
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 76 -
Here filename is the name of the file, which can include a path specifier. The
value of the mode determines how the file is opened. It must be a value of type
openmode, which is an enumeration defined by ios that contains the following
value:
ios::app
ios::ate
ios::binary
ios::in
ios::out
ios::trunc
You can combine two or more of these values.
ios::app: causes all output to that file to be appended to the end. Only with files
capable of output.
ios::ate: causes a seek to the end of the file to occur when the file is opened.
ios::out: specify that the file is capable of output.
ios::in: specify that the file is capable of input.
ios::binary: causes the file to be opened in binary mode. By default, all files are
opened in text mode. In text mode, various character translations might take
place, such as carriage return/linefeed sequences being converted into
newlines. However, when a file is opened in binary mode, no such character
translations will occur. Keep in mind that any file, whether it contains formatted
text or raw data, can be opened in either binary or text mode. The only
difference is whether character translations take place.
ios::trunc: causes the contents of a pre-existing file by the same name to be
destroyed and the file to be truncated to zero length. When you create an
output stream using ofstream, any pre-existing file is automatically truncated.
The following fragment opens an output file called test:
ofstream mystream;
mystream.open("test");
Since the mode parameter to open( ) defaults to a value appropriate to the type
of stream being opened, there is no need to specify its value in the preceding
example.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 77 -
If open( ) fails, the stream will evaluate to false when used in a Boolean
expression. You can make sure of this fact to co,firm that the open operation
succeeded by using a statement like this:
if (!mystream) {
cout << "Cannot open file.\n";
// handle error
}
In general, you should always check the result of a call to open( ) before
attempting to access the file.
You can also check to see if you have successfully opened a file by using the
is_open( ) function, which is a member of fstream, ifstream and ofstream. It
has a prototype as:
bool is_open( );
It returns true if the stream is linked to an open file and false otherwise. For
example, the following check if mystream is currently opened:
if (!mystream.is_open())
cout << "File is not open.\n";
// ...
Although it is entirely proper to open a file by using the open( ) function, most of
the time you will not do so because the ifstream, ofstream and fstream classes
have constructor functions that automatically open files. The constructor
functions have the same parameters and defaults as the open( ) function.
Therefore, the most common way you will see a file opened is shown in this
example:
ifstream mystream("myfile"); // open a file
Whether you use a constructor function to open a file or an explicit call to open(
), you will want to confirm that the file has been opened by testing the value of
the stream.
To close a file use the member function close( ). For example, to close the file
linked to a stream called mystream, use this statement,
mystream.close( );
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 78 -
The close( ) function takes no parameters and returns no value.
You can detect when the end of an input file has been reached by using the eof(
) member function of ios. It has a prototype:
bool eof( );
It returns true when the end of the file has been encountered and false otherwise.
Once a file has been opened, it is very easy to read textual data from it or writ
formatted textual data to it. simply use the << and >> operators the same way
you do when performing console I/O, except that instead of using cin and cout,
substitute a stream that is linked to a file.
A file produced by using << is a formatted text file, and any file read by >> must
be a formatted text file.
Here an example that creates an output file, writes information to it, closes the
file and opens it again as an input file, and reads in the information:
#include <iostream>
#include <fstream>
using namespace std;
int main( ) {
ifstream fout("test"); // create output file
if (!fout) {
cout << "Cannot open output file.\n";
return 1;
}
fout << "Hello"!\n";
fout << 100 << " " << hex << 100 << endl;
fout.close( );
ifstream fin("test"); // open input file
if (!fin) {
cout << "Cannot open input file.\n";
return 1;
}
char str[80];
int i;
fin >> str >> i;
cout << str << " " << i << endl;
fin.close( );
return 0;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 79 -
Another example that reads strings entered at the keyboard and writes them to
disk. To use the program, specify the name of the output file on the command
line.
#include <iostream>
#include <fstream>
using namespace std;
int main( int argc, char *argv[]) {
if (argc!=2) {
cout << "Usage: WRITE <filename>\n";
return 1;
}
ofstream out(arg[1]); // output file
if (!out) {
cout << "Cannot open output file.\n";
return 1;
}
char str[80];
cout << "Write strings to disk, '$' to stop\n";
do {
cout << ": ";
cin >> str;
out << str << endl;
} while (*str !='$');
out.close( );
return 0;
}
In Standard C++ the open( ) does not support the parameter that specified the
file's protection mode that is supported by old C++.
When using an old C++ library, you must explicitly specify both the ios::in and
the ios::out mode values.
Finally, in the old I/O system, the mode parameter could also include either
ios::nocreate or ios::moreplace. These values are not supported by Standard
C++.
Unformatted, binary I/O
C++ supports a wide range of unformatted file I/O functions. The unformatted
functions give you detailed control over how files are written and read.
The lowest-level unformatted I/O functions are get( ) and put( ). You can read a
byte by using get( ) and write a byte by using put( ). These functions are
member functions of all input and output stream classes, respectively. The get( )
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 80 -
function has many forms, but the most commonly used version is shown here,
along with put( ):
istream &get(char &ch);
ostream &put(char &ch);
To read and write blocks of data, use read( ) and write( ) functions, which are
also member functions of the input and output stream classes, respectively. Their
prototypes are:
istream &read(char *buf, streamsize num);
ostream &write(const char *buf, streamsize num);
The read( ) function reads num bytes from the stream and puts them in the buffer
pointed to by buf. The write( ) function writes num bytes to the associated stream
from the buffer pointed by buf.
The streamsize type is some form of integer. An object of type streamsize is
capable of holding the largest number of bytes that will be transferred in any I/O
operation.
If the end of file is reached before num characters have been read, read( ) stops
and the buffer contains as many characters as were available. You can find out
how many characters have been read by using the member function gcount( ),
which has this prototype:
streamsize gcount( );
It returns the number of characters read by the last unformatted input operation.
When you are using the unformatted file functions, most often you will open a file
for binary rather than text operations. The reason for this is easy to understand:
specifying ios::binary prevents any character translations from occurring. This is
important when the binary representations of data such as integers, floats and
pointers are stored in the file. However, it is perfectly acceptable to use the
unformatted functions on a file opened in text mode, as long as that the file
actually contains only text. But remember, some character translation may occur.
Here some very simple examples:
// Display the content of any file on screen
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[]) {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 81 -
char ch;
if (argc!=2) {
cout << "Usage: PR <filename>\n";
return 1;
}
ifstream in(arg[1], ios::in | ios::binary);
if (!in) {
cout << "Cannot open file\n";
return 1;
}
while (!in.eof( )) {
in.get(ch);
cout << ch;
}
in.close( );
return 0;
}
// Write character to a file until user enters $ sign
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[]) {
char ch;
if (argc!=2) {
cout << "Usage: WRITE <filename>\n";
return 1;
}
ofstream out(arg[1], ios::out | ios::binary);
if (!out) {
cout << "Cannot open file\n";
return 1;
}
cout << "Enter a $ to stop\n";
do {
cout << ": ";
cin.get(ch);
out.put(ch);
} while (ch!='$')
out.close( );
return 0;
}
Notice that the program uses get( ) to read characters from cin. This prevents
leading spaces from being discarded.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 82 -
// Use write( ) to write a double and a string to a file called test
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
int main( ) {
ofstream out("test", ios::out | ios::binary);
if (!out) {
cout << "Cannot open file\n";
return 1;
}
double num = 100.45;
char str[] = "This a test";
out.write((char *) &num, sizeof(double));
out.write(str, strlen(str));
out.close( );
return 0;
}
Note that the type cast (char *) inside the call to write( ) is necessary when
outputting a buffer that is not defined as a character array. Because of C++
strong type checking, a pointer of one type will not automatically be converted
into a pointer of another type. The same applies to read( ).
// Use read( ) to read a file by the previous program
#include <iostream>
#include <fstream>
using namespace std;
int main( ) {
ifstream in("test", ios::in | ios::binary);
if (!in) {
cout << "Cannot open input file\n";
return 1;
}
double num;
char str[80];
in.read((char *) &num, sizeof(double));
in.read(str, 14);
str[14] = '\0'; // null terminate str
cout << num << " " << str;
in.close( );
return 0;
}
More unformatted I/O functions
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 83 -
In addition to the form shown earlier, there are several different ways in which the
get( ) function is overloaded. The prototypes for the three most commonly used
overloaded forms are:
istream &get(char *buf, streamsize num);
istream &get(char *buf, streamsize num, char delim);
int get( );
The first form reads characters into the array pointed to by buf, until either num-l
characters have been read, a new line is found, or the end of the file has been
encountered. The array pointed to by buf, will be null terminated by get( ). If the
newline character is encountered in the input stream, it is not extracted. Instead,
it remains in the stream until the next input operation.
The second form reads characters into the array pointed to by buf, until either
num-l characters have been read, the character specified by delim has been
found, or the end of file has been encountered. The array pointed to by buf, will
be null terminated by get( ). If the delimiter character is encountered in the input
stream, it is not extracted. Instead, it remains in the stream until the next input
operation.
The third overloaded form of get( ) returns the next character from the stream. It
returns EOF if the end of file is encountered. This form of get( ) is similar to the C
getc( ) function.
Another function that performs input is getline( ). It is a member function of each
input stream class. Its prototypes are:
istream &getline(char *buf, streamsize num);
istream &getline(char *buf, streamsize num, char delim);
The first form reads characters into the array pointed to by buf until either num-l
characters have been read, a newline character is found, or the end of the file
has been encountered. The array pointed to by buf will be null terminated by
getline( ). If the newline character is encountered in the input stream, it is
extracted, but it is not put into buf.
The second form reads characters into the array pointed to by buf until either
num-l characters have been read, the character specified by delim has been
found, or the end of file has been encountered. The array pointed to by buf will
be null terminated by getline( ). If the newline character is encountered in the
input stream, it is extracted, but it is not put into buf.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 84 -
The difference between get( ) and getline( ) is that getline( ) reads and removes
the delimiter from the input stream; get( ) does not.
You can obtain the next character in the input stream without removing it from
that stream by using peek( ). This function is a member function of the input
stream classes and its prototype is
int peek( );
It returns the next character in the stream; it returns EOF if the end of file is
encountered.
You can return the last character read from a stream to that stream by using
putback( ), which is a member function of the input stream classes. Its prototype
is as shown:
istream &putback(char c);
where c is the last character read.
When output is performed, data are not immediately written to the physical
device linked to the stream. Instead, information is stored in an internal buffer
until the buffer is full. Only, then are the contents of that buffer written to disk.
However, you can force the information to be physically written to disk before the
buffer is full by calling flush( ). flush( ) is a member function of the output stream
classes and has a prototype:
ostream &flush( );
Random access
In C++ I/O system, you perform random access by using the seekg( ) and
seekp( ) functions, which are members of the input and output stream classes,
respectively. Their most common forms are:
istream &seekg(off_type offset, seekdir origin);
ostream &seekp(off_type offset, seekdir origin);
Here off_type is an integer type defined by the ios that is capable of containing
the largest valid value that offset can have. seekdir is an enumeration defined by
ios that has these values:
Value Meaning
Ios::beg Seek for beginning
Ios::cur Seek for current location
Ios::end Seek for end
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 85 -
The C++ I/O system manages two pointers associated with a file. One is the get
pointer, which specifies where in the file the next input operation will occur. The
other is the put pointer, which specifies where in the file the next output operation
will occur. Each time an input or output operation takes place, the appropriate
pointer is automatically sequentially Advanced. However, by using the seekg( )
and the seekp( ) functions, it is possible to access the file in a nonsequential
fashion.
The seekg( ) function moves the associated file's current get pointer offset
number of bytes from the specified origin.
The seekp( ) function moves the associated file's current put pointer offset
number of bytes from the specified origin.
In general, files that will be accessed via seekg( ) and seekp( ) should be
opened for binary operations. This prevents character translations from
occurring, which may affect the apparent position of an item within the file.
You can determine the current position of each file pointer by using these
member functions:
pos_type tellg( );
pos_type tellp( );
Here pos_type is an integer type defined by the ios that is capable of holding
the largest value that defines a file position.
There are overloaded versions of seekg( ) and seekp( ) that move the file
pointers to the location specified by the returned value of tellg( ) and tellp( ).
Their prototypes are:
istream &seekg(pos_type position);
ostream &seekp(pos_type position);
The following program allows you to change a specific character in a file. Specify
a file name on the command line, filled by the number of the byte in the file you
want to change, followed by the new character. The file is opened for read/write
operations.
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main(int argc, char *argv[ ] ) {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 86 -
if (argc!=4) {
cout <<"Usage: CHANGE <filename> <byte> <char>\n;
return 1;
}
fstream out(argv[1], ios::out | ios::binary);
if (!out) {
cout << "Cannot open file\n";
return 1;
}
out.seekp(atoi(argv[2]),ios::beg);
out.put(*argv[3]);
out.close( );
return 0;
}
The next program position the get pointer into the middle of the file and then
displays the contents of that file from that point.
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main(int argc, char *argv[ ] ) {
char ch;
if (argc!=3) {
cout <<"Usage: LOCATE <filename> <loc>\n";
return 1;
}
istream in(argv[1], ios::in | ios::binary);
if (!in) {
cout << "Cannot open file\n";
return 1;
}
in.seekg(atoi(argv[2]),ios::beg);
while (!in.eof( )) {
in.get(ch);
cout << ch;
}
in.close( );
return 0;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 87 -
Checking the I/O status
The C++ I/O system maintains status information about the outcome of each I/O
operation. The current I/O status of an I/O stream is described in an object of
type iostate, which is an enumeration defined by ios that includes the members:
Name Meaning
Goodbit No errors occurred
Eofbit End of file has been encountered
Failbit A non fatal I/O eoor has occurred
Badbit A fatal error has occurred
For older compilers, the I/O status flags are held in an int rather than an object of
type iostate.
There are two ways in which you can obtain the I/O status information. First, you
call the rdstate( ) function, which is a member of ios. It has this prototype:
iostate rdstate( );
It returns the current status of the error flags. rdstate( ) returns goodbit when no
error has occurred. Otherwise, an error flag is returned.
The other way you can determine whether an error has occurred is by using one
of these ios member functions:
bool bad( );
bool eof( );
bool fail( );
bool good( );
The eof( ) function was discussed earlier. The bad( ) function returns true if
badbit is set. The fail( ) function returns true if failbit is set. The good( ) function
returns true if there are no errors. Otherwise, they return false.
Once an error has occurred, it might need to be cleared before your program
continues. To do this, use the ios member function clear( ), whose prototype is
void clear(iostate flags = ios::goodbit);
If flags is goodbit (as it is by default), all error flags are cleared. Otherwise, set
flags to the settings you desire.
Customised I/O and files
As stated in the previously, overloaded inserters and extractors, as well as I/O
manipulators, can be used with any stream as long as they are written in a
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 88 -
general manner. If you 'hard-code' a specific stream into an I/O function, its use
is, of course, limited to only that stream. This is why you were recommended to
generalise your I/O functions whenever possible.
In the following program, the coord class overloads the << and >> operators.
Notice you can use the operator functions to write both to the screen and to file.
#include <iostream>
#include <fstream>
using namespace std;
class coord {
int x, y;
public:
coord(int i, int j) { x=i; y=j; }
friend ostream &operator<<(ostream &stream,
coord ob);
friend istream &operator>>(istream &stream,
coord &ob);
};
ostream &operator<<(ostream &stream, coord ob) {
stream << ob.x << " " << ob.y << "\n";
return stream;
}
istream &operator>>(istream &stream, coord &ob) {
stream >> ob.x >> ob.y;
return stream;
}
int main( ) {
coord o1(1, 2) o2(3, 4);
ofstream out( "test" );
if( !out ) {
cout << "Cannot open file\n";
return 1;
}
cout << o1 << o2;
out.close( );
ifstream in( "test" );
if ( !in ) {
cout << "Cannot open file\n";
return 1;
}
coord o3(0, 0), o4(0, 0);
in >> o3 >> o4;
cout << o3 << o4;
in.close( );
return 0;
}
Remember that all the I/O manipulators can be used with files.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 89 -
TEMPLATES AND EXCEPTION HANDLING
Two of C++ most important high-level features are the templates and exception
handling. They are supported by all modern compilers.
Using templates, it is possible to create generic functions and classes. In generic
functions or classes, the type of data that is operated upon is specified as a
parameter. This allows you to use one function or class with several different
types of data without having to explicitly recode a specific version for each type
of data type. Thus, templates allow you to create reusable code.
Exception handling is a subsystem of C++ that allows you to handle errors that
occur at run time in a structured and controlled manner. With C++ exception
handling, you program can automatically invoke an error handling routine when
an error occurs. The principle advantage of exception handling is that it
automates much of the error handling code that previously had to be coded 'by
hand' in any large program. The proper use of exception handling helps you to
create resilient code.
Generic functions
A generic function defines a general set of operations that will be applied to
various types of data. A generic function has the type of data that it will operate
upon passed to it as a parameter. Using this mechanism, the same general
procedure can be applied to a wide range of data. For example the Quicksort
algorithm is the same whether it is applied to an array of integers or an array of
floats. It is just that the type of data being sorted is different. By creating a
generic function, you can define, independent of any data, the nature of the
algorithm. Once this is done, the compiler automatically generates the correct
code for the type of data that is actually used when you execute the function. In
essence, when you create a generic function you are creating a function that can
automatically overload itself.
A generic function is created by using the keyword template. The general form of
a template function definition is as
template <class Ttype> ret-type-name(parameter list)
{
// body of function
}
cout << "Original x, y: " << x << y <<endl;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 90 -
// Function template example
#include <iostream>
using namespace std;
// This is a function template
template <class X> void swapargs(X &a, X &b) {
X temp;
temp = a;
a = b;
b = temp;
}
int main( ) {
int i=10, j=20;
float x=10.1, y=23.3;
Here Ttype is a placeholder name for a data type used by the function. This
name can be used within the function definition. However, it is only a
placeholder; the compiler will automatically replace this placeholder with an
actual data type when it creates a specific version of the function.
Although the use of the keyword class to specify a generic type in a template
declaration is traditional, you can also use the keyword typename.
The following example creates a generic function the swaps the values of the two
variables it is called with.
cout << "Original i, j: " << i << j <<endl;
swapargs(i, j); // swap integers
swapargs(x, y); // swap floats
cout << "Swapped i, j: " << i << j <<endl;
cout << "Swapped x, y: " << x << y <<endl;
return 0;
}
The keyword template is used to define a generic function. The line
template <class X> void swapargs(X &a, X &b) tells the compiler two things:
that a template is being creates and that a generic function is beginning.. Here X
is a generic type that is used as a placeholder. After the template the template
portion, the function swapargs( ) is declared, using X as a data type of the
values that will be swapped. In main( ), the swapargs( ) function is called using
two different types of data: integers and floats. Because swapargs( ) is a
generic function, the compiler automatically creates two versions of swapargs( );
one that will exchange integer values and one that will exchange floating-point
values.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 91 -
Here are some other terms that are sometimes used when templates are
discussed and that you might encounter in other C++ literature. First, a generic
function is also called a template function. When the compiler creates a specific
version of the function, it is said to have created 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.
The template portion of a generic function does not have to be on the same line
as the function's name. For example, the following is also a 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 statement can
occur between the template statement and the start of the generic function
definition.
// 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 rest of the
function definition.
As mentioned before, instead of using the keyword class, you can use the keyword
typename:
// Use typename
template <typename X> void swapargs(X &a, X &b) {
X temp;
temp = a;
a = b;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 92 -
b = temp;
}
You can define more than one generic data type with the template statement. Here an
example that creates a generic function that has two generic types:
#include <iostream>
using namespace std;
template <class type1, type2>
void myfunct(type1 x, type2 y) {
cout << x << " " << y << endl;
}
int main( ) {
myfunct(10, "hi");
myfunct(0.23, 10L);
return 0;
}
Generic functions are similar to overloaded functions except the they are more
restrictive. When functions are overloaded, you can have different actions
performed with the body of each function. But generic functions must perform the
same general action for all versions.
Even though a template function overloads itself as needed, you can explicitly
overload one, too. If you overload a generic function, that overloaded function
overrides (or 'hides') the generic function relative to that specific version.
// Function template example
#include <iostream>
using namespace std;
// Overriding a template function
template <class X> void swapargs(X &a, X &b) {
X temp;
temp = a;
a = b;
b = temp;
}
// This overrides the generic version of swapargs( )
void swapargs(int a, int b) {
cout << "this is inside swapargs(int, int)\n";
}
int main( ) {
int i=10, j=20;
float x=10.1, y=23.3;
cout << "Original i, j: " << i << j <<endl;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 93 -
cout << "Original x, y: " << x << y <<endl;
swapargs(i, j); // calls overloaded swapargs( )
swapargs(x, y); // swap floats
cout << "Swapped i, j: " << i << j <<endl;
cout << "Swapped x, y: " << x << y <<endl;
return 0;
}
Manual overloading of template, as shown in this example, allows you to tailor a
version of a generic function to accommodate a special situation. However, in
general, if you need to have different versions of a function for different data
types, you should use overloaded functions rather than templates.
Generic classes
You can also define generic classes. When you do this, you create a class that
defines all algorithms used by that class, but the actual type of data being
manipulated will be specified as a parameter when objects of that class are
created.
Generic classes are useful when a class contains generalisable logic. For
example, the same algorithm that maintains a queue of integers will also work for
a queue of characters. Also, the same mechanism that maintains a linked list of
mailing addresses will also maintain a linked of auto part information. By using a
generic class, you can create a class that will maintain a queue, a linked list, and
so on for any type of data. The compiler will automatically generate the correct
type of object based upon the type you specify when the object is created.
The general form of a generic class declaration is as shown
template <class Ttype> class class-name {
.
.
.
};
Here Ttype is the placeholder type name that will be specified when a class is
instantiated. If necessary, you can define more than one generic data type by
using a comma-separated list.
Once you have created a generic class, you create a specific instance of that
class by using the using the following general form:
class-name<type> ob;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 94 -
Here type is the type name of the data that the class will bee operating upon.
Member functions of a generic class are, themselves, automatically generic.
They need not be explicitly specified as such using template.
C++ provides a library that is built upon template classes. This library is usually
referred to as the Standard Template Library, or STL for short. It provides generic
versions of the most commonly used algorithms and data structures. If you want
to use STL effectively, you will need a solid understanding of template classes
and their syntax.
// Simple generic linked list class.
#include <iostream>
using namespace std;
template <class data_t> class list {
data_t data;
list *next;
public:
list(data_t d);
void add(list *node) { node- >next = this; next =0 }
list *getnext( ) { return next; }
data_t getdata( ) { return data; }
};
template <data_t> list<data_t>::list(data_t d) {
data = d;
next = 0;
}
int main( ) {
list<char> start('a');
list<char> *p, *last;
int i;
// build a list
last = &start;
for (i=0; i<26; i++) {
p = new list<char> ('a'+ i);
p- >add(last);
last = p;
}
// follow the list
p = &start;
while (p) {
cout << p- >getdata( );
p = p- >getnext( );
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 95 -
return 0;
}
As you can see, the actual type of data stored by the list is generic in the class
declaration. It is not until an object of the list is declared that the actual data type
is determined. In this example, objects and pointers are created inside main( ),
that specify that the data type of the list will be char. Pay attention to this
declaration
list<char> start('a');
Note how the desired type is passed inside angle brackets.
You could create another object that stores integers by using:
list<int> start(1);
You could also use list to store data types that you create. For example, to store
address information, you could use this structure:
struct addr {
char name[40];
char street[40];
char city[40];
char postcode[40];
}
Then to use list to generate objects that will store objects of type addr, use a
declaration like this (assuming that structvar contains a valid addr structure):
list<addr> obj(structvar);
Here is another example, the stack class is a template class. It can be used to
store any type of object. In the example a character stack and a floating-point
stack are created.
#include <iostream>
using namespace std;
#define SIZE 10
// Create a generic stack class
template <class StackType> class stack {
StackType stck[SIZE]; // hold the stack
int tos; // index of top of stack
public:
void init( ) { tos = 0; } // initialise stack
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 96 -
void push(StackType ch); // 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 <StackType>
StackType stack<StackType>::pop( ) {
if (tos==0) {
cout << "Stack is empty\n";
return O; // return null on empty stack
}
tos--;
return stck[tos];
}
int main( ) {
// Demonstrate character stack
stack <char> s1, s2; // create two stacks
int i;
// initialise the stacks
s1.init( );
s2.init( );
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;
// initialise stacks
ds1.init( );
ds2.init( );
ds1.push(1.1);
ds2.push(2.2);
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 97 -
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 generic functions, a template class can have more than one generic data
type. Simply declare all the data types required by the class in a comma-
separated list within the template specification.
// This example uses two generic data type
// in a class definition
#include <iostream>
using namespace std;
template <class Type1, class Type1> class myclass {
Type1 i;
Type2 j;
public:
myclass(Type1 a, Type2 b) { i = a; j = b; }
void show( ) { cout << i << " " << j << "\n";
};
int main( ) {
myclass<int, double> ob1(10, 0.23);
myclass<char, char *> ob2('X', "This a test");
ob1.show( ); // show int, double
ob2.show( ); // show char, char *
return 0;
}
For both cases, the compiler automatically generates the appropriate data and
functions to accommodate the way the objects are created.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 98 -
Exception Handling
C++ provides a build-in error handling mechanism that is called exception
handling. Using exception handling, you can more easily manage and respond to
run-time errors. C++ exception handling is built upon three keywords: try, catch
and throw. In the most general terms, program statements that you want to
monitor for exceptions are contained in a try block. If an exception (i.e. an error)
occurs within the try block, it is thrown (using throw). The exception is caught,
using catch, and processed.
As stated, any statement that throws an exception must have been executed
from within a try block (a function called from within a try block can also throw
exception.) Any exception must be caught by a catch statement that immediately
follows the try statement that throws the exception. The general form if try and
catch are as shown:
try {
// try block
}
catch(type1 arg) {
// catch block
}
catch(type2 arg) {
// catch block
}
...
catch(typeN arg) {
// catch block
}
The try block must contain the portion of your program that you want to monitor
for errors. This can be as specific as monitoring a few statements within one
function or as all encompassing as enclosing the main( ) function code within the
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. The catch statement that 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, that catch statement is executed (all other are bypassed). When
an exception is caught, arg will receive its value. If you don't need access to the
exception itself, specify only type in the catch clause (arg is optional). Any type
of data can be caught, including classes that you create. In fact, class types are
frequently used as exceptions.
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 99 -
The general form of a throw statement is
throw exception;
throw must be executed either from within the try block or from any function that
the code within the block calls (directly or indirectly). exception is the value
thrown.
If you throw an exception for which there is no applicable catch statement, an
abnormal program termination might occur. If your compiler complies with
Standard C++, throwing an unhandled exception causes the standard library
function terminate( ) to be invoked. By default, terminate( ) calls abort( ) to stop
your program, but you can specify your own termination handler, if you like. You
will need to refer to your compiler's library reference for details.
// A simple exception handling example
#include <iostream>
using namespace std;
int main( ) {
cout << "Start\n";
try { // start a try block
cout << "Inside try block\n";
throw 10; // throw an error
cout << "This will not execute\n";
}
catch( int i) { // catch an error
cout << "Caught One! Number is: ";
cout << i << "\n";
}
cout << "end";
return 0;
}
This program displays the following:
start
Inside try block
Caught One! Number is: 10
end
As you can see, once an exception has been thrown, control passes to the catch
expression and the try block is terminated. That is catch is not called. Rather,
program execution is transferred to it. (The stack is automatically reset as
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 100 -
needed to accomplish this) Thus, the cout statement following the throw will
never execute.
After the catch statement executes, program control continues with the
statements following the catch. Often, however, a catch block will end with a call
to exit( ) or abort( ), or some other function that causes program termination
because exception handling is frequently used to handle catastrophic errors.
Remember that the type of the exception must match the type specified in a
catch statement.
An exception can be thrown from a statement that is outside the try block as long
as the statement is within a function that is called from within the try block.
// 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 one! Number is: ";
cout << i << "\n";
}
cout << "end";
return 0;
}
This program displays:
start
Inside try block
Inside Xtest, test is: 0
Inside Xtest, test is: 1
Caught one! Number is: 1
end
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 101 -
A try block can be localised in a function. When this is the case, each time the
function is entered, the exception handling relative to that function is reset. Here
is an example:
#include <iostream>
using namespace std;
// A try/catch can be handle inside a function
// other than main( ).
void Xhandler(int test) {
try {
if (test) throw test;
}
catch(int i) {
cout << "Caught one! Ex. #: " << i << "\n";
}
}
int main( ) {
cout << "start";
Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout << "end";
return 0;
}
This program displays:
start
Caught one! Ex. #: 1
Caught one! Ex. #: 2
Caught one! Ex. #: 3
End
As you can see, three exceptions are thrown. After each exception, the function
returns. When the function is called again, the exception handling is reset.
As stated before, you can have more than one catch associated with a try. In
fact, it is common to do so. However each catch must catch a different type of
exception. For example,
#include <iostream>
using namespace std;
// Different type of exception can be caught.
void Xhandler(int test) {
try {
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 102 -
if (test) throw test;
else throw "Value is zero";
}
catch(int i) {
cout << "Caught one! Ex. #: " << i << "\n";
}
catch(char *str) {
cout << "Caught a string: " << str << "\n";
}
}
int main( ) {
cout << "start";
Xhandler(1);
Xhandler(2);
Xhandler(0);
Xhandler(3);
cout << "end";
return 0;
}
This program displays:
start
Caught one! Ex. #: 1
Caught one! Ex. #: 2
Caught one! Ex. #: 3
end
More about exception handling
In some circumstances you will want an exception handler to catch all exceptions
instead of just a certain type. Simply use this form of catch:
catch(...) {
// process all exception
}
Also, you can control what type of exceptions a function can throw outside itself.
In fact, you can also prevent a function from throwing any exceptions
whatsoever. To apply these restrictions, you must add a throw clause to the
function definition. The general form is as follows,
ret-type-func-name(arg-list) throw(type-list)
{
// ....
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 103 -
}
Here only those data types contained in the comma-separated list type-list may
be thrown by the function. Throwing any other type of expression will cause the
program termination. If you don't want a function to be able to throw any
exceptions, use an empty list.
If your compiler complies with Standard C++, when a function attempts to throw a
disallowed exception the standard library function unexpected( ) is called. By
default, this causes the terminate( ) function to be called, which causes
abnormal program termination. However, you can specify your own termination
handler, if you like. You will need to refer to your compiler documentation for
directions.
If you wish to rethrow an exception from within an exception handler, you can do
so by simply calling thrown, by itself, with no exception. This causes the current
exception to be passed on to an outer try/catch sequence.
// 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;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 104 -
This program displays:
start
Caught one!
Caught one!
Caught one!
end
One very good use for catch(...) is as last catch of a cluster of catches.
// Uses catch(...) as default
#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(int i) { // catch an int exception
cout << "Caught " << i << "\n";
}
catch(...) { // catch all other exceptions
cout << "Caught one!\n";
}
}
int main( ) {
cout << "start\n";
Xhandler(0);
Xhandler(1);
Xhandler(2);
cout << "end";
return 0;
}
This program displays:
start
Caught 0
Caught one!
Caught one!
Caught one!
end
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 105 -
The following program shows how to restrict the types of exceptions that can be
thrown from a function:
// Restricting function throw types
#include <iostream>
using namespace std;
// can only throw ints, chars and doubles
void Xhandler(int test) throw(int, char, double) {
if (test==0) throw test; // throw int
if (test==1) throw 'a'; // throw char
if (test==2) throw 123.23;// throw double
}
int main( ) {
cout << "start\n";
try {
Xhandler(0); // also try passing 1 and
// 2 to Xhandler( )
}
catch(int i) {
cout << "Caught int\n";
}
catch(char c) {
cout << "Caught char\n";
}
catch(double c) {
cout << "Caught double\n";
}
cout << "end";
return 0;
}
Finally, here is an example of rethrowing an exception. An exception can only be
rethrown from within a catch block. When you rethrow an exception, it will not be
recaught by the same catch statement. It will propagate to an outer catch
statement.
// Rethrowing an exception
#include <iostream>
using namespace std;
void Xhandler( ) {
try {
throw "hello"; // throw char *
catch(char *) { // catch a char *
cout << "Caught char * inside Xhandler\n";
throw ; // rethrow char * out of function
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 106 -
}
}
int main( ) {
cout << "start\n";
try {
Xhandler( );
}
catch(char *) {
cout << "Caught char * inside main\n";
}
cout << "end";
return 0;
}
This program displays:
start
Caught char * inside Xhandler
Caught char * inside main
end
Handling exceptions thrown by new
As you know, the modern specification for the new operator states that it will
throw an exception of an allocation request fails.
In Standard C++, when an allocation request is not honoured, new throws a
bad_alloc exception. If you don't catch this exception, your program will be
terminated. Although this behaviour is fine for short sample program, in real
applications you must catch this exception and process it in some rational
manner. To have access to this exception, you must include the header <new>
in your program.
Note that originally this exception was called xalloc, and many old compilers still
use the older name. However, bad_alloc is the name specified by Standard
C++, and it is the name that will be used in future.
In Standard C++, it is also possible to have new return null instead of throwing
an exception when an allocation failure occurs. This form of new is most useful
when you are compiling older code with a modern C++ compiler. It is also
valuable when you are replacing calls to malloc( ) with new. This form of new is
shown here:
p-var = new(nothrow) type;
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 107 -
Here p-var is a pointer variable of type. The nothrow from new works like the
original version of new, from years ago. Since, it returns null on failure, it can be
'dropped into' older code and you won't have to add exception handling.
However, for new code, exceptions provide a better alternative.
// Example of new that uses a try/catch to
// monitor for allocation failure
#include <iostream>
#include <new>
using namespace std;
int main( ) {
int *p;
try {
p = new int; // allocate memory for int
}
catch (bad_alloc xa) {
cout << "Allocation failure\n";
return 1;
}
for (*p=0; *p<10; (*p)++)
cout << *p << " ";
delete p; // free memory
return 0;
}
Since the the above program is unlikely to fail under normal circumstances, the
following program forces an allocation failure. It does this by allocating memory
until it is exhausted.
// Force an allocation failure
#include <iostream>
#include <new>
using namespace std;
int main( ) {
int *p;
// this will eventually run out of memory
do {
try {
p = new double(100000);
}
catch (bad_alloc xa) {
cout << "Allocation failure\n";
return 1;
}
Programming with C++ notes on the topics Pointers, Operator and Function
Overloading, Basic and advanced I/O, File System and Template and
Exceptation Handling
Prepared and hosted by Deepak Gour Faculty School of Engineering,
SPSU, Udaipur
- 108 -
cout << "Allocation OK.\n";
} while (p);
return 0;
}
The following program shows how to use the new(nothrow) alternative.
// Demonstrate the new(nothrow) alternative and
// force a failure
#include <iostream>
#include <new>
using namespace std;
int main( ) {
int *p;
// this will eventually run out of memory
do {
p = new(nothrow) double(100000);
if (p) cout << "Allocation OK.\n";
else cout << "Allocation Error.\n";
} while (p);
return 0;
}
As shown here, when you use nothrow approach, you must check the pointer
returned by new after each allocation request.