- •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
440 Technique 70: Converting Numbers to Words
1. First, we break the number down into the highest unit, in this case thousands. So, the first part of our given number produces the number 123, with a unit of thousand.
2. Next, we split off the hundreds. So, we have one hundred.
3. The next step is to look at the tens unit. If this number were zero, we would skip it. In this case, it is a two, so the number is twenty. An important exception here is the number one. In this case, we have to apply special English rules (i.e. eleven, twelve, thirteen) and skip the ones digit. So, because the second digit is a two, we now have one hundred twenty.
4. Finally, we look at the ones digit. In our case, it is a three, so we have one hundred twenty three.
5. Append the units from step 1: one hundred twenty three thousand.
6. Repeat for the next block. If we are under a thousand, we skip the units part. Put both blocks together to produce: one hundred twenty three thousand four hundred fifty six.
From an object-oriented design viewpoint, the process shows that its cases have some elements in common elements — as well as some elements that are discrete for different cases. This suggests that we have a common base class, and then derived classes that manage those discrete elements. Furthermore, we can build some of the elements from the base classes to create new extended classes, such as when we create thousands from ones and tens.
This technique shows you how to convert numbers into written English.
Always take a step back from the problem when you are trying to do an object-oriented design. Doing so gives you the opportunity to see the problem from a big-picture perspective, which often allows you to break it down into small components much more easily. When you see all the pieces, you can also usually see the overlap between them — which can be factored into your base classes.
You can save a lot of time in the long-run by getting the design right from the beginning. Understanding how all the pieces fit together is essential to getting that design right.
Creating the Conversion Code
The first step toward implementing the system is to create the base classes used to build the application. The following steps show you how. The base classes represent the number ranges we are going to use to parse the existing number into digits and convert those digits into words.
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 ch70.cpp, although you can use whatever you choose.
2. Type the code from Listing 70-1 into your file.
Or better yet, copy the code from the source file on this book’s companion Web site.
LISTING 70-1: THE CONVERSION BASE CLASSES: SOURCE CODE
#include <vector> #include <string> #include <iostream>
using namespace std;
class RangeEntry
{
long _lMin; long _lMax;
long _lIncrement; public:
RangeEntry(void)
{
_lMin = 0; _lMax = 0; _lIncrement = 1;
Creating the Conversion Code 441
}
RangeEntry(long min, long max, long inc=1)
{
_lMin = min; _lMax = max;
_lIncrement = inc;
}
RangeEntry( const RangeEntry& aCopy )
{
_lMin = aCopy._lMin; _lMax = aCopy._lMax;
_lIncrement = aCopy._lIncrement;
}
RangeEntry operator=(const RangeEntry& aCopy )
{
_lMin = aCopy._lMin; _lMax = aCopy._lMax;
_lIncrement = aCopy._lIncrement; return *this;
}
// Accessors long Min()
{
return _lMin;
}
long Max()
{
return _lMax;
}
long Increment()
{
return _lIncrement;
}
bool InRange( long lVal)
{
if ( lVal >= Min() && lVal <= Max() ) return true;
return false;
}
virtual string getString(int iVal)
{
return “Unknown”;
}
};
class OnesRangeEntry : public RangeEntry
{
(continued)
442 Technique 70: Converting Numbers to Words
LISTING 70-1 (continued)
public:
OnesRangeEntry(void)
: RangeEntry(0,19,1)
{
}
virtual string getString(int iVal)
{
switch ( iVal )
{
case 1:
return string(“one”); case 2:
return string(“two”); case 3:
return string(“three”); case 4:
return string(“four”); case 5:
return string(“five”); case 6:
return string(“six”); case 7:
return string(“seven”); case 8:
return string(“eight”); case 9:
return string(“nine”); case 10:
return string(“ten”); case 11:
return string(“eleven”); case 12:
return string(“twelve”); case 13:
return string(“thirteen”); case 14:
return string(“fourteen”); case 15:
return string(“fifteen”); case 16:
return string(“sixteen”); case 17:
return string(“seventeen”); case 18:
return string(“eighteen”); case 19:
return string(“nineteen”);
}
return string(“”);
}
};
Creating the Conversion Code 443
class TensRangeEntry : public RangeEntry
{
public:
TensRangeEntry(void)
: RangeEntry(20,90,10)
{
}
virtual string getString(int iVal)
{
int iDigit = iVal / 10; switch ( iDigit )
{
case 1:
return string(“ten”); case 2:
return string(“twenty”); case 3:
return string(“thirty”); case 4:
return string(“forty”); case 5:
return string(“fifty”); case 6:
return string(“sixty”); case 7:
return string(“seventy”); case 8:
return string(“eighty”); case 9:
return string(“ninety”);
}
return string(“”);
}
};
class HundredsRangeEntry : public RangeEntry
{
public:
HundredsRangeEntry(void)
: RangeEntry(100,1000,100)
{
}
virtual string getString(int iVal)
{
OnesRangeEntry ore;
int iDigit = iVal / 100;
string s = ore.getString( iDigit ); s += “ hundred”;
(continued)
444 Technique 70: Converting Numbers to Words
LISTING 70-1 (continued)
return s;
}
};
class ThousandsRangeEntry : public RangeEntry
{
public:
ThousandsRangeEntry(void) |
|
|
|
{ |
: RangeEntry(1000,999999,1000) 1 |
||
} |
|
|
|
virtual string getString(int iVal) |
|
|
|
{ |
HundredsRangeEntry hre; |
|
|
|
|
|
|
|
TensRangeEntry tre; |
|
|
|
OnesRangeEntry ore; |
|
|
|
int iDigit = iVal / 1000; |
|
|
|
int iNum = iDigit; |
|
|
|
string s = “”; |
|
2 |
|
if ( hre.InRange(iDigit) ) |
|
|
|
{ |
|
s += hre.getString( iDigit );
iDigit = iDigit - (( iDigit/100 ) * 100);
}
if ( hre.InRange(iNum) )
{
s += “ “;
s += tre.getString( iDigit );
iDigit = iDigit - (( iDigit/10 ) * 10);
}
if ( ore.getString( iDigit ).length() ) s += “ “;
s += ore.getString( iDigit ); s += “ thousand”;
return s;
}
};
These base classes “know” how to convert a single string into a series of words that describe a number. However, because there are differences for thousands, hundreds, and single digit values, we need a set of classes to do each of these. After we have created the three basic ones
(digits, hundreds, thousands), we can then parse any number up to one million. If we wanted to parse numbers over one million, of course, we would need to add a new class, and so forth for each further magnitude we want to handle.
Creating the Conversion Code 445
The important thing is how the higher level classes (thousand, for example) call the lower level classes (hundred, ones) to process the smaller numbers. For example, take a look at the ThousandsRangeEntry class. The class contains a
range value that it processes, numbers between |
|
1000 and 999999 (shown at 1). Within the |
|
getString method, which converts |
the number |
into a human readable string, the class then uses the hundred, ten, and one digit parsing classes to do its work (see lines beginning at 2). We don’t duplicate a lot of code and we don’t have to go searching through the code to see which piece broke when there is an exception. For example, if we wanted to properly hyphenate output strings (thirty-five, instead of thirty five) we would just modify the tens class.
3. Save the source code in your code editor.
The next step is to implement the processing object that gathers up all the individual conversions into the output text string.
4. Reopen the source file in the code editor.
5. Append the code from Listing 70-2 to the source file.
LISTING 70-2: THE CONVERSION CLASS: SOURCE CODE
class NumberToWords
{
private:
vector< RangeEntry *> _entries;
protected:
virtual void InitializeToDefaults()
{
_entries.insert( _entries.end(), new OnesRangeEntry() ); 3
_entries.insert( _entries.end(), new TensRangeEntry() );
_entries.insert( _entries.end(), new HundredsRangeEntry() ); _entries.insert( _entries.end(), new ThousandsRangeEntry() );
}
public:
NumberToWords( void )
{
InitializeToDefaults();
}
virtual ~NumberToWords(void)
{
vector< RangeEntry *>::iterator iter;
for ( iter = _entries.begin(); iter != _entries.end(); ++iter )
delete (*iter);
} |
|
|
string Convert( int iVal ) |
|
|
{ |
|
|
string sRet = “”; |
|
4 |
while ( iVal > 0 ) |
|
|
{ |
|
bool bFound = false;
vector< RangeEntry *>::iterator iter;
for ( iter = _entries.begin(); iter != _entries.end(); ++iter )
{
if ( (*iter)->InRange( iVal ) )
{
if ( sRet.length()
)
sRet += “ “; sRet += (*iter)-
>getString( iVal );
iVal = iVal - ( (iVal / (*iter)- >Increment()) * (*iter)- >Increment());
bFound = true; break;
}
}
if ( !bFound ) iVal = 10;
}
return sRet;
}
};