Appendix 1, Section 3
Classes and Objects in C++
C++ IS BASED ON AN EARLIER PROGRAMMING LANGUAGE called C. The main distinguishing feature of C++ is its support for object-oriented programming with classes and objects. (Although, in fact, C++ adds many other features to C as well.)
Classes in Java are modeled on classes in C++, but with many differences. The most obvious of these is due to the distinction between declaration and definition in C++. (See Section 1.) In C++, a class has two distinct components, which can be -- and most often are -- placed in different files. First of all, there is the class declaration, which is similar to a class in Java except that it omits the definitions of the methods. The class declaration contains only what are called prototypes for the methods. It also declares any variables that belong to the class. The class declaration is generally placed in a header file so that it can be used easily by other parts of the program. The second component of the class consists of the actual definitions of the class's methods. These definitions are separate from the class declaration, and usually occur in a separate file.
Here, for example, is a simple C++ class declaration:
class SimpleCalc { // A class for computing some statistics about // a list of numbers; the numbers are entered // into the data set one-by-one by calling the // method enter(). int count; // Number of items that have been entered. double sum; // The sum of items that have been entered. double sqSum; // The sum of the squares of all the items // that have been entered so far. public: // The following members of the class are // visible from outside the class. SimpleCalc(); // Constructor void enter(double dataItem); // Adds dataItem to the data set. int getCount(); // Returns the number of items that // have been entered into the data set. double getMean(); // Gets the average of the items that // have been entered so far. double getSD(); // Gets the standard deviation. void clear(); // Clears the data set, resets count to zero. }; // end of declaration of class SimpleCalcThis should look mostly familiar from Java, except for the omission of the bodies of the methods. The syntax for the "public" modifier is a bit different. Instead of being applied to individual items, the modifiers public, private and protected apply to groups of items. In the above example, all the methods that follow "public:" are public. Items at the beginning of a class, before any access modifier is specified, are private by default. (Also note the semicolon at the end of the class declaration, which is absolutely required. Making this semicolon optional was one of the things that the designers of Java did right.)
You can apply the "static" modifier to individual methods and variables in a class, with the same meaning as in Java. Unfortunately, neither static nor instance variables can be given initial values in a class declaration. (This is one of Java's more substantial improvements on C++.) Instance variables should be initialized in a constructor. There is a way to do initialization of static variables, but it has to be done outside the class declaration, as part of the definition of the class.
By the way, in addition to constructors, a C++ class can contain a special method called a destructor. Just as a constructor is called automatically by the system when an object is first created, a destructor is called by the system when the object is destroyed. For an object created with the new operator, this happens when the object is explicitly deleted with the delete operator. For objects that are not created with the new operator, the system is responsible for making sure that the object is destroyed at the appropriate time.
The purpose of a destructor is to do any cleanup that is necessary when the object is destroyed (beyond reclaiming the memory that is occupied by the object, which is being done in any case). For example, the destructor might close files that were opened in the constructor. Or if the constructor created an object with the new operator, the destructor can apply the delete operator to that object.
The name for a destructor is the same as the name of the class, preceded by the character '~'. A destructor for the SimpleCalc class would be named ~SimpleCalc. (Java does not have destructors. However, a class in Java can contain a "finalizer()" method which serves a similar purpose.)
Now, let's say that the declaration of the class SimpleCalc is in a header file named SimpleCalc.h. To complete the definition of the class, you would write another file -- probably called SimpleCalc.cpp or SimpleCalc.cc -- to give the definition of each of the methods in the class. Here's what would go in that file:
#include "SimpleCalc.h" #include "math.h" SimpleCalc::SimpleCalc() { count = 0; // initialize the instance variables sum = 0; sqSum = 0; } void SimpleCalc::enter(double dataItem) { count++; sum += dataItem; sqSum += dataItem * dataItem; } int SimpleCalc::getCount() { return count; } double SimpleCalc::getMean() { return sum / count; } double SimpleCalc::getSD() { double mean = getMean(); return sqrt( (1.0/count) * (sqSum - count * mean * mean) ); } void SimpleCalc::clear() { count = 0; sum = 0; sqSum = 0; }The first line of the file might surprise you. To compile the definition of the class methods, the compiler must know the complete declaration of the class. However, the C++ compiler never goes out and looks for information -- if it needs to know something that's in another file, then that file must be explicitly #included. So in this case, the header file SimpleCalc.h, which contains the declaration of the class, must be included.
The second line, which #includes math.h, is required since math.h declares the sqrt function, which is used later in the file.
The rest of the file consists of definitions for the six methods of SimpleCalc, counting the constructor. The only surprise here is that the name of each method is preceded by "SimpleCalc::". This identifies the definition as a method in the class SimpleCalc, rather than an independent subroutine. (The same notation, by the way, is used to access static members of a class. If SimpleCalc had a static variable named data, then outside the class it would be called SimpleCalc::data.)
To finish up this example, here is a main program that uses the class, SimpleCalc. This would be in yet another file, perhaps named main.cpp. Note that it includes the header file SimpleCalc.h, which declares the class, but it does not include the definitions of the methods in the class, which are defined in the file SimpleCalc.cpp. These definitions are inside the "black box," and the main program doesn't need to know them. This program also uses the standard iostream.h header file to get access to the standard input and output streams, cout and cin.
#include "SimpleCalc.h" #include "iostream.h" main() { // Main routine for the program. // Reads a list of numbers input by the user and // prints some simple statistics about the data. SimpleCalc calc; // A SimpleCalc object to keep track // of the data and compute the statistics. double x; // A number entered by the user cout << "Enter some numbers.\n"; cout << "Enter 0 to end.\n"; do { cout << " ? "; cin >> x; if (x != 0) calc.enter(x); } while (x != 0); cout << "You entered " << calc.getCount() << " numbers.\n"; cout << "The average is " << calc.getMean() << ".\n"; cout << "The standard deviation is " << calc.getSD() << ".\n"; }One important note is that the SimpleCalc object is created without using the new operator. In C++, a variable of object type, such as calc in this program, holds an object and not just a reference to an object. When such a variable is declared, an object is constructed and is stored in the variable. If the constructor requires parameters, they are provided as part of the variable declaration. For example, if the constructor in the class SimpleCalc required a parameter of type int, the declaration of calc would take this form:
SimpleCalc calc(10);This is very different from Java. To get behavior more similar to what happens in Java, you have to use pointers to objects. For example, you could declare a variable pcalc that can hold a pointer to an object of type SimpleCalc:
SimpleCalc *pcalc;In this case, just as in Java, this does not automatically create an object for pcalc to point to. For that you need the new operator:
pcalc = new SimpleCalc();One odd thing that happens in C++ is that when you use a pointer to refer to an object, the notation for referring to instance variables and methods in the object also changes. Since *pcalc is the name of the object, you could say "(*pcalc).clear()" to refer to the clear() method in the object *pcalc. (The parentheses are necessary because *pcalc.clear() would be taken to mean *(pcalc.clear()). The '.' operation has higher precedence than the '*' operation.) The notation (*pcalc).clear() is rarely used, however, because the alternative notation "pcalc->clear()" is defined to mean the same thing. The operator "->" means "first follow the pointer to find an object, then access a member of the object.
To have real object-oriented programming, of course, you have to have inheritance and polymorphism. Inheritance is easy. Just as in Java, you can declare one class to be a subclass of another. For example, to declare ComplexCalc to be a so-called "public" subclass of SimpleCalc, you would say:
class ComplexCalc : public SimpleCalc { . . // (additions and modification to stuff . // that is inherited from SimpleCalc) . }This would be equivalent to saying in Java that ComplexCalc extends SimpleCalc. Just as in Java, ComplexCalc inherits everything that is contained in the definition of SimpleCalc and it can modify and add to what it inherits. (There are also "private" and "protected" subclasses, but I won't try to explain them here.)
Polymorphism, unfortunately, is a bit murky in C++. First of all, polymorphism works only with pointer variables and variables of reference type, never with variables that contain actual objects. A pointer variable can point to objects belonging to some specified class or to a subclass of that class. So, you would expect a subroutine call statement of the form ptr->doSomething() to be polymorphic. That is, you would expect that the doSomething method that is called will depend on the type of object that ptr is actually pointing to at the time the statement is executed. However, this will happen only for so-called virtual methods. You can declare a method to be virtual by adding the modifier virtual to the declaration of the method. What you are saying when you do this is that the method is meant to be polymorphic. If you think this is confusing, don't worry too much about it -- I've talked to people who had studied C++ for quite a while and who still could not correctly explain exactly what virtual means. Just remember that if you want to do real object-oriented programming in C++, then you have to use pointers and virtual methods. You might also remember this as one more reason to appreciate Java, where polymorphism is automatic and natural.
This has been just a brief introduction to classes in C++, which have many features that I haven't even mentioned. More generally, C++ is a very large and complex language, which takes a long time to master. I certainly haven't told you enough about it to enable you to write serious programs in C++, but I hope I've given you some of the its general flavor and a sense of how it differs from Java.
End of Appendix 1
| Previous Section | Appendix Index | Main Index ]