![]() |
Essentials of C++: Section XI |
Classes Another way to extend the C++ type system is through the use of
class declarations. The class system is C++'s way of implementing
object-oriented programming . The
class system therefore allows for encapsulation, inheritance,
and polymorphism. A. Encapsulation B. Inheritance C. Polymorphism D. Class Declarations
The placement of the public and private sections can be swapped.
Everything placed in the private part is encapsulated. Variables declared
inside a class represent the data or properties of instances of the class
and usually are declared in the private part of the class. Such variables
are often referred to as data members or member data. Functions declared as part of a class are often referred to as
member functions. Member functions that other parts of the
program must access for any purpose, including to get access to or
manipulate the private data members of instances of the class, must be
declared in the public part of the class. Functions used only by the
member functions themselves and which are otherwise unavailable to the
program should be declared in the private part. Aside from their placement inside a class, member data and function
declarations are the same as non-member declarations. There are, however,
two special types of member functions. First, all classes need one or more
constructors. These are functions automatically called when an
instance of a class is declared. Their purpose is to create or 'construct'
an instance of the class. The data members of the instance are
initialized according to the instructions in the constructor function.
C++ will create a default constructor that simply creates the instance,
but, usually, the programmer creates one or more constructors for a class
to perform initializations unique to instances of that class. Constructors
always have the same name as the class they are members of and they have
no return type - not even 'void'. Their parameter list looks the same as
any other function declaration with the parameters themselves usually
being used to provide the initial property values for the instance being
created. For example, if one is declaring a 'person' class, the
constructor declaration might be:
If we assume that the 'person' class includes the data members: then one could interpret the parameter list above to indicate that the
parameter 'nm' holds the value to be given to the data member 'name; 'ag'
holds the value to be given to 'age', 'streetAddr' holds the value for
streetAddress, etc. As with any other set of variables, the parameter
names and data member names must be different. For that reason, 'stte',
for example, is used in the parameter list so that 'state' could be the
data member name. All constructors for a specific class have the same name and the same
return type. It is via function
overloading that multiple constructors are created. Unlike other member functions, constructors cannot be inherited. It is
possible, however, for a constructor of an inheriting class to call the
inherited class's constructor. Since constructors are used in instance
declarations (see Part D below), they should be declared in the public
section. Note: constructors are not called except indirectly as part of
instance declarations and as part of constructors for inheriting
classes. When initializing an instance with another
instance (as might happen when declaring an instance and immediately
initializing it with the values from another instance) and when passing an
instance as a value parameter to a function, the property values of one
instance are copied to another instance. Such copying requires a copy constructor. C++
automatically provides one that works as long as the class declaration
does not include pointer, class, struct, or array-based data members. In
these situations it may be necessary for programmers to declare their own
copy constructors. The second special type of member function
declaration is the destructor. Any instance for which special code
must be executed when the instance disappears must have a destructor
containing that special code. While there can be multiple constructors -
based on the idea of function
overloading, there can be only one destructor per class. It is called
whenever the duration of a class instance ends. The format for the destructor is: Note that destructors always start with a tilde (~), use the class name
(as with constructors), have no return type, and no parameters. They
cannot be inherited but they can be explicitly called. A simple declaration for the 'Person' class might be:
class Person
{public:
Person(char* nm, int ag, char* streetAddr, char* cty, char*
stte, char* zip);Discussion in "The Story of C++ E. Class Definitions Since the system must know that a specific definition belongs to a
specific class, the scope resolution operator, '::', is used with
member function definitions. To define the member function that returns
the age of a person one might write: To define the member function 'ChangeAge', one might write: Note that the first line of each definition is the same as the
corresponding declaration except that the class name and scope resolution
operator are inserted. The same approach is used to define constructors
and destructors. Below are incomplete definitions for each:
Person :: Person(char* nm, int ag, char* streetAddr,
char* cty, char* stte, char* zip)
{
// the code for the constructor would go here
}
Person :: ~Person()
{
// the code for the destructor would go here
}Discussion of the scope
resolution operator in "The Story of C++"
F. Inline Member Functions One line member functions are ideal candidates for inline functions. To
re-code 'ChangeAge' as an inline function one would write the following in
the class declaration and remove the definition code: The code that was in the definition has been moved into the
declaration. Note that the declaration does NOT end with a
semi-colon. G. Creating Class Instances Note that the instantiation process automatically calls a constructor.
If a class declaration has multiple constructors, the one called is
determined using the same rules used in other function overloading. If the
constructor to be used has no parameters, the parentheses are dropped. To
declare an instance of class 'Person' one might write: This instantiation process can be seen as declaring and initializing
the identifier 'pablo' as a variable of type 'Person' with the property
values shown. A variable such as 'pablo', which represents an instance of
a specific class, is ofter referred to as an instance variable. One must be careful when creating an array of instances of a class type
because one is instantiating as many instances of the class as there are
elements in the array and a constructor must be called for each instance.
The following code will not work: because there is no constructor for the class 'Person' that has no
parameters. One can fix this by declaring and defining a constructor with
no parameters or by defining a constructor with default values (Section III.A) for all
parameters. Either way, the properties for each instance of 'Person' in
the array may not be what the user wants. If so, the program must include
explicit code for giving values to these properties. H. Using Instances The private members of an instance are simply unaccessable from outside
the instance. To access the public members of an instance one can use the
dot operator: When the public member is a function (which is most likely to be the
case since data members tend to be private), the part of the code to the
right of the 'dot' is written the same as any other function call. For
example: or In the language of object-oriented programming, function calls like
this are often referred to as messages. Using this terminology one
can interpret the above as: or In other words, the code is explicitly asking that something be done by 'person1' or 'person[3]'. This is how the system determines which instance is being referred to. Both these function calls really have an implicit or hidden argument - the specific instance under consideration. At times one needs to refer explicitly to this instance. Therefore, each member function has a built-in local variable referred to as this which is a pointer to the instance "sent the message". A common use for 'this' is in member functions that need to return the instance itself. When using pointer references to class instances, the dot operator
notation can be cumbersome because of the precedence rules. Consider the
example: The parentheses must be used because the dot operator has higher
precedence than the indirection operator. To simplify matters C++ provides
another operator for accessing members, ->. Using this operator
the above code becomes: I. The Syntax of Inheritance This declares 'some-new-class' as a derived class type
inheriting from base class, 'the class-inherited-from'.
Since the new class only inherits from one class, we have an example of
single inheritance. To provide some control over what gets inherited and the kinds of
access users of the derived class have to members of the base class, C++
includes additional keywords both for the base class declaration and the
inheritance declaration. In addition to the public and private sections of a class declaration,
there can be a protected section. Private members of a base class
are not directly accessible to member functions of derived classes. They
can only be accessed through the base class's interface. To allow member
data and functions to be directly accessible to a derived class but not
accessible outside the class, such member data and functions should be
placed in the 'protected' section. This level of access control is handled by the base class. The
declaration of the derived class can further refine access by adding the
words 'public', 'private' or protected in front of each base class
inherited from. The word 'public' signifies that there is no change in
access either by users or classes that inherit from this derived class. On
the other hand, 'private' signifies that even 'public' and 'protected'
members of the base class are now treated as if they were private. When
'protected' is used, the public or protected members of the base class
become protected. Note that in all cases, private members in the base
class remain private. The default is 'private' so 'public' must be used if one wishes to keep
access rights the same. For documentation purposes it is recommended that
the desired option be included in the declaration. The basic form of a
declaration involving single inheritance now becomes: Thus, to declare a new class 'Student' that inherits from 'Person' and
allows the same level of access to the protected and public members of
'Person', one would write:
class Person
{public:
Person(char* nm, int ag, char* streetAddr, char* cty,
char* stte, char* zip);
~Person();
char* ProvideName();
int ProvideAge();
// 'Provide...' functions for other properties
void ChangeName(char* nm);
void ChangeAge(int ag);
// 'Change...' functions for the other properties
protected:
char* name;
int age;
char* streetAddress;
char* city;
char* state;
char* zipCode // zip codes are not handled as integers so
// they are declared as strings
};
class Student : public Person
{
// member declarations new to class Student
}Although not used as often, it is possible to have multiple
inheritance - where one class inherits from more than one class. There
are a number of issues involved in this and you are encouraged to check
other references for a more thorough discussion. Syntactically, it is easy
to accomplish. You simply add a comma followed by the access type (public,
protected, or private) and the class name for each additional base class.
If class 'X' is to inherit from classes 'Y' and 'Z' one might write:J. Friend Functions To declare that a function is a friend of (has full access to) a class,
one includes the declaration of that function in the class declaration
with the word 'friend' in front of the function declaration. By putting
the word friend here, you are declaring that this function is NOT a member
function of the class but it does have access to the private and protected
members of that class. The definition of the friend function may or may
not be included in the file containing the definitions for the class's
member functions. One can also declare a whole class to be a friend of another class.
This allows ALL member functions of the friend class to have full access
to all the members of the other class. The general form for this is: where this line is placed inside a class declaration and
'some-class-name' is the name of a class known in the file holding
the class declaration. If the class 'Teacher' was to be declared as a
friend of 'Student' one could write: The first line of this example is an incomplete declaration of the
'Teacher' class. It acts as a promise that the class is declared
somewhere. (The other way to accomplish the same goal would be to use the
'include' preprocessor directive to copy in the code for the declaration
of 'Teacher'.) By including 'Teacher' as a friend of 'Student', any
instance of 'Teacher' has full access to ALL the members of instances of
the 'Student' class. (Sounds right - don't students feel they have no
privacy from teachers and administrators!) K. Static Members This data member exists whether or not any instances of 'Person' exists
- which is not true of non-static data members because they belong to
specific instances. This means that a static variable requires both a
declaration (just given) and a definition. The definition must be outside
the class declaration, usually in the file for the member functions. The
form is: as in: This form can be extended to include an initialization as in:
One can access static variables in the same way one can access other
data members - following the same rules about public, protected, and
private membership. In addition, since these variables exist independent
of any specific instance, one can declare static member functions to
access them. Such functions are declared the same as member functions
except that they too have the keyword 'static' placed before the
declaration. To declare a static member function to update the 'oldestAge'
the following code would be placed in the public section of the class
declaration: To call this function one uses the class name instead of an instance
name as in: Such functions, since they do not refer to a specific instance, do not
have access to the data members of the class. L. Implementing Polymorphism In C++ polymorphic functions can only be those belonging to classes
involved in an inheritance structure. Such a structure represents
the one or more base classes and all the classes that derive directly or
indirectly from this (these) base classes. (See Chapter 11, Section 1, Figure 2
for an example of an inheritance structure.) To state which functions are
to have their linkage postponed until run-time, one adds the word
virtual in front of the highest declaration of this function in the
hierarchy. This text does not deal further with polymorphism and you should check
elsewhere if you want to include polymorphic functions in your
code. Links to 'The Story of C++" and other documents
|