- •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
358 Part V: Optional Features
Notice that the TemplateVector handles both int values and Name objects with equal ease. Notice also how similar the nameFn() and intFn() functions are, even though integers and names have nothing to do with each other.
A sample session appears as follows:
Give me a series integer values to add to a vector (Enter a negative number to terminate):
5
10
15 -1
Here are the numbers you entered 0:5 1:10 2:15
Enter names
(Enter an ‘x’ to quit): Chester
Fox
Penny x
Here are the names you entered 0:Chester
1:Fox
2:Penny
Press any key to continue . . .
Do I Really Need Template Classes?
“But,” you say, “can’t I just create a simple Array class? Why mess with templates?”
Sure you can, if you know a priori what types of things you need arrays for. For example, if all you ever need is arrays of integers, you have no reason to create a template Vector<T>; you could just create the class IntArray and be finished.
The only other alternative is to use void*, which can point to any type of object. The following VoidVector program is based upon the use of void pointers:
//VoidVector - implement a vector that relies on void*
//as the storage element
#include <cstdlib> #include <cstdio> #include <iostream>
Chapter 27: Tempting C++ Templates 359
using namespace std;
typedef void* VoidPtr;
class VoidVector
{
public:
VoidVector(int nArraySize)
{
// store off the number of elements nSize = nArraySize;
ptr = new VoidPtr[nArraySize]; reset();
}
int size() { return nWriteIndex; }
void reset() { nWriteIndex = 0; nReadIndex = 0; } void add(void* pValue)
{
if (nWriteIndex < nSize)
{
ptr[nWriteIndex++] = pValue;
}
}
VoidPtr get(){ return ptr[nReadIndex++]; }
protected: int nSize;
int nWriteIndex; int nReadIndex; VoidPtr* ptr;
};
int main(int argc, char* pArgs[])
{
//create a vector VoidVector vv(10);
//add values to the vector
cout << “Give me a series integer values to add to a vector\n”
<< “(Enter a negative number to terminate):” << endl;
for(;;)
{
int* p = new int; cin >> *p;
if (*p < 0)
{
delete p; break;
}
360 Part V: Optional Features
vv.add((void*)p);
}
cout << “\nHere are the numbers you entered” << endl; for(int i = 0; i < vv.size(); i++)
{
int* p = (int*)vv.get();
cout << i << “:” << *p << endl;
}
system(“PAUSE”); return 0;
}
This program defines a type VoidPtr to be equivalent to a void*.
The typedef keyword does nothing more than create a new name for an existing class. You can mentally insert void* everywhere you see VoidPtr. typedefs can make reading a function easier, and they also improve the syntax of a statement. It is sometimes not possible to get an existing template class to work properly when the type is a pointer. Wrapping a complex type like a pointer in a typedef solves the problem.
VoidVector provides the same add() and get() methods provided by the
TemplateVector template class in the previous program.
This solution has (at least) three problems. First, it is somewhat clumsy to use, as demonstrated in main(). It is not possible to store a value, such as 10; you can pass only the address of an object. This means that you must allocate an int* off the heap to use as a storage place for the value read from the keyboard.
The second problem is the serious opportunity for screw up. You may have been tempted to simply add an int to the collection as follows:
int n; cin >> n;
vv.add((void*)&n);
That would not work. The variable n has local scope. Its address will “go away” once control exits the for loop. At that point, the addresses in the vector will make no sense.
Actually, the problem is slightly worse — the address of n is the same for every iteration through the for loop.
The third problem is more serious. In order to retrieve a value from a
VoidVector, you must know the type of object stored there. C++ cannot
Chapter 27: Tempting C++ Templates 361
check the types to make sure that your assumption is correct. Suppose, for example, you thought that double variables were stored in vv instead of int values. The following code would have stored garbage into dValue:
double dValue = *(double*)get();
The program would certainly go astray; the casts to and from void* defeat the strong typing built into C++.
Tips for Using Templates
You should remember a few things when using templates. First, no code is gen erated for a template. (Code is generated after the template is converted into a concrete class or function.) This implies that a .cpp source file is almost never associated with a template class. The entire template class definition, including all the member functions, is contained in the include file so that it can be available for the compiler to expand.
Second, a template class does not consume memory. Therefore, there is no penalty for creating template classes if they are never instanced. On the other hand, a template class uses memory every time it is instanced. Thus, the code for Array<Student> consumes memory even if Array<int> already exists.
Finally, a template class cannot be compiled and checked for errors until it is converted into a real class. Thus, a program that references the template class Array<T> might compile even though Array<T> contains obvious syntax errors. The errors won’t appear until a class such as Array<int> or
Array<Student> is created.
362 Part V: Optional Features
Chapter 28
Standardizing on the Standard Template Library
In This Chapter
Using the string class
Maintaining entries in a Standard Template Library list
Accessing container elements from an iterator
Using a map container
Some programs can deal with data as it arrives and dispense with it. Most programs, however, must store data for later processing. A structure that is used to store data is known generically as a container or a collection (I use
the terms interchangeably). This book has relied heavily on the array for data storage so far. The array container has a couple of very nice properties: It stores and retrieves things very quickly. In addition, the array can be declared to hold any type of object in a type-safe way. Weighed against that, however, are two very large negatives.
First, you must know the size of the array at the time it is created. This require ment is generally not achievable, although you will sometimes know that the number of elements cannot exceed some “large value.” Viruses, however, com monly exploit this type of “it can’t be larger than this” assumption, which turns out to be incorrect. There is no real way to “grow” an array except to declare a new array and copy the contents of the old array into the newer, larger version.
Second, inserting elements anywhere within the array involves copying ele ments within the array. This is costly in terms of both memory and comput ing time. Sorting the elements within an array is even more expensive.
C++ now comes with the Standard Template Library or STL, which includes many different types of containers, each with its own set of advantages (and disadvantages).
The C++ Standard Template Library is a very large library of sometimescomplex containers. This session is considered just an overview of the power of the STL.