1: Start with the following class declaration:// base class class Cd { // represents a CD disk private: char performers[50]; char label[20]; int selections; // number of selections do
Trang 11: Start with the following class declaration:
// base class class Cd { // represents a CD disk private:
char performers[50];
char label[20];
int selections; // number of selections double playtime; // playing time in minutes public:
Cd(char * s1, char * s2, int n, double x);
Cd(const Cd & d);
Cd();
~Cd();
void Report() const; // reports all CD data
Cd & operator=(const Cd & d);
};
Derive a Classic class that adds an array of char members that will hold a string identifying the primary work on the CD If the base class requires that any functions be virtual, modify the base class declaration to make it so If a declared method is not needed, remove it from the definition Test your product with the following program:
#include <iostream>
using namespace std;
#include "classic.h" // which will contain #include cd.h void Bravo(const Cd & disk);
int main() {
Cd c1("Beatles", "Capitol", 14, 35.5);
Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C", "Alfred Brendel", "Philips", 2, 57.17);
Cd *pcd = &c1;
cout << "Using object directly:\n";
Trang 2c1.Report(); // use Cd method c2.Report(); // use Classic method cout << "Using type cd * pointer to objects:\n";
pcd->Report(); // use Cd method for cd object pcd = &c2;
pcd->Report(); // use Classic method for classic object cout << "Calling a function with a Cd reference argument:\n";
Bravo(c1);
Bravo(c2);
cout << "Testing assignment: ";
Classic copy;
copy = c2;
copy.Report() return 0;
} void Bravo(const Cd & disk) {
disk.Report();
}
2: Repeat exercise 1, but this time use dynamic memory allocation instead of fixed-size arrays for the various strings tracked by the two classes
3: Revise the baseDMA-lacksDMA-hasDMA class hierarchy so that all three classes are derived from an abstract base class Test the result with a program similar to the one in Listing 13.10 That is, it should feature an array of pointers to the abstract base class and allow the user to make runtime decisions as to what types of objects are created
4: The Benevolent Order of Programmers maintains a collection of bottled port To describe it, the BOP Portmaster has devised a Port class as declared below:
#include <iostream>
using namespace std;
class Port
Trang 3{ private:
char * brand;
char style[20]; // i.e., tawny, ruby, vintage int bottles;
public:
Port(const char * br = "none", const char * st = "none", int b = 0);
Port(const Port & p); // copy constructor virtual ~Port() { delete [] brand; }
Port & operator=(const Port & p);
Port & operator+=(int b); // adds b to bottles Port & operator-=(int b); // subtracts b from bottles, if //available
int BottleCount() const { return bottles; } virtual void Show() const;
friend ostream & operator<<(ostream & os, const Port & p);
};
The Show() method presents information in the following format:
Brand: Gallo Kind: tawny Bottles: 20
The operator<<() function presents information in the following format (no newline at the end):
Gallo, tawny, 20
The Portmaster completed the method definitions for the Port class and then derived the VintagePort class as follows before being relieved of his position for accidentally routing a bottle of '45 Cockburn to someone preparing an experimental barbecue sauce
class VintagePort : public Port // style necessarily = "vintage"
{
Trang 4char * nickname; // i.e., "The Noble" or "Old Velvet", etc.
int year; // vintage year public:
VintagePort();
VintagePort(const char * br, int b, const char * nn, int y);
VintagePort(const VintagePort & vp);
~VintagePort() { delete [] nickname; } VintagePort & operator=(const VintagePort & vp);
void Show() const;
friend ostream & operator<<(ostream & os, const VintagePort & vp);
};
You get the job of completing the VintagePort work
Your first task is to re-create the Port method definitions, for the former Portmaster immolated his upon being relieved
a.
Your second task is to explain why certain methods are redefined and others are not
b.
Your third task is to explain why operator=() and operator<<() are not virtual
c.
Your fourth task is to provide definitions for the VintagePort methods
d.
CONTENTS
Trang 5Chapter 14 REUSING CODE IN C++
You will learn about the following in this chapter:
Classes with Object Members Private Inheritance
Multiple Inheritance Class Templates Summary
Review Questions Programming Exercises
One of the main goals of C++ is to facilitate the reuse of code Public inheritance is one
mechanism for achieving this goal, but not the only one This chapter will investigate other
choices One technique is using class members that are themselves objects of another
class This is referred to as containment or composition or layering Another option is
using private or protected inheritance Containment, private inheritance, and protected
inheritance typically are used to implement has-a relationships, that is, relationships for
which the new class has an object of another class For example, a Stereo class might
have a CdPlayer object Multiple inheritance lets you create classes that inherit from two
or more base classes, combining their functionality
Chapter 10, "Objects and Classes," introduced function templates Now we'll look at class
templates, which provide another way of reusing code Class templates let you define a
class in generic terms Then you can use the template to create specific classes defined for
specific types For example, you could define a general stack template and then use the
template to create one class representing a stack of int values and another class
representing a stack of double values You could even generate a class representing a
stack of stacks
Classes with Object Members
Let's begin with classes that include class objects as members Some classes, such as the
Trang 6String class of Chapter 12, "Classes and Dynamic Memory Allocation," or the standard
C++ classes and templates of Chapter 16, "The string Class and the Standard Template
Library," offer convenient ways of representing components of a more extensive class
We'll look at a particular example now
What is a student? Someone enrolled in a school? Someone engaged in thoughtful
investigation? A refugee from the harsh exigencies of the real world? Someone with an
identifying name and a set of quiz scores? Clearly, the last definition is a totally inadequate
characterization of a person, but it is well-suited for a simple computer representation So
let's develop a Student class based on that definition
Simplifying a student to a name and a set of quiz scores suggests using a String class
object (Chapter 12) to hold the name and an array class object (coming up soon) to hold
the scores (assumed to be type double) (Once you learn about the library classes
discussed in Chapter 16, you probably would use the standard string and vector classes.)
You might be tempted to publicly derive a Student class from these two classes That
would be an example of multiple public inheritance, which C++ allows, but it would be
inappropriate here The reason is that the relationship of a student to these classes doesn't
fit the is-a model A student is not a name A student is not an array of quiz scores What
we have here is a has-a relationship A student has a name, and a student has an array of
quiz scores The usual C++ technique for modeling has-a relationships is to use
composition or containment; that is, to create a class composed of, or containing, members
that are objects of another class For example, we can begin a Student class declaration
like this:
class Student
{
private:
String name; // use a String object for name
ArrayDb scores; // use an ArrayDb object for scores
.
};
As usual, the class makes the data members private This implies that the Student class
member functions can use the public interfaces of the String and ArrayDb (for array of
double) classes to access and modify the name and scores objects, but that the outside
world cannot do so The only access the outside world will have to name and scores is
through the public interface defined for the Student class (see Figure 14.1) A common
Trang 7way of describing this is saying that the Student class acquires the implementation of its
member objects, but doesn't inherit the interface For example a Student object uses the
String implementation rather than a char * name or a char name[26] implementation for
holding the name But a Student object does not innately have the ability to use the String
operator==() function
Figure 14.1 Containment.
Interfaces and Implementations
With public inheritance, a class inherits an interface, and, perhaps, an implementation (Pure virtual functions in a base class can provide an interface without an
implementation.) Acquiring the interface is part of the is-a relationship With composition, on the other hand, a class acquires the implementation without the interface Not inheriting the interface is part of the has-a relationship
Trang 8The fact that a class object doesn't automatically acquire the interface of a contained
object is a good thing for a has-a relationship For example, one could extend the String
class to overload the + operator to allow concatenating two strings, but, conceptually, it
doesn't make sense to concatenate two Student objects That's one reason not to use
public inheritance in this case On the other hand, parts of the interface for the contained
class may make sense for the new class For example, you might want to use the
operator<() method from the String interface to sort Student objects by name You can
do so by defining a Student::Operator<() member function that, internally, uses the
String::Operator<() function Let's move on to some details
The first detail is developing the ArrayDb class so that the Student class can use it This
class will be quite similar to the String class because the latter is an array, too, in this
case, of char First, let's list some necessary and/or desirable features for the ArrayDb
class
It should be able to store several double values
It should provide random access to individual values using bracket notation with an index
One can assign one array to another
The class will perform bounds checking to ensure that array indices are valid
The first two features are the essence of an array The third feature is not true of built-in
arrays but is true for class objects, so creating an array class will provide that feature The
final feature, again, is not true for built-in arrays, but can be added as part of the second
feature
At this point, much of the design can ape the String declaration, replacing type char with
type double That is, you can do this:
class ArrayDb
{
private:
Trang 9unsigned int size; // number of array elements
double * arr; // address of first element
public:
ArrayDb(); // default constructor
// create an ArrayDb of n elements, set each to val
ArrayDb(unsigned int n, double val = 0.0);
// create an ArrayDb of n elements, initialize to array pn
ArrayDb(const double * pn, unsigned int n);
ArrayDb(const ArrayDb & a); // copy constructor
virtual ~ArrayDb(); // destructor
double & operator[](int i); // array indexing
const double & operator[](int i) const; // array indexing
// other stuff to be added here
ArrayDb & operator=(const ArrayDb & a);
friend ostream & operator<<(ostream & os, const ArrayDb & a);
};
The class will use dynamic memory allocation to create an array of the desired size
Therefore, it also will provide a destructor, a copy constructor, and an assignment
operator For convenience, it will have a few more constructors
Tweaking operator[]()
To provide random access using array notation, the class has to overload the [] operator,
just as the String class did This will allow code like the following:
ArrayDb scores(5, 20.0); // 5 elements, each set to 20.0
double temp = scores[3];
scores[3] = 16.5:
Clients of the ArrayDB class can access array elements individually only through the
overloaded [] operator This gives you the opportunity to build in some safety checks In
particular, the method can check to see if the proposed array index is in bounds That is,
you can write the operator this way:
double & ArrayDb::operator[](int i)
{
Trang 10// check index before continuing
if (i < 0 || i >= size)
{
cerr << "Error in array limits: "
<< i << " is a bad index\n";
exit(1);
}
return arr[i];
}
This slows down a program, for it requires evaluating an if statement every time the
program accesses an array element But it adds safety, preventing a program from placing
a value in the 2000th element of a 5-element array
As with the String class, we need a const version of the [] operator to allow read-only
access for constant ArrayDb objects:
const double & ArrayDb::operator[](int i) const
{
// check index before continuing
if (i < 0 || i >= size)
{
cerr << "Error in array limits: "
<< i << " is a bad index\n";
exit(1);
}
return arr[i];
}
The compiler will select the const version of operator[]() for use with const ArrayDb
objects and use the other version for non-const ArrayDb objects
Listing 14.1 shows the header file for the ArrayDb class For extra convenience, the class
definition includes an Average() method that returns the average of the array elements
Listing 14.1 arraydb.h
Trang 11// arraydb.h array class
#ifndef ARRAYDB_H_
#define ARRAYDB_H_
#include <iostream>
using namespace std;
class ArrayDb
{
private:
unsigned int size; // number of array elements
double * arr; // address of first element
public:
ArrayDb(); // default constructor
// create an ArrayDb of n elements, set each to val
explicit ArrayDb(unsigned int n, double val = 0.0);
// create an ArrayDb of n elements, initialize to array pn
ArrayDb(const double * pn, unsigned int n);
ArrayDb(const ArrayDb & a); // copy constructor
virtual ~ArrayDb(); // destructor
unsigned int ArSize() const {return size;}// returns array size
double Average() const; // return array average
// overloaded operators
double & operator[](int i); // array indexing
const double & operator[](int i) const; // array indexing
ArrayDb & operator=(const ArrayDb & a);
friend ostream & operator<<(ostream & os, const ArrayDb & a);
};
#endif
Compatibility Note
Older implementations don't support the explicit keyword