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

C++ For Mathematicians (2006) [eng]

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

106

C++ for Mathematicians

bool Point::operator==(const Point& Q) const { return ( (x==Q.x) && (y==Q.y) );

}

The beginning of this code matches the declaration in Point.h. The only difference is that we prepend Point:: to the procedure name (operator==) to signal that this is a member of the Point class. This also implies that the left argument of == must be a Point.

The program has only one line. It checks if the x and y coordinates of the invoking object match those of Q. That is, the statement A==B causes this code to compare A.x to B.x and A.y to B.y. The variable Q is a reference to B and the unadorned x and y refer to the data in A.

The declaration for the != operator is nearly identical. Within the class declaration we have this:

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

We could write the procedure (in Point.cc) this way:

bool Point::operator!=(const Point &Q) const { return ( (x != Q.x) || (y != Q.y) );

}

This works just as does the == operator; we compare the x and y coordinates of the two points to see if they are different.

However, we present a different method for creating the != method. It is conceivable that the steps taken to check if two objects are equal are complicated. It would be useful if we could use the == method in defining the != method. We would simply see if the two objects are equal, and then compute the “not” of the result (using the ! operator).

The problem is this: Only one of the two arguments is named. In the declaration only the right hand argument to != has a name. The left argument is nameless. This does not create a problem if all we need to do is access the left argument’s data; we simply refer to the data elements by name. In x != Q.x the first x is the left argument’s x. How can we refer to the left argument in its entirety? The solution is this: Use *this. The expression *this is a reference to the object whose method is being defined. This enables us to build on the == procedure and use it in writing the != procedure. Here is the code (from Point.cc).

bool Point::operator!=(const Point &Q) const { return ! ( (*this) == Q );

}

The return statement first invokes the == method on two objects. The left-hand argument is *this referring to the object on which != is called and the right-hand argument is Q. For example, if this were invoked by the expression A!=B in some other procedure, then *this is A and Q is B. By is we mean that *this is not a copy of A, but A itself. Likewise, Q is not a copy of B, but is B itself.

(Extra for experts: The expression *this consists of two parts: the operator * and the pointer this. The this pointer always points to the location that holds the

Points in the Plane

107

object whose method is being defined. The * operator dereferences the pointer; this means that *this is the object housed at location this. In other words, *this is the object for whom the current method is being applied.)

The final operator we consider in this chapter is the << operator. We want to be able to write points to the computer screen with a statement of the form cout<<P;. The name of the procedure that does this is, not surprisingly, operator<<. However, we cannot declare this within the boundary of the class declaration because the object on the left is not of type Point. What is the type of cout? This has been hidden from you because cout is defined in the header file iostream.

The object cout is of type ostream (which stands for output stream) and is declared in the header iostream. Therefore, the expression cout << P contains two arguments: the left argument is of type ostream and the right is of type Point. Furthermore, the result of this expression is also of type ostream. A statement of the form

cout << P << " is in quadrant I" << endl;

contains the operator << three times. The order of operations rules for C++ (which you do not need to know) insert implicit parentheses into this expression to dictate the order in which the three different << procedures are called. With the hidden parentheses revealed, the expression looks like this:

( (cout << P) << " is in quadrant I" ) << endl;

The leftmost << is executed first. The effect of this operation is to print P on the screen (we see how that is done in a moment) and the result of the operation is to return cout. (We do not return a copy of cout, but the object itself—we explain how to do that in a moment.)

After the first << executes and returns cout what remains is this:

( cout << " is in quadrant I" ) << endl;

Now the second << is called. The effect is to send the character array to the screen, and again cout is returned. Finally we are left with this:

cout << endl;

This causes the a new line to be started on the screen.

With this background we are ready to declare and define the << operator for Point. In the file Point.h (but outside the class declaration) we have the following (line 31 of Point.h).

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

Let’s examine this declaration piece by piece.

The return type of this procedure is ostream&. When we invoke the << operator via the expression cout << P the result of the procedure is cout, an object of type ostream. The ampersand indicates that this procedure returns a reference to (not a copy of) the result. We explain how this is accomplished when we examine the code.

108

C++ for Mathematicians

The name of this procedure is operator<<.

The procedure takes two arguments. The first is the left-hand argument to << and the second is taken from the right.

The first (left) argument is of type ostream. We call this argument os which stands for “output stream”. Recall that << can be used with either cout or cerr; these are both objects of type ostream. The call is by reference, not by value. Objects of type ostream are large and complicated; we do not want to make a copy of them. Furthermore, operators should be invoked using call by reference. This argument is not declared const because the act of printing changes the data held in the ostream object.

The second (right) argument is of type Point. Again, we use call by reference because that is what C++ requires for operators. Printing a Point does not affect its data; we certify this by flagging this argument as const.

When we execute the statement cout << P, in effect we create a call that could be thought of like this: operator<<(cout,P).

Next we write the code for the << operator. A nice format for the output is to print the two coordinates separated by a comma and enclosed in parentheses. Here is the code that makes this work.

ostream& operator<<(ostream& os, const Point& P) { os << "(" << P.getX() << "," << P.getY() << ")"; return os;

}

Watch what happens when we encounter the statement cout << A << endl;. The left << is executed first. We pass cout and A to the procedure. The first argument os becomes cout and the second argument becomes A. (Remember, in call by reference, the arguments are not copies of the calling arguments, but the arguments themselves.)

Effectively, the first line inside the procedure is this:

cout << "(" << A.getX() << "," << A.getY() << ")";

(The argument os becomes cout and the argument P becomes A.) If A is the point (2,5), this line causes (2,5) to be written on the computer’s screen.

The second line in the procedure returns os. Because the return type of this procedure is ostream& (as opposed to plain ostream), this return does not send a copy of os back to the invoking procedure; it sends os itself. In this example, os is precisely cout, so the result of this procedure is cout (and not a copy of cout).

Therefore, after the first << in cout << A << endl; finishes, the statement is reduced to this: cout << endl;.

To summarize, here is the file Point.cc in its entirety.

Points in the Plane

109

Program 6.2: Code for the Point class methods and procedures.

1 #include "Point.h"

2#include <cmath>

3

4 Point::Point() {

5x = y = 0.;

6}

7

8 Point::Point(double xx, double yy) {

9 x = xx;

10y = yy;

11}

12

13double Point::getX() const {

14return x;

15}

16

17double Point::getY() const {

18return y;

19}

20

21void Point::setX(double xx) {

22x = xx;

23}

24

25void Point::setY(double yy) {

26y = yy;

27}

28

29double Point::getR() const {

30return sqrt(x*x + y*y);

31}

32

33void Point::setR(double r) {

34// If this point is at the origin, set location to (r,0)

35if ( (x==0.) && (y==0.) ) {

36x = r;

37return;

38}

39

40// Otherwise, set position as (r cos A, r sin A)

41double A = getA();

42x = r * cos(A);

43y = r * sin(A);

44}

45

46double Point::getA() const {

47if ( (x==0.) && (y==0.) ) return 0.;

48

49double A = atan2(y,x);

50if (A<0) A += 2*M_PI;

51return A;

52}

53

54 void Point::setA(double theta) {

110

C++ for Mathematicians

55double r = getR();

56x = r * cos(theta);

57y = r * sin(theta);

58}

59

60void Point::rotate(double theta) {

61double A = getA();

62A += theta;

63setA(A);

64}

65

66bool Point::operator==(const Point& Q) const {

67return ( (x==Q.x) && (y==Q.y) );

68}

69

70bool Point::operator!=(const Point &Q) const {

71return ! ( (*this) == Q );

72}

73

74double dist(Point P, Point Q) {

75double dx = P.getX() - Q.getX();

76double dy = P.getY() - Q.getY();

77return sqrt(dx*dx + dy*dy);

78}

79

80Point midpoint(Point P, Point Q) {

81double xx = ( P.getX() + Q.getX() ) / 2;

82double yy = ( P.getY() + Q.getY() ) / 2;

83return Point(xx,yy);

84}

85

86ostream& operator<<(ostream& os, const Point& P) {

87os << "(" << P.getX() << "," << P.getY() << ")";

88return os;

89}

Once a class has been created (or even during its development) it’s important to test its features. Here is a program to check the various aspects of the Point class.

 

Program 6.3: A program to check the Point class.

1

#include "Point.h"

2

#include <iostream>

3using namespace std;

4

5/**

6* A main to test the Point class.

7

*/

8

 

9int main() {

10

Point

X;

// Test constructor

version

1

11

Point

Y(3,4);

// Test constructor

version

2

12

 

 

 

 

 

13cout << "The point X is " << X << " and the point Y is "

14<< Y << endl;

15cout << "Point Y in polar coordinates is ("

Points in the Plane

111

16 << Y.getR() << "," << Y.getA() << ")" << endl;

17

18cout << "The distance between these points is "

19<< dist(X,Y) << endl;

20cout << "The midpoint between these points is "

21<< midpoint(X,Y) << endl;

22

23Y.rotate(M_PI/2);

24cout << "After 90-degree rotation, Y = " << Y << endl;

25

26Y.setR(100);

27cout << "After rescaling, Y = " << Y << endl;

28

29Y.setA(M_PI/4);

30cout << "After setting Y’s angle to 45 degrees, Y = " << Y << endl;

31

32Point Z;

33Z = Y; // Assign one point to another

34cout << "After setting Z = Y, we find Z = " << Z << endl;

35

36X = Point(5,3);

37Y = Point(5,-3);

38

39cout << "Now point X is " << X << " and point Y is " << Y << endl;

40if (X==Y) {

41cout << "They are equal." << endl;

42}

43

44if (X != Y) {

45cout << "They are not equal." << endl;

46}

47

48return 0;

49}

The output of this main follows.

The point X is (0,0) and the point Y is (3,4) Point Y in polar coordinates is (5,0.927295) The distance between these points is 5

The midpoint between these points is (1.5,2) After 90-degree rotation, Y = (-4,3)

After rescaling, Y = (-80,60)

After setting Y’s angle to 45 degrees, Y = (70.7107,70.7107) After setting Z = Y, we find Z = (70.7107,70.7107)

Now point X is (5,3) and point Y is (5,-3)They are not equal.

112

C++ for Mathematicians

6.9Exercises

6.1To complement the Point class created in this chapter, create your own Line class to represent a line in the Euclidean plane. The class should have the following features.

Because every line in the Euclidean plane can be represented by an equation of the form ax + by + c = 0 (where a and b are not both zero), the Line class should hold three private data elements: a, b, and c.

The class should include the following constructors.

A zero-argument default constructor. Choose a sensible behavior for this constructor (e.g., construct the line y = 0).

A two-argument constructor whose input arguments are both type Point. Of course, this should create the line through these points. Hint: In order for the Line class to use Point objects, you need to have #include "Point.h" at the top of your Line.h file.

A three-argument constructor whose double arguments are simply assigned to a, b, and c.

In the latter two cases, give sensible behaviors in the event that the user gives bad inputs (either the two points are the same or a = b = 0).

• Include get methods to inspect the values held in a, b, and c.

• Include methods named reflectX() and reflectY() that reflect this Line through the x- and y-axis, respectively.

• Include a method to check if a given Point is on the Line.

• Include a method that generates a Point on the Line.

• Include an == operator to check if two Line objects are the same.

Note: This is not as simple as checking that a, b, and c are the same for the two lines.

• Include an operator << for printing a Line to the screen (e.g., cout<<L; where L is type Line). Pick a sensible format for the output.

• Include a procedure (not a method in the class) that calculates the distance between a Point and a Line. The procedure should accept the

two arguments in either order: dist(P,L) or dist(L,P).

Hint: The standard C++ procedures sqrt(x) (for x) and fabs(x) (for |x|) may be of assistance here. Some systems may require the directive #include <cmath> to use these. See Appendix C.6.1.

6.2To test the Line class from Exercise 6.1, a programmer wrote the following code.

Points in the Plane

113

#include "Line.h" #include <iostream> using namespace std;

int main() { Point X(5,3); Point Y(-2,8); Line L(X,Y);

cout << "X = " << X << endl; cout << "Y = " << Y << endl;

cout << "The line L through X and Y is " << L << endl;

Point Q;

Q = L.find_Point();

cout << "Q = " << Q << " is a point on L" << endl; Line M(X,Q);

cout << "The line M through X and Q is " << M << endl; cout << "Are lines L and M the same?\t" << (L==M) << endl; cout << "Is Y incident with M?\t" << M.incident(Y) << endl; cout << "Distance from Y to M is zero?\t"

<< (dist(Y,M)==0) << endl; return 0;

}

In this program, we establish two points X = (5,3) and Y = (−2,8), and the line L through them.

Next we construct a point Q on L and then we construct another line M through X and Q. Because the points X, Y , and Q are collinear, it must be the case that L and M are the same line. However, when the code is run, we see the following

 

 

output.

 

 

 

 

 

 

 

 

X =

(5,3)

 

 

 

 

 

 

 

 

 

 

 

 

 

Y =

(-2,8)

 

 

 

 

 

 

 

 

The

line L through

X and Y is

[5,7,-46]

 

 

 

 

 

Q =

(0,6.57143) is

a point on

L

 

 

 

 

 

The

line M through

X and Q is

[3.57143,5,-32.8571]

 

 

 

 

 

Are

lines L and M the same?

0

 

 

 

 

 

Is Y incident with

M?

0

 

 

 

 

 

 

Distance from Y to

M is zero?

0

 

 

 

 

 

 

 

 

 

 

The program reports that L 6= M, that Y is not on the line M, and that Y is a nonzero distance away from M. All of these are mathematically incorrect. What’s wrong? How might these problems be addressed?

6.3In the Line class developed in these exercises, we represent a line as a triple (a,b,c) standing for the equation ax + by + c = 0. Alternatively, we could represent a Line as a pair of Points. How would the header and code files for the Line class need to be modified were we to decide to switch to this alternative? How would programs that use the Line class need to be modified?

6.4Create a procedure to test if two Line objects represent intersecting lines and, if not, to find their Point of intersection.

114

C++ for Mathematicians

6.5Create a LineSegment class. The data for the class should be two Point objects representing the end points of the segment. You may consider making these data elements public even though this practice is usually discouraged. Why might this be acceptable in this case?

6.6Suppose we wish to add a translate method to the Point class developed in this chapter. The effect of invoking P.translate(dx,dy) would be to move the point from its current location (x,y) to the new location (x + dx,y + dy). In addition (and this is the point of this exercise), this method should return the new value of P. For example, consider this code:

Point P(4,5);

cout << P.translate(1,2) << endl;

This should print (5,7) on the computer’s screen.

This procedure is declared by adding the following line to the public section of the Point declaration in Point.h.

Point translate(double dx, double dy);

Explain how to code this procedure in the Point.cc file.

6.7In Exercise 6.6 we considered how to add a translate procedure to the Point class. With this addition in place, consider the following code in a main().

Point P(3,5); (P.translate(1,2)).translate(10,10); cout << P << endl;

This causes (4,7) to be printed on the screen.

We might have expected (14,17) to have been printed. Explain the behavior of the code.

How can you modify the code to achieve the desired behavior.

Hint: Normally a return X; statement returns a copy of X. Examine how we coded operator<< for the Point class to implement a different behavior for return.

Chapter 7

Pythagorean Triples

A Pythagorean triple is a list of three integers (a,b,c) such that a2 + b2 = c2. Such a triple is called primitive if the integers are nonnegative and gcd(a,b,c) = 1. For example, (3,4,5) is a Pythagorean triple because 32 +42 = 52. Although (30,40,50) and (−3,4,−5) are also Pythagorean triples, they are not primitive.

In this chapter and the next we develop the C++ machinery necessary to find all primitive Pythagorean triples with a,b,c ≤ 1000 (or any other given value).

7.1Generating Pythagorean triples

There is a simple method to generate Pythagorean triples. Let z be a Gaussian integer; that is, z = m + ni where m,n Z. Consider |z4|. On the one hand,

 

 

z4 = |z|2 2 = m2 + n2 2

 

 

 

 

but

z2 2

 

 

 

 

 

 

 

 

 

 

|z4| =

= (m2

−n2) + (2mn)i 2

= (m2 −n2)2 + (2mn)2.

Therefore

 

 

 

 

 

 

 

 

 

 

 

 

 

(m2 −n2)2 + (2mn)2 = (m2 + n2)2.

 

( )

Because m,n Z, it follows that

 

m2 −n2,2mn,m2 + n2

 

 

is a Pythagorean triple.

This can also be checked directly

by expanding both sides (

 

).

 

 

 

 

 

 

 

Although every triple of the form

m2 −n2,2mn,m2 + n2

 

 

is a Pythagorean triple,

not all Pythagorean triples arise in

this manner. For example, (9,12,15) cannot be so

 

 

 

 

 

 

expressed. [Proof. If (9,12,15) were of this form, then 2mn = 12, so mn = 6. This implies that {m,n} = {±2,±3} or {±1,±6} none of which yields (9,12,15).]

However, it can be shown that every primitive Pythagorean triple is of the formm2 −n2,2mn,m2 + n2 . We use this as fact to create a class that represents primitive Pythagorean triples.

115