Sie sind auf Seite 1von 35

Introduction to Polymorphism

Polymorphism is by far the most important and widely used concept in object oriented
programming. Some of the widely used technologies and libraries like COM, MFC etc. have
polymorphism as their foundation. If you look at all the original design patterns, almost every
pattern uses polymorphism in its structure.
Polymorphism is a mechanism that allows you to implement a function in different ways.

Pointers to base class


We have seen that it is possible to derive a class from a base class and that we can add
functionality to member functions.
One of the features of derived classes is that a pointer to a derived class is type-compatible with a
pointer to its base class. Polymorphism takes advantage of this feature.
Lets take a look at an example of a base class and derived classes:

#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
};
class CRectangle: public CPolygon
{
public:
int area()
{
return (width * height);
}
};
class CTriangle: public CPolygon
{
public:
int area()
{
return (width * height / 2);
}
};
int main ()

CRectangle rectangle;
CTriangle triangle;
CPolygon * ptr_polygon1 = &rectangle;
CPolygon * ptr_polygon2 = &triangle;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
cout << rectangle.area () << endl;
cout << triangle.area () << endl;
return 0;

}
As you can see, we create two pointers (ptr_polygon1 and ptr_polygon2) that point to the objects
of class CPolygon. Then we assign to these pointers the address of (using the reference
ampersand sign) the objects rectangle and triangle. Both rectangle and triangle are objects of
classes derived from CPolygon.
In the cout statement we use the objects rectangle and triangle instead of the pointers
ptr_polygon1 and ptr_polygon2. We do this because ptr_polygon1 and ptr_polygon2 are of the
type CPolygon. This means we can only use the pointers to refer to members that CRectangle and
CTriangle inherit from Cpolygon.
If we want to use the pointers to class CPolygon then area() should be declared in the class
CPolygon and not only in the derived classes CRectangle and Ctriangle.
The problem is that we use different versions of area() in the derived classes CRectangle and
Ctriangle so we cant implement one version of area() in the base class CPolygon. (If they were
the same we had no problem.)
We can fix this by using virtual members.

Virtual Members
A virtual member is a member of a base class that we can redefine in its derived classes. To
declare a member as virtual we must use the keyword virtual.
Lets change our previous example:

#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{

width= first;
height= second;

};

}
virtual int area()
{
return (0);
}

class CRectangle: public CPolygon


{
public:
int area()
{
return (width * height);
}
};
class CTriangle: public CPolygon
{
public:
int area()
{
return (width * height / 2);
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
CPolygon polygon;
CPolygon * ptr_polygon1 = &rectangle;
CPolygon * ptr_polygon2 = &triangle;
CPolygon * ptr_polygon3 = &polygon;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
ptr_polygon3->setup(2,2);
cout << ptr_polygon1->area () << endl;
cout << ptr_polygon2->area () << endl;
cout << ptr_polygon3->area () << endl;
return 0;
}
Because of the change adding area() as a virtual member of CPolygon now all the three
classes have all the same members (width, height, setup() and area().)
A class that declares or inherits a virtual function is called a polymorphic class.

Abstract Base Classes (ABC)


At the design level, an abstract base class (ABC) corresponds to an abstract concept. For instance:
if you ask to draw a shape, I will probably ask what kind of shape. The term shape is an abstract
concept, it could be a circle, a square, etc, etc. You could say in C++ that class CShape is an
abstract base class (ABC) and class circle (etc) could be a derived class.
As we look at the C++ language we could say that an abstract base class has one or more pure
virtual member functions.
In the example above we put an implementation (return (0);) in the virtual member function
area(). If we want to change it into a pure virtual member function we use =0; instead of the
return (0). So the class will look like this:

class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
virtual int area() = 0;
};
This pure virtual function area() makes CPolygon an abstract base class. But you have to
remember the following: by adding a pure virtual member to the base class, you are forced to also
add the member to any derived class.
So our example should now look like this:

#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
virtual int area() = 0;
};
class CRectangle: public CPolygon
{
public:

int area(void)
{
return (width * height);
}
};
class CTriangle: public CPolygon
{
public:
int area(void)
{
return (width * height / 2);
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
CPolygon * ptr_polygon1 = &rectangle;
CPolygon * ptr_polygon2 = &triangle;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
cout << ptr_polygon1->area () << endl;
cout << ptr_polygon2->area () << endl;
return 0;
}
Note: there is also an extra void in the derived classes CRectangle and CTriangle.
Using a unique type of pointer (CPolygon*) we can point to objects of different but related classes.
We can make use of that. For instance: we could implement an extra function member in the
abstract base class CPolygon that can print the result of the area() function. (Remember that
CPolygon itself has no implementation for the function area() and still we can use it, isnt it cool.)
After implementation of such a function the example will look like this:

#include <iostream>
using namespace std;
class CPolygon
{
protected:
int width, height;
public:
void setup (int first, int second)
{
width= first;
height= second;
}
virtual int area(void) = 0;
void onscreen(void)
{

cout << this->area() << endl;

};
class CRectangle: public CPolygon
{
public:
int area(void)
{
return (width * height);
}
};
class CTriangle: public CPolygon
{
public:
int area(void)
{
return (width * height / 2);
}
};
int main ()
{
CRectangle rectangle;
CTriangle triangle;
CPolygon * ptr_polygon1 = &rectangle;
CPolygon * ptr_polygon2 = &triangle;
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
ptr_polygon1->onscreen();
ptr_polygon2->onscreen();
}

return 0;

As you can see this can be very useful.

C++ Exceptions and exception


handling
Exceptions are situations which must be avoided during program executions. Exceptions are
caused by errors, invalid inputs or invalid processing. Exceptions can lead to either program
termination or generating unexpected outputs.

In general, it is assumed that exceptions are errors but this is not always true. We can state:
All errors are exceptions but not necessarily all exceptions are errors.
Exceptions can be handled or avoided by a simple control statement such as an if-else statement,
but most languages provide a separate mechanism of exception handling.

Exception Handling
Exception handling is a process of handling exceptional situations that may occur in a program due
to the above stated reasons in such a way that:

The program will terminate gracefully i.e. it will give a proper message and then will
terminate the program.
After giving the proper message stating the reason of the exception the program continues
to execute after correcting the error.
In the C++ language, exception handling is performed using the following keywords:

try
catch
throw
Take a look at the process of exception handling:
To catch exceptions we must place a portion of code under exception inspection. We can do this by
putting that portion of code in a try block. So, when an exceptional circumstance arises (within
that block) an exception is thrown. This in turn transfers the control to the exception handler. If
there are no exceptions thrown, the code will continue normally and all handlers are ignored.
Take a look at an example:

#include<iostream>
using namespace std;
int main ()
{
try
{
}

throw 5;

catch (int a)
{

cout << "An exception occurred!" << endl;


cout << "Exception number is: " << a << endl;
}
return 0;
}

As you can see, the code under exception handling is enclosed in a try block. We simply throw an
exception with the statement: throw 5;
The throw expression can accept on parameter, which is passed as an argument to the exception
handler.
As you can see, in this example we passed the integer value five.
We declared the exception handler with the catch keyword. As you can see, the catch format looks
the same as a normal function. The catch statement always has at least one parameter.
The type of the parameter used in the catch statement must be the same as the type used in the
throw statement. If it is not, the exception is not caught. For this reason it is possible to use
multiple exception handlers. We just use different catch statements which in turn use different
parameter types. Only the handler that matches its type with the argument specified in the throw
statement is executed.
Lets take a look at such an example:

#include<iostream>
#include <string>
using namespace std;
int main ()
{
int num;
string str_bad = "wrong number used";
cout << "Input 1 or 2: ";
cin >> num;
try
{
if ( num == 1
{
throw
}
if ( num == 2
{
throw
}
if ( num != 1

)
5;
)
1.1f;
|| num != 2 )

{
}

throw str_bad;

catch (int a)
{
cout <<
cout <<
}
catch (float b)
{
cout <<
cout <<
}
catch (...)
{
cout <<
cout <<
}
return 0;

"An exception occurred!" << endl;


"Exception number is: " << a << endl;

"An exception occurred!" << endl;


"Exception number is: " << b << endl;

"A default exception occurred!" << endl;


"Why? : " << str_bad << endl;

The program above will throw an exception after you input something. If the number is a 1 then
an integer is thrown. If the input is a 2 then a float is thrown. If it is neither of these two (not an
integer or float) the default exception handler is used. This default exception handler uses the
ellipsis () as the parameter of catch. That handler will catch any exception no matter what the
type of the throw exception is. (In this case a string is used.)
It is possible to nest try-catch blocks, but you have to test it very well because it is easy to make
a mistake which in turn can lead to an unexpected result.

Exception and functions


If we declare a function, we can limit the exception type that the function might throw. We can do
this by adding a throw suffix to the function declaration. For example:

int a_function (int param) throw(int);


int a_function (int param) throw();
int a_function (int param);

The first function of the examples above takes one argument of the type integer and will return an
integer.
The only exception that this function might throw is an exception of type int.
The second function also takes one argument of the type integer and will return an integer. The
function may not throw exceptions, because there is no type specified between the round
brackets. (No type specified means that the function is not allowed to throw exception.)
The last function may throw exception of any type.

Standard exceptions
The C++ standard library provides a base class specifically designed to declare objects to be
thrown as exceptions. To make use of the standard exceptions we have to include the exception
header file.
All of the exceptions thrown by parts of the C++ Standard library will throw exceptions derived
from the std::exception class. These exceptions are:
bad_alloc
A bad_alloc is thrown by new if an allocation failure occurs.
bad_cast
A bad_cast is thrown by dynamic_cast when it fails with a referenced type.
bad_exception
A bad_exception is thrown when an exception type doesnt match any catch
bad_typeid
A bad_typeid is thrown by typeid
ios_base::failure
An ios_base::failure is thrown by functions in the iostream library.

So lets use it in an example, but be warned this example can lock your computer! It should
not happen, but it might.

#include<iostream>
#include <exception>
using namespace std;
int main()
{
try
{

int *
int *
//int
//int
//add

my_array1= new int[100000000];


my_array2= new int[100000000];
* my_array3= new int[100000000];
* my_array4= new int[100000000];
more if needed.

catch (bad_alloc&)
{
cout << "Error allocating the requested memory." << endl;
}
}

return 0;

Note: you should add more my_arrays until exception occurs. You could also add some free
statements in the catch block, freeing up already claimed memory.
It is recommended to use try blocks if you use dynamic memory allocation. This way you are in
control if an allocation fails. For example: you unload all used resources and then end the
program.

C++ Typecasting
Typecasting is the concept of converting the value of one type into another type. For example, you
might have a float that you need to use in a function that requires an integer.

Implicit conversion
Almost every compiler makes use of what is called automatic typecasting. It automatically
converts one type into another type. If the compiler converts a type it will normally give a
warning. For example this warning: conversion from double to int, possible loss of data.

The problem with this is, that you get a warning (normally you want to compile without warnings
and

errors)

and you are not in control. With control we mean, you did not decide to convert to another type,
the compiler did. Also the possible loss of data could be unwanted.

Explicit conversion
The C and C++ languages have ways to give you back control. This can be done with what is
called an explicit conversion. Sure you may still lose data, but you decide when to convert to
another type and you dont get any compiler warnings.
Lets take a look at an example that uses implicit and explicit conversion:

#include <iostream>
using namespace std;
int main()
{
int a;
double b=2.55;
a = b;
cout << a << endl;
a = (int)b;
cout << a << endl;

a = int(b);
cout << a << endl;

Note: the output of all cout statements is 2.


The first conversion is an implicit conversion (the compiler decides.) As explained before, the
compiler should give a warning.
The second conversion is an explicit typecast, in this case the C style explicit typecast.
The third conversion is also explicit typecast, in this case the C++ style explicit typecast.

Four typecast operators


The C++ language has four typecast operators:

static_cast
reinterpret_cast
const_cast
dynamic_cast

Static_cast
Automatic conversions are common in every C++ program. You have:
Standard conversion. For instance: from short to int or from int to float.
User defined conversions (Class conversions.)
Conversion from derived class to base class.

The static_cast can be used for all these types of conversion. Take a look at an example:

int a = 5;
int b = 2;
double out;
// typecast a to double
out = static_cast<double>(a)/b;

It may take some time to get used to the notation of the typecast statement. (The rumour goes
that Bjarne Stroustrup made it difficult on purpose, to discourage the use of typecasting.) Between
the angle brackets you place to which type the object should be casted. Between the parentheses
you place the object that is casted.
It is not possible to use static_cast on const objects to non-const objects. For this you have to use
const_cast. (Further down we take a look at const_cast.)
If an automatic conversion is valid (from enum to int for instance) then you can use static_cast to
do

the

opposite

(from

int

to

For instance:

enum my_numbers { a=10, c=100, e=1000 };


const my_numbers b = static_cast<my_numbers> (50);
const my_numbers d = static_cast<my_numbers> (500);

Note: We add some new values (b and d). These are type-cast from int to enum.

enum.)

Reinterpret_cast
The reinterpret_cast is used for casts that are not safe:

Between integers and pointers


Between pointers and pointers
Between function-pointers and function-pointers
For instance the typecast from an integer to a character pointer:

char *ptr_my = reinterpret_cast<char *>(0xb0000);

Note: the example above uses a fixed memory location.


If we use the reinterpret_cast on a null-pointer then we get a null-pointer of the asked type:

char *ptr_my = 0;
int *ptr_my_second = reinterpret_cast<int *>(ptr_my);

The reinterpret_cast is almost as dangerous as an old fashion cast. The only guaranty that you
get is that if you cast an object back to the original data-type (before the first cast) then the
original value is also restored (of course only if the data-type was big enough to hold the value.)
The only difference with an old fashion cast is that const is respected. This means that a
reinterpret_cast can not be used to cast a const object to non-const object. For instance:

char *const MY = 0;
// This is not valid because MY is a const!!
int *ptr_my = reinterpret_cast<int *>( MY);

Const_cast
The only way to cast away the const properties of an object is to use const_cast. Take a look at an
example:

void a(Person* b);


int main()

const Person *ptr_my = new Person("Joe");


a( const_cast<Person *>(ptr_my) );

The use of const_cast on an object doesnt guarantee that the object can be used (after the const
is cast away.) Because it is possible that the const-objects are put in read-only memory by the
program.
The const_cast can not be used to cast to other data-types, as it is possible with the other cast
functions.
Take a look at the next example:

int a;
const char *ptr_my = "Hello";
a

= const_cast<int *>(ptr_my);

a = reinterpret_cast<const char*>(ptr_my);
a = reinterpret_cast<int *>(const_cast<char *>(ptr_my) );

Note: casting from const char * to int * isnt very good (not to say a very dirty trick.) Normally
you wont do this.
The first statement (const_cast) will give an error, because the const_cast cant convert the type.
The second statement (reinterpret_cast) will also give an error, because the reinterpret_cast cant
cast the const away. The third statement will work (mind the note. It is a dirty trick, better not use
it.)

C++ Typecasting RTTI,


dynamic_cast, typeid and type_info
Runtime Type Information (RTTI)

Runtime Type Information (RTTI) is the concept of determining the type of any variable during
execution (runtime.) The RTTI mechanism contains:

The operator dynamic_cast


The operator typeid
The struct type_info
RTTI can only be used with polymorphic types. This means that with each class you make, you
must have at least one virtual function (either directly or through inheritance.)
Compatibility note: On some compilers you have to enable support of RTTI to keep track of
dynamic

types.

So to make use of dynamic_cast (see next section) you have to enable this feature. See you
compiler documentation for more detail.

Dynamic_cast
The dynamic_cast can only be used with pointers and references to objects. It makes sure that the
result of the type conversion is valid and complete object of the requested class. This is way a
dynamic_cast will always be successful if we use it to cast a class to one of its base classes. Take a
look at the example:

class Base_Class { };
class Derived_Class: public Base_Class { };
Base_Class a; Base_Class * ptr_a;
Derived_Class b; Derived_Class * ptr_b;
ptr_a = dynamic_cast<Base_Class *>(&b);
ptr_b = dynamic_cast<Derived_Class *>(&a);

The first dynamic_cast statement will work because we cast from derived to base. The second
dynamic_cast statement will produce a compilation error because base to derived conversion is
not allowed with dynamic_cast unless the base class is polymorphic.
If a class is polymorphic then dynamic_cast will perform a special check during execution. This
check ensures that the expression is a valid and complete object of the requested class.
Take a look at the example:

// dynamic_cast

#include <iostream>
#include <exception>
using namespace std;
class Base_Class { virtual void dummy() {} };
class Derived_Class: public Base_Class { int a; };
int main () {
try {
Base_Class * ptr_a = new Derived_Class;
Base_Class * ptr_b = new Base_Class;
Derived_Class * ptr_c;
ptr_c = dynamic_cast< Derived_Class *>(ptr_a);
if (ptr_c ==0) cout << "Null pointer on first type-cast" << endl;
ptr_c = dynamic_cast< Derived_Class *>(ptr_b);
if (ptr_c ==0) cout << "Null pointer on second type-cast" << endl;

} catch (exception& my_ex) {cout << "Exception: " << my_ex.what();}


return 0;

In the example we perform two dynamic_casts from pointer objects of type Base_Class* (namely
ptr_a and ptr_b) to a pointer object of type Derived_Class*.
If everything goes well then the first one should be successful and the second one will fail. The
pointers ptr_a and ptr_b are both of the type Base_Class. The pointer ptr_a points to an object of
the type Derived_Class. The pointer ptr_b points to an object of the type Base_Class. So when the
dynamic type cast is performed then ptr_a is pointing to a full object of class Derived_Class, but
the pointer ptr_b points to an object of class Base_Class. This object is an incomplete object of
class Derived_Class; thus this cast will fail!
Because this dynamic_cast fails a null pointer is returned to indicate a failure. When a reference
type is converted with dynamic_cast and the conversion fails then there will be an exception
thrown out instead of the null pointer. The exception will be of the type bad_cast.
With dynamic_cast it is also possible to cast null pointers even between the pointers of unrelated
classes.
Dynamic_cast can cast pointers of any type to void pointer(void*).

Typeid and typ_info


If a class hierarchy is used then the programmer doesnt have to worry (in most cases) about the
data-type of a pointer or reference, because the polymorphic mechanism takes care of it. In some

cases the programmer wants to know if an object of a derived class is used. Then the programmer
can make use of dynamic_cast. (If the dynamic cast is successful, then the pointer will point to an
object of a derived class or to a class that is derived from that derived class.) But there are
circumstances that the programmer (not often) wants to know the prizes data-type. Then the
programmer can use the typeid operator.
The typeid operator can be used with:

Variables
Expressions
Data-types
Take a look at the typeid example:

#include <iostream>
#include <typeinfo>
using namespace std;
int main ()
{
int * a;
int b;
a=0; b=0;
if (typeid(a) != typeid(b))
{
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}

Note: the extra header file typeinfo.


The result of a typeid is a const type_info&. The class type_info is part of the standard C++ library
and contains information about data-types. (This information can be different. It all depends on
how it is implemented.)
A bad_typeid exception is thrown by typeid, if the type that is evaluated by typeid is a pointer that
is preceded by a dereference operator and that pointer has a null value.

STL Description:

The Standard Template Libraries (STL's) are a set of C++ template classes to
provide common programming data structures and functions such as doubly
linked lists (list), paired arrays (map), expandable arrays (vector), large string
storage and manipulation (rope), etc. The STL library is available from the STL
home page. This is also your best detailed reference for all of the STL class
functions available.
Also see our C++ Templates tutorial
STL can be categorized into the following groupings:
Container classes:
o Sequences:
vector: Dynamic array of variables, struct or objects. Insert
data at the end.
deque: Array which supports insertion/removal of elements
at beginning or end of array
list: (this tutorial) Linked list of variables, struct or objects.
Insert/remove anywhere.
o Associative Containers:
set (duplicate data not allowed in set), multiset (duplication
allowed): Collection of ordered data in a balanced binary
tree structure. Fast search.
map (unique keys), multimap (duplicate keys allowed):
Associative key-value pair held in balanced binary tree
structure.
o Container adapters:

stack LIFO
queue FIFO
priority_queue returns element with highest priority.
o String:
string: Character strings and manipulation
rope: String storage and manipulation
o bitset: Contains a more intuitive method of storing and
manipulating bits.
Operations/Utilities:
o iterator: (examples in this tutorial) STL class to represent position
in an STL container. An iterator is declared to be associated with a
single container class type.
o algorithm: Routines to find, count, sort, search, ... elements in
container classes
o auto_ptr: Class to manage memory pointers and avoid memory
leaks.

STL vector:
vector: Dynamic array of variables, struct or objects. Insert data at the end.
Simple example of storing STL strings in a vector. This example shows three
methods of accessing the data within the vector:

01
02
03

#include <iostream>
#include <vector>
#include <string>

04
05

using namespace std;

06
07
08
09

main()
{
vector<string> SS;

10
11
12
13

SS.push_back("The number is 10");


SS.push_back("The number is 20");
SS.push_back("The number is 30");

14
15

cout << "Loop by index:" << endl;

16
17
18
19
20
21

int ii;
for(ii=0; ii < SS.size(); ii++)
{
cout << SS[ii] << endl;
}

22
23

cout << endl << "Constant Iterator:" << endl;

24
25
26
27
28
29

vector<string>::const_iterator cii;
for(cii=SS.begin(); cii!=SS.end(); cii++)
{
cout << *cii << endl;
}

30
31

cout << endl << "Reverse Iterator:" << endl;

32
33
34
35
36
37

vector<string>::reverse_iterator rii;
for(rii=SS.rbegin(); rii!=SS.rend(); ++rii)
{
cout << *rii << endl;
}

38
39

cout << endl << "Sample Output:" << endl;

40
41

cout << SS.size() << endl;

42

cout << SS[2] << endl;

43
44
45
46

Compile: g++

swap(SS[0], SS[2]);
cout << SS[2] << endl;
}
exampleVector.cpp

Run: ./a.out
Output:
Loop by index:
The number is 10
The number is 20
The number is 30
Constant Iterator:
The number is 10
The number is 20
The number is 30
Reverse Iterator:
The number is 30
The number is 20
The number is 10
Sample Output:
3
The number is 30
The number is 10

[Potential Pitfall]: Note that the iterator is compared to the end of the vector
with "!=". Do not use "<" as this is not a valid comparison and may or may not
work. The use of "!=" will always work.

Two / Three / Multi Dimensioned arrays using vector:


A two dimensional array is a vector of vectors. The vector contructor can
initialize the length of the array and set the initial value.
Example of a vector of vectors to represent a two dimensional array:

01
02

#include <iostream>
#include <vector>

03
04

using namespace std;

05
06
07 {
08
09

main()
// Declare size of two dimensional array and initialize.
vector< vector<int> > vI2Matrix(3, vector<int>(2,0));

10
11
12
13
14
15
16

vI2Matrix[0][0]
vI2Matrix[0][1]
vI2Matrix[1][0]
vI2Matrix[1][1]
vI2Matrix[2][0]
vI2Matrix[2][1]

=
=
=
=
=
=

0;
1;
10;
11;
20;
21;

17
18

cout << "Loop by index:" << endl;

19
20
21
22
23
24
25
26
27
28

Compile: g++

int ii, jj;


for(ii=0; ii < 3; ii++)
{
for(jj=0; jj < 2; jj++)
{
cout << vI2Matrix[ii][jj] << endl;
}
}
}
exampleVector2.cpp

Run: ./a.out
Loop by index:
0
1
10
11
20
21

A three dimensional vector would be declared as:

01
02

#include <iostream>
#include <vector>

03
04

using namespace std;

05
06
07 {
08
09

main()
// Vector length of 3 initialized to 0
vector<int> vI1Matrix(3,0);

10
11

// Vector length of 4 initialized to hold


another

// vector vI1Matrix which has been


initialized to 0
13
vector< vector<int> > vI2Matrix(4, vI1Matrix);
12
14
// Vector of length 5 containing two
dimensional vectors
16
vector< vector< vector<int> > > vI3Matrix(5, vI2Matrix);
15
17
18

...

or declare all in one statement:


01
02

#include <iostream>
#include <vector>

03
04

using namespace std;

05
06
07 {
08

main()

vector< vector< vector<int> > > vI3Matrix(2, vector< vector<int> > (3,
vector<int>(4,0)) );

09
10
11
12
13

for(int kk=0; kk<4; kk++)


{
for(int jj=0; jj<3; jj++)
{

14
15
16
17

for(int ii=0; ii<2; ii++)


{
cout << vI3Matrix[ii][jj][kk] << endl;
}

18
19
20

}
}
}

Using an iterator:
Example of iterators used with a two dimensional vector.
01
02

#include <iostream>
#include <vector>

03
04

using namespace std;

05
06
07 {
08
09
10
11

main()
vector< vector<int> > vI2Matrix;
// Declare two dimensional array
vector<int> A, B;
vector< vector<int> >::iterator iter_ii;
vector<int>::iterator
iter_jj;

12
13
14
15
16
17
18

A.push_back(10);
A.push_back(20);
A.push_back(30);
B.push_back(100);
B.push_back(200);
B.push_back(300);

19
20
21

vI2Matrix.push_back(A);
vI2Matrix.push_back(B);

22
23

cout << endl << "Using Iterator:" << endl;

24
25
26
27

for(iter_ii=vI2Matrix.begin(); iter_ii!=vI2Matrix.end(); iter_ii++)


{
for(iter_jj=(*iter_ii).begin(); iter_jj!=(*iter_ii).end(); iter_jj+
+)

28
29
30
31
32

Compile: g++

{
cout << *iter_jj << endl;
}
}
}
exampleVector2.cpp

Run: ./a.out
Using Iterator:
10
20
30
100
200
300

[Potential Pitfall]: Note that "end()" points to a position after the last element
and thus can NOT be used to point to the last element.
iter_jj = SS.end();
cout << *iter_jj << endl;

This will result in a "Segmentation fault" error.


Constructor/Declaration:
Method/operator

Description

vector<T> v;

Vector declaration of data type "T".

vector<T> v(size_type n);

Declaration of vector containing type "T" and of size


"n" (quantity).

vector<T> v(size_type n,const T& Declaration of vector containing type "T", of size "n"
t);
(quantity) containing value "t".
Declaration: vector(size_type n, const T& t)
vector<T>
v(begin_iterator,end_iterator);

Copy of Vector of data type "T" and range


begin_iterator to end_iterator.
Declaration: template vector(InputIterator,
InputIterator)

Size methods/operators:
Method/operator

Description

empty()

Returns bool (true/false). True if empty.


Declaration: bool empty() const

size()

Number of elements of vector.

Declaration: size_type size() const


resize(n, t=T())

Adjust by adding or deleting elements of vector so that its size is


"n".
Declaration: void resize(n, t = T())

capacity()

Max number of elements of vector before reallocation.


Declaration: size_type capacity() const

reserve(size_t n)

Max number of elements of vector set to "n" before reallocation.


Declaration: void reserve(size_t)

max_size()

Max number of elements of vector possible.


Declaration: size_type max_size() const

Note: size_type is an unsigned integer.


Other methods/operators:
Method/operator

Description

erase()
clear()

Erase all elements of vector.


Declaration: void clear()

erase(iterator)
erase(begin_iterator,end_iterator)

Erase element of vector. Returns iterator to


next element.
Erase element range of vector. Returns
iterator to next element.
Declarations:

=
Example: X=Y()

iterator erase(iterator pos)

iterator erase(iterator first,


iterator last)

Assign/copy entire contents of one vector into


another.
Declaration: vector& operator=(const
vector&)

<

Comparison of one vector to another.


Declaration: bool operator<(const vector&,

const vector&)

==

Returns bool. True if every element is equal.


Declaration: bool operator==(const vector&,
const vector&)

at(index)
v[index]

Element of vector. Left and Right value


assignment: v.at(i)=e; and e=v.at(i);
Declaration: reference operator[](size_type
n)

front()
v[0]

First element of vector. (Left and Right value


assignment.)
Declaration: reference front()

back()

Last element of vector. (Left and Right value


assignment.)
Declaration: reference back()

push_back(const T& value)

Add element to end of vector.


Declaration: void push_back(const T&)

pop_back()

Remove element from end of vector.


Declaration: void pop_back()

assign(size_type n,const T& t)

Assign first n elements a value "t".

assign(begin_iterator,end_iterator)

Replace data in range defined by iterators.


Declaration:

insert(iterator, const T& t)

Insert at element "iterator", element of value


"t".
Declaration: iterator insert(iterator pos,
const T& x)

insert(iterator pos, size_type n, const


T& x)

Starting before element "pos", insert first n


elements of value "x".
Declaration: void insert(iterator pos,
size_type n, const T& x)

insert(iterator pos,
begin_iterator,end_iterator)

Starting before element "pos", insert range


begin_iterator to end_iterator.
Declaration: void insert(iterator pos,
InputIterator f, InputIterator l)

swap(vector& v2)

Swap contents of two vectors.


Declaration: void swap(vector&)

Iterator methods/operators:
Method/operator

Description

begin()

Return iterator to first element of vector.


Declaration: const_iterator begin() const

end()

Return iterator to end of vector (not last element of vector but past
last element)
Declaration: const_iterator end() const

rbegin()

Return iterator to first element of vector (reverse order).


Declaration: const_reverse_iterator rbegin() const

rend()

Return iterator to end of vector (not last element but past last
element) (reverse order).
Declaration: const_reverse_iterator rend() const

++

Increment iterator.

--

Decrement iterator.

STL list:
list: Linked list of variables, struct or objects. Insert/remove anywhere.
Two examples are given:
1. The first STL example is for data type int
2. The second for a list of class instances.
They are used to show a simple example and a more complex real world
application.
1. Lets start with a simple example of a program using STL for a linked list:

01

// Standard Template Library example

02
03
04

#include <iostream>
#include <list>

05

using namespace std;

06
07

// Simple example uses type int

08
09
10
11
12
13

main()
{

list<int> L;
L.push_back(0);
L.push_front(0);
L.insert(++L.begin(),2);
14
argument
15
16
L.push_back(5);
17
L.push_back(6);

// Insert a new element at the end


// Insert a new element at the beginning
// Insert "2" before position of first
// (Place before second argument)

18
19

list<int>::iterator i;

20
21
22

for(i=L.begin(); i != L.end(); ++i) cout << *i << " ";


cout << endl;

23

return 0;

24

Compile: g++

example1.cpp

Run: ./a.out
Output: 0

2 0 5 6

[Potential Pitfall]: In Red Hat Linux versions 7.x one could omit the "using
namespace std;" statement. Use of this statement is good programming practice
and is required in Red Hat 8.0 and later.
[Potential Pitfall]: Red Hat 8.0 and later requires the reference to "#include
<iostream>". Red Hat versions 7.x used "#include <iostream.h>".

2. The STL tutorials and texts seem to give simple examples which do not
apply to the real world. The following example is for a doubly linked list. Since
we are using a class and we are not using defined built-in C++ types we have
included the following:
To make this example more complete, a copy constructor has been
included although the compiler will generate a member-wise one
automatically if needed. This has the same functionality as the
assignment operator (=).
The assignment (=) operator must be specified so that sort routines can
assign a new order to the members of the list.
The "less than" (<) operator must be specified so that sort routines can
determine if one class instance is "less than" another.
The "equals to" (==) operator must be specified so that sort routines can
determine if one class instance is "equals to" another.
001

// Standard Template Library example using a class.

002
003
004

#include <iostream>
#include <list>

005

using namespace std;

006
007 // The List STL template requires overloading operators =, == and <.
008
009
010
011

class AAA
{
friend ostream &operator<<(ostream &, const AAA &);

012
013
014

public:
int x;

015

int y;

016

float z;

017

018

AAA();

019

AAA(const AAA &);


~AAA(){};

020
021

AAA &operator=(const AAA &rhs);

022

int operator==(const AAA &rhs) const;

023
024

int operator<(const AAA &rhs) const;


};

025
026
027
028
029
030
031

AAA::AAA()

// Constructor
{
x = 0;
y = 0;
z = 0;
}

032
033 AAA::AAA(const AAA &copyin)
value.
034 {
035
x = copyin.x;
036
y = copyin.y;
037
z = copyin.z;
038
}

// Copy constructor to handle pass by

039
040

ostream &operator<<(ostream &output, const AAA &aaa)


041 {
042

output << aaa.x << ' ' << aaa.y << ' ' << aaa.z << endl;

043
044

return output;
}

045
046
047
048
049
050

AAA& AAA::operator=(const AAA &rhs)


{
this->x = rhs.x;
this->y = rhs.y;
this->z = rhs.z;

051
052

return *this;
}

053
054
055

int AAA::operator==(const AAA &rhs) const


{

056

if( this->x != rhs.x) return 0;

057

if( this->y != rhs.y) return 0;

058

if( this->z != rhs.z) return 0;

059
060

return 1;
}

061
062 // This function is required for built-in STL list functions like sort
063
064

int AAA::operator<(const AAA &rhs) const


{

065

if( this->x == rhs.x && this->y == rhs.y && this->z < rhs.z) return 1;

066

if( this->x == rhs.x && this->y < rhs.y) return 1;

067

if( this->x < rhs.x ) return 1;

068

return 0;

069

070
071
072
073
074

main()
{
list<AAA> L;
AAA Ablob ;

075
076
077
078
079

Ablob.x=7;
Ablob.y=2;
Ablob.z=4.2355;
L.push_back(Ablob); // Insert a new element at the end

080
081

Ablob.x=5;
L.push_back(Ablob); // Object passed by value. Uses default member082
wise
083
// copy constructor
084
Ablob.z=3.2355;
085
L.push_back(Ablob);
086
087
088
089
090

Ablob.x=3;
Ablob.y=7;
Ablob.z=7.2355;
L.push_back(Ablob);

091
092

list<AAA>::iterator i;

093
for(i=L.begin(); i != L.end(); ++i) cout << (*i).x << " "; // print
member
095
cout << endl;
094
096
097

for(i=L.begin(); i != L.end(); ++i) cout << *i << " "; // print with
overloaded operator

098

cout << endl;

099
100

cout << "Sorted: " << endl;


L.sort();
for(i=L.begin(); i != L.end(); ++i) cout << *i << " "; // print with
102
overloaded operator
103
cout << endl;
101

104
105

return 0;

106

Output:
7 5 5 3
7 2 4.2355
5 2 4.2355
5 2 3.2355
3 7 7.2355
Sorted:
3 7 7.2355
5 2 3.2355
5 2 4.2355
7 2 4.2355

STL vector vs list function comparison:


Function

vector

list

deques

constructor

yes

yes

yes

destructor

yes

yes

yes

empty()

yes

yes

yes

size()

yes

yes

yes

max_size()

yes

yes

yes

resize()

yes

yes

yes

capacity()

yes

no

no

reserve()

yes

no

no

erase()

yes

yes

yes

Function

vector

list

deques

clear()

yes

yes

yes

operator=

yes

yes

yes

operator<

yes

yes

no

operator==

yes

yes

no

operator[]

yes

no

yes

at()

yes

no

yes

front()

yes

yes

yes

back()

yes

yes

yes

push_back()

yes

yes

yes

pop_back()

yes

yes

yes

assign()

yes

yes

yes

insert()

yes

yes

yes

swap()

yes

yes

yes

push_front()

no

yes

yes

pop_front()

no

yes

yes

merge()

no

yes

no

remove()

no

yes

no

remove_if()

no

yes

no

reverse()

no

yes

no

sort()

no

yes

no

splice()

no

yes

no

unique()

no

yes

no

Das könnte Ihnen auch gefallen