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

C++ For Mathematicians (2006) [eng]

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

236

C++ for Mathematicians

return b;

}

if (b<c) return c; return b;

}

At some point, we may want to apply max_of_three to another class. We could write more and more versions of this procedure, each with a different first line to accommodate the additional types.

The result is many different incarnations of the max_of_three procedure. This is legal in C++; one can have multiple procedures with the same name provided the types or number of arguments are different for each version. The procedure max_of_three(double,double,double) happily coexists with the version of max_of_three that acts on arguments of type PTriple. It is annoying that we need to create the exact same procedure over and over again for different types of arguments. Furthermore, if we want to change the algorithm for max_of_three, we need to edit all the different versions.

Fortunately, there is a better way. It is possible to write a single version of max_of_three that automatically adapts to the required situation using templates. Here is the template for the max_of_three procedure.

Program 12.1: The header file containing the template for the max of three procedure.

1 #ifndef MAX_OF_THREE_H

2#define MAX_OF_THREE_H

3

4template <class T>

5 T max_of_three(T a, T b, T c) {

6if (a<b) {

7 if (b<c) return c;

8return b;

9}

10if (b<c) return c;

11return b;

12}

13

14 #endif

There are two points to notice.

The procedure is given, in full, in the header file, max_of_three.h. This is standard for templates. One does not separate the declaration in the header file from the code in the .cc file.

In addition, the keyword inline is not needed.

The procedure begins with template <class T>. This sets up the letter T to stand for a type.

The remainder of the code is exactly what we had before with T standing for long, double, PTriple, or any other type. The letter T acts as a “type variable.”

Polynomials

237

One may think of a template as a procedure schema. When the C++ compiler encounters max_of_three in a program, it uses the template to create the appropriate version.

For example, here is a main that utilizes the adaptable nature of max_of_three.

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

int main() { double x = 3.4; double y = -4.1; double z = 11.2;

long a = 14; long b = 17; long c = 0;

cout << "The max of " << x << ", " << y << ", " << z << " is " << max_of_three(x,y,z) << endl;

cout << "The max of " << a << ", " << b << ", " << c << " is " << max_of_three(a,b,c) << endl;

return 0;

}

The first invocation of max_of_three has three double arguments; the compiler uses the max_of_three template with T equal to double to create the appropriate code. The second invocation has three long arguments, and as before, the compiler uses the template to create the appropriate version of the procedure.

The output of this main is as we expect.

The max of 3.4, -4.1, 11.2 is 11.2The max of 14, 17, 0 is 17

The max_of_three template works for any type T for which < is defined. If < is not defined for the type, the compiler generates an error message. For example, consider the following main.

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

int main() { complex<double> x(3,0); complex<double> y(-2,3); complex<double> z(1,1);

cout << "The max of " << x << ", " << y << ", " << z << " is " << max_of_three(x,y,z) << endl;

return 0;

}

238

C++ for Mathematicians

 

 

 

 

When we attempt to compile this program, we get the following error messages (your

 

 

 

 

computer might produce different error messages).

 

 

 

 

max_of_three.h: In function ‘T max_of_three(T, T, T) [with T =

 

 

 

 

 

 

 

std::complex<double>]’:

 

 

 

 

main.cc:12:

instantiated from here

 

 

 

 

max_of_three.h:6: error: no match for ‘std::complex<double>& <

 

 

 

 

 

std::complex<double>&’ operator

 

 

 

 

max_of_three.h:7: error: no match for ‘std::complex<double>& <

 

 

 

 

 

std::complex<double>&’ operator

 

 

 

 

max_of_three.h:10: error: no match for ‘std::complex<double>& <

 

 

 

 

 

std::complex<double>&’ operator

 

 

 

 

 

 

 

The compiler is complaining that it cannot find an operator< that takes two arguments of type complex<double> at lines 6, 7, and 10 in the file max_of_three.h; this is precisely where the < expressions occur.

It is possible to create templates with multiple type parameters. Such templates look like this:

template <class A, class B>

void do_something(A arg1, B arg2) { ... }

When the compiler encounters code such as

long alpha = 4; double beta = 4.5;

do_something(alpha, beta);

it creates and uses a version of do_something in which A stands for long and B stands for double.

12.2Class templates

12.2.1 Using class templates

In Section 2.7 we showed how to declare C++ variables to hold complex numbers. After the directive #include <complex>, we have statements such as these:

complex<double> w(-3.4, 5.1); complex<long> z(4, -7);

The first declares a complex variable w in which the real and imaginary parts are double real numbers and the second declares z to be a Gaussian integer (long integer real and imaginary parts).

The class complex is, in fact, a class template. By specifying different types between the < and > delimiters, we create different classes. For example, we could use the Mod type (see Chapter 9):

 

 

Polynomials

239

 

 

 

#include <complex>

 

 

 

 

#include "Mod.h"

 

 

 

 

using namespace std;

 

 

 

 

int main() {

 

 

 

 

Mod::set_default_modulus(11);

 

 

 

 

complex<Mod> z(4,7);

 

 

 

 

cout << "z = " << z << endl;

 

 

 

 

cout << "z squared = " << z*z << endl;

 

 

 

 

return 0;

 

 

}

 

 

 

 

This program calculates (4 + 7i)2 where 4 and 7 are elements of Z11 where we have

 

 

 

(4 + 7i)2 = −33 + 56i = i. This is confirmed when the program is run.

 

 

 

 

z = (Mod(4,11),Mod(7,11))

 

 

 

 

 

 

z squared = (Mod(0,11),Mod(1,11))

 

 

 

 

 

 

 

Likewise, vector is a class template. To create a vector that contains integers,

 

 

 

we use vector<long>. To create a vector containing complex numbers with real

 

 

 

coefficients, the following mildly baroque construction is required,

 

 

 

 

vector<complex<double> > zlist;

 

 

 

 

Note the space between the two closing > delimiters; the space is mandatory here.

 

 

 

If it were omitted, the >> would look like the operator we use in expressions such

 

 

 

as cin >> x causing angst for the compiler. For better readability, you may prefer

 

 

 

this:

 

 

 

 

vector< complex<double> > zlist;

 

 

 

 

The pair class template takes two type arguments. To create an ordered pair

 

 

 

where the first entry is a real number and the second is a Boolean, we write this:

 

 

 

 

pair<double,bool> P;

 

 

 

 

Using class templates is straightforward. The template is transformed into a spe-

 

 

 

cific class by adding the needed type argument(s) between the < and > delimiters.

 

 

 

The main pitfall to avoid is supplying a type that is incompatible with the tem-

 

 

 

plate. For example, it would not make sense to declare a variable to be of type

 

 

 

complex<PTriple>.

 

 

 

 

By the way: The use of < and > delimiters in #include <iostream> is unrelated

 

 

 

to their use in templates.

 

 

12.2.2 Creating class templates

Now that we have examined how to use class templates we are led to the issue: How do we create class templates? The technique is similar to that of creating procedure templates. To demonstrate the process, we create our own, extremely limited version of the complex template that we call mycomplex. The template resides in a file named mycomplex.h; there is no mycomplex.cc file. Here is the header file containing the template.

240

C++ for Mathematicians

Program 12.2: The template for the mycomplex classes.

1 #ifndef MY_COMPLEX_H

2#define MY_COMPLEX_H

3

4 #include <iostream>

5using namespace std;

6

7 template <class T>

8class mycomplex {

9

10private:

11T real_part;

12T imag_part;

13

14public:

15mycomplex<T>() {

16real_part = T(0);

17imag_part = T(0);

18}

19

20mycomplex<T>(T a) {

21real_part = a;

22imag_part = T(0);

23}

24

25mycomplex<T>(T a, T b) {

26real_part = a;

27imag_part = b;

28}

29

30T re() const { return real_part; }

31T im() const { return imag_part; }

32

33 };

34

35template<class T>

36ostream& operator<<(ostream& os, const mycomplex<T>& z) {

37os << "(" << z.re() << ") + (" << z.im() << ")i";

38return os;

39}

40

41 #endif

The overall structure of the class template is this:

template <class T> class mycomplex {

// private and public data and methods

};

Let’s examine this code.

The initial template <class T> serves the same purpose as in the case of template procedures. This establishes T as a parameter specifying a type. When we declare a variable with a statement like this

Polynomials

241

mycomplex<double> w;

the T stands for the type double. See lines 7–8.

The class template has two private data fields: real_part and imag_part. These are declared as type T. Thus if w is declared type mycomplex<double> then w.real_part and w.imag_part are both type double. See lines 10– 12.

Three constructors are given. The same name is used for these constructors: mycomplex<T>. (The <T> is optional here; the constructors could be named simply mycomplex with the <T> implicitly added.)

The zero-argument constructor creates the complex number 0 + 0i. Note that the value 0 is explicitly converted to type T. This requires that type T be able to convert a zero into type T. If some_type is a class that does not have a single-argument numerical constructor, then declaring a variable to be a mycomplex<some_type> causes a compiler error. See lines 15–18.

The singleand double-argument constructors follow (lines 20–28). They are self-explanatory.

We provide the methods re() and im() to retrieve a mycomplex variable’s real and imaginary parts. Note that the return type of these methods is T. See lines 30–31.

The last part of the file mycomplex.h is the operator<< procedure for writing mycomplex variables to the computer’s screen. This procedure is not a member of the mycomplex class template, so it needs a separate introductory template <class T> in front of the procedure’s definition. See line 35.

The next line looks like the usual declaration of the << operator. The second argument’s type is a reference to a variable of type mycomplex<T>.

After this comes the inline code for the procedure. The keyword inline is optional because all templates must be defined inline. (If we want to include the keyword inline, it would follow template <class T> and precede ostream&.)

Of course, to make this a useful template, we would need to add methods/procedures for arithmetic, comparison, exponentiation, and so forth.

We could create a different version of mycomplex in which the real and imaginary parts are allowed to be different types. The code would look like this.

Program 12.3: A revised version of mycomplex that allows real and imaginary parts of different types.

1 template <class T1, class T2>

2class mycomplex {

3

242

C++ for Mathematicians

4private:

5 T1 real_part;

6T2 imag_part;

7

8public:

9mycomplex() {

10real_part = T1(0);

11imag_part = T2(0);

12}

13

14mycomplex(T1 a) {

15real_part = a;

16imag_part = T2(0);

17}

18

19mycomplex(T1 a, T2 b) {

20real_part = a;

21imag_part = b;

22}

23

24T1 re() const { return real_part; }

25T2 im() const { return imag_part; }

26

27 };

28

29template<class T1, class T2>

30ostream& operator<<(ostream& os, const mycomplex<T1,T2>& z) {

31os << "(" << z.re() << ") + (" << z.im() << ")i";

32return os;

33}

If we use this alternative mycomplex template, variable declarations require the specification of two types, such as this:

mycomplex<long, double> mixed_up_z;

12.3The Polynomial class template

Our goal is to create a C++ class template to represent polynomials over any field K and we call this class template Polynomial. Consider the following declarations,

Polynomial<double> P;

Polynomial< complex<double> > Q;

Polynomial<Mod>

R;

These are to create polynomials in R[x], C[x], and Zp[x], respectively. (Of course, we need #include <complex> and #include "Mod.h".)

Polynomials

243

12.3.1 Data

We need to store the coefficients of the polynomial. For this, we use a vector variable (see Section 8.4) named coef. The coefficient of xk is held in coef[k]. We therefore require the directive #include <vector> in the file Polynomial.h.

We hold the degree of the polynomial in a long variable named dg. The degree is the largest index k such that coef[k] is nonzero. In the case where the polynomial is equal to zero, we set dg equal to −1.

See lines 11–12 in Program 12.4.

12.3.2 Constructors

A basic, zero-argument constructor produces the zero polynomial. This is accomplished by a call to a clear() method that resizes coef to hold only one value, sets that value to zero, and sets dg equal to −1. See lines 23–25 and 78–81 of Program 12.4.

A single-argument constructor is used to produce a constant polynomial; that is, a polynomial in which ck = 0 for all k ≥ 1. This constructor may be used explicitly or implicitly to convert scalar values to polynomials. For example, consider this code:

Polynomial<double> P(5.);

Polynomial<double> Q;

Polynomial< complex<double> > R;

Q = 6;

R = -M_PI;

The first line creates the polynomial p(x) = 5 using the constructor explicitly. The polynomials q(x) = 6 and r(x) = (−π + 0i) both use the constructor implicitly. Notice that for both Q and R there is also a conversion of the right-hand side of the assignment into another numeric type. The 6 is an integer type and is cast into double for storage in Q. Similarly, -M_PI is a real number and this needs to be converted to a complex type for storage in R.

To do this, we allow the argument to be of any type and then cast that argument to type K. Let’s look at the full code for this constructor (lines 27–33):

template <class J> Polynomial<K>(J a) {

coef.clear();

coef.resize(1); coef[0] = K(a); deg_check();

}

This constructor is a template within a template! (The outer template has the structure template <class K> Polynomial { ... };.) Thus, in code such as

Polynomial< complex<double> > P(val);

Polynomial< complex<double> > Q;

Q = val;

the variable val may be of any type.

244

C++ for Mathematicians

There is, of course, a catch. We assign coef[0] with the value K(a). This is an explicit request to convert the value a to type K. This is fine if we are converting a long to a double or a double to a complex<double>, but fails when a is not convertible to type K, for example, if K were type double and a were type PTriple.

Notice the call to the private method deg_check(). This method scans the data held in coef to find the last nonzero value and resets dg accordingly. Various operations on polynomials might alter their degree (e.g., addition of polynomials, changing coefficients, etc.) and this method makes sure dg holds the correct value.

Next we have a three-argument constructor (lines 35–43). To create the polynomial ax2 + bx + c one simply invokes Polynomial<K>(a,b,c). As before, the three arguments need not be type K; it is enough that they be convertible to type K. For example, consider this code.

long

a

= 7;

complex<double>

b(4.,-3.);

double

c

= M_PI;

Polynomial< complex<double> > P(a,b,c);

This creates a polynomial P equal to 7x2 + (4 −3i)x + π.

Finally, it is useful to be able to create a polynomial given an array of coefficients. The constructor on lines 45–60 takes two arguments: the size of an array and an array of coefficients. The array is declared as type J*; that is, an array of elements of type J. The only requirement on J is that values of that type must be convertible to type K.

12.3.3 Get and set methods

We include an assortment of methods to inspect and modify the coefficients held in a Polynomial.

The deg() method returns the degree of the polynomial. (See line 62.)

The get(k) method returns the coefficient of xk. In the case where k is invalid (negative or greater than the degree), we return zero. (See lines 64–67.)

As an alternative to get(k) we provide operator[] (line 69). For a polynomial P, both P[k] and P.get(k) have the same effect. However, we have not set up operator[] to work on the left-hand side of an assignment; we cannot change the kth coefficient by a statement such as P[k]=c;.

The isZero() method returns true if the polynomial is identically zero. See line 89.

The set(idx,a) method sets the coefficient coef[idx] equal to the value a. See lines 71–76.

Polynomials

245

Some care must be taken here. First, if idx is negative, no action is taken. If idx is greater than the degree, we need to expand coef accordingly. Also, this method might set the highest coefficient to zero, so we invoke deg_check().

The clear() method sets all coefficients to zero. See lines 78–82.

The minimize() method frees up any wasted memory held in coef. It is conceivable that a polynomial may at one point have large degree (causing coef to expand) and then later have small degree. Although the size of coef grows and shrinks with the degree, the capacity of coef would remain large. This method causes the vector method reserve to be invoked for coef. See lines 84–87.

Finally, we have the shift(n) method. See lines 91–105.

If n is positive, this has the effect of multiplying the polynomial by xn. Each coefficient is shifted upwards; the coefficient of xk before the shift becomes the coefficient of xk+n after.

Shifting with a negative index has the opposite effect. Coefficients are moved to lower powers of x. Coefficients shifted to negative positions are discarded.

The polynomial P

4x2

+ 6x −2

After P.shift(2)

4x4

+ 6x3 −2x2

After P.shift(-1)

4x + 6.

Notice that we give the argument n a default value of 1 (see line 91). Thus

P.shift() is the same as P.shift(1).

12.3.4 Function methods

Polynomials are functions and to use them as such we provide a method named of. Invoking P.of(a) evaluates the polynomial p(x) at x = a. See lines 109–118.

An efficient way to evaluate a polynomial such as 3x4 +5x3 −6x2 +2x+7 at x = a is this:

 

(3 ×a) + 5 ×a + (−6)

×a + 2 ×a + 7.

 

 

 

In addition to the of method, we provide an operator() method (line 120). This way we can express function application in the natural way P(a) in addition to

P.of(a).

Because polynomials are functions, they may be composed and the result is again a polynomial. We use the same method names, of and operator(), for polynomial composition. For polynomials P and Q, the result of P.of(Q) (and also P(Q)) is the polynomial p(q(x)). See lines 122–135.

These methods depend on the ability to do polynomial arithmetic, and we describe those methods below (Subsection 12.3.6).