- •Table of Contents
- •Introduction
- •What Is C++?
- •Conventions Used in This Book
- •How This Book Is Organized
- •Part I: Introduction to C++ Programming
- •Part III: Introduction to Classes
- •Part IV: Inheritance
- •Part V: Optional Features
- •Part VI: The Part of Tens
- •Icons Used in This Book
- •Where to Go from Here
- •Grasping C++ Concepts
- •How do I program?
- •Installing Dev-C++
- •Setting the options
- •Creating Your First C++ Program
- •Entering the C++ code
- •Building your program
- •Executing Your Program
- •Dev-C++ is not Windows
- •Dev-C++ help
- •Reviewing the Annotated Program
- •Examining the framework for all C++ programs
- •Clarifying source code with comments
- •Basing programs on C++ statements
- •Writing declarations
- •Generating output
- •Calculating Expressions
- •Storing the results of expression
- •Declaring Variables
- •Declaring Different Types of Variables
- •Reviewing the limitations of integers in C++
- •Solving the truncation problem
- •Looking at the limits of floating-point numbers
- •Declaring Variable Types
- •Types of constants
- •Special characters
- •Are These Calculations Really Logical?
- •Mixed Mode Expressions
- •Performing Simple Binary Arithmetic
- •Decomposing Expressions
- •Determining the Order of Operations
- •Performing Unary Operations
- •Using Assignment Operators
- •Why Mess with Logical Operations?
- •Using the Simple Logical Operators
- •Storing logical values
- •Using logical int variables
- •Be careful performing logical operations on floating-point variables
- •Expressing Binary Numbers
- •The decimal number system
- •Other number systems
- •The binary number system
- •Performing Bitwise Logical Operations
- •The single bit operators
- •Using the bitwise operators
- •A simple test
- •Do something logical with logical calculations
- •Controlling Program Flow with the Branch Commands
- •Executing Loops in a Program
- •Looping while a condition is true
- •Using the for loop
- •Avoiding the dreaded infinite loop
- •Applying special loop controls
- •Nesting Control Commands
- •Switching to a Different Subject?
- •Writing and Using a Function
- •Divide and conquer
- •Understanding the Details of Functions
- •Understanding simple functions
- •Understanding functions with arguments
- •Overloading Function Names
- •Defining Function Prototypes
- •Variable Storage Types
- •Including Include Files
- •Considering the Need for Arrays
- •Using an array
- •Initializing an array
- •Accessing too far into an array
- •Using arrays
- •Defining and using arrays of arrays
- •Using Arrays of Characters
- •Creating an array of characters
- •Creating a string of characters
- •Manipulating Strings with Character
- •String-ing Along Variables
- •Variable Size
- •Address Operators
- •Using Pointer Variables
- •Comparing pointers and houses
- •Using different types of pointers
- •Passing Pointers to Functions
- •Passing by value
- •Passing pointer values
- •Passing by reference
- •Limiting scope
- •Examining the scope problem
- •Providing a solution using the heap
- •Defining Operations on Pointer Variables
- •Re-examining arrays in light of pointer variables
- •Applying operators to the address of an array
- •Expanding pointer operations to a string
- •Justifying pointer-based string manipulation
- •Applying operators to pointer types other than char
- •Contrasting a pointer with an array
- •Declaring and Using Arrays of Pointers
- •Utilizing arrays of character strings
- •Identifying Types of Errors
- •Choosing the WRITE Technique for the Problem
- •Catching bug #1
- •Catching bug #2
- •Calling for the Debugger
- •Defining the debugger
- •Finding commonalities among us
- •Running a test program
- •Single-stepping through a program
- •Abstracting Microwave Ovens
- •Preparing functional nachos
- •Preparing object-oriented nachos
- •Classifying Microwave Ovens
- •Why Classify?
- •Introducing the Class
- •The Format of a Class
- •Accessing the Members of a Class
- •Activating Our Objects
- •Simulating real-world objects
- •Why bother with member functions?
- •Adding a Member Function
- •Creating a member function
- •Naming class members
- •Calling a Member Function
- •Accessing a member function
- •Accessing other members from a member function
- •Defining a Member Function in the Class
- •Keeping a Member Function After Class
- •Overloading Member Functions
- •Defining Arrays of and Pointers to Simple Things
- •Declaring Arrays of Objects
- •Declaring Pointers to Objects
- •Dereferencing an object pointer
- •Pointing toward arrow pointers
- •Passing Objects to Functions
- •Calling a function with an object value
- •Calling a function with an object pointer
- •Calling a function by using the reference operator
- •Returning to the Heap
- •Comparing Pointers to References
- •Linking Up with Linked Lists
- •Performing other operations on a linked list
- •Hooking up with a LinkedListData program
- •A Ray of Hope: A List of Containers Linked to the C++ Library
- •Protecting Members
- •Why you need protected members
- •Discovering how protected members work
- •Protecting the internal state of the class
- •Using a class with a limited interface
- •Creating Objects
- •Using Constructors
- •Why you need constructors
- •Making constructors work
- •Dissecting a Destructor
- •Why you need the destructor
- •Working with destructors
- •Outfitting Constructors with Arguments
- •Justifying constructors
- •Using a constructor
- •Defaulting Default Constructors
- •Constructing Class Members
- •Constructing a complex data member
- •Constructing a constant data member
- •Constructing the Order of Construction
- •Local objects construct in order
- •Static objects construct only once
- •Global objects construct in no particular order
- •Members construct in the order in which they are declared
- •Destructors destruct in the reverse order of the constructors
- •Copying an Object
- •Why you need the copy constructor
- •Using the copy constructor
- •The Automatic Copy Constructor
- •Creating Shallow Copies versus Deep Copies
- •Avoiding temporaries, permanently
- •Defining a Static Member
- •Why you need static members
- •Using static members
- •Referencing static data members
- •Uses for static data members
- •Declaring Static Member Functions
- •What Is This About, Anyway?
- •Do I Need My Inheritance?
- •How Does a Class Inherit?
- •Using a subclass
- •Constructing a subclass
- •Destructing a subclass
- •Having a HAS_A Relationship
- •Why You Need Polymorphism
- •How Polymorphism Works
- •When Is a Virtual Function Not?
- •Considering Virtual Considerations
- •Factoring
- •Implementing Abstract Classes
- •Describing the abstract class concept
- •Making an honest class out of an abstract class
- •Passing abstract classes
- •Factoring C++ Source Code
- •Defining a namespace
- •Implementing Student
- •Implementing an application
- •Project file
- •Creating a project file under Dev-C++
- •Comparing Operators with Functions
- •Inserting a New Operator
- •Overloading the Assignment Operator
- •Protecting the Escape Hatch
- •How Stream I/O Works
- •The fstream Subclasses
- •Reading Directly from a Stream
- •Using the strstream Subclasses
- •Manipulating Manipulators
- •Justifying a New Error Mechanism?
- •Examining the Exception Mechanism
- •What Kinds of Things Can I Throw?
- •Adding Virtual Inheritance
- •Voicing a Contrary Opinion
- •Generalizing a Function into a Template
- •Template Classes
- •Do I Really Need Template Classes?
- •Tips for Using Templates
- •The string Container
- •The list Containers
- •Iterators
- •Using Maps
- •Enabling All Warnings and Error Messages
- •Insisting on Clean Compiles
- •Limiting the Visibility
- •Avoid Overloading Operators
- •Heap Handling
- •Using Exceptions to Handle Errors
- •Avoiding Multiple Inheritance
- •Customize Editor Settings to Your Taste
- •Highlight Matching Braces/Parentheses
- •Enable Exception Handling
- •Include Debugging Information (Sometimes)
- •Create a Project File
- •Customize the Help Menu
- •Reset Breakpoints after Editing the File
- •Avoid Illegal Filenames
- •Include #include Files in Your Project
- •Executing the Profiler
- •System Requirements
- •Using the CD with Microsoft Windows
- •Using the CD with Linux
- •Development tools
- •Program source code
- •Index
284 Part IV: Inheritance
|
|
|
|
Account |
|
|
Figure 22-4: |
|
Conventional |
|
Timed |
|
Market |
|
|
|
|
|
|
|
A more |
|
|
|
|
|
|
developed |
Savings |
Checking |
CD |
501K |
Stock |
MutualFunds |
bank |
|
|
|
|
|
|
account |
|
SpecialChecking |
|
|
|
|
hierarchy. |
|
|
|
|
|
Suppose that the bank allows account holders to access checking and stock market accounts remotely. Withdrawals from other account types can be made only at the bank. Although the class structure in Figure 22-4 seems nat ural, the one shown in Figure 22-5 is also justifiable given this information. The programmer must decide which class structure best fits the data and leads to the cleanest, most natural implementation.
|
|
|
|
Account |
|
|
|
|
RemotelyAccessible |
|
LocallyAccessible |
||
|
|
|
||||
Figure 22-5: |
|
|||||
|
|
|
|
|
||
An alternate |
|
|
|
|
|
|
class |
Stock |
Checking |
|
Market |
Savings |
|
hierarchy to |
|
|
|
|
|
|
the one in |
|
SpecialChecking |
|
MutualFunds |
|
|
Figure 22-4. |
|
CD |
501K |
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Implementing Abstract Classes
As intellectually satisfying as factoring is, it introduces a problem of its own. Return one more time to the bank account classes, specifically the common base class Account. Think for a minute about how you might go about defin ing the different member functions defined in Account.
Most Account member functions are no problem because both account types implement them in the same way. Implementing those common functions with Account::withdrawal() is different, however. The rules for withdrawing from
Chapter 22: Factoring Classes 285
a savings account are different than those for withdrawing from a checking account. You’ll have to implement Savings::withdrawal() differently than you do Checking::withdrawal(). But how are you supposed to implement
Account::withdrawal()?
Let’s ask the bank manager for help. I imagine the conversation going some thing like the following:
“What are the rules for making a withdrawal from an account?” you ask expectantly.
“What type of account? Savings or checking?” comes the reply.
“From an account,” you say. “Just an account.”
Blank look. (One might say a “blank bank look” . . . then again, maybe not.)
The problem is that the question doesn’t make sense. There’s no such thing as “just an account.” All accounts (in this example) are either checking accounts or savings accounts. The concept of an account is an abstract one that factors out properties common to the two concrete classes. It is incomplete because it lacks the critical property withdrawal(). (After you get further into the details, you may find other properties that a simple account lacks.)
An abstract class is one that only exists in subclasses. A concrete class is a class that is not abstract. Hardly an abstract concept.
Let me borrow an example from the animal kingdom. You can observe the dif ferent species of warm-blooded, baby-bearing animals and conclude that there is a concept called mammal. You can derive classes from mammal, such as canine, feline, and hominid. It is impossible, however, to find anywhere on earth a pure mammal, that is, a mammal that isn’t a member of some sub species of mammal. Mammal is a high-level concept that man has created — no instances of mammal exist.
Note that I can make this assertion confidently although time has passed since I first wrote this (it took you only a few seconds to get from there to here — I hope). Scientists discover new animals all the time. One scientist even dis covered a new phylum in the 1990s (if you’re a biologist, that’s a big deal). Not once has a scientist come back and said, “This new thing is a mammal and nothing more . . . just a mammal.” The problem with a statement like this is that this animal surely has properties that other mammals don’t share and, even if doesn’t, there’s a distinct possibility that someone will find such a property in the future.
C++ supports a concept known as an abstract class to describe an incomplete concept such as mammal.
286 Part IV: Inheritance
Describing the abstract class concept
An abstract class is a class with one or more pure virtual functions. Oh, great! That helps a lot.
Okay, a pure virtual function is a virtual member function that is marked as having no implementation. Most likely it has no implementation because no implementation is possible with the information provided in the class, including any base classes.
It doesn’t make sense to ask exactly how to implement the withdrawal() function in the class Account. However, the concept of a withdrawal from an account does make sense. The C++ programmer can write a function withdrawal() that is impossible to implement. Such a function is called a pure virtual function. (Don’t ask me how they came up with that name.)
The syntax for declaring a function pure virtual is demonstrated in the follow ing class Account:
// Account - this class is an abstract class class Account
{
protected:
Account(Account& c); // avoid making any copies public:
Account(unsigned accNo, float initialBalance = 0.0F);
//access functions unsigned int accountNo( ); float acntBalance( ); static int noAccounts( );
//transaction functions void deposit(float amount);
//the following is a pure virtual function virtual void withdrawal(float amount) = 0;
protected:
//keep accounts in a linked list so there’s no limit
//to the number of accounts
static int count; |
// number of accounts |
|
unsigned |
accountNumber; |
|
float |
balance; |
|
};
The = 0 after the declaration of withdrawal() indicates that the programmer does not intend to define this function. The declaration is a placeholder for the subclasses. The subclasses of Account are expected to override this function
Chapter 22: Factoring Classes 287
with a concrete function. The programmer must provide an implementation for each member function not declared pure virtual.
I think this notation is silly, and I don’t like it any more than you do. But it’s here to stay, so you just have to learn to live with it. There is a reason, if not exactly a justification, for this notation. Every virtual function must have an entry in a special table. This entry contains the address of the function. The entry for a pure virtual function is zero. Some other languages define an abstract keyword — no, I mean a keyword abstract.
An abstract class cannot be instanced with an object; that is, you can’t make an object out of an abstract class. For example, the following declaration is not legal:
void fn( )
{
// declare an account with 100 dollars
Account acnt(1234, 100.00);// this is not legal acnt.withdrawal(50); // what would you expect
// this call to do?
}
If the declaration were allowed, the resulting object would be incomplete, lacking in some capability. For example, what should the preceding call do? Remember, there is no Account::withdrawal().
Abstract classes serve as base classes for other classes. An Account con tains all the properties associated with a generic bank account. You can create other types of bank accounts by inheriting from Account, but they can’t be instanced with an object.
Making an honest class out of an abstract class
The subclass of an abstract class remains abstract until all pure virtual func tions have been overridden. The class Savings is not abstract because it overrides the pure virtual function withdrawal() with a perfectly good defi nition. An object of class Savings knows how to perform withdrawal() when called on to do so. The same is true of class Checking. The class is not virtual because the function withdrawal() overrides the pure virtual func tion in the base class.
A subclass of an abstract class can remain abstract, however. Consider the following classes:
288 Part IV: Inheritance
class Display
{
public:
virtual void initialize( ) = 0; virtual void write(char *pString) = 0;
};
class SVGA : public Display
{
// override both member functions with “real” functions virtual void initialize( );
virtual void write(char *pString); };
class HWVGA : public Display
{
// override the only function we know how to up until now virtual void write(char *pString);
};
class ThreedVGA : public HWVGA
{
virtual void initialize( ); };
void fn( )
{
SVGA mc; ThreedVGA vga;
// ...what the function chooses to do from here...
}
The class Display, intended to represent video PC displays, has two pure virtual functions: initialize() and write(). You can’t implement either function for adapters in general. The different types of video cards do not ini tialize or write in the same way.
One of the subclasses, SVGA, is not abstract. This is a particular type of video adapter that the programmer knows how to program. Therefore, the class SVGA has overridden both initialize() and write() appropriately for this adapter.
HWVGA, another one of the subclasses, is also not abstract. Here again, the programmer knows how to program the accelerated VGA adapter hardware. In this case, however, a level of abstraction is between the generic Display and the specific case of the ThreedVGA display, which represents the special 3-D hardware display cards.
For this discussion, assume that all hardware-accelerated VGA cards are writ ten to in the same way, but that each must be initialized in its own way. (This