- •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
446 Technique 70: Converting Numbers to Words
The main parts of the NumberToWords class are the entries (shown at 3 in the Listing 70-2) and the Convert method. The entries are simply extensions of the base RangeEntry class that process given ranges of the value being converted (the iVal parameter). The number is broken down by the increment of each range (thousands, hundreds, tens, ones) and each entry is called to process that particular unit. This continues until the input value is reduced to a value of zero. The loop to process the value is shown at 4.
Testing the Conversion Code
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 show you how.
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 ch70.cpp.
2. Type the code from Listing 70-3 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 70-3: THE NUMBER-CONVERSION TEST PROGRAM
int main()
{
NumberToWords nw;
string s1 = nw.Convert(123);
cout << “String: “ << s1 << endl; string s2 = nw.Convert(1);
cout << “String: “ << s2 << endl; string s3 = nw.Convert(23);
cout << “String: “ << s3 << endl; string s4 = nw.Convert(807);
cout << “String: “ << s4 << endl; string s5 = nw.Convert(123456); cout << “String: “ << s5 << endl;
}
The purpose of our little test driver is simply to show that the class works with all of the exceptional cases that exist for numeric conversions. For example, we want examples of ones, tens, hundreds, and thousands. We also want a simple example that requires the code to check all of its conditions, such as 23.
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
String: one hundred twenty three String: one
String: twenty three String: eight hundred seven
String: one hundred twenty three thousand four hundred fifty six
As you can see from the output listing, the code works properly. All of the various scenarios are handled correctly and the output is in expected English. As mentioned previously, possible enhancements to the application would be extending the classes to process millions, billions, and so forth, or adding hyphens, if desired.
71 Reducing the
Complexity of Code
Technique
Save Time By
Componentizing your code
Restructuring programs
Specializing components
Programmers know the best program design is always simple. In fact, among programmers, the KISS principle has become a cliché: “Keep It Simple, Stupid.” To keep things simple, you have to follow three
basic principles when writing and maintaining code:
Componentizing
Restructuring
Specializing
By following these few simple processes when you develop and debug your code, you can drastically cut down on your maintenance time. In this technique, we will look at these four pillars of programming simplicity — and examine how to apply them.
A Sample Program
Imagine, for a moment, that you’re working on a program that parses input files for words. This sort of program might be used to get a list of words for a spell-checker or a stop list for an indexing program. In text indexing, a stop list gives the program a list of words to ignore when placing them in the index. The code for this type of program is shown in Listing 71-1. Its obviously a very simple, stripped-down program, but it illustrates the basic idea of what we’re trying to accomplish.
448 |
Technique 71: Reducing the Complexity of Code |
|
|
LISTING 71-1: THE ORIGINAL WORD-PARSER PROGRAM |
|
|
|
|
|
|
|
#include <stdio.h> |
|
|
|
#include <string.h> |
|
|
|
#include <vector> |
|
|
|
using |
namespace std; |
|
|
void my_func( std::vector< char *>& words ) |
|
|
|
{ |
|
|
|
FILE *fp = fopen(“myfile.txt”, “r”); |
|
|
|
if ( fp == NULL ) |
|
|
|
|
return -1; |
|
|
while ( !feof(fp) ) |
|
|
|
{ |
char szBuffer[ 81 ]; |
|
|
|
|
|
|
|
memset( szBuffer, 0, 80 ); |
|
|
|
if ( fgets( szBuffer, 80, fp ) == NULL ) |
|
|
|
break; |
|
|
|
// Parse the line |
|
|
|
char szWord[80]; |
|
|
|
memset ( szWord, 0, 80 ); |
|
|
|
int pos = 0; |
|
|
|
for ( int i=0; i<(int)strlen(szBuffer); ++i ) |
|
1 |
|
{ |
|
switch ( szBuffer[i] )
{
case ‘:’:
if ( strlen(szWord) )
{
char *str = new char[strlen(szWord)+1]; strcpy( str, szWord );
words.insert( words.end(), str ); szWord[0] = 0;
pos = 0;
memset ( szWord, 0, 80 );
}
break;
default:
szWord[pos] = szBuffer[i]; pos++;
break;
}
}
}
fclose(fp);
}
Componentizing 449
int main(int argc, char **argv )
{
std::vector< char *> words; my_func( words );
std::vector< char *>::iterator iter;
for ( iter = words.begin(); iter != words.end(); ++iter ) printf(“Word: %s\n”, (*iter) );
return 0;
}
The code above is supposed to read lines in from a file, parse them into words, and store the words in an array. It is assumed that the lines have a specific format: word1:word2:word3 followed by a carriage return. Given an input like that, the assumption is that the program will produce a list that contains word1, word2, and word3. The code accomplishes this by stepping through each character in the line, looking for a colon (:) and taking whatever precedes it as a word. You can see this code in the loop shown at 1.
This code generally works, except it has a rather severe bug — it will skip words at the end of a line — and anyway the real issue is that this code is hard to maintain. If we add a new separator to the line (for example), what happens? If someone comes along and has no idea what the code does, is it at all intuitive? The first step to making things better is to separate it into components.
If we run the program with an input file that looks like this
word1:word2:word3
line2:word2:word3
line3:word3:word4
The problem is shown by the fact that the word3 from line 1 and word3 from line 2 are not shown.
Componentizing
Componentizing is my own term for the process of splitting something up into components. In our code, there are two major components, a file component and a parser component. Components differ from functions, methods, or classes. A component is a single functional element — that is, a collection of code that accomplishes a single task or deals with a single area such as a file or parsing text. Componentizing simplifies your code by reducing the amount of cohesion between the various units of a module, and by limiting the areas in which you need to search for a given piece of functionality. If we are looking for something that reads from or writes to a file, we look in the file component. We wouldn’t bother to look in the parser component, because that has nothing to do with reading or writing from a file. We have not yet split our class into components, we are merely identifying the different units in the current code.
the program will then parse the individual lines into
Line1:
word1
word2
Line2:
line2
word2
The next step toward making our code simpler is to break it down into separate components. Let’s identify and split out the pieces into their own componentized classes. Our new structure will contain two separate classes. This is how you do it.
450 Technique 71: Reducing the Complexity of Code
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 ch71.cpp, although you can use whatever you choose.
2. Type the code from Listing 71-2 into your file.
Better yet, copy the code from the source file on this book’s companion Web site.
LISTING 71-2: THE COMPONENTIZED SAMPLE PROGRAM
#include <stdio.h> #include <string.h> #include <vector> #include <string>
using namespace std; |
|
|
class ParserFile |
|
2 |
{ |
|
private: FILE *fp;
public:
ParserFile(void)
{
fp = NULL;
}
ParserFile( const char *fileName )
{
if ( fileName != NULL )
fp = fopen( fileName, “r” );
}
string getLine()
{
string s = “”;
if ( fp == NULL ) return s;
char c = 0;
while ( !feof(fp) && c != ‘\n’ )
{
c = fgetc(fp);
if ( c != ‘\n’ && c != ‘\r’ && c != EOF)
s += c;
}
return s;
}
bool eof()
{
if ( fp == NULL ) return true; return feof(fp);
}
}; |
|
|
class Parser |
|
3 |
{ |
|
|
private: |
|
|
char delimiter; |
|
|
vector< string > words; |
|
|
public: |
|
|
Parser(void) |
|
|
{ |
|
|
delimiter = ‘;’; // default |
|
|
} |
|
|
Parser( const char& delim ) |
|
|
{ |
|
|
delimiter = delim; |
|
|
} |
|
|
void clear() |
|
|
{ |
|
|
words.erase( words.begin(), |
|
|
words.end() ); |
|
|
} |
|
|
bool parse( const string& in )
{
string sWord = “”;
if ( delimiter == 0 ) return false;
if ( in.length() == 0 ) return false;
for ( int i=0; i<(int)in.length(); ++i )
{
// End of word or string?
if ( in[i] == delimiter)
{
words.insert( words.end(), sWord );
sWord = “”;
}
else