Beruflich Dokumente
Kultur Dokumente
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.
#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 = ▵
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 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 = ▵
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)
{
};
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 = ▵
ptr_polygon1->setup(2,2);
ptr_polygon2->setup(2,2);
ptr_polygon1->onscreen();
ptr_polygon2->onscreen();
}
return 0;
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)
{
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;
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.
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
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;
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:
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:
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:
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.)
Runtime Type Information (RTTI) is the concept of determining the type of any variable during
execution (runtime.) The RTTI mechanism contains:
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;
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*).
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;
}
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
06
07
08
09
main()
{
vector<string> SS;
10
11
12
13
14
15
16
17
18
19
20
21
int ii;
for(ii=0; ii < SS.size(); ii++)
{
cout << SS[ii] << endl;
}
22
23
24
25
26
27
28
29
vector<string>::const_iterator cii;
for(cii=SS.begin(); cii!=SS.end(); cii++)
{
cout << *cii << endl;
}
30
31
32
33
34
35
36
37
vector<string>::reverse_iterator rii;
for(rii=SS.rbegin(); rii!=SS.rend(); ++rii)
{
cout << *rii << endl;
}
38
39
40
41
42
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.
01
02
#include <iostream>
#include <vector>
03
04
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
19
20
21
22
23
24
25
26
27
28
Compile: g++
Run: ./a.out
Loop by index:
0
1
10
11
20
21
01
02
#include <iostream>
#include <vector>
03
04
05
06
07 {
08
09
main()
// Vector length of 3 initialized to 0
vector<int> vI1Matrix(3,0);
10
11
...
#include <iostream>
#include <vector>
03
04
05
06
07 {
08
main()
vector< vector< vector<int> > > vI3Matrix(2, vector< vector<int> > (3,
vector<int>(4,0)) );
09
10
11
12
13
14
15
16
17
18
19
20
}
}
}
Using an iterator:
Example of iterators used with a two dimensional vector.
01
02
#include <iostream>
#include <vector>
03
04
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
24
25
26
27
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;
Description
vector<T> v;
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);
Size methods/operators:
Method/operator
Description
empty()
size()
capacity()
reserve(size_t n)
max_size()
Description
erase()
clear()
erase(iterator)
erase(begin_iterator,end_iterator)
=
Example: X=Y()
<
const vector&)
==
at(index)
v[index]
front()
v[0]
back()
pop_back()
assign(begin_iterator,end_iterator)
insert(iterator pos,
begin_iterator,end_iterator)
swap(vector& v2)
Iterator methods/operators:
Method/operator
Description
begin()
end()
Return iterator to end of vector (not last element of vector but past
last element)
Declaration: const_iterator end() const
rbegin()
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
02
03
04
#include <iostream>
#include <list>
05
06
07
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);
18
19
list<int>::iterator i;
20
21
22
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
002
003
004
#include <iostream>
#include <list>
005
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
020
021
022
023
024
025
026
027
028
029
030
031
AAA::AAA()
// Constructor
{
x = 0;
y = 0;
z = 0;
}
032
033 AAA::AAA(const AAA ©in)
value.
034 {
035
x = copyin.x;
036
y = copyin.y;
037
z = copyin.z;
038
}
039
040
output << aaa.x << ' ' << aaa.y << ' ' << aaa.z << endl;
043
044
return output;
}
045
046
047
048
049
050
051
052
return *this;
}
053
054
055
056
057
058
059
060
return 1;
}
061
062 // This function is required for built-in STL list functions like sort
063
064
065
if( this->x == rhs.x && this->y == rhs.y && this->z < rhs.z) return 1;
066
067
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
099
100
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
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