- •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
Chapter 22: Factoring Classes 291
See how pure virtual functions correct the problem. Here’s the same situation with Account declared as an abstract class:
class Account
{
public:
//just like preceding
//declare withdrawal pure virtual virtual void withdrawal(float amnt) = 0;
};
class Savings : public Account
{
public:
virtual void withdrawal(float amnt);
};
void fn(Account *pAcc)
{
// withdraw some money pAcc->withdrawal(100.00F); // now it works
};
int main( )
{
Savings s; // open an account fn(&s);
// ...same as before...
}
The situation is the same except the class Account includes the member function withdrawal(). Now when the compiler checks to see whether pAcc->withdrawal() is defined, it sees the definition of Account:: withdrawal() just as it expects. The compiler is happy. You’re happy. That makes me happy, too. (Frankly, a football game and a cold beer are enough to make me happy.)
The pure virtual function is a placeholder in the base class for the subclass to override with its own implementation. Without that placeholder in the base class, there is no overriding.
Factoring C++ Source Code
Factoring a problem has a physical side. Classes that have been factored out of the jumble of separate concepts that make up a program should be moved into their own “space.”
292 Part IV: Inheritance
The programmer can divide a single program into separate files known as modules. These individual source files are compiled separately and then com bined during the build process to generate a single program. Modules can then be allocated to separate groups known as namespaces.
The process of combining separately compiled modules into a single exe cutable is called linking.
There are a number of reasons to divide programs into more manageable pieces. First, dividing a program into modules results in a higher level of encapsulation. Classes wall off their internal members in order to provide a certain degree of safety. Programs can wall off functions to do the same thing.
Encapsulation is one of the advantages of object-oriented programming.
Second, it is easier to comprehend and, therefore, easier to write and debug a program that consists of a number of well-thought-out modules than a single source file full of all of the classes and functions that the program uses.
Next comes reuse. I used the reuse argument to help sell object-based pro gramming. It is extremely difficult to keep track of a single class reused among multiple programs when a separate copy of the class is kept in each program. It is much better if a single class module is automatically shared among programs.
Finally, there is the argument of time. A compiler such as Visual C++ or Dev- C++ doesn’t need very long to build the examples contained in this book using a high-speed computer like yours. Commercial programs sometimes consist of millions of source lines of code. Rebuilding a program of that size can take more than 24 hours. A programmer would not tolerate rebuilding a program like that for every single change. However, the majority of the time is spent compiling source files that haven’t changed. It is much faster to recompile just those modules that have changed and then quickly link all modules together.
Separate namespaces allow a further level of encapsulation. A namespace should consist of a set of modules that perform a single capability. For example, all of the mathematical functions might be combined into a Math namespace.
This lesson builds a simplistic program, called SeparateModules, that con sists of a Student class, a GraduateStudent subclass, and a main() module to test both.
Dividing the program — Student
You begin by deciding what the logical divisions of SeparateModules should be. First, you notice that Student is an entity of its own. It does not depend
Chapter 22: Factoring Classes 293
on any other functions (besides C++ functions). Thus, it would make sense to put Student in a module by itself. Because the class will be used in several places, you break the declaration into a student.h file and a separate imple mentation file, Student.cpp. By convention, the include file carries the name of the primary class it defines, but in lowercase letters. Ideally, the include file defines only one class. This allows the user program to include just the files that it needs.
Historically, all include files carried the extension .h. This was changed in the current C++ standard. System include files such as iostream now have no extension at all. However, many programmers stick with the .h convention for include files they write. This allows such include files to be easily differen tiated by the reader of the program.
The resulting student.h file appears as follows:
// Student - basic student #ifndef _STUDENT_
#define _STUDENT_
namespace Schools
{
class Student
{
public:
Student(char* pszName, int nID); virtual char* display();
protected:
// student’s name char* pszName; int nID;
};
}
#endif
The #ifndef is a preprocessor control much like #include. #ifndef _ STUDENT_ says to include only the following lines if the argument _STUDENT_ is defined. The first time that student.h is included, _STUDENT_ is not defined. However, the #define immediately following the #ifndef then defines it. This has the effect that student.h is processed only once, no matter how many times it is included in a given file.
Defining a namespace
The second feature of the Student class is the creation of the Schools namespace.
294 Part IV: Inheritance
A namespace is a collection of loosely coupled classes that are somehow logi cally similar. In this case, I intend to throw all classes that I create concerning students, graduate students, classes, course schedules, and so forth into the Schools namespace.
The classes that make up the Schools namespace are like members of a family. One class within a namespace may refer to other members of the same namespace directly. However, external classes must specify the namespace. You will see the ways of specifying a class’s namespace in the follow ing SeparatedMain application.
Another reason for dividing modules into namespaces is to avoid “name colli sion.” For example, the class Grade within the namespace Schools does not interfere with the class Grade in the namespace FoodProduction.
Implementing Student
I put the implementation of the Student class in the file Student.cpp:
// Student - implement the methods of the Student class #include <cstdio>
#include <cstdlib> #include <iostream> #include <string> #include “student.h”
namespace Schools
{
Student::Student(char* pszNameArg, int nIDArg) : nID(nIDArg)
{
pszName = new char[strlen(pszNameArg) + 1];
strcpy(pszName, pszNameArg);
}
// display - return a description of student char* Student::display()
{
//copy the student’s name into a block of heap
//memory that we can return to the caller char* pReturn = new char[strlen(pszName) + 1]; strcpy(pReturn, pszName);
return pReturn;
}
}
The constructor for Student copies off the name and id provided it. The vir tual display() method returns a string that describes the Student object.
Chapter 22: Factoring Classes 295
Compiling the Student.cpp file generates an intermediate file. This interme diate file can be combined quickly with other intermediate files to form a completed executable program.
For historical reasons, this intermediate file carries the extension .o (for “object file”) in most C++ environments.
Dividing the program — GraduateStudent
The next module that seems quasi-independent is GraduateStudent. Logically, one could fold the GraduateStudent class into Student.cpp; however, some programs may want to deal with Student as an abstraction and not worry about students versus graduate students.
I made the GraduateStudent class as simple as possible. The include file appears as follows:
// GraduateStudent - a special type of Student #ifndef _GRADUATE_STUDENT_
#define _GRADUATE_STUDENT_
#include “student.h” namespace Schools
{
class GraduateStudent : public Student
{
public:
//trivial constructors GraduateStudent(char* pszName, int nID)
:Student(pszName, nID){}
//demonstration virtual function virtual char* display();
};
}
#endif
Notice that the graduateStudent.h file includes student.h. This is because the GraduateStudent class is dependent upon the definition of Student.
The resulting source file implements the display() method, the only member function that is yet to be implemented:
// GraduateStudent - a special type of Student #include <cstdio>
#include <cstdlib> #include <iostream>
#include “graduateStudent.h”