C++ for Mathematicians An Introduction for Students and Professionals phần 6 doc

52 325 1
C++ for Mathematicians An Introduction for Students and Professionals phần 6 doc

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Polynomials 237 One may think of a template as a procedure schema. When the C++ compiler en- counters 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.2 The 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 argu- ments 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.2 Class 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 Z 11 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 pro- cedure 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> 5 using namespace std; 6 7 template <class T> 8 class mycomplex { 9 10 private: 11 T real_part; 12 T imag_part; 13 14 public: 15 mycomplex<T>() { 16 real_part = T(0); 17 imag_part = T(0); 18 } 19 20 mycomplex<T>(T a) { 21 real_part = a; 22 imag_part = T(0); 23 } 24 25 mycomplex<T>(T a, T b) { 26 real_part = a; 27 imag_part = b; 28 } 29 30 T re() const { return real_part; } 31 T im() const { return imag_part; } 32 33 }; 34 35 template<class T> 36 ostream& operator<<(ostream& os, const mycomplex<T>& z) { 37 os << "(" << z.re() << ") + (" << z.im() << ")i"; 38 return 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 single- and 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 writ- ing 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> 2 class mycomplex { 3 242 C++ for Mathematicians 4 private: 5 T1 real_part; 6 T2 imag_part; 7 8 public: 9 mycomplex() { 10 real_part = T1(0); 11 imag_part = T2(0); 12 } 13 14 mycomplex(T1 a) { 15 real_part = a; 16 imag_part = T2(0); 17 } 18 19 mycomplex(T1 a, T2 b) { 20 real_part = a; 21 imag_part = b; 22 } 23 24 T1 re() const { return real_part; } 25 T2 im() const { return imag_part; } 26 27 }; 28 29 template<class T1, class T2> 30 ostream& operator<<(ostream& os, const mycomplex<T1,T2>& z) { 31 os << "(" << z.re() << ") + (" << z.im() << ")i"; 32 return 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.3 The 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 Z p [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 x k 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 accom- plished 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 Pro- gram 12.4. A single-argument constructor is used to produce a constant polynomial; that is, a polynomial in which c k = 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. No- tice that for both Q and R there is also a conversion of the right-hand side of the as- signment 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 struc- ture 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 opera- tions 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 polyno- mial ax 2 + 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 7x 2 + (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 x k . 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 polyno- mial 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 x n . Each coefficient is shifted upwards; the coefficient of x k before the shift becomes the coefficient of x k+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 4x 2 + 6x −2 After P.shift(2) 4x 4 + 6x 3 −2x 2 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 3x 4 +5x 3 −6x 2 +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). 246 C++ for Mathematicians 12.3.5 Equality To check if two polynomials are equal, we make sure they have the same degree and that corresponding coefficients are equal. The operators == and != are given on lines 139–149. 12.3.6 Arithmetic We provide methods for the usual arithmetic operators: addition, subtraction, mul- tiplication, and division (quotient and remainder). See lines 153ff. Each of the basic operators is defined like this: Polynomial<K> operator+(const Polynomial<K>& that) const { } If P and Q are of type Polynomial<double>, then the expression P+Q invokes this method with that referring to Q. However, the expression P+5 also engages this method; implicitly the 5 is cast to a polynomial via Polynomial<double>(5). However, the expression 5+P cannot be handled by this method (because 5 is not a polynomial). Therefore, we also provide procedure templates such as this: template <class J, class K> Polynomial<K> operator+(J x, const Polynomial<K>& P) { return P + K(x); } See lines 291–298. In addition to the usual operators + - * / % we provide their operate/assign cousins: += -= * = /= %=. We also give methods for unary minus and exponenti- ation (to a nonnegative integer power). With the exception of division, the code for these various operators is reasonably straightforward. Division requires a bit more attention. As in the case of integers, division of polynomials produces two results: the quo- tient and the remainder. Let a(x) and b(x) be polynomials with b = 0. Then there exist polynomials q(x) and r(x) for which a(x) = q(x)b(x) + r(x) and degr(x) < degb(x). Furthermore, q and r are unique. For example, if a(x) = 3x 2 +x−2 and b(x) = 2x+1, then q(x) = 3 2 x − 1 4 and r(x) = − 7 4 . We define A/B and A%B to be the quotient and remainder, respectively, when we divide a(x ) by b(x). To this end, we define the procedure quot_rem(A,B,Q,R) (see lines 300–321) to find the quotient and remainder. The operator/ and operator% make use of quot_rem to do their work. Please note that the division methods require that K be a field. If K is only a commutative ring (e.g., long integers), then most of the class template works fine, but the division methods do not. [...]... half_k = (k-1)/2; { 252 227 228 229 230 231 232 233 234 235 2 36 237 238 239 240 241 242 243 244 245 2 46 247 248 249 250 251 252 253 254 255 2 56 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 2 76 277 278 279 280 281 282 C++ for Mathematicians Polynomial ans; ans = (*this).pow(half_k); ans *= ans; ans *= *this; return ans; } Polynomial operator/(const Polynomial& that)... 1 26 127 128 129 130 131 132 133 134 135 1 36 137 138 139 140 141 142 143 144 145 1 46 147 148 149 150 151 152 153 154 155 1 56 157 158 159 160 161 162 163 164 165 166 167 168 169 170 C++ for Mathematicians ans += coef[k]; } return ans; } K operator()(K a) const { return of(a); } Polynomial of(const Polynomial& that) const { if (dg . creates and uses a version of do_something in which A stands for long and B stands for double. 12.2 Class templates 12.2.1 Using class templates In Section 2.7 we showed how to declare C++ variables. quo- tient and the remainder. Let a(x) and b(x) be polynomials with b = 0. Then there exist polynomials q(x) and r(x) for which a(x) = q(x)b(x) + r(x) and degr(x) < degb(x). Furthermore, q and r. arithmetic, and we describe those methods below (Subsection 12.3 .6) . 2 46 C++ for Mathematicians 12.3.5 Equality To check if two polynomials are equal, we make sure they have the same degree and that

Ngày đăng: 12/08/2014, 12:20

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan