Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

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: