- •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
430 Technique 68: Creating and Using Guardian Classes
The code above doesn’t really do anything that the standard C functions don’t already do for you. The puts function, for example, does exactly what the puts method (shown at 5) method does with one very important difference.
If the file is not open, or the string is NULL, the puts method in the class above checks for the error and returns an error code. The puts function, on the other hand, crashes the application if either of those conditions is true.
Note also the open function, shown at the line marked 6. The mode problem cannot exist in this class, as it did in the fopen function, because we pass in an enumerated value that must be one of a list of valid values. If it is not, an error occurs.
3. Save the source code in the code editor.
Note that we have replaced the problematic “mode” parameter of the open class with a safer, more secure enumeration that we can validate. Also notice that all the various read and write functions (shown at lines 7 and 8) work, whether or not the file was successfully opened.
Testing the File-Guardian Class
After you create a 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. The following steps tell you how:
1. In the code editor of your choice, reopen the source file to hold the code for your test program.
In this example, I named the test program ch68.cpp.
2. Type the code from Listing 68-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 68-2: THE FILE-GUARDIAN TEST PROGRAM
int func2()
{
throw “This is bad!”;
}
int old_func()
{
FILE *fp = fopen(“anoldfile.out”, “w”); if ( fp == NULL )
return -1;
fprintf(fp, “This is a test\n”); try
{
func2();
}
catch ( ... )
{
printf(“An error occurred\n”); return -2;
}
printf(“Closing file\n”); fclose(fp);
return 0;
}
int new_func()
{
FileWrapper out(“anewfile.out”, Write); if ( out.open() == false )
return -1;
out.puts(“This is a test\n”); try
{
func2();
}
catch ( ... )
{
printf(“An error occurred\n”); return -2;
}
out.close(); return 0;
}
Testing the File-Guardian Class |
431 |
int main(int argc, char **argv)
{
if ( argc < 2 )
{
printf(“Usage: ch7_9 filename\n”); return -1;
}
FileWrapper fw(argv[1], Write);
//Note that we do not check the results.
fw.open(); |
|
|
9 |
|
// Write out |
all the arguments. |
|
||
for ( int i=2; |
i<argc; ++i ) |
|
||
{ |
fw.puts |
( |
argv[i] ); |
10 |
} |
//Note that we don’t call close.
//Now test the various functions. old_func();
new_func();
return 0;
}
The test driver simply opens an output file (shown at 9 in Listing 68-2), writes out any arguments from the command line to the file (shown at 10) and then finishes. In addition, it uses two functions to illustrate how the old-style and new-style functions are used. If you look at the output, you will see that when the exception is thrown the new-style file routines properly close the file, whereas the old-style functions do not. This is an important difference, especially if you are writing to a file throughout your application as in the case of a log file.
3. Save the source code in the code editor and then close the editor application.
4. Compile the source code with your favorite compiler, on your favorite operating system.
5. Run the program on your favorite operating system’s console.
If you have done everything properly, you should see the following output from the program on the console window:
$./a.exe test.out this is a test of the emergency broadcast system
File test.out is now open
An error occurred
File anewfile.out is now open
An error occurred
File anewfile.out is now closed
File test.out is now closed
As you can see, the new file was properly closed, as was the test.out file. These files were both created via the new functionality. The old-style file, however, was never closed, because the exception was thrown and the fclose statement was never executed.
You should also see two files created in your file system, anewfile.out and anoldfile.out. If you look at the contents of the files, you should see the following.
$ cat anewfile.out This is a test
$cat anoldfile.out
The anewfile.out file has the text we expected. The anoldfild.out file, on the other hand, is empty.
Depending on your operating system and settings, you may or may not see text in the anoldfile.out text file. This uncertainty alone makes it worthwhile to close the files. Some operating systems flush data as it is written to them to the disk. Others keep it in memory until the file is closed, or there is enough data to write out a full buffer. You do not want to rely on the operating system to determine this, if the data being written is important to you.
69 Working with Complex Numbers
Technique
Save Time By
Understanding complex numbers
Creating a complex numbers class
Testing your class
The mathematical world deals with complex numbers all of the time. A complex number is simply a combination of a “real” number (that is, a floating-point value), and an “imaginary” number (i — the
square root of –1 — or a multiple of i). Complex numbers are usually written out in the form x + yi, where x is the real number and y is the multiplier of the imaginary number.
You might not believe it, but some folks think they never need to know anything about complex math — and never expect to use a complex number in your applications. Okay, complex numbers are rarely used in applications — but when they are needed (in scientific projects, for example), they can be tricky to work with. Creating a class that deals with these numbers in advance of working on such a project is to your advantage. Understanding the fundamentals of complex mathematics can be tricky; you just need a class that does the work for you so that you don’t have to think about it.
If you attempt to become an expert in all the expert subject matter in every application you work on, you will quickly find yourself not only frustrated, but buried in work. Sometimes it’s best just to accept that others know the science or business of the area better than you ever will. In such cases, you save a lot of time by finding a good set of classes that do the work of the expert subject matter — and then working on the rest of the application. It is more important that you have an excellent suite of tests to validate your classes than it is that you have the ability to write them from the start. Save time and energy, and work on the test suite rather than the class. This technique shows you how.
Implementing the Complex Class 433
Implementing the
Complex Class
1. In the code editor of your choice, create a new file to hold the code for the implementation of source file.
In this example, the file is named ch69.cpp,
Most versions of the Standard Template Library have |
although you can use whatever you choose. |
a complex template in the <complex> header file. |
2. Type the code from Listing 69-1 into your file. |
However, this is not universal — and using this tem- |
|
plate means loading in the entire STL when you link |
Better yet, copy the code from the source file on |
your application. If all you need is a complex-number |
this book’s companion Web site. |
class, you’d be better off creating your own class. |
|
First, we need to implement the definition of the |
|
complex number class. The following steps show |
|
you how. |
|
LISTING 69-1: THE COMPLEX CLASS DEFINITION |
|
|
|
#include <math.h> |
|
#include <stdio.h> |
|
#include <iostream> |
|
using namespace std; |
|
class Complex |
|
{ |
|
private: |
|
double real; |
|
double imaginary; |
|
protected: |
|
// mathematical functionality
void add(const Complex &a, const Complex &b);
void subtract(const Complex &a, const Complex &b); void multiply(const Complex &a, const Complex &b);
void negative();
public:
Complex();
Complex(double realValue, double imaginaryValue); Complex( const Complex& aCopy );
Complex operator=(const Complex &a);
// accessor functions double magnitude() const;
double get_real() const; double get_imaginary() const;
};
434 |
Technique 69: Working with Complex Numbers |
|
|
This listing is simply the class definition. In com- |
|
operators, because they “live” outside of the |
|
plex terms, the x + yi part maps to the real and |
|
class definition itself. |
|
the imaginary terms in the code. As you can see, |
3. |
Append the code from Listing 69-2 into your |
|
we support constructors and accessors to get |
|||
back the real and imaginary portions of the com- |
|
file. |
|
|
|
||
plex number. |
|
Better yet, copy the code from the source file on |
|
The next step is to do the actual implementation |
|
this book’s companion Web site. |
|
|
|
of the class. This will not include any external
LISTING 69-2: THE COMPLEX CLASS IMPLEMENTATION
// constructors Complex::Complex()
{
real = imaginary = 0;
}
Complex::Complex(double realValue, double imaginaryValue)
{
real = realValue; imaginary = imaginaryValue;
}
// Copy constructor
Complex::Complex( const Complex& aCopy )
{ |
|
real |
= aCopy.real; |
imaginary |
= aCopy.imaginary; |
} |
|
// accessor functions
double Complex::magnitude() const
{
return sqrt(pow(real,2) + pow(imaginary,2));
}
double Complex::get_real() const
{
return real;
}
double Complex::get_imaginary() const
{
return imaginary;
}
Implementing the Complex Class 435
void Complex::add(const Complex &a, const Complex &b)
{
real = a.get_real() + b.get_real();
imaginary = a.get_imaginary() + b.get_imaginary();
}
void Complex::subtract(const Complex &a, const Complex &b)
{
real = a.get_real() - b.get_real();
imaginary = a.get_imaginary() - b.get_imaginary();
}
void Complex::multiply(const Complex &a, const Complex &b)
{
real = a.get_real()*b.get_real() - a.get_imaginary()*b.get_imaginary(); imaginary = a.get_real()*b.get_imaginary() + a.get_imaginary()*b.get_real();
}
void Complex::negative()
{
imaginary = -imaginary;
}
1
2
3
// assigns one complex number to another Complex Complex::operator=(const Complex &a)
{
real = a.get_real();
imaginary = a.get_imaginary();
return *this;
}
The above listing is the implementation of the |
Finally, we need to implement the utility func- |
code that we defined in the class definition. These |
tions for this class — in particular, the external |
two listings could, and often are, split into two |
operators that allow us to override the mathe- |
files. The class definition is placed in a header |
matical operations, as well as the streaming |
file, and the class implementation is placed in a |
operator that allows us to output a complex |
source file. For simplicity, we are placing them |
number simply. These functions are imple- |
all in the same file for this technique. The func- |
mented separately because they are not a part of |
tionality for the underlying complex variable |
the class itself, but rather they are global func- |
class is implemented in the add (shown at 1), |
tions that can reside anywhere. |
subtract (shown at 2) and multiply (shown |
4. Append the code from Listing 69-3 into your file. |
at 3) methods that are protected methods of |
|
the class. This is necessary so that we can over- |
Better yet, copy the code from the source file on |
ride the operators +, –, and * later on. |
this book’s companion Web site. |
436 Technique 69: Working with Complex Numbers
LISTING 69-3: THE COMPLEX VARIABLE UTILITY METHODS
Complex operator+(const Complex &a, const Complex &b)
{
Complex c(a.get_real() + b.get_real() , a.get_imaginary() + b.get_imaginary());
return c;
}
Complex operator-(const Complex &a, const Complex &b)
{
Complex c(a.get_real() - b.get_real() , a.get_imaginary() - b.get_imaginary()); return c;
} |
|
|
Complex operator*(const Complex &a, const Complex &b) |
|
4 |
{ |
|
Complex c(a.get_real()*b.get_real() - a.get_imaginary()*b.get_imaginary(), a.get_real()*b.get_imaginary() + a.get_imaginary()*b.get_real());
return c;
}
ostream& operator<<( ostream& out, const Complex& aComplex )
{
out << aComplex.get_real() << “+” << aComplex.get_imaginary() << “i”; return out;
}
The operators simply construct new Complex objects by using the components of the input Complex object. For example, in the operator* code, shown at 4, the multiplication result is computed by multiplying the two real parts of the complex variables input, subtracting the two imaginary parts multiplied together, and assigning the result to the real portion of the returned variable.
Testing the Complex
Number Class
If you provide a test suite with your classdefinition file, application programmers can determine right away whether any changes they’ve made to the class have broken things. In addition, this arrangement gives the developer of the original code a simple way to run regression tests (suites of tests which indicate whether previous functionality is still working) when problems occur.
1. In the code editor of your choice, re-open the source file to hold the code for your test program.
In this example, I named the test program ch69.cpp.
After you create a class, you should create a test driver that not only ensures that your code is correct, but also show people how to use your code. The following steps tell you how.
2. Type the code from Listing 69-4 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
Testing the Complex Number Class |
437 |
LISTING 69-4: THE COMPLEX-NUMBER TEST PROGRAM
int main( int argc, char **argv)
{
Complex c1( 2.0, 1.0 );
Complex c2( 3.0, 3.0 );
Complex c3 = c1 + c2;
Complex c4 = c1 * c2;
Complex c5 = c2 - c1;
cout << “C1 “ << c1 << endl; cout << “C2 “ << c2 << endl; cout << “C3 “ << c3 << endl; cout << “C4 “ << c4 << endl; cout << “C5 “ << c5 << endl;
// Output the pieces cout << endl;
cout << “Real” << “\t” << “Imaginary” << endl;
cout << c1.get_real() << “\t” << c1.get_imaginary() << endl; cout << c2.get_real() << “\t” << c2.get_imaginary() << endl; cout << c3.get_real() << “\t” << c3.get_imaginary() << endl; cout << c4.get_real() << “\t” << c4.get_imaginary() << endl; cout << c5.get_real() << “\t” << c5.get_imaginary() << endl;
return 0;
}
5
6
7
Listing 69-4 simply exercises the various components of the Complex class functionality. We create a few Complex objects at the block of lines shown starting at 5. Our next set of tests exercises the mathematical operators to add, subtract and multiply the Complex objects, as shown
in the block of lines starting at |
6. We then out- |
|
put the results for each of the objects, |
using the |
streaming operator (<<) to illustrate how the formatting is done, and check the results. This is shown in the block starting at 7. Finally, we use the accessor routines to print out the real and imaginary portions of each object.
3. Save the source code as a file in the code editor and then close the editor application.
4. Compile the source code with your favorite compiler, on your favorite operating system.
If you have done everything properly, you should see the following output from the program on the console window. Note that in this listing C3 is the sum of C1 and C2, C4 is the product, and C5 is the difference. We should see that reflected in the output:
$ ./a C1 2+1i C2 3+3i C3 5+4i C4 3+9i C5 1+2i
Real Imaginary
21
33
54
3 9
12
5. Run the program on your favorite operating system’s console.
438 Technique 69: Working with Complex Numbers
The components listed above are simply the objects from our test driver. Because C1 is equal to 2 + 1i and C2 is equal to 3 + 3i, we would expect that if we added the two objects, we would get 5 + 4i, which is exactly what is shown for the value of C3 (the sum of C1 and C2). As we expected, we get the results we should. Likewise, in the bottom listing of real and imaginary, C3 is the third entry and has a real component of 5 and an imaginary component of 4, as expected.
As you can see, the output is what we would expect from the Complex number class. We can now drop
this class into our application and use it — because it has no real ties to any other classes.
A class is useful in an inverse proportion to the number of other classes it has to include. Cumbersome is bad. If you create a class that drags in an entire library of functionality just to use a single function in that library, people will avoid it. If (instead) you create a class that does a single task — such as representing complex numbers — and have that class stand alone, people will tend to use it in their applications.
70 Converting
Numbers to Words
Technique
Save Time By
Understanding the value of converting numbers to words
Understanding the basic logic of such a program
Creating a conversion class
Testing your conversion class
If you go into a bank and ask for a cashier’s check, your bank computer system will print the check for the appropriate amount; look closely at that check and you’ll see that it has the entire amount spelled out in
English. For example, if I got a cashier’s check for $1,200.60, the check would read One thousand two hundred dollars and sixty cents. This is not an unusual use for a software program, and the ability to translate numbers to words can be applied in many different types of applications, from education to finance.
The design of the codesystem that performs this kind of translation is very interesting. The process we go through for this is always done the same way. We break the number — no matter how large — down into hundreds, and then parse the results into English. For example, if you are given the number 123,456, you look first at the 123 and append a thousand to it — resulting in one hundred twenty-three thousand. Further, within a block of hundreds, you will always look at numbers from one to twenty, then multiples of ten, then multiples of a hundred. For example, you will list the numbers from one to nineteen for a given hundred, then it is twenty, twenty-one, thirty, thirty-one, and so forth.
A process that breaks something into smaller pieces — and then assembles the pieces into larger components — should naturally make you think about objects. In this case, you can see that there are objects for the one-to-twenty conversion, the twenty-and-up conversion, and the hundreds conversion. The thousands conversion is really just a variant of the hundreds. All these cases have a common set of things to look at:
the specific range of the value
convert that range into a string
Let’s look at an example, because this is all rather confusing to explain and much easier to show. If we start with the number 123,456 and want to convert it to English, we would do the following: