Handout 1 - Introduction To C++ and Classes
Handout 1 - Introduction To C++ and Classes
Handout Overview
1. Course Overview
One of the reasons that C++ has become so widely used is that it enhances the C
programming language to introduce a number of powerful object-oriented features,
whilst preserving the features of C that made it such a popular language for so long.
The most important of these new features are the ideas of classes and inheritance.
Classes will be discussed later in this handout and inheritance will be dealt with in
Handout 2.
In Handout 4 we will look at the idea of templates, which is another new feature of
C++ that enables programmers to write code that is independent of the data types it
operates on, thus making more effective reuse of code possible.
2. Object-Oriented Programming
Object-oriented languages have three main features that distinguish them from
other types of programming language:
• Encapsulation
• Inheritance
• Polymorphism/dynamic binding
The term polymorphism literally means “taking many different forms”. This
important concept will be discussed in Handout 2 (Inheritance) and also Handout
4 (Templates). Related to polymorphism is the concept of dynamic binding. C++
provides dynamic binding capabilities by the use of virtual functions which will
be discussed in Handout 2.
Let us illustrate this with an example. Suppose you are required to write a
battlefield simulation program. The battlefield should consist of a number of
battefield units. Battelfield units can be either soldiers or tanks. Tanks and
soldiers both have weapons, although both can have different types of weapon,
and can even carry more than one type of weapon each. When a tank or soldier
fires a weapon, the program should be able to simulate the trajectory of the
bullet/missile and determine the damage it has caused.
4
Each of the ellipses represents an object and the lines represent relationships.
You can see that we have objects to represent battlefield units, tanks, soldiers and
weapons. Also there is an object called trajectory, which is reponsible for
calculating the trajectory of projectiles fired by weapons and determining the
damage done. Clearly there is a relationship between a battlefield unit and a tank,
because a tank is a type of battelfield unit. Also, there is a clear relationship
between a tank and a weapon, because tanks have weapons. Finally, there is a
relationship between weapons and the trajectory object, since when a weapon is
fired we must calculate the trajectory of the projectile. Although all of these are
relationships, they are not the same type of relationship. We can say that a soldier
is-a battlefield unit, but we cannot say that a weapon is-a soldier. Similarly we
can say that a tank has-a weapon but we cannot say that a battlefield unit has-a
soldier. We say that the weapon object uses the trajectory object to calculate the
damage done by the weapon.
Before we start to discuss the object-oriented features of C++, we will deal with a
couple of new simple statements introduced in C++. The first new addition we will
consider is the new input/output statements it provides.
Whereas the C programming language provided the built-in functions scanf and
printf to input information from the user, or output information to the user,
C++ provides an alternative mechanism. In C++ you can still use scanf and
printf, but you can also use the commands cin and cout. For example, the
following program will print a message to the screen, then read in two int values
from the user:
#include <iostream.h>
main()
{
int n1, n2;
cout << “Enter 2 numbers:” << endl;
cin >> n1 >> n2;
}
First of all, notice that the arrow symbols (<< and >>) are pointing in different
directions for the cin and cout statements. Also, note that you can have more
than one of these symbols in each statement: the cin statement has two >>
symbols, which means that it reads in two numbers one after the other. Similarly
the cout statement prints the piece of text “Enter 2 numbers:” followed by the
endl symbol. The endl symbol just starts a new line on the screen.
You may wonder why C++ provides the cin and cout statements when the
scanf and printf statements work perfectly well. The main reason for this is
related to the idea of operator overloading, which we will discuss in Handout 3.
File I/O statements are performed in a similar way to the standard I/O statements
just described. Consider the following program:
#include <fstream.h>
int x;
ifstream f;
ofstream g;
f.open(“inputfile”);
g.open(“outputfile”);
f >> x;
g << x;
f.close();
g.close();
6
This program reads in a single integer from an input file called “inputfile”, and
writes the same integer to an output file called “outputfile”. The actual statements
that do the reading and writing,
f >> x;
g << x;
are similar to the cin and cout statements. The important differences to note here
are that the input and output files must be opened before use and closed after use.
Also, ifstream or ofstream objects must be declared for input and output files
respectively.
C++ also provides an alternative way of entering comments statements into your
program. You can still use the C language comments (i.e. /* ...*/), but in
addition you can add comments by typing a double-slash (‘//’). This causes the
compiler to ignore the remainder of the line after the double-slash, e.g.
4. Objects in C++
4.1. Structures
Before discussing how to define objects in C++, we will briefly recap on the
structure advanced data type in C. Structure types are used for storing a
collection of values of different types. Each value in a structure type is called a
member of the structure. The following is an example of a structure type to store
information about students:
Here we have defined two new data types: an enumeration type called RegExt,
and a structure type (struct) called Student. Variables declared to be of the
Student type will consist of 4 values. The first two members (name and
fathersName) are both pointers to char types. The other two members are of
types RegExt and float respectively. Notice that the format for declarations of
7
structure members is the same as that for ordinary variable declarations, they just
appear within a struct declaration. To illustrate the syntax for accessing or
assigning to members of structure variables, consider the following code:
Student s;
s.name = “Teklay”;
s.fathersName = “Zenawi”;
s.programme = Regular;
s.gpa = 3.2;
Here we first declare a variable s of type Student. Next all of the members of s
are initialised by typing a full stop (‘.’) after the variable name followed by the
member identifier. When a character string (e.g. “Teklay”) is assigned to a
variable of type char* the C++ compiler will automatically allocate enough
memory to store the sequence of characters.
For further discussion of the idea of data abstraction in relation to C++ classes,
see Dale, Weams & Headington (p838) or Cohoon & Davidson (p401).
8
We will first illustrate the concept of a class by means of a simple example. The
following code defines a class for storing information about and solving quadratic
equations.
”quadratic.cpp”
#include <iostream.h>
class quadratic {
// data members
public:
float a, b, c;
// member functions
void solve () {
float root1, root2;
float x = b * b - 4 * a * c;
if (x < 0)
cout << "Roots complex" << endl;
else {
root1 = (-b + x) / (2 * a);
root2 = (-b - x) / (2 * a);
cout << "Roots = " << root1 << ", " << root2 <<
endl;
}
}
};
int main () {
quadratic q;
cout << "Enter a b c:" << endl;
cin >> q.a >> q.b >> q.c;
q.solve();
}
Examine the code given above. Look first at the way the quadratic class is
defined. After the C++ keyword class and the class identifier (i.e. it’s name) there
are some data member definitions. The quadratic class has 3 float data
members, called a, b and c. These are like ordinary variable declarations, except that
they are preceded by the C++ keyword public. Data members in C++ classes can
be public, private or protected. Public data members are accessible to code
outside of the class, whereas private data members can be accessed only by member
functions of the class. We will see later what is meant by a protected data member.
After the data members have been specified, the member functions are defined. In the
quadratic class there is a single member function called solve. Note that this
member function is also specified as public. This means that this function can be
9
called by code outside of the class. In the main program an object q of the class
quadratic is declared. This declaration appears to be the same as a normal
variable declaration. However, when we wish to perform an operation on the class,
we see the difference: the operations are also members of the class so the object name
has to be specified along with the operation required. e.g. q.solve() will print out
the solutions to the quadratic equation contained in object q. Try entering and running
this code and make sure that you understand how it works.
Now we will look at a more complex example. The following code defines a class for
storing student data. It is the convention when defining C++ classes to separate the
class definitions from the member function bodies, to improve program clarity. Class
definitions are normally held in header files with the suffix “.h”, and function bodies
in C++ source files with the suffix “.cpp”. Using the Quincy editor you can create
C++ source files (i.e. “.cpp” files) by selecting the C++ Source File option after
selecting New from the File menu. To create a header file just select Header File as
the file type. Because this example involves more than one C++ source file, we must
create a Quincy project to contain the files. A project is just a collection of files that
are part of the same program. To create a new project choose New from the File
menu, and then choose the Project option when asked to select the file type. Add the
files to the project by selecting Insert File(s) from the Project menu.
The definition for the student class is given in “student.h”, and the member function
bodies are given in “student.cpp”. The main program is in “process.cpp”.
“process.cpp”
#include <iostream.h>
#include "student.h"
main ()
{
Student s;
s.Print();
Student s2 ("Rahel", "Gezahegn", Extension, 3.7);
s2.Print();
“student.h”
class Student {
//data members
private:
char *_name;
char *_fathersName;
RegExt _programme;
float _gpa;
//member functions
public:
Student();
Student(char *n, char *fn, RegExt p, float a);
~Student();
void setName(char *n, char *fn);
void setProg(RegExt p);
void setGPA(float a);
void Print() const;
};
“student.cpp”
#include <iostream.h>
#include "student.h"
Student::Student() {
_name = "";
_fathersName = "";
_programme = Regular;
_gpa = 0.0;
}
Student::~Student() {
cout << "Student Destructor" << endl;
}
First look at the Student class definition in “student.h”. Note in this example that
the data member definitions are preceded by the C++ keyword private. This
means that the data members cannot be accessed by code outside of the class itself.
Another way of putting this is to say that this information is hidden from code outside
of the class. The public, private and protected access levels are the mechanism that
C++ uses to achieve the concept of encapsulation, or information hiding. (See
Section 1.1.) Typically the logical properties of a class will be defined by the public
members (i.e. the public interface), whereas the implementation details will be either
private or protected.
Notice also that the names of the data members all begin with the underscore
character (‘_’). This is just convention. Many C++ programmers like to start data
member names with the underscore to make it easier to distinguish between class data
members and ordinary variables. You do not have to do this though – in the
quadratic example the data member names did not start with underscores.
After the data members have been specified, the member functions are defined. The
member functions are all specified as public so they can be called by code outside
of the class. Because the data members are all private we need to define public
member functions so that the program can modify the values of these data members.
You do not have to do it this way: in the previous example all data members were
public and could be modified from anywhere in the program, but using information
hiding like this allows the class to retain control over how its data is changed. The
class can validate any changes requested by the program. Note that it is also possible
to have private member functions – they can only be called by other member
functions. The collection of public data members and public member functions can be
seen as the public interface to the class.
In the “student.h” file only the member function prototypes are given. It is possible to
include the function bodies inside the class definition as in the quadratic equation
example, but generally it improves program readability if you specify the function
bodies elsewhere. In our example we have included them in the “student.cpp” file.
Notice how we use the scope operator ‘::’ to indicate that the function body being
defined is a member function of a particular class.
Notice also the const keyword after the void Print() function, both in the
prototype and the function body. If you specify a member function as const it will
12
not be able to change the values of any of the data members of the class. Note that in
most cases it is not necessary to declare a member function as const – in the quadratic
equation example the solve function does not change the values of any data
members but we did not define it as a const function. However, it is good
programming practice: if there was a bug in the Print function that meant that it did
accidentally change the value of a data member it would be reported as a compilation
error. Therefore using const member functions where appropriate can help you to
debug your programs.
As C++ classes often contain a number of different data members; it is often desirable
to perform some initialisation on them when an object of the class is first declared.
This is possible by using constructors. Constructors are special member functions
which are normally only called when an object of the class is declared. The function
name of a constructor is the same as the class name. In the Student class there are
two constructors, defined by the following function prototypes in “student.h”:
Student();
Student(char *n, char *fn, RegExt p, float a);
The corresponding function bodies are specified in “student.cpp”. Precisely which
constructor is called when an object is declared depends on the number and type of
arguments provided. For example, the declaration
Student s;
will cause the first constructor to be executed, whereas the declaration
Student s2 ("Rahel", "Gezahegn", Extension, 3.7);
will cause the second constructor to be executed. Notice also that constructor
functions have no return type.
In C++ classes, all objects of a particular class have their own copies of the data
members. For instance, in the student example, Rahel Gezahegn and Teklay Zenawi
have different GPAs, programmes and names. However, sometimes it is necessary for
all objects of a class to have access to the same variable. In C++ we can do this by
making a data member static. If you define a data member of a class as static then
only one copy of it is stored irrespective of how many objects of the class are created.
13
All objects access this same copy. Static data members must be initialised at the
beginning of the program using the class scope operator ‘::’.
Suppose we wish to store a count of the total number of students in the database.
Every time a new student object is created we want to add one to this count, and
every time a student object is destroyed we want to subtract one. We accomplish this
in the modified code below by defining an extra public static data member called
_count in the Student class. This is incremented in the Student constructors,
and decremented in the Student destructor.
“process.cpp”
#include <iostream.h>
#include "student.h"
main () {
Student s;
s.Print();
Student s2 ("Rahel", "Gezahegn", Extension, 3.7);
s2.Print();
cout << “Number of students = “ << s.Count() << endl;
s.setName ("Teklay", "Zenawi");
s.setProg (Regular);
s.setGPA (3.2);
s.Print();
}
“student.h”
class Student {
//data members
private:
char *_name;
char *_fathersName;
RegExt _programme;
float _gpa;
static int _count;
//member functions
public:
Student();
Student(char *n, char *fn, RegExt p, float a);
~Student();
void setName(char *n, char *fn);
void setProg(RegExt p);
void setGPA(float a);
void Print() const;
14
Student::Student() {
_name = "";
_fathersName = "";
_programme = Regular;
_gpa = 0.0;
_count += 1;
}
Student::Student(char *n, char *fn, RegExt p, float a) {
_name = n;
_fathersName = fn;
_programme = p;
_gpa = a;
_count += 1;
}
Student::~Student() {
cout << "Student Destructor" << endl;
_count -= 1;
}
The above example will work fine in most cases. However, there is a slight
problem. The value of the private _count static data member is being accessed
through a public member function Count(). The only way to access a member
function of a class is through an object of that class. But what happens if there
are no objects of the class to access the static data member through? In other
words, what happens if we want to access the value of _count before any
Student objects have been declared? Currently it is not possible. We can
rectify this situation by making the Count() function a static member function.
Static member functions of a class can be called even when there are no objects
of the class to call it through. The following modified code declares Count() as a
static member function and illustrates how to make a call to it when there are no
objects declared.
#include <iostream.h>
#include "student.h"
main () {
cout << “Number of students = “ << Student::Count() << endl;
Student s;
s.Print();
Student s2 ("Rahel", "Gezahegn", Extension, 3.7);
s2.Print();
cout << “Number of students = “ << Student::Count() << endl;
s.setName ("Teklay", "Zenawi");
s.setProg (Regular);
s.setGPA (3.2);
s.Print();
}
class Student {
//data members
private:
char *_name;
char *_fathersName;
RegExt _programme;
float _gpa;
static int _count;
//member functions
public:
Student();
Student(char *n, char *fn, RegExt p, float a);
~Student();
void setName(char *n, char *fn);
void setProg(RegExt p);
void setGPA(float a);
void Print() const;
static int Count() {return _count;}
};
Class member functions are generally divided into 3 categories, based upon their
function. This categorisation is just object-orintedn programming terminology –
the functions are not any different as far as the C++ compiler is concerned.
The first category is inspector functions. Inspector functions simply return the
value of a data member of the class. For example, the Count() function in the
student example is an inspector function. It is common to have public inspector
functions for private or protected data members.
The second category is mutator functions. Mutator functions change or set the
value of a data member. The setName, setProg and setGPA functions are
16
all mutator functions. Again, it is common to have public mutator functions for
private or protected data members. Mutator functions may also perform some
validation on the data to be assigned into the data member(s), to maintain the
integrity of the class data.
• In the past, most programming languages were procedural (i.e. they consist of
a sequence of statements executed one after the other).
• In object-oriented languages programs consist of a number of objects that
consist of both data and operations on that data.
• C++ is a language that extends the procedural language C by introducing a
number of object-oriented features. As such it is a mixture of procedural and
object-oriented languages. It is not a pure object-oriented language.
• Many features of object-oriented languages are aimed at allowing more
effective code reuse
• Object-oriented languages have three main features: encapsulation,
inheritance and polymorphism/dynamic binding.
• Encapsulation means separating the logical properties of an object (i.e. what it
does) from its implementation details (i.e. how it does it). Encapsulation is
also known as information hiding.
• Inheritance is a mechanism that allows programmers to reuse code, and leads
to programs that better model the structure of the real world problem.
• Polymorphism means “taking many different forms”. implements
polymorphism in two ways: through the use of virtual functions and
templates.
• Dynamic binding is a related concept to polymorphism, as is implement in
C++ through the use of virtual functions.
• Input/output in C++ can be performed using the cin and cout statements.
• Comments in C++ can be written using the double-backslash, i.e. //
• C++ classes consist of a set of related data (data members) together with
operations on that data (member functions).
• A class is like a data type. When we ‘declare a variable’ of a class, it is called
an object.
• Classes can be thought of as a formal mechanism for writing abstract data
types (ADTs).
• Data members and member functions must have an access level. This access
level will be public, private or protected.
• Public members of a class can be accessed from anywhere within the
program.
• Private members of a class can only be accessed by member functions of the
class.
• Use of the public, private and protected access levels is the way in which C++
implements encapsulation, or information hiding.
• The collection of public data members and member functions defines the
logical properties of the class (i.e. what it does), and is sometimes called the
public interface of the class.
• A const member function cannot change the values of any of the data
members of the class.
18
Note: The full source code listings for the examples in this handout can be found on the
FBE network server: to access them, open My Network Places, double-click on
MU-FBE, then FBE-SERVER and then browse to:
Courses\ICT122 – Object-Oriented Programming\src\Handout 1