Computing in C Part II: Object Oriented Programming in C: 1 What Is OOP?
Computing in C Part II: Object Oriented Programming in C: 1 What Is OOP?
Computing in C Part II: Object Oriented Programming in C: 1 What Is OOP?
Computing in C++
Part II : Object Oriented Programming in C++
Dr Robert Nürnberg
1 What is OOP?
Object oriented programming is a fairly new way to approach the task of programming. It supersedes the
so called procedural or structured programming languages like Algol, Pascal or C, that have been around
since the 1960s. The essence of structured programming is to reduce a program into smaller parts and
then code these elements more or less independently from each other.
Although structured programming has yielded excellent results when applied to moderately complex
programs, it fails when a program reaches a certain size. To allow for more complex programs to be
written, the new approach of OOP was invented. OOP, while allowing you to use the best ideas from
structured programming, encourages you to decompose a problem into related subgroups, where each
subgroup becomes a self-contained object that contains its own instructions and data that relate to that
object. In this way complexity is reduced, reusability is increased and the programmer can manage larger
programs more easily.
All OOP languages, including C++, share the following three capabilities:
• Encapsulation – the ability to define a new type and a set of operations on that type, without
revealing the representation of the type.
• Inheritance – the ability to create new types that inherit properties from existing types.
• Polymorphism – the ability to use one name for two or more related but technically different
purposes; “one interface, multiple methods.”
1.2 Compilers
• Windows: · Microsoft Visual C++ .NET with Integrated Development Environment (IDE)
· Borland C++ compiler, free version without IDE
· GNU C++ compiler (g++) as part of Cygwin, without IDE
· Dev-C++ – free compiler/IDE that is GNU compatible
• Linux: · GNU C++ compiler (g++) – part of any distribution
· Intel C++ compiler (icc) – free for students
· Code::Blocks – IDE only, also available for Windows
1
OOP in C++ Dr Robert Nürnberg
2 Revision
Before we embark on the topic of object oriented programming, let’s remind ourselves of some other
important features of C++.
int main() {
cout << "Hello World!" << endl;
return 0;
}
The first two lines of the above example show the new ANSI-C++ compliant way to include the standard
C++ libraries. Although many compilers still support the old style, eventually all C++ compilers will only
accept the ANSI standard.
Note that without the second line the code would have to look something like
#include <iostream>
int main() {
std::cout << "Hello World!" << std::endl;
return 0;
}
See §8.1 below for further details on namespaces.
int main() {
cout << "exp(1.0) = " << exp(1.0) << endl;
return 0;
}
For more details on the ANSI-C++ way to include header files from the standard library visit
www.cplusplus.com/doc/ansi/hfiles.html.
2.3 Macros
The C/C++ compiler evaluates certain preprocessing directives before actually starting to compile a
source code. These preprocessing directives are lines in your program that start with #. An example is
the #include statement, which tells the compiler what kind of system calls and libraries you want to use
in your program.
A macro is another preprocessing directive. In its simplest form, a macro is just an abbreviation. It is a
name which stands for a fragment of code. Usually, this will be some specific constant, such as Pi and
DEBUG in the example below. Before you can use a macro, you must define it explicitly with the #define
directive. It is followed by the name of macro and then the code it should be an abbreviation for.
Macros can also be defined to take arguments. In this case, any expansion you define for the macro, will
be applied with the arguments substituted by the values you are giving. The function macros SQR() and
MAX() in the example below demonstrate the usage.
Finally, you can also use conditional directives that allow a part of the program to be ignored during
compilation, on some conditions. A conditional can test either an arithmetic expression or whether a
name is defined as a macro. Check the usage of #if, #else, #endif, and #ifdef, #ifndef in the example
below.
2
OOP in C++ Dr Robert Nürnberg
#include <iostream>
using namespace std;
int main() {
#ifdef MAX
if (MAX(2.0,3.4) > 2.5) cout << "Square of pi is: " << SQR(Pi) << endl;
#endif
#if DEBUG
cout << "Debugging switched on." << endl;
cout << " --- " << __FILE__ << " line " << __LINE__ << "--- Compiled on "
<< __DATE__ << "." << endl;
#else
cout << "Debugging switched off." << endl;
#endif
return 0;
}
Note that the example also uses some standard predefined macros. They are available with the same
meanings regardless of the machine or operating system on which you are using C/C++. Their names all
start and end with double underscores, e.g. __FILE__, __LINE__ and __DATE__. More information on
the C/C++ preprocessor can be found at doc.ddart.net/c/gcc/cpp toc.html.
2.4 Enumerations
As in C, enumerations provide a convenient way to define variables that have only a small set of meaningful
values. Here is a simple example.
enum colour {red, green, blue, black};
colour foreground = black;
2.5 Strings
C++ may use the C string functions, where a string is represented by a char*, but they rely on a null
termination and proper memory allocation to hold the string. The C++ string class attempts to simplify
string manipulation by automating much of the memory allocation and management.
#include <iostream>
#include <string>
using namespace std;
int main() {
string a("abcd efg");
string b = "xyz ijk";
string c = a + b; // concatenation
cout << "First character of ’" << c << "’ is : " << c[0] << endl; // as in C
return 0;
}
3
OOP in C++ Dr Robert Nürnberg
#include <iostream>
using namespace std;
int main() {
int a(1);
double b(1.23);
add_some(&a); add_some(&b);
cout << "a = " << a << ", b = " << b << endl; // a = 4, b = 2.73
return 0;
}
This feature of C++ is also referred to as function overloading. More on that in §4.
int main() {
double a(1.23), b(0.0);
add_one_pt(&a);
add_one_rf(b);
cout << "a = " << a << ", b = " << b << endl;
return 0;
}
However, there are times when we have to use pointers in order to pass a variable to a function. In
particular, if we cannot be sure that the referred to variable will always be defined, we have to pass it
via a pointer. Here is a simple example of such an instance.
#include <iostream>
using namespace std;
void print() {
cout << "Hello World!" << endl;
}
int main() {
void (*p)() = print;
execute(NULL);
execute(p);
return 0;
}
4
OOP in C++ Dr Robert Nürnberg
by reference using the keyword const. This ensures that the compiler will not allow any change to the
passed variable within the function. The following two functions demonstrate the two concepts.
#include <iostream>
using namespace std;
int main() {
double p = 3.14;
print_by_val(p); print_by_ref(p);
return 0;
}
void print(int i = 10, int j = 5) { cout << i << " " << j << endl; }
int main () {
print(2); // ’2 5’
return 0;
}
Note that all default parameters must be to the right of any parameters that do not have defaults. Also,
once you define a default parameter, all the following parameters have to have a default as well.
int main() {
ofstream fout("output.txt");
fout << "Hello World!" << endl;
fout.close();
return 0;
}
3 Objects
The term object in C++ refers to an instance of a class. In the example below, studlist[0] is an instance
of the struct student, where we have noted that a struct (as known from C) is simply a class with
special properties, see §3.2 for details.
The support for classes is the most significant difference between C and C++. A class is essentially an
abstract data type. It is a collection of data together with functions that operate on the data. We will
refer to these as member data and member functions of a class, respectively.
By declaring a class we can create a new data type that, if we are careful, is as powerful as any of the
basic types.
5
OOP in C++ Dr Robert Nürnberg
3.1 Structures
Structures allow us to bundle data that naturally belong together into one object. E.g.
#include <iostream>
#include <string>
using namespace std;
struct student {
string name, firstname;
int year;
};
int main() {
int N;
cout << "Number of students: "; cin >> N;
student *studlist = new student[N];
for (int i=0; i<N; ++i) {
cin >> studlist[i].name >> studlist[i].firstname >> studlist[i].year;
}
// etc
return 0;
}
Note that a struct defines a type that can be used (almost) as freely as any of the basic types, such as
double, int, bool etc. In particular, we can pass variables of this newly defined type to functions, use
references and pointers to and declare arrays with these variables.
6
OOP in C++ Dr Robert Nürnberg
Nevertheless, it is somewhat bad programming practice to declare member data to be public. For a safe
and easy to update code one should always declare these as private.
But if the private members of a class are not accessible outside the class, we have to find a way to change
and inspect their values. This can be done with the help of functions that have access to the member
data and are themselves declared inside the class.
3.3 Methods
Member functions of a class are also referred to as methods. We have come across an instance of this
already during file input and output, cf. §2.8, where the line fout.close(); called the member function
close() for the instance fout of the class ofstream.
If we make the member data of our student class private, we should provide functions to create and print
the data, say. The declaration of the class would then look as follows.
class student {
string name, firstname;
int year;
public:
void create(string n, string fn, int y);
void print();
};
Observe that the member data is declared private, since this is the default setting for a class.
So far we have only declared the new member functions. Before we can actually use them, we have to
give their definitions. This can be done as follows.
void student::create(string n, string fn, int y) {
name = n; firstname = fn; year = y;
}
void student::print() {
cout << name << ", " << firstname << " (YEAR " << year << ")" << endl;
}
Note the usage of the scope operator :: in order to define the member functions.
Here is an example of how to use the newly defined methods.
int main() {
student s1;
s1.create("Smith", "Alan", 1);
s1.print();
return 0;
}
The big advantage of defining all the member data as private is that firstly we can easily trace an error if
something goes wrong, since the only place where the data is changed is in some of the member functions.
And secondly, it is very easy to enrich and modify the class later on. For example, we could choose to
include a variable address in the class. All we have to do in order to make sure that this new variable
is always properly initialized in our program is to change the create method to something like
void student::create(string n, string fn, int y) {
name = n; firstname = fn; year = y; address = "unknown";
}
and maybe provide another create method to initialize all four variables, e.g.
void student::create(string n, string fn, int y, string a) {
name = n; firstname = fn; year = y; address = a;
}
3.4 Constructors
In order to properly initialize an object, C++ allows to define a constructor function that is called each
time an object of that class is created. A constructor function has the same name as the class of which
it is a part and has no return type. On recalling default arguments from §2.7.4, the constructor for the
original student class can be defined as follows.
7
OOP in C++ Dr Robert Nürnberg
class student {
string name, firstname;
int year;
public:
student(string n = "unknown", string fn = "unkown", int y = 1) {
name = n; firstname = fn; year = y;
}
void print();
};
3.5 Destructors
The complement of a constructor is a destructor. This function is called when an object is destroyed,
i.e. for local variables when they go out of scope and for global variables when the program ends. One
only really needs to specify a destructor for classes that have a pointer to dynamically allocated memory.
The destructor function has the name of the class preceded with a ~. It has no return type and takes no
parameters.
Here is an example.
class array {
double *a;
int len;
public:
array(int l = 0) { len = l; a = new double[len]; } // constructor
~array() { delete[] a; } // destructor
};
8
OOP in C++ Dr Robert Nürnberg
class myclass {
int a;
public:
void print() const { cout << a << endl; }
class local_type {
double t;
public:
local_type(double x) { t = x; }
void print() const { cout << "Local type: " << t << endl; }
};
void print() const { cout << a << endl; }
};
int main() {
myclass::local_type z(2.3);
z.print();
return 0;
}
Note that the same access restriction for member data and member functions also applies to classes
defined inside other classes. I.e. these classes can only be used from outside the class in which they are
defined, if they were declared as public.
class classA {
int a;
friend bool is_equal(const classA &, const classB &);
};
class classB {
int b;
friend bool is_equal(const classA &, const classB &);
9
OOP in C++ Dr Robert Nürnberg
};
bool is_equal(const classA &A, const classB &B) { return (A.a == B.b); }
As a general rule of thumb, use member functions where possible, and friend functions only where
necessary. In much the same way as you should make data and methods of your classes private where
possible, and public where necessary.
10
OOP in C++ Dr Robert Nürnberg
return 0;
}
String f : Leading and trailing blanks
String length : 37
String f : Leading and trailing blanks ZZZ
String length : 40
substr(5,9) : eading an
assign("xyz",0,3) : xyz
The above code produces the following output: insert(1,"abc") : xabc yz .
String g : abc abc abd abc
replace(12,1,"xyz") : abc abc abd xyzbc
replace(4,3,"xyz",2): abc xy abd xyzbc
replace(4,3,"ijk",1): abc iabd xyzbc
find("abd",1) : 5
find("xyzb") : 9
A further important member function is string::c_str() which returns a pointer to a character array
with the same characters as the string that generates the call. This method is needed if you want to use
some “older” library routines such as sscanf.
4 Overloading
As already indicated in §2.6 and §2.7.4, in C++ it is possible to define several functions with the same
name, provided that the type of their arguments or the number of the arguments they take differ. After
classes, this is perhaps the next most important feature of C++.
void print(double a, double b) { cout << "a = " << a << ", b = " << b << endl; }
int main() {
void (*p)(int) = print;
void (*q)(double, double) = print;
p(2); // ’i = 2’
q(1.0,2.0); // ’a = 1, b = 2’
return 0;
}
delete p;
11
OOP in C++ Dr Robert Nürnberg
class A {
public:
void operator[] (int i) { // overloading A[int]
cout << "You want: [" << i << "]." << endl;
}
};
int main() {
A a;
a[5]; // ’You want: [5].’
return 0;
}
Although it is permissible to have an operator function perform virtually any action (see example above),
it is highly advisable to stick to operations similar to the operator’s traditional use.
12
OOP in C++ Dr Robert Nürnberg
#include <iostream>
using namespace std;
class Point {
double x,y;
public:
Point(double a = 0, double b = 0) { x = a; y = b; }
Point &operator-= (const Point &p) { x -= p.x; y -= p.y; return *this; }
Point operator- (const Point &p) { Point r(x-p.x,y-p.y); return r; }
void print() { cout << x << "," << y << endl; }
};
int main() {
Point p(1.0,2.0), q(0.0,0.5), r;
r = p -= q;
p.print(); // ’1,1.5’
r = p - q;
r.print(); // ’1,1’
return 0;
}
Observe that the operator-= () function could have been defined to return void instead. But then
statements like r = p -= q; would no longer work.
class Point {
double x,y;
public:
Point &operator++ () { ++x; ++y; return *this; } // prefix
Point operator++ (int) { Point r(x,y); ++x; ++y; return r; } // postfix
Point operator- () { Point r(-x,-y); return r; }
// etc
13
OOP in C++ Dr Robert Nürnberg
};
int main() {
Point p(0,0);
cout << p++ << endl; // ’(0,0)’ // postfix
cout << p << endl; // ’(1,1)’
class Point {
double x,y;
public:
void operator() (double x) {
cout << "Function called with double x=" << x << "." << endl;
}
int operator() () {
cout << "Function called with ()." << endl;
return 1;
}
// etc
};
int main() {
Point p(0,0);
p(2.0); // ’Function called with double x=2.’
int i = p(); // ’Function called with ().’
return 0;
}
class Point {
friend ostream &operator<< (ostream &os, const Point &p);
friend Point operator- (const double &x, const Point &p);
// etc
};
14
OOP in C++ Dr Robert Nürnberg
int main() {
Point z(0.5,1.5), r;
r = 2.0 - z;
cout << r << endl; // ’(1.5,-1.5)’
return 0;
}
Note, however, that it is not really necessary to use friend operators. See the example in §5.5 for a way
to keep the private member data of a class really private.
5 Inheritance
Inheritance is one of the most important features of C++. It allows classes to inherit properties from
another class and hence enables you to build a hierarchy of classes, moving from the most general one to
the most specific one.
When one class inherits another, it uses this general form:
class derived_class_name : access base_class_name {
// etc
}
Here, access is one of three keywords: public, private, or protected. The access specifier determines
how elements of the base class are inherited by the derived class. When the access specifier is public,
all public members of the base become public members of the derived class. If the access specifier is
private, however, all public members of the base become private members of the derived class. In either
case, all private members of the base remain private to it and are inaccessible by the derived class.
Here is a simple example.
class base {
public:
int a;
void print() { cout << a << endl; }
};
15
OOP in C++ Dr Robert Nürnberg
class class_name {
// private members
protected: // optional
// protected members
public:
// public members
}
How members of the base class can be accessed in the derived class is shown in the following table.
16
OOP in C++ Dr Robert Nürnberg
Finally, everything mentioned here about pointers to derived classes also holds true for passing arguments
by reference. I.e. a function that expects a reference to a base class object can also be passed a reference
to a derived class object. The following example illustrates that.
void show(const base &b); // reference to base class
int main() {
sub s(1,2);
show(s); // pass reference to derived object
// etc
}
class base {
public:
virtual void print() { cout << "base" << endl; } // virtual !
};
int main() {
base b; sub s; subsub ss;
b.print(); s.print(); ss.print(); // "base", "sub", "subsub"
17
OOP in C++ Dr Robert Nürnberg
subsubsub sss;
p[0] = &sss;
p[0]->print();
Finally, the virtual nature of a base member function is also invoked, when the function is called inside
another base member function. Here one can think of the this pointer being used, see §3.6. The this
pointer inside a base member function is a pointer of type base *. When that pointer is used to call a
virtual member function, C++ determines which version of that function to call, depending on the object
it points to. The following example demonstrates this.
#include <iostream>
using namespace std;
class base {
public:
virtual void print() { cout << "base" << endl; } // virtual !
void show(); // non-virtual
};
int main() {
base b; sub s;
s.show(); b.show(); // "sub", "base"
return 0;
}
18
OOP in C++ Dr Robert Nürnberg
class base {
public:
virtual void print() = 0; // pure virtual !
};
int main() {
sub1 s1; sub2 s2;
base *p[2]; // pointers to base class
p[0] = &s1; p[1] = &s2;
for (int i=0; i<2; ++i) p[i]->print();
return 0;
}
class base {
public:
base() { cout << "Constructing base." << endl; }
virtual ~base() { cout << "Destructing base." << endl; } // virtual !
virtual void print() { cout << "base" << endl; }
};
19
OOP in C++ Dr Robert Nürnberg
int main() {
base *p = new sub;
class base {
public:
void print() { cout << "base" << endl; } // here not virtual!
virtual void show() { cout << "show base" << endl; }
};
int main() {
base b; sub s; subsub ss; base *pb[3]; sub *ps[2];
pb[0] = &b; pb[1] = &s; pb[2] = &ss;
for (int i=0; i<3; ++i) { // base, show base
pb[i]->print(); pb[i]->show(); // base, show sub
} // base, show subsub
ps[0] = &s; ps[1] = &ss;
for (int i=0; i<2; ++i) {
ps[i]->print(); ps[i]->show(); // sub, show sub
} // subsub, show subsub
return 0;
}
20
OOP in C++ Dr Robert Nürnberg
class base {
protected:
int a;
public:
virtual void print(ostream &os) const { os << a << endl; }
};
int main() {
base b; sub s;
cout << b << s;
return 0;
}
6 Templates
Templates are a very powerful feature of C++. They are a relatively new addition to C++, and they
introduce the concept of generic programming. Generic programming is a data structure independent
way of developing and delivering algorithms that need not be bound to a specific object.
In particular, C++ templates allow you to implement generic functions or classes independent of a certain
parameter’s type. Once a template is defined, it can be used in connection with objects from any type,
provided that all the operations within the template are well defined for this type.
Templates are very useful when implementing generic constructs like vectors, stacks or lists, which can
be used with an arbitrary type. The most famous example for this kind of application is the Standard
Template Library, cf. §7.
C++ templates provide a way to reuse source code as opposed to inheritance and composition which
provide a way to reuse object code. In C++ you can define two kinds of templates: class templates
and function templates. The Standard Template Library, for example, uses function templates to define
generic algorithms, and the containers are implemented as class templates.
21
OOP in C++ Dr Robert Nürnberg
int main() {
int a = -5;
double b = 1.2;
float c = -0.43;
long int d = -123456789;
cout << "Absolute values are : " << absolute(a) << "," << absolute(b) << ","
<< absolute(c) << "," << absolute(d) << endl;
return 0;
}
Note that for the above program the compiler creates four different versions of the function absolute()
— one for each of the four types the function is invoked for.
Moreover, it is worth noting that one could have referred to the different overloaded versions directly, as
in the following example.
cout << "Absolute values are : " << absolute<int>(a) << endl;
In the above example, this is not really necessary. However, this way of identifying the intended function
has to be used, whenever at least one template typename T does not appear in the signature of the
function. Here is a small example.
#include <iostream>
using namespace std;
int main() {
foo<double>(2); return 0;
}
22
OOP in C++ Dr Robert Nürnberg
int main() {
my_pair<int> a(-1,2);
my_pair<double> b(1.2,0.1);
a.print(); b.print();
return 0;
}
6.2.1 Inheritance
When inheriting from a class template, one has to specify which version of the base class one refers to.
In particular, one could either inherit the full template, or just one instantiation. Here is an example for
both.
#include <iostream>
using namespace std;
int main() {
subsub a(-1,2);
sub<double> b(1.2,0.1);
a.print(); b.print();
return 0;
}
Here a short remark is due. Observe that in the member functions of the inherited class template sub<T>
particular attention has to be paid on how to refer to member data that was inherited from base<T>. In
this example, the correct syntax is
base<T>::a
which is slightly cumbersome. However, this is the only way we can refer to that member data. The
reason behind that lies in the C++ standard for templates. Strictly speaking, when the member function
in sub<T> is compiled, the base class itself has not yet been instantiated and as a consequence we have
no knowledge of what member data it will provide. Referring to the desired member data directly with
the help of the scope operator :: circumvents this problem.
23
OOP in C++ Dr Robert Nürnberg
template <>
int my_pair<int>::mod() { // overload definition for my_pair<int>
return a % b;
}
int main() {
my_pair<int> a(-1,2);
my_pair<double> b(1.2,0.1);
cout << a.mod() << endl; // ’-1’
cout << b.mod() << endl; // ’0’ by default
return 0;
}
Of course, we can also use template specialization in order to identify the typename that our template
was instantiated for. The following example demonstrates this.
#include <iostream>
using namespace std;
template <>
int my_pair<int>::type_id() { return 1; } // specialize for int
template <>
int my_pair<double>::type_id() { return 2; } // specialize for double
int main() {
my_pair<int> a(-1,2);
my_pair<double> b(1.2,0.1);
my_pair<float> c(-1.1,0.2);
24
OOP in C++ Dr Robert Nürnberg
template <typename T1, typename T2> class my_pair { // base template class
T1 a; T2 b;
public:
my_pair(T1 i, T2 j) : a(i), b(j) {}
void print();
};
int main() {
my_pair<int, int> a(-1,2);
my_pair<double, double> b(1.2,0.1);
a.print(); b.print();
return 0;
}
Note that the definition of any specialization is independent from the base template. I.e. in theory a
specialization could implement very different methods, though this would be very bad programming
practice indeed.
template<int N>
class Factorial {
25
OOP in C++ Dr Robert Nürnberg
public:
enum { value = N * Factorial<N-1>::value };
};
class Factorial<0> {
public:
enum { value = 1 };
};
int main () {
cout << "10! = " << Factorial<10>::value << endl;
return 0;
}
The beauty of the above example is that no computation time is needed at run time.
7 STL
The Standard Template Library (STL) is a general purpose C++ library of algorithms and data structures.
The STL is part of the standard ANSI-C++ library and is implemented by means of the C++ template
mechanism, hence its name. While some aspects of the library are very complex, it can often be applied
in a very straightforward way, facilitating reuse of the sophisticated data structures and algorithms it
contains.
For a full documentation see www.sgi.com/tech/stl.
7.1 Vectors
The vector template is probably the most important and most widely used container class offered by
the STL. What makes it so attractive is that one can use it in the same fashion as C style arrays without
having to worry about memory management or possible memory leaks. Furthermore, by virtue of the
size() member function, one knows at any point in time how many objects a vector contains. With C
style arrays, of course, failure to monitor the size of an array properly can lead to incorrect programs
and segmentation faults.
All of the STL container classes are treated by C++ almost like the built-in standard types. In particular,
C++ takes care of the proper destruction of vectors and other containers as soon as one of these objects
goes out of scope. Moreover, for the vector class basic operators like =, == and < are already defined
and functional.
The following table gives an overview over the vector class’s most important member functions. Here
we assume that the size of the vector is of type int, that the objects contained in the vector are of type
T and that the iterators are of type T*. Some of the type declarations will be omitted in the table.
Details can be found at www.sgi.com/tech/stl/Vector.html.
Strictly speaking, the last two functions are no member functions. Instead they are implemented as
global functions, taking two parameters of type const vector&.
26
OOP in C++ Dr Robert Nürnberg
Table 3: Insert and erase overheads for vector, list and deque.
int main() {
list<int> L;
L.push_back(0);
L.push_front(0);
L.insert(++L.begin(),2,3);
L.push_back(5);
L.push_back(6);
27
OOP in C++ Dr Robert Nürnberg
int main() {
map<string, int> freq; // map of words and their frequencies
string word;
7.4 Iterators
Iterators are a fundamental feature of the Standard Template Library. The concept of iterators was
introduced in order to unify access to and traversal of different kinds of STL containers and, in particular,
to allow most of the STL algorithms to work for any container type.
Let us look at a simple example.
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <string>
using namespace std;
int main() {
vector<double> v(20,1.2);
list<int> l(10,4);
map<string, int> m;
m["ab"] = 1; m["yz"] = 4; m["fg"] = 0;
list<int>::iterator litr;
for(litr=l.begin(); litr != l.end(); ++litr) cout << *litr << " ";
cout << endl;
vector<double>::iterator vitr;
for(vitr=v.begin(); vitr != v.end(); ++vitr) cout << *vitr << " ";
cout << endl;
return 0;
}
Observe that the traversal of e.g. a map, a list and a vector can be done in exactly the same way,
although the internal data structures of these containers are fundamentally different.
You can think of iterators as a special class that belongs to the STL containers and that is overloaded
differently for each container. Through appropriately overloading operators such as =, !=, ++, * (see
above) and other operators, this iterator class provides access to the elements stored in a container and
allows its traversal. Note that pointers are nothing other than special iterators for both C style arrays
and STL vectors.
There are several advantages for the use of iterators. As you have seen, iterators can be uniformly used
for all containers and also arrays. Furthermore, on creating your own container class with an appropriate
iterator class, you can use the existing STL algorithms for your new class. Similarly, if you code a new
28
OOP in C++ Dr Robert Nürnberg
(template based) algorithm for container classes that is written using iterators, it will apply to all existing
containers for which there are iterators.
More details on iterators can be found at www.sgi.com/tech/stl/Iterators.html.
7.5 Functors
Many of the STL algorithms (cf. §7.6) can be given so called function objects, or functors, as one of
their parameters. A functor is simply any object that can be called as if it is a function. Hence an
ordinary function is a functor, and so is a function pointer. Moreover, any object of a class that defines
an operator() is a functor.
The STL offers many predefined generic functors, such as plus, minus, times, divides and modulus.
Furthermore, many bool functors – so called predicates – are defined within the STL as well. E.g.
equal_to, not_equal_to, greater, less, greater_equal and less_equal.
The following program shows how some of these templates can be used.
#include<iostream>
#include<cmath>
#include<functional>
using namespace std;
int main() {
double a = -5.1, b = 3.4;
abs_greater a_g;
plus<double> pl;
7.6 Algorithms
The STL also includes a large collection of algorithms that manipulate the data stored in containers.
The following example demonstrates the use of some of the STL algorithms. Although the program uses
a vector<int> container, most of the algorithms also work for many other STL containers. To make this
possible, all the algorithms are defined as global functions. Secondly, most of the algorithms are given a
range to operate on. E.g. a full vector<int> v is given by the range (v.begin(), v.end()).
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
vector<int> v(1000);
29
OOP in C++ Dr Robert Nürnberg
cout << "Number after first five = " << *(ffive+1) << endl;
cout << "max(1.2,4.3) ** 10 = " << power(a, 10) << endl; // power
vector<int> u(10);
fill(u.begin(), u.end(), 9); // fill
int main() {
int A[] = {1, 4, 2, 8, 5, 7};
const int N = sizeof(A) / sizeof(int);
return 0;
}
30
OOP in C++ Dr Robert Nürnberg
8 Miscellaneous
8.1 Namespaces
Namespaces allow you to group a set of global classes, objects and/or functions under a name. This can
be useful when writing large programs, where one wants to use the same name for two different objects
in different contexts, say. If one chooses not to use the using namespace keyword, one can still refer to
the defined objects with the help of the scope operator ::. Here is an example.
#include <iostream>
using namespace std;
int main () {
{
using namespace first;
cout << var << endl;
}
{
using namespace second;
cout << var << endl;
}
cout << first::var + second::var << endl;
return 0;
}
When using the ANSI-C++ compliant include files one has to bear in mind that all the included functions,
classes and objects will be declared under the std namespace.
int main() {
cout << 123.23 << endl; // ’123.23’
cout.setf(ios::scientific | ios::showpos | ios::uppercase);
cout << 123.23 << endl; // ’+1.232300e+02’
cout.unsetf(ios::showpos | ios::uppercase);
cout << 123.23 << endl; // ’1.232300e+02’
cout.precision(1);
cout << 123.23 << endl; // ’1.2e+02’
cout.precision(5); cout.width(13);
cout << 123.23 << endl; // ’ 1.23230e+02’
cout.precision(5); cout.width(13); cout.fill(’0’);
cout << 123.23 << endl; // ’001.23230e+02’
cout << 123.23 << endl; // ’1.232e+02’
return 0;
}
Of interested could also be the flags ios::right and ios::fixed.
For more details visit www.cppreference.com/cppio.html.
Another convenient way to simplify I/O operations is to define your own manipulators. Here is a short
example.
#include <iostream>
using namespace std;
31
OOP in C++ Dr Robert Nürnberg
os.fill(’*’);
return os;
}
int main() {
cout << tri << myformat << 123.23 << endl; // ’The result is: *****123.2’
return 0;
}
class myclass {
public:
static int i;
myclass(int ii) { i = ii; }
};
int main() {
myclass a(5);
myclass b(8);
cout << a.i << endl; // ’8’
return 0;
}
class cmplx {
double r, i;
public:
cmplx(double a, double b) : r(a), i(b) {}
operator double() { return sqrt(r*r + i*i); }
};
int main() {
cmplx c(2.0,1.5);
double a = c; // conversion from cmplx to double
cout << a << endl; // ’2.5’
return 0;
}
Obviously, one should use these conversion functions carefully.
32
OOP in C++ Dr Robert Nürnberg
int main () {
try {
double *m;
m = new double[10];
if (m == NULL) throw "Allocation failure";
for (int i=0; i<100; ++i) {
if (i>9) throw i;
m[i]=sqrt(i);
}
// etc
delete [] m;
}
catch (int n) {
cout << "Exception: index " << n << " is out of range." << endl;
}
catch (string s) {
cout << "Exception: " << s << endl;
}
return 0;
}
If you want to catch any exception within a try block, use catch(...) (i.e. three single dots within the
brackets).
class A {
int a,b;
public:
A(int i = 0, int j = 0) : a(i), b(j) {}
void print() const { cout << a << "," << b << endl; }
};
int main () {
show(A(), A(1,2));
33
OOP in C++ Dr Robert Nürnberg
int main() {
return combine(print, abs, -2.5);
}
See also §2.7.1 for another example.
34