Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C++ For Mathematicians (2006) [eng]

.pdf
Скачиваний:
194
Добавлен:
16.08.2013
Размер:
31.64 Mб
Скачать

336

C++ for Mathematicians

Apply Lagrange’s theorem to the group Z_p.

What would you like to do?

(a)Prove Fermat’s little theorem

(b)Prove Fermat’s last theorem

(c)List some Fermat primes

(d)End this program

Type the character and hit return:

b

I have a great proof for this but, alas, it will not fit on this computer screen.

What would you like to do?

(a)Prove Fermat’s little theorem

(b)Prove Fermat’s last theorem

(c)List some Fermat primes

(d)End this program

Type the character and hit return: C

Here are the only ones I know: 3 5 17 257 25537.

What would you like to do?

(a)Prove Fermat’s little theorem

(b)Prove Fermat’s last theorem

(c)List some Fermat primes

(d)End this program

Type the character and hit return: x

Sorry. Your response was not recognized.

Please try again.

What would you like to do?

(a)Prove Fermat’s little theorem

(b)Prove Fermat’s last theorem

(c)List some Fermat primes

(d)End this program

Type the character and hit return: D

Goodbye.

 

15.2Labels and the goto statement

In addition to switch and all the various other control structures, C++ also provides the simple goto statement. For good reasons, use of the goto statement is highly discouraged. It can lead to programs that are impossibly difficult to understand. There is, however, one situation where a goto is handy: breaking out of nested loops. Here is how goto works.

 

 

Odds and Ends

337

 

 

 

Any statement in C++ may be prefixed by a label. A statement is labeled by a

 

 

 

name; a label name is formed according to the same rules as variables are named. To

 

 

 

label a statement, use the label name followed by a colon, like this:

 

 

 

 

increment: x += 10;

 

 

 

 

This names the statement x += 10; as increment. Now, anywhere in the same

 

 

 

procedure as this labeled statement, we may have the statement goto increment;.

 

 

 

Whenever the goto statement is invoked, the program immediately jumps to the

 

 

 

statement labeled increment and proceeds from there.

 

 

 

 

If possible, use one of C++’s other control structures rather than resorting to the

 

 

 

use of goto. However, if you need to break from a nested loop, a goto is a clean

 

 

 

solution. Here’s the issue: if you want to exit a for loop before the end condition

 

 

 

has been reached, you can execute a break statement. For example,

 

 

 

 

int k;

 

 

 

 

for (k=0; k<100; k++) {

 

 

 

 

if (x[k] < 0) break;

 

 

 

 

// otherwise do something

 

 

}

 

 

 

 

This loop stops running if any element of the array x is negative. The break state-

 

 

 

ment forces the for loop in which it is contained to stop looping. Therefore, if the

 

 

 

break is enclosed in, say, a double for loop, only the inner loop is interrupted. How

 

 

 

can we stop the execution of the outer loop? Unfortunately, break won’t help us.

 

 

 

A solution is to label the first statement after the double for loop and use a goto.

 

 

 

Here is an example.

 

 

 

 

#include <iostream>

 

 

 

 

using namespace std;

 

 

 

 

int main() {

 

 

 

 

int i,j;

 

 

 

 

for (i=0; i<10; i++) {

 

 

 

 

for (j=0; j<10; j++) {

 

 

 

 

if (i+j == 15) goto alpha;

 

 

}

 

 

}

 

 

 

 

alpha:

 

 

 

 

cout << "i = " << i << " and j = " << j << endl;

 

 

 

 

return 0;

 

 

}

 

 

 

 

The two for loops execute until i+j reaches the value 15. At that point, the loops

 

 

 

are interrupted and execution proceeds at the statement labeled alpha. The output

 

 

 

is as follows.

 

 

 

 

i = 6 and j = 9

 

 

 

 

 

 

In practice, this situation rarely occurs. You are unlikely to ever need a goto

 

statement in your programs.

338

C++ for Mathematicians

15.3Exception handling

In the course of a computation unexpected situations can arise. For example, the computer might attempt to find the inverse of a noninvertible object. If the computer is asked to divide by zero, it signals the invalid result in one of two ways: the division 1./0. results in the special value inf (which stands for ∞) and the division 0./0. results in the special value nan (which stands for not a number). Similarly, in Chapter 9 we designed the Mod class so that if the inverse method is invoked on a noninvertible Mod object, the return value is an invalid Mod.

To check that a Mod computation took place without error, the is_invalid method can be applied to the results. To check if a floating point calculation resulted in inf or nan, use the C++ procedures isinf() or isnan().1

In some cases, it is difficult to communicate an abnormal event through a return value. In this case, C++’s exception system is useful.

Although you might elect not to use the exception system, it is worth knowing its basics because packages that you download from the Web might use this feature.

15.3.1 The basics of try, throw, and catch

C++ provides an alternative method for detecting and handling such situations. When a procedure detects an erroneous situation, it can throw an exception. When the exception is thrown, the normal flow of the program is interrupted and control is passed to code that catches the exception. The code that might throw an exception is contained inside a try block. The exception-handling mechanism involves the three keywords try, throw, and catch and is structured like this:

// start of the procedure try {

// some calculations

if (something_bad_happens) throw object; // more calculations

}

catch (object_type var) { // deal with bad situation

}

// rest of the procedure

To illustrate how this works, we present the following short program that asks the user for two numbers and returns their quotient.

1In order to use these procedures, you need the directive #include <cmath> at the beginning of your program. Also, at the time of this writing, there is a bug in the Mac OS X version of C++ of cmath. Examine the file /usr/include/gcc/darwin/default/c++/cmath. Comment out the lines that read #undef isinf and #undef isnan.

Odds and Ends

339

Program 15.2: A program to divide two numbers and illustrate basic exception handling.

1#include <iostream>

2

3using namespace std;

4

5 int main() {

6 double x,y;

7try {

8 cout << "Enter numerator: ";

9cin >> x;

10cout << "Enter denominator: ";

11cin >> y;

12

13 if (y==0.) throw x;

14

15cout << x << " divided by " << y << " is " << x/y << endl;

16}

17catch (double top) {

18cerr << "Unable to divide " << top << " by zero" << endl;

19}

20

21 cout << "Thank you for dividing." << endl;

22

23return 0;

24}

After some initialization, we encounter the try block (lines 7–16). Within this block, we prompt the user for two real numbers. Then, just before we divide, we test if the denominator is zero. At this point there are two possible ways the program might proceed.

If the denominator is zero, the exception is thrown (line 13). In this case, we throw the value of the numerator. The remaining statements inside the try block are skipped; line 15 is not executed.

At this point, the computer skips to the end of the try block and searches for a catch statement whose type agrees with the type of the object that was thrown. Because throw x; throws a double value, execution proceeds to the catch statement on line 17. The variable top named in the catch statement is set equal to the value thrown (the value held in x).

Now the statements embedded inside the catch block execute. In this example, there is only one statement (line 18) that prints an error message on the screen.

Once the catch block is finished, execution continues at line 21.

Alternatively, if the denominator is not zero, then the throw statement on line 13 does not execute. Instead, the program continues with the remaining statements inside the try block. In this example, there is only one more statement (line 15).

340

C++ for Mathematicians

Because no exception was thrown, the catch block is skipped, and execution proceeds to the statements following the try block—that is, to line 21.

Here are two runs of the program to illustrate how this works.

 

Enter numerator: 17 Enter denominator: 4

17 divided by 4 is 4.25

Thank you for dividing.

Enter numerator: -9.8 Enter denominator: 0

Unable to divide -9.8 by zeroThank you for dividing.

For this example, it would be simpler to use an if/then/else construction. The exception-throwing mechanism is particularly useful when the exception is thrown by one procedure and caught by another. We illustrate this idea in the following program.

Program 15.3: A program to illustrate catching exceptions thrown by other procedures.

1#include <iostream>

2

3using namespace std;

4

5 double quotient(double p, double q) {

6 if (q==0.) throw p;

7return p/q;

8}

9

10int main() {

11double x,y;

12try {

13cout << "Enter numerator: ";

14cin >> x;

15cout << "Enter denominator: ";

16cin >> y;

17

18 double q = quotient(x,y);

19

20cout << x << " divided by " << y << " is " << q << endl;

21}

22catch (double top) {

23cerr << "Unable to divide " << top << " by zero" << endl;

24exit(1);

25}

26

27 cout << "Thank you for dividing." << endl;

28

29 return 0;

30 }

Odds and Ends

341

This program defines a procedure named quotient (see lines 5–8) that takes two double arguments and returns their quotient. If this procedure is invoked with q equal to zero, then the quotient is undefined. In this case, the procedure throws an exception. Notice that there is no try/catch block in the quotient procedure; it is the responsibility of the procedure that invokes quotient (in this example, main) to handle the exception.

Inside main, the call to quotient is embedded in a try block (lines 12–21). If quotient runs normally (i.e., its second argument is not zero), then line 18 executes normally. Execution then proceeds to line 20, then the catch block (lines 22–25) is skipped, and the program continues at line 27.

However, if (at line 18), the second argument sent to quotient is zero, then quotient throws an exception. The value of the numerator is thrown. The rest of the try block is skipped; that is, line 20 is not executed. At this point the computer searches for a catch statement whose type matches the type of the thrown value. Because a double value is thrown by quotient, it is caught at line 22. Now the catch block executes with top set equal to the thrown value. An error message is printed (line 23) followed by a call to a system procedure named exit. This causes the program to stop executing immediately. Consequently, the statements following the catch block—statements that would typically execute were it not for the call to exit—are not executed.

If possible, the catch block should repair any damage caused by the exceptional situation so the program can continue running. However, some errors are sufficiently serious that continued execution does not make sense. In that case, a call to exit causes the program to stop running. Note that exit can be called inside any procedure (not just in main).

The exit procedure takes an integer valued argument; this value is passed back to the operating system. If your C++ program is invoked by a script, the script can use that value to detect that something unusual occurred during the execution of the C++ program. By convention, a return value of 0 signals normal execution. That is why we end our main programs with the statement return 0;. However, if the program is forced to stop executing inside a procedure other than main, then exit can be used to return a value to the operating system.

Here are two runs of Program 15.3.

 

Enter numerator: 10 Enter denominator: 4 10 divided by 4 is 2.5

Thank you for dividing.

Enter numerator: -10 Enter denominator: 0

Unable to divide -10 by zero

342

C++ for Mathematicians

15.3.2 Other features of the exception-handling system

Multiple catches

A try block must be followed by a catch block. However, there may be more than one catch block, provided each catches a different type. The general structure looks like this:

try {

// code that might generate exceptions

}

catch (type_1 x1) {

// handle this exception

}

catch (type_2 x2) {

// handle this exception

}

...

catch (type_n xn) {

// handle this exception

}

// rest of the code

If an exception is thrown in the try block, it is caught by the catch block that matches the type of the object that was thrown. That, and only that, catch block is executed; the other catch blocks are skipped. Of course, if no exception is thrown, none of the catch blocks executes.

Uncaught exceptions and a catch-all block

It is important that there be a catch block for every type of exception that might be thrown by a try block. Otherwise, the uncaught exception causes the program to terminate by calling the system procedure abort. The abort procedure is a more drastic version of exit. abort takes no arguments and prints a message such as Abort trap on the screen.

It is conceivable that you might not know every type of exception your program might produce. For example, you may be using a package you downloaded and you might not be familiar with the various exceptions that package’s procedures can produce. In such a case, you can create a catch-all block that catches any exception. Here’s how: after the various known exceptions, create a block that looks like this,

catch(...) {

// code to handle an exception of an unknown type

}

It is difficult for a catch-all block to handle errors because any information contained in the thrown object is lost; unlike a typical catch block, there is no value sent to a catch-all block.

Objects to throw

Any C++ object may be thrown by a throw statement. If you were to create your own arc sine function, it would be natural to throw an exception if the argument to

Odds and Ends

343

the function were outside the interval [−1,1]. In this case, it would be sensible to throw the argument of the function so the calling procedure knows that it sent an illegal value.

Alternatively, we can throw an object specifically designed to convey detailed information on the error. For example, we can create a class named TrigException like this:

class TrigException { public:

double value; string message;

};

Then our arc sine function would have the following form.

double arc_sin(double x) { if ((x<-1.) || (x>1.)) { TrigException err;

err.value = x;

err.message = "Argument to arc_sin not in [-1,1]"; throw err;

}

// calculate the arc sine of x, etc.

}

A procedure that calls arc_sin, or other trigonometric functions of our own creation, can be structured like this:

try {

// various trig calculations

}

catch (TrigException E) {

cerr << "Trouble occurred during the calculation" << endl; cerr << E.message << endl;

cerr << "The faulty argument was " << E.value << endl; exit(1);

}

Packages you download from the Web might contain their own exception types. For example, a geometry package might include a procedure to find the point of intersection of two lines. What should such a procedure do when presented with a pair of parallel lines? A sensible answer is to throw an exception.

Rethrowing exceptions

It is possible for a procedure to throw an exception, catch it, partially handle the exception, and then rethrow the exception to be handled by another procedure. It is unlikely that you will need this feature for mathematical work. Nevertheless, we describe how this is done.

A catch block that both handles an exception and also passes that exception on is structured like this:

catch(type x) {

// handle the exception

344

C++ for Mathematicians

throw;

}

When this catch is invoked, the various statements in the catch block are executed. Finally, the statement throw; is reached; this causes the original exception that was caught to be thrown again.

The keyword throw in a procedure declaration

When you write a procedure that throws exceptions, it is advisable to announce explicitly the types of exceptions that might be thrown. This is done immediately after the argument list as in the following example.

double quotient(double p, double q) throw(double) { if (q==0.) throw p;

return p/q;

}

If a procedure throws more than one type of exception, we list the various types within the parentheses like this:

return_type proc_name(type1 p1, type2 p2, ..., typeN pN) throw(xtype1, xtype2, ..., xtypeM) {

// the procedure

}

The addition of a throw list to the introduction of a procedure makes it clear to the user of the procedure what kinds of exceptions the procedure might throw. Users of the procedure need only inspect the first line to deduce this information and do not need to hunt through the code for the various places an exception is generated.

Exceptions that are not listed on the list of throw types cannot escape from the procedure. Either they must be caught inside the procedure or they trigger a runtime error.

15.4Friends

Private data members of a class are accessible to the class’s methods; other procedures cannot inspect or modify these values. This is the essence of data hiding and it protects objects from being corrupted. When you use a class, you do not interact directly with its data, but only use its public methods.

However, when you create a class, it may be useful to create some procedures that are permitted to access the class’s private elements. For example, recall the Point class of Chapter 6. In addition to the various methods (member procedures of the class), we also defined the procedures dist and midpoint. Neither of these is a member of the class Point, and so they need to use getX and getY to learn their arguments’ coordinates. C++ provides a mechanism by which midpoint and dist

Odds and Ends

345

can bypass data hiding; we do this by declaring these procedures friends of the class

Point.

The original header file for the Point class, Point.h, is given in Program 6.1. Here we present an alternative version in which dist and midpoint are declared to be friends of the Point class.

Program 15.4: A new Point.h header with friend procedures.

1 #ifndef POINT_H

2#define POINT_H

3 #include <iostream>

4using namespace std;

5

6class Point {

7

8private:

9 double x;

10 double y;

11

12public:

13Point();

14Point(double xx, double yy);

15double getX() const;

16double getY() const;

17void setX(double xx);

18void setY(double yy);

19double getR() const;

20void setR(double r);

21double getA() const;

22void setA(double theta);

23void rotate(double theta);

24bool operator==(const Point& Q) const;

25bool operator!=(const Point& Q) const;

26

27friend double dist(Point P, Point Q);

28friend Point midpoint(Point P, Point Q);

29

30 };

31

32 ostream& operator<<(ostream& os, const Point& P);

33

34 #endif

Notice that dist and double are now declared inside the Point class declaration, and their declarations begin with the keyword friend. The friend keyword signals that they are not Point class members (i.e., methods), but rather privileged procedures that are permitted to access the private elements of Point.

With this header in place, the definitions of dist and midpoint (in the file Point.cc) look like this:

double dist(Point P, Point Q) { double dx = P.x - Q.x; double dy = P.y - Q.y; return sqrt(dx*dx + dy*dy);