25
Mekelle University Faculty of Business & Economics Computer Science Department ICT122: Object-Oriented Programming Handout 1 – Introduction to C++ and Classes Handout Overview This handout introduces the concept of object-oriented programming. The principle differences between conventional procedural programming languages and object- oriented programming languages are described. The new input/output and comment statements provided by C++ are introduced. Finally the concept of a C++ class is described in detail. 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 addition to the new object-oriented programming language features, C++ provides an interesting and potentially very useful new feature known as operator overloading, which will be discussed in Handout 3. 1

Handout 1 - Introduction to C++ and Classes

Embed Size (px)

Citation preview

Mekelle University Faculty of Business & Economics

Computer Science Department

ICT122: Object-Oriented Programming

Handout 1 – Introduction to C++ and Classes

Handout Overview

This handout introduces the concept of object-oriented programming. The principle differences between conventional procedural programming languages and object-oriented programming languages are described. The new input/output and comment statements provided by C++ are introduced. Finally the concept of a C++ class is described in detail.

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 addition to the new object-oriented programming language features, C++ provides an interesting and potentially very useful new feature known as operator overloading, which will be discussed in Handout 3.

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.

Finally in Handout 5 the object-oriented design process will be examined in some detail, through the use of a realistic design case study.

To begin with, in this handout we will introduce the concept of object-oriented programming, briefly discuss the object-oriented view of the world and introduce some object-oriented design concepts. These are the concepts that underly many of the topics in this course.

1

2. Object-Oriented Programming

2.1. Procedural vs. Object-Oriented Programs

Historically, most programming languages have been procedural languages. This means that programs work by executing a sequence of instructions provided by the programmer. Examples of procedural languages include BASIC, Pascal and C. More recently, another way of programming has gained in popularity, known as object-oriented programming. Object-oriented programming languages allow the programmer to break down the problem into objects: self-contained entities consisting of both data and operations on the data.

C++ is an object-oriented programming language, but since C++ is derived from C, it also contains many procedural language features too. Other examples of object-oriented programming languages are Java and Smalltalk. The concept of object-oriented programming will become clearer as you learn more about C++ and its programming features.

2.2. The Benefits of Object-Oriented Programming

In object-oriented programming data plays a leading role. Data that is related is grouped together with the operations that process the data. This enables object-oriented programs to more accurately model the structure of the real world. Because of this, many programmers find the object-oriented approach to problem solving more intuitive and easier to reason with.

Object-oriented languages have three main features that distinguish them from other types of programming language:

Encapsulation Inheritance Polymorphism/dynamic binding

Encapsulation means separating the logical properties of a data structure (what it does) from its implementation details (how it does it). Encapsulation is also sometimes referred to as information hiding. It is a common technique that the human mind uses to tackle complex problems: break the problem down into a number of smaller, simpler, well-defined sub-problems and solve each individually. By specifying the required inputs and outputs of a sub problem we are abstracting it away from the main problem. An example of the way in which people use abstraction is driving a car – when we drive a car we do not think about how the engine is working (the implementation), we only think about how we manipulate the steering wheel, pedals and gear stick (the public interface). If the program is designed well, encapsulation can lead to more secure and reliable software.

2

Inheritance is a mechanism by which object-oriented languages allow objects to reuse all or part of the code in another object or objects. Inheritance can also lead to more reliable and easily understandable software that better models the structure of the real-world problem, and will be dealt with in depth in Handout 2.

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.

2.3. Code Reuse

Many of the features of object-oriented programming languages are aimed at allowing the programmer to reuse existing code more effectively. Code reuse simply means to somehow reuse an existing piece of code to perform a new operation, rather than writing an entirely new piece of code every time. The concept of code reuse is not new: ever since programming began programmers have tried to reuse code. For example, functions in C allow code reuse. Code reuse is generally considered to be a good thing in computer programming because it reduces the amount of work involved in writing programs, allowing faster code development. Also, programs that reuse code are easier tom understand, debug and modify. One of the advantages of object-oriented programming languages is that they generally allow greater code reuse than conventional programming languages.

2.4. The Object-Oriented View of the World

In object-oriented programming a program is viewed as a system of interacting objects. Each of these objects represents a thing in the real world. The objects are related and interact with each other by sending messages to each other and eacting to these messages. The role of the object-oriented programmer is to define the objects, their relationships, the messages that they send and how they respond to these messages.

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.

How would we approach the design of this program from an object-oriented perspective? Remember that our final program must consist of objects that

3

represent things in the real world, and that these objects have relationships between them. One possible object-oriented solution is given in Figure 1.

Figure 1 – Objects and relationships for a battlefield simulation program

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.

It is important to understand the difference between these types of relationship, as we will come back to them later in the course. The process of reading a program specification like the one given above and producing a set of objects and relationships is one that comes with exeprience, but some guidance will be given when the subject of objct-oriented design is covered in more depth in Handout 5.

3. Extra Features of C++

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.

3.1. Input/Output Statements

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+

4

+ 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.

The #include <iostream.h> statement must be included at the start of the program if you want to use input & output statements in C++. This is the standard way of including definitions from a predefined library of routines. C++ has many such libraries, and we will use some different ones later on.

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.

3.2. File I/O

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();

5

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.

3.3. Comment Statements

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.

// declare program variablesint a = 3, b;

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:

enum RegExt {Regular, Extension};struct Student { char *name; char *fathersName; RegExt programme; float gpa;};

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 structure members is the same as that for ordinary variable declarations, they just

6

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.

4.2. C++ Classes

Remember that in object-oriented programming, objects consist of a set of related data, together with the operations that process that data. You can define objects in C++ using classes. A class is a similar concept to a structure in C, except that as well as containing a set of data members, it can also contain member functions for operating on that data. This is an important difference: in traditional procedural programming there was a clear division between data and the operations that process the data; in object-oriented programming this division is not so clear. Rather than having separate data and functions, we instead have objects, which consist of data together with operations on that data. In C++, we call these objects classes. You can think of a class as a special kind of data type. When we ‘declare a variable’ of this data type, it is called an object.

4.3. Classes and Abstract Data Types

A class in C++ is actually an example of an abstract data type (ADT). An ADT is a user defined data type whose properties are specified independently of any particular implementation. That is, the programmer specifies the inputs and outputs of the data type, but hides the details of the implementation from the rest of the program. This means that (s)he can change this implementation at a later date without changing the way the rest of the program interacts with the ADT. Use of ADTs has been common in procedural languages for many years, but object-oriented languages like C++ provide a formal mechanism for implementing ADTs and enforcing the hiding of implementation details.

For further discussion of the idea of data abstraction in relation to C++ classes, see Dale, Weams & Headington (p838) or Cohoon & Davidson (p401).

7

5. Example 1 – Solving Quadratic Equations

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 memberspublic:

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 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,

8

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.

6. Example 2 – A Student Database

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();

s.setName ("Teklay", "Zenawi"); s.setProg (Regular); s.setGPA (3.2); s.Print();}

“student.h”

enum RegExt {Regular, Extension};

class Student {//data membersprivate:

9

char *_name; char *_fathersName; RegExt _programme; float _gpa;

//member functionspublic: 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(char *n, char *fn, RegExt p, float a) { _name = n; _fathersName = fn; _programme = p; _gpa = a;}

Student::~Student() { cout << "Student Destructor" << endl;}

void Student::setName (char *n, char *fn) { _name = n; _fathersName = fn;}void Student::setProg (RegExt p) { _programme = p;}

void Student::setGPA (float a) { _gpa = a;}

void Student::Print () const { cout << "Name: " << _name << " " << _fathersName << endl; if (_programme == Regular)

10

cout << "Regular Programme\n"; else cout << "Extension Programme\n"; cout << "GPA = " << _gpa << endl << 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 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

11

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.

7. Constructors and Destructors

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.

Similarly, it is also possible to define a destructor. A destructor is a member function that is only called when the program has finished with an object. Class destructors also have no return type, and have the same name as the class but are preceded by the ‘~’ symbol. For many classes you may not need to define a destructor, but they can be useful for more complex classes. If no constructor or destructor is defined, then the default will be an empty function.

8. Static Class Members

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. All objects access this same copy. Static data members must be initialised at the beginning of the program using the class scope operator ‘::’.

12

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"

// initialise static data memberint Student::_count = 0;

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”

enum RegExt {Regular, Extension};

class Student {//data membersprivate: char *_name; char *_fathersName; RegExt _programme; float _gpa; static int _count;

//member functionspublic: 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; int Count() {return _count;}};

13

”student.cpp” (modified parts only)

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;}

8.1. Static Member Functions

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.

“process.cpp” (modified parts in bold)

#include <iostream.h>#include "student.h"

// initialise static data memberint Student::_count = 0;

main () { cout << “Number of students = “ << Student::Count() << endl; Student s;

14

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();}

“student.h” (modification in bold)

enum RegExt {Regular, Extension};

class Student {//data membersprivate: char *_name; char *_fathersName; RegExt _programme; float _gpa; static int _count;

//member functionspublic: 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;}};

8.2. Inspectors, Mutators and Facilitators

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 all mutator functions. Again, it is common to have public mutator functions for private or protected data members. Mutator functions may also perform some

15

validation on the data to be assigned into the data member(s), to maintain the integrity of the class data.

Finally, facilitator functions cause an object to perform some action or service. The Print() function in the Student class is an example of a facilitator function.

16

Summary of Key Points

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.

A constructor is a special member function that is called once when an object of the class is instantiated.

17

A destructor is a special member function that is called once when an object is destroyed.

If a data member is declared as static, there will only be one instance of it for the entire class (regardless of how many objects are instantiated).

Static member functions can be called even when there are no objects of the class declared.

Inspector member functions return the value of an attribute (data member) of a class.

Mutator member functions change or set the value of an attribute of a class. Facilitator member functions cause an object to perform some action or

service.

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

Notes prepared by: FBE Computer Science Department.

18