Until now, the programming you have done has been "traditional" programming. In the last lesson, you saw that working with structures and memory allocation started to get complicated, especially when keeping track of all that memory. In this
lesson, you'll see that C++ provides you with a number of useful tools to help you contain this complexity.
The gathering of a number of special techniques has come to be called object-oriented programming (OOP). In this unit, you will see what this really means and start learning some new C++ tricks.
When working with computers, it is very important to be fashionable! In the 1960s, the new fashion was what were called high-level languages such as FORTRAN and COBOL, in which the programmer did not have to understand the machine instructions.
In the 1970s, people realized that there were better ways to program than with a jumble of GOTO statements, and the structured programming languages such as PASCAL were invented. (PASCAL looks and works much like the C++ you have seen so far.) In the
1980s, much time was invested in trying to get good results out of fourth-generation languages (4GLs), in which complicated programming structures could be coded in a few words (if you could find the right words with so many words to choose from).
There were also schemes such as Analyst Workbenches, which made systems analysts into highly paid and overqualified programmers. The fashion of the 1990s is most definitely object-oriented programming.
Read any book on object-oriented programming, and the first things you will read about are three words:
Well, that's enough to scare you away from OOP before you start! Don't panic, though. Before you do the spelling test at the end of the unit, take comfort in the fact that only the most difficult to spell, polymorphism, is a really novel programming
language feature. As you will see, there is not much in OOP that isn't common sense, although it might seem to be black magic at first.
Let's take a look at each one of those words and find out what they really mean.
Definition
Encapsulation means hiding away the workings of your code.
Encapsulation means that you hide away the inner workings of your system and present a well-defined interface to the rest of the world that tells it only what it needs to know.
A good real-world example of encapsulation is a wristwatch. It is important to know what the time is. The way your watch keeps timewhether it is battery-powered or has a spring and lots of cogsis not important as long as the accuracy is
there. You look at the face, and that tells you all you need to know.
Writing good code means not only that it is fast and does what it is supposed to do (bug-free), but also that it can easily be maintained and amended. Over the years, programmers have come to recognize that hiding the workings of one piece of code away
from another helps the programmer change code, fix errors, or improve its workings without breaking some piece elsewhere in the program.
Another example of encapsulation is the PC that you've been doing battle with over the lessons in this book. IBM-compatible PCs are examples of a set of encapsulated hardware systems. You can buy screens, printers, and disk drives from different sources
and plug them together, and they work. You might have spent three weeks trying to install your sound card last month, but normally it is simple. When a standard interface has been defined to the rest of the electronic jungle that is your PC, you have a
fighting chance. You can buy a bigger screen and a laser printer to replace your dot-matrix printer, plug them together, and they talk. The different units know how to talk to each other; you don't need to wire your disk drive into your screen and your
printer into the sound card to make everything work. (That might seem like a stupid remark, but real programmers often write code that does the software equivalent of just that.) When the workings of a device have not been successfully encapsulated, your
troubles begin.
Definition
Inheritance is the capability to borrow pieces of code to reuse.
Inheritance is the process of taking an object that does most of the job that you want it to do and adding some extra bits to do a more useful or specialized job. For instance, in the example of the wristwatch, you could take an ordinary wristwatch and
add a date display to it. The world of consumer durables does this all the timetaking a basic model and adding extra features that give lots of added value for very little effort on the manufacturer's part.
It is a lot easier to build a car with an automatic gearbox by throwing away the old manual gearbox and adding a new automatic box, than it is to design a complete car from scratch. You might need to throw away some old parts, but usually the car
designer has taken the option into account in the first place. The extra benefit is that if the designer wants to change features of the two "different" cars, he can change a single design and both versions are updated. In the world of
programming, Visual C++ gives you some tools to easily borrow some code you have already written and add or change parts. If the underlying way it works is changed, only one set of code needs changing.
Definition
Polymorphism is the capability to ask different objects to perform the same task and have the object know how to achieve that task in its own way.
Polymorphism is the most novel idea in OOP. It is easiest to explain by an example. You can press the Play button on a CD player, VCR, or cassette player and each will play some sound (one will also give you a picture). You don't have to understand how
they work; you just understand the interface.
In Visual C++, this is implemented so that you can ask an object to perform a function, and depending on what type of object you ask, the function responds in a different way. The classic programming example is that of a graphics program in which you
create objects such as circles, rectangles, and triangles. The drawing program is written to store shapes and know where the shapes should be placed on the drawing. When it is time to print, it only has the code to tell the object where to print, and it
asks each object to draw itself in the right place. By defining the right interface, the program can be provided with more and more shapes and the main program does not need to change.
Definition
Object-oriented programming is a productivity tool.
So you have read about all of these wonderful concepts. Why choose these features? Each of these features is a step on the road to reliable and productive programming. By using prebuilt libraries of code, you can save time and still have the flexibility
of altering the way that they work to suit your own needs. Comparing C++ with C, you will find that there are lots of extra features that encourage putting thought into structuring programs so that they are more maintainable. By gathering code into what
C++ calls classes, the language helps you divide large programs into small manageable sections, in the same way that you divide small programs into functions. This is very important, because the difficulty of understanding pieces of code increases
exponentially (in other words, a lot!) as the pieces of code get bigger and bigger. C++ does not guarantee productivity and maintainability, but it provides you with some tools to help you along the path.
There is no Stop and Type here due to the textual nature of this section.
The class is the cornerstone of object orientation in Visual C++.
Cast your mind back to when we looked at structures. Do you remember the comment about a structure being a gathering of related data items? With a quick change in terminology, you can change a structure into a class and a structure variable into an
object. An object needs two things:
You already know how to make class data. In C++, a structure is a special case of a class. To make a class, change the struct keyword to class and add a special extra label, public, and you've done it!
class C { public: int i; float f; char str[30]; };
Remarkable! It is almost identical to a dull old structure, but now it is a shiny new object-oriented class. Don't worry about that public word yet; we'll deal with that before the end of the unit. Just don't miss it. What's the catch? There isn't one!
Let's define an object:
C anObjectOfTypeC;
So you've created an object called anObjectOfTypeC. All the rules for accessing data members are the same as for structs. Let's not labor the point and move straight to what's new about classes.
A class can hold functions as well as data
Recall, back in Lesson 9, that you built a program dealing with contacts (Listing 17.2). There were two functions in this program that were prototyped like this:
int AddContact(Contact& contact); void DisplayContact(const Contact& contact);
Both of these functions had the sole purpose of taking a Contact struct (or a contact object) and providing a function that used that object. All they need to know is what Contact they are operating on. You want to tidy up your program so that all the
code is encapsulated and related. At the moment, these functions are available globally, but they are useless without a Contact object. Visual C++ provides a way of declaring the function so that it is only available when there is a class object to use it
with.
A function that is part of a class is called a member function, in the same way that a variable is called a data member.
class Contact { public: char name[50]; int age; char phoneNo[15]; void AddContact(); void DisplayContact(); };
The only odd thing about these declarations is that the Contact object has been removed. This is because, to use member functions, you use the member access operatorsthe dot operator (.) and the pointer operator (->)to tell Visual C++
which data to work on. In main() the code could look like this:
Contact contact; contact.Add(); contact.Display();
That looks familiar! Now you understand what those funny cout.precision() calls were about.
The class definition only declares the member function; it does not define how the function works. It is time to meet a new operator, ::, which is called the scope operator. If you try to define the functions as before, Visual C++ lets you, but all
sorts of problems will arise because Visual C++ doesn't realize that you meant to define the member functions. You tell Visual C++ the class that the function belongs to by adding the class name and scope operator to the function name.
void Contact::Display() { // body of function
The class name precedes the function name, not the return type.
Now that you have taken away the Contact parameter, you need to access the members in a different way. In a member function, Visual C++ knows which object was used when calling the function, so member access is easy. Within a member function, you can
access all the class data members and member functions directly, and you get the data for the object that calls that member function:
void Contact::Display() { cout << "Name : " << name << endl; cout << "Age : " << age << endl;
You can access member functions in the same way that you access the data members.
We've covered enough to look at an example. Let's turn the contact program into an object-oriented version in Listing 19.1.
1:// Filename: CONTACTO.CPP 2:// Allow the display and entry of contacts 3:// using OOP techniques 4:// 5:#include <iostream.h> 6: 7:class Contact 8: { 9: public: 10: char name[50]; 11: int age; 12: char phoneNo[15]; 13: 14: // Member function declarations (prototypes) 15: void AddContact(); 16: void DisplayContact() const; 17: }; 18: 19:// Maximum number of contacts 20:const int MAXCONTACTS = 10; 21: 22:// Global Prototypes 23:void DisplayAllContacts(const Contact contacts[MAXCONTACTS],int count); 24: 25:void main() 26: { 27: Contact contacts[MAXCONTACTS]; 28: char answer; 29: int count = 0; 30: while( count < MAXCONTACTS )// while more room 31: { 32: // Ask the user if they want to continue 33: cout << "Do you want to add a contact [Y]es/[N]o: "; 34: cin >> answer; 35: cin.ignore(80,'\n'); // skip rubbish 36: 37: if (answer == 'y' || answer == 'Y') 38: { 39: // Add the information 40: contacts[count].AddContact(); 41: count++; 42: } 43: else 44: break; 45: } 46: 47: DisplayAllContacts(contacts,count); 48: 49: } 50:// 51:// DisplayContact displays a single name 52:// 53:void Contact::DisplayContact() const 54: { 55: cout << endl; 56: cout << "Name : " << name << endl; 57: cout << "Age : " << age << endl; 58: cout << "phone no: " << phoneNo << endl; 59: cout << endl; 60: } 61:// 62:// DisplayAllContacts calls DisplayContact to show 63:// all entered names 64:// 65:void DisplayAllContacts(const Contact contacts[MAXCONTACTS],int count) 66: { 67: for (int i = 0; i < count; i++) 68: { 69: contacts[i].DisplayContact(); 70: } 71: } 72:// 73:// Add contact asks for one contact 74:// 75:void Contact::AddContact() 76: { 77: cout << "Name: "; 78: cin.getline(name,30); 79: cout << "Phone no: "; 80: cin.getline(phoneNo,15); 81: cout << "Age "; 82: cin >> age; 83: cin.ignore(); 84: }
Output
Do you want to add a contact [Y]es/[N]o: y Name: Ian Spencer Phone no: 0-672-30600-X Age 36 Do you want to add a contact [Y]es/[N]o: y Name: Konrad Borkowy Phone no: Ex-directory Age 41 Do you want to add a contact [Y]es/[N]o: y Name: Wendy Smith Phone no: 111-1111 Age 40 Do you want to add a contact [Y]es/[N]o: no Name : Ian Spencer Age : 36 phone no: 0-672-30600-X Name : Konrad Borkowy Age : 41 phone no: Ex-directory Name : Wendy Smith Age : 40 phone no: 111-1111
Analysis
The program works much the same as it did last time. Let's go through the special OOP differences. Lines 7 to 17 declare the class. The only differences are the public label on line 9 and the member functions in lines 15 and 16. These function
declarations are similar to the global declarations. There is an extra feature on DisplayContact. In the original CONTACT.CPP, DisplayContact() took a constant parameter. Because there is no parameter, C++ needs a new way to say "I will not change the
object I'm operating on." This is done by adding an extra const keyword after the function name (in both the declaration in line 16 and the definition in line 53).
Note that the definition in line 53 uses the scope operator (::)to tell C++ which class this function definition belongs to. This means that you can have the same function in different classes and C++ can tell them apart.
The main() code is very similar to the original program. Notice that the main() code does not reference any of the object's data items. All the data items are referenced in the member code of AddContact() and DisplayContact(). The interesting part of
the code in the member functions is that within a member function, the function has access to the class member data items without qualificationfor example, name in line 56.
There is nothing stopping the code in main() from directly accessing the data members of the class. This is a source of great danger, and you haven't gained a great deal of safety from putting the code into the class.
In this book, I have not used header files for the code. Normally, a C++ programmer places the class definition (such as lines 7 to 17 in the previous code) in a header file called CONTACT.H. Then the definition is included by using #include "contact.h" in the main code. The quotation marks mean that C++ should look for the file in the current directory first. This enables you to split code that uses the class into many CPP files rather than having one long file. You can build several files into one executable by using the Visual Workbench's project facility. (Projects are explained in a comprehensive section in the Visual Workbench online help, which you can easily find from the help contents page.)
Classes have a new sort of scope: class scope.
When writing code within a member function, the code can access all the members of the current object as if they are local variables. How does Visual C++ decide which object's members the function is working with when there is more than one member? The
access to members is always with the object that the function is called with, as in the following example:
class A { public: int x; void SetX(int X); int GetX(); }; void A::SetX(int X) { x = X; } int A::GetX() { return x; } void main() { A a1; a1.SetX(5); A a2; a2.SetX(10); int y = a1.GetX(); int z = a2.GetX(); }
In each access of the class member function, the programmer specifies which object the member function is associated with. So although GetX() only refers to x inside the function, C++ knows to access a1.x or a2.x because the function, when called,
remembers which object was referred to.
Even though you have class functions, you can still access the members in the normal way. Outside member functions (or outside class scope), you need the member access operators; inside the class, access is given automatically.
However, you need to remember one of our long words, encapsulation. If everyone can get at data members, there is no advantage to classes over structures. To control access to both data members and function members, Visual C++ provides the access
labels public, protected, and private to declare what is available to the world outside the member functions. public means that the members are available for access by the class member access operators; private and protected are only available to member
functions. A class can contain as many of these access labels as you like.
The reason for having private members is that the class can ensure that only sensible values are set. For example, a Person class might have an age member. If age was a public member, any programmer could set the age to an unreasonable amount. If age is
private, the only functions that can access age are the classes own functions. This doesn't mean that the age can't be set from outside the class, only that it has to be set by passing a parameter of a member function and then assigning it into the member.
Similarly, to get a private member out, another accessing function is provided. This provides two benefits:
If age has a wrong value, only the class member functions need to be looked at to understand how this could happen. Correcting this might mean adding code into the access functions to validate the data passed to the class by the function. After the
access functions are provided, the class can change the way the data member is held, and the outside world need not be aware of the change. For example, your Person class might hold a date of birth. Externally, it might be convenient to pass this as a
character array; internally, it might be better to hold it as a number for calculations. The outside world never needs to know that the date is held as a number, as long as the access functions know how to convert the data.
Member functions provided just for giving and receiving data are known as access functions.
A struct is not really a separate mechanism in C++. It is a special case of the class. The difference between a struct and a class is that in a struct, the default access without a label is public, and the default access for a class is private. By
convention, structs are only used to represent a data-only class.
The final listing of this section, Listing 19.2, shows a simple class for handling retirement ages.
1:// File name: PERSON.CPP 2:// A simple class for handling age of a person 3:// 4:#include <iostream.h> 5:#include <string.h> 6: 7:// Class declaration 8:class Person 9: { 10: // Public - available like a struct member 11: public: 12: // Set up data 13: int AskForPerson(); 14: void SetRetirementAge(int year); 15: // Get the name, but const char * stops the 16: // pointer being used to change the name 17: // and the following const after GetName means 18: // that the class will not be changed by calling 19: // this function 20: const char * GetName() const; 21: int GetAge() const; 22: int YearsToRetirement() const; 23: // Private - only member functions of this class 24: // can see the following members 25: private: 26: char name[30]; 27: int age; 28: int retirementAge; 29: }; 30: 31:int MoreNames(); 32: 33: 34:void main() 35: { 36: // Array to hold up to 20 people - initialized to 0 pointers 37: Person * people[20] = {0}; 38: int more = 1; 39: int count = 0; 40: 41: while (more) 42: { 43: more = MoreNames(); // Ask whether more input 44: if (more) 45: { 46: people[count] = new Person; 47: if (people[count]->AskForPerson()) 48: count++; 49: else 50: { 51: cout << "** Invalid input - rejected **" << endl; 52: delete people[count]; // Tidy up problem 53: } 54: } 55: } 56: 57: // 58: // Output collected data 59: // 60: cout << endl << endl 61: << "Name Age" 62: << " Years to Retirement" << endl; 63: 64: for (int i = 0; i < count; i++) 65: { 66: cout << people[i]->GetName(); 67: for (int j = strlen(people[i]->GetName()); j < 31; j++) 68: cout << ' '; 69: cout << people[i]->GetAge(); 70: if (people[i]->YearsToRetirement()) 71: cout << " " << people[i]->YearsToRetirement() << endl; 72: else 73: cout << " " << "Retired" << endl; 74: } 75: 76: for (i = 0;i < count; i++) // Tidy up dynamic data 77: delete people[i]; 78: } 79://************************************************************ 80:// 81:// Global functions 82:// 83: 84:int MoreNames() 85: { 86: char temp; 87: cout << endl << endl << "Do you want to add more names? "; 88: cin >> temp; 89: cin.ignore(); 90: if (temp == 'y' || temp == 'Y') 91: return 1; 92: else 93: return 0; 94: } 95://************************************************************ 96:// 97:// Person functions 98:// 99:int Person::AskForPerson() 100: { 101: cout << endl << "Name: "; 102: cin.getline(name,30); 103: if (strlen(name) == 0) 104: return 0; 105: cout << "Age : "; 106: cin >> age; 107: cin.ignore(); 108: if (age < 0 || age > 120) 109: return 0; 110: cout << "Retirement age [0 for default]: "; 111: cin >> retirementAge; 112: if (retirementAge <= 0 || retirementAge > 120) 113: retirementAge = 65; 114: return 1; 115: } 116: 117:const char * Person::GetName() const 118: { 119: return name; 120: } 121: 122:int Person::GetAge() const 123: { 124: return age; 125: } 126: 127:int Person::YearsToRetirement() const 128: { 129: if (age >= retirementAge) 130: return 0; 131: else 132: return retirementAge - age; 133: }
Output
Do you want to add more names? y Name: Phill Dorrell Age : 29 Retirement age [0 for default]: 0 Do you want to add more names? y Name: Neil Hoskins Age : 33 Retirement age [0 for default]: 0 Do you want to add more names? y Name: Stefan Winman Age : 28 Retirement age [0 for default]: 60 Do you want to add more names? y Name: Trevor Cox Age : 124 ** Invalid input - rejected ** Do you want to add more names? n Name Age Years to Retirement Phill Dorrell 29 36 Neil Hoskins 33 32 Stefan Winman 28 32
Analysis
The program is similar to the Contact program listed earlier. In this program, the class protects its own data. The class definition in lines 8 through 29 divides up its functions and members. The labels in lines 11 and 25 tell Visual C++ how to treat
the following declarations. In this case, no outside function is allowed to see any of the data of class Person declared in lines 26 through 28 after the private label.
The main loop knows nothing about the class and how the data is held. Line 37 takes advantage of the initialization feature to initialize each element of the array to zero.
The class provides the functions GetName(), GetAge(), and YearsToRetirement() to allow nonmember functions to see the information. Because the return type is a copy of the data, the nonmember functions can't change the internal data. GetName() returns a
const char *, which you'll recall was used for parameters to ensure that the data pointed to by a character pointer was not allowed to be changed.
The const after the parameter list of the functions for getting the data (for example line 117) does not mean that you can't change the returned data. Instead, it means that you are guaranteeing that the function will not change the data inside the
class when the function executes. Visual C++ can't tell this for itself. This allows Visual C++ to let you call functions against data items that are constant:
const Person p; p.AskForPerson(); // Invalid, updates Person p.GetName(); // Valid, does not change Person
The storage of name in line 26 limits the class to a maximum of 29 characters for a name. In fact, there is no need for the limit. In AskForPerson() (lines 99 through 115) you could get the input into a temporary character string, dynamically allocate a
string of the length to match the input, and hold a pointer to the string as a class member. Although you can do this to create the string and no external program needs to be aware of the change in the way the string is stored, you would need to introduce
a function to tidy up the data, deleteing the character string before deleteing the Person structure. This is very important because without such a function, the application loses memory. The need to tidy up a class before it is deleted is a common and
important problemso important that we will look at it in the next unit.
The other problem with this class occurs if a new Person object is created, but the AskForPerson() function is not called. Following functions could go seriously wrong because the string and age data would not have been initialized. Because the data is
private, there is no way that the main() function can initialize the data, and in any case the user of the class should not know how the data is held. This is another important hole to fill if you want to ensure that your classes are robust and error free.
The simple function in lines 127 through 133 shows how you can start to protect external functions from errors. For a start, line 132 knows how to calculate retirement ages. If Person was a simple structure, the user would have to remember that was the
calculation. Furthermore, in line 129, the program ensures that it does not give negative numbers back to the calling function. In future revisions of the program, it might be useful to change the default logic of retirement ages to take into account
different rules for different types of employees. Although some changes might be needed to get this information, other programs that just use the YearsToRetirement() function would not need to be changed to account for the new rules. This means that you've
started to succeed in your objective of encapsulation, keeping all the code that is specific to the class hidden away inside member functions.
Given the code
class A { public: int a; int b; void Update(int a); }; void main() { A a; a.Update(3); a.b = a.a; a.b++; int c = a.a; } void A::Update(int A) { a = A; b = 0; }
what is the value of the following:
class A { int GetA(); void SetA(int a); int a; }; void main() { A a; a.SetA(5); int b = a.GetA(); } int GetA() { return a; } void SetA(int A) { a = A; }
class B { private: char b[30]; public: void SetB(const char B); } void main() { B b; b.SetB("Hello there!"); }
class C { public: int a; void SetA(int a); }; void main() { int a; SetA; }