- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
68 Technique 12: Creating Your Own Types
LISTING 12-4 (continued)
Matrix operator*(double scalar, Matrix& m1 )
{
return scalar_multiplication( m1, scalar );
}
Note that this code replaces the existing operator* that we implemented earlier. Remove the existing implementation or you will get a duplicate definition error from the compiler for having two of the same methods defined.
This code allows us to write either:
Matrix m2 = 4 * m1;
Or
Matrix m2 = m1 * 4;
Because the compiler can resolve either order into a valid method, it will allow you to do both in your code. Because the actual multiplication action is the same in either case, we factor out the code that does the “real” work and call it from both methods. This refactoring reduces the total amount of code, and makes it easier to track down problems.
2. Save the source-code file.
Testing the Matrix Class
After you create a Matrix class, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code.
Here’s the procedure that creates a test driver that validates various kinds of input from the user, and illustrates how the Matrix class is intended to be used:
1. In the code editor of your choice, open the existing file to hold the code for your test program.
In this example, I named the test program ch12.cpp.
2. Type the code from Listing 12-5 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 12-5: THE MATRIX TEST DRIVER
int main()
{
Matrix |
m1(5,5); |
Matrix |
m2(5,5); |
m1[2][2] = 3; |
|
1 |
|
m2[2][2] = |
4; |
|
|
m2[3][3] = |
5; |
|
|
Matrix m3 |
= |
m1 |
+ m2; |
2 |
|
printf(“Matrix 1:\n”); |
|
|
|||
m1.Print(); |
|
|
|
|
|
printf(“Matrix 3:\n”); |
|
|
|||
m3.Print(); |
|
|
|
|
|
Matrix m4 |
= |
m3 |
* 5; |
|
3 |
Matrix m5 |
= |
4 * m4; |
|
printf(“Matrix 4:\n”); m4.Print(); printf(“Matrix 5:\n”); m5.Print();
}
3. Compile and run the application in the operating system of your choice.
If you have done everything right, you should see the output from Listing 12-6 in the shell window on your system.
Testing the Matrix Class |
69 |
LISTING 12-6: OUTPUT FROM THE MATRIX TEST PROGRAM
$ ./a.exe |
|
|
|
Matrix 1: |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
3.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
Matrix 3: |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
7.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
5.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
Matrix 4: |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
35.000000 |
0.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
25.000000 |
0.000000 |
|
|
|
0.000000 |
0.000000 |
0.000000 |
0.000000 |
0.000000 |
|
|
Matrix 5:
0.000000 0.000000 0.000000 0.000000
0.000000
0.000000 0.000000 0.000000 0.000000
0.000000
0.000000 0.000000 140.000000 0.000000
0.000000
0.000000 0.000000 0.000000 100.000000
0.000000
0.000000 0.000000 0.000000 0.000000
0.000000
As you can see from the above output, we are displaying the individual matrix objects that are created in the test program. The output shows that the first matrix (m1) displays the data values which we
placed into it in the line marked |
1 in the test |
|||
driver code. The line marked |
|
shows the addi- |
||
|
|
2 |
|
|
we then display as |
||||
tion of two matrices, which |
|
|
|
|
Matrix 3. Likewise, the code marked with |
|
3 indi- |
||
|
|
scalar value, |
||
cates the multiplication of a matrix by a |
|
|
which is displayed in the output as Matrix 4. If you do the math, you will see that all of the output is correct, indicating that our Matrix class and its manipulation methods are working properly.
As you can see, the matrices display properly and the math is done correctly. We know our test program is correct, and we can use it in the future when we change things.
13 Using Enumerations
Technique
Save Time By
Defining enumerations
Implementing the
Enumeration class
Testing the Enumeration class
An enumeration is a language type introduced with the C language, which has migrated almost untouched into the C++ language. Enumerations are not true types, as classes are. You can’t define
operators for enumerations, nor can you change the way in which they behave. As with pre-processor commands, the enumeration command is really more of a syntactical sugar thing, replacing constant values with more readable names. It allows you slightly better readability, but does nothing to change the way your code works. Enumerations allow you to use meaningful names for values, and allow the compiler to do better type checking. The downside of an enumeration is that because it is simply a syntactical replacement for a data value, you can easily fool the compiler by casting invalid values to the enumerated type.
The basic form of an enumeration looks like this:
enum <name> { value1[=number], value2,
value3,
. . .
valuen
} EnumerationTypeName;
where the <name> field is the enumeration type we are creating, the value parameters are the individual values defined in the enumeration, and number is an optional starting point to begin numbering the enumerated values. For example, take a look at this enumeration:
enum color { Red = 1. White = 2. Blue
} ColorType;
In this example, every time the compiler encounters ColorType::Red in our application, it understands the value to be 1, White would be 2, and Blue 3 (because the numbers are consecutive unless you specify otherwise).
Implementing the Enumeration Class |
71 |
If enumerations actually do not change the logic of your code, why would you bother with them? The primary reason for enumerations is to improve the readability of your code. To illustrate this, here I show you a simple technique involving enumerations you can use to make your code a little safer to use, and a lot easier to understand.
Enumerations are a great way to have the compiler enforce your valid values on the programmer. Rather than checking after the fact to see whether the value is valid, you can let the compiler check at compile-time to validate that the input will be within the range you want. When you specify that a variable is of an enumerated type, the compiler ensures that the value is of that type, insisting that it be one of the values in the enumeration list.
Implementing the Enumeration
Class
An enumeration is normally used when the real-world object it is modeling has very simple, very discrete values. The first example that immediately leaps to mind is a traffic light, which has three possible states: red, yellow, and green. In the following steps, let’s create a simple example using the traffic light metaphor to illustrate how enumerations work and can be used in your application.
1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.
In this example, the file is named ch13.cpp,
You might notice that enumerations are a simpler form of the Range validation class we developed in Technique 11. Enumerations are enforced by the compiler, not by your code, and require considerably less effort to implement than a Range checking class. At the same time, they are not as robust. Your mileage may vary, but enumerations are usually used more for readability and maintenance concerns than for validation.
although you can use whatever you choose.
2. Type the code from Listing 13-1 into your file.
Better yet, copy the code you find on this book’s companion Web site and change the names of the constants and variables as you choose.
3. Save the source-code file.
LISTING 13-1: THE ENUMERATION PROGRAM
#include <stdio.h>
typedef enum
{
Red = 0,
Yellow,
Green
} TrafficLightColor;
int ChangeLight( int color )
{
switch ( color )
{
case 1: // Red
printf(“Changing light to RED. Stop!!\n”); break;
case 2: // Yellow
printf(“Changing light to YELLOW. Slow down\n”); break;
case 3: // Green
(continued)