Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 51 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
51
Dung lượng
445,88 KB
Nội dung
Ira Pohl’s C++ by Dissection Exercises 238 To test your understanding, write a rational constructor that, given two integers as dividend and quotient, uses a greatest common divisor algorithm to reduce the internal representation to its smallest a and q value. 5. Overload the equality and comparison operators for rational. Notice that two rationals are equal in the form given by the previous exercise if and only if their dividends and quotients are equal. (See Section 5.9, Overloading and Signature Matching, on page 209.) 6. Define class complex as class complex { public: complex(double r) : real(r), imag(0) { } void assign(double r, double i) { real = r; imag = i; } void print() { cout << real << " + " << imag << "i "; } operator double() { return (sqrt(real * real + imag * imag));} private: double real, imag; }; We wish to augment the class by overloading a variety of operators. For example, the member function print() could be replaced by creating the friend function opera- tor<<(): ostream& operator<<(ostream& out, complex x) { out << x.real << " + " << x.imag << "i "; return out; } Also, code and test a unary minus operator. It should return a complex whose value in each part is negated. 7. For the type complex, write the binary operator functions add, multiply, and sub- tract. Each should return complex. Write each as a friend function. Why not write them as member functions? 8. Write two friend functions: friend complex operator+(complex, double); friend complex operator+(double, complex); In the absence of a conversion from type double to type complex, both types are needed to allow completely mixed expressions of complex and double. Explain why writing one with an int parameter is unnecessary when these friend functions are available. Ira Pohl’s C++ by Dissection Exercises 239 9. Overload assignment for complex: complex& complex::operator=(complex c) {return c;} If this definition were omitted, would this be equivalent to the default assignment that the compiler generates? In the presence of the conversion operator for convert- ing complex to double, what is the effect of assigning a complex to a double? Try to overload assignment with a friend function in class complex. friend double operator=(double d, complex c); // assign d = real_part(c) Why won’t this work? 10.Program a class vec_complex that is a safe array type whose element values are complex. Overload operators + and * to mean, respectively, element-by-element complex addition and dot-product of two complex vectors. For added efficiency, you can make the class vec_complex a friend of class complex. 11. Redo the my_string ADT by using operator overloading. (See Section 5.5, Strings Using Reference Semantics, on page 201.) The member function assign() should be changed to become operator=. Also, overload operator[] to return the ith charac- ter in the my_string. If there is no such character, the value -1 is to be returned. 12. Test your understanding of my_string by implementing additional members of my_string. // strcmp is negative if s < s1, // is 0 if s == s1, // and is positive if s > s1 // where s is the implicit argument int my_string::strcmp(const my_string& s1); // strrev reverses the my_string void my_string::strrev(); // print overloaded to print the first n characters void my_string::print(int n) const; 13. Explain why friendship to str_obj was required when overloading << to act on objects of type my_string. (See Section 5.5, Strings Using Reference Semantics, on page 201.) Rewrite my_string by adding a conversion member function operator char*(). This now allows << to output objects of type my_string. Discuss this solution. 14. What goes wrong with the following client code when the overloaded definition of operator=() is omitted from my_string? (See Section 5.5, Strings Using Reference Semantics, on page 201.) Ira Pohl’s C++ by Dissection Exercises 240 // Swapping my_strings that are reference counted class my_string { ····· }; void swap(my_string x, my_string y) { my_string temp; temp = x; x = y; y = temp; } int main() { my_string b("do not try me "), c(" try me"); cout << b << c << endl; swap(b, c); cout << b << c << endl; } 15. We can further develop our my_string class with a substring operation by overload- ing the function call operator (). The notation is my_string(from, to), where from is the beginning of the substring and to is the end. Use this to search a string for a character sequence and return true if the subsequence is found. my_string my_string::operator()(int from, int to) { my_string temp(to - from + 1); //code this for (int i = from; i < to + 1; ++i) temp.st -> s[i - from] = st -> s[i]; temp.st[to - from + 1] = 0; return temp; } 16. Given this code for overloaded [] for my_string from Section 5.1.6, The Copy Con- structor, on page 194, why would the following be buggy? char& my_string::operator[](int position) { return st -> s[position]; } 17. Rewrite the substring function, using a char* constructor. Is this better or worse? If you have a profiler, run this example with both forms of substring creation on the following client code: Ira Pohl’s C++ by Dissection Exercises 241 int main() { my_string large("A verbose phrase to search"); for (i = 0; i < MANY; ++i) count += (large(i, i + 3) == "ver"); } For this exercise, code operator==() to work on my_strings. 18. To test your understanding, use the preceding substring operation to search a string for a given character sequence and to return true if the subsequence is found. To further test your understanding, recode this function to test that the positions are within the actual string. This means that they cannot have negative values and they cannot go outside the null character terminator of the string. 19. Code a class int_stack. Use this to write out integer subsequences in increasing order by value. In the sequence (7, 9, 3, 2, 6, 8, 9, 2), the subsequences are (7, 9), (3), (2, 6, 8, 9), (2). Use a stack to store increasing values. Pop the stack when a next sequence value is no longer increasing. Keep in mind that the stack pops values in reverse order. Redo this exercise using a queue, thus avoiding this reversal problem. 20. Redo the list ADT by using operator overloading. (See Section 5.4, Example: A Singly Linked List, on page 196.) The member function prepend() should change to oper- ator+(), and del() should change to operator (). Also, overload operator[]() to return the ith element in the list. 21. The postfix operators ++ and can be overloaded distinct from their prefix mean- ings. Postfix can be distinguished by defining the postfix overloaded function as having a single unused integer argument, as in class T { public: // postfix invoked as t.operator++(0); T operator++(int); T operator (int); }; There is no implied semantic relationship between the postfix and prefix forms. Add postfix decrement and increment to class my_clock in Section 5.12, Unary Operator Overloading, on page 214. Have them subtract a second and add a second, respec- tively. Write these operators to use an integer argument n that is subtracted or added as an additional argument. my_clock c(60); c++; // adds a second c ; // subtracts a second c.operator++(5); // adds 1 + 5 seconds c.operator (5); // subtracts 6 seconds Ira Pohl’s C++ by Dissection Exercises 242 22. (Uwe F. Mayer) Rewrite istream& operator>>(istream& in, rational& x).You can improve on this input function by allowing it to read the input a/q where the “/ ” acts a separator for the two integer values. 23. (Project) You should start by writing code to implement a polynomial class with overloaded operators + and * for polynomial addition and multiplication. You can base the polynomial on a linked list representation. Then write a full-blown polyno- mial package that is consistent with community expectations. You could include dif- ferentiation and integration of polynomials as well. 24. (Project) Write code that fleshes out the rational type of Section 5.17, Overloading << and >>, on page 222. Have the code work appropriately for all major operators. Allow it to properly mix with other number types, including integers, floats, and complex numbers. There are several ways to improve the rational implementation. You can try to improve the precision of going from double to rational. Also, many algorithms are more convenient when the rational is in a canonical form in which the quotient and divisor are relatively prime. This can be accomplished by adding a greatest common division algorithm to reduce the representation to the canonical form. (See exercise 4 on page 237.) 25. (Java) Rewrite in Java the class rational in Section 5.9, Overloading and Signature Matching, on page 209. You must substitute ordinary methods for any operator overloading. A key problem in programming is programmer productivity. An important technique is code reuse. Generic programming is a critical methodology for enhancing code reuse. Generic programming is about code that can be used over a wide category of types. In C++, there are three different ways to employ generic coding techniques: void* point- ers, templates, and inheritance. We show a simple use of each of these methods. This lets us concentrate on C++ templates and how they are used effectively. We start with a small piece of code that can benefit from genericity: assigning the con- tents of one array to a second array. In file transferArray.cpp // Simple array assignment function int transfer(int from[], int to[], int size) { for (int i = 0; i < size; i++) to[i] = from[i]; return size; } This code works for the int array type and depends on an appropriate size array being allocated. This piece of code can be readily replicated for different types, but replica- tion has a cost and can introduce errors. For the following declarations: int a[10], b[10]; double c[20], d[20]; transfer(b, a, 10); // works fine transfer(d, c, 20); // syntax error C++ has a void pointer type that can be used to create generic code. Generic code is code that can work with different types. Templates and Generic Programming CHAPTER 6 Ira Pohl’s C++ by Dissection 244 In file voidTransferArray.cpp // void* generic assignment function int transfer(void* from, void* to, int elementSize, int size) { int nBytes = size * elementSize; for (int i = 0; i < nBytes; i++) static_cast<char*>(to)[i] = static_cast<char*>(from)[i]; return size; } Dissection of the transfer() Function Using void* ■ int transfer(void* from, void* to, int elementSize, int size) This code works for any array type. Since void* is a universal pointer type, any array type can be passed as a parameter. However, the com- piler does not catch type errors. Here are some declarations and func- tion calls: int a[10], b[10]; double c[20], d[20]; transfer(a, b, sizeof(int), 10); // works fine transfer(c, d, sizeof(double), 20); // works fine transfer(a, c, sizeof(int), 10); // sys dependent In this last call, a is an int* type but c is a double*. On many machines, an int fits in 4 bytes and a double fits in 8 bytes. The effect of these transfers can be very different where these underlying size limits differ. This presents a diffuculty in writing portable code that C++ templates will solve. ■ int nBytes = size * elementSize; The number of bytes to be transferred is computed as the elemen- tSize times the size for an individual element. For a 10-element array of 4-byte ints, this would be 40 bytes. ■ for (int i = 0; i < nBytes; i++) static_cast<char*>(to)[i] = static_cast<char*>(from)[i]; This for loop performs the actual transfer. It does it byte by byte, with each byte being treated as a character. . Ira Pohl’s C++ by Dissection 245 C++ has template functions that can be used to create generic code. Template functions are written using the keyword template followed by angle brackets. The angle brack- ets contain an identifier that is used as a placeholder for an arbitrary type. Here, we write the transfer() function using templates. In file templateTransferArray.cpp // Template generic assignment function template<class T> int transfer(T* from, T* to, int size) { for (int i = 0; i < size; i++) to[i] = from[i]; return size; } The template function requires that the type be properly instantiated. It does not allow two distinct types to be used in this form of array transfer. It continues to provide type- safety, which is important to program correctness. Templates conveniently solve porta- bility problems that void* techniques have difficulty with. Dissection of the transfer() Function Using template ■ template<class T> int transfer(T* from, T* to, int size) This code works for any array type. T can be any type. For the follow- ing declarations: int a[10], b[10]; double c[20], d[20]; transfer(a, b, 10); // works fine transfer(c, d, 20); // works fine transfer(a, c, 10); // syntax error In the first case, a function transfer(int*, int*, int) is com- piled. In the second case, a function transfer(double*, double*, int) is compiled. In this last case, a is an int* type, but c is a dou- ble*. The template mechanism cannot produce an actual function because these are two different types. This leads to the syntax error “failure to unify the two argument types.” ■ for (int i = 0; i < size; i++) to[i] = from[i]; This for loop performs the actual transfer. It does it array-element by array-element, which is generally more efficient than a byte transfer. Ira Pohl’s C++ by Dissection 6.1 Template Class stack 246 C++ uses the keyword template to provide parametric polymorphism, which allows the same code to be used with respect to various types, in which the type is a parameter of the code body. This is a form of generic programming. Many of the classes used in the text so far contained data of a particular type, although the data have been processed in the same way regardless of type. Using templates to define classes and functions allows us to reuse code in a simple, type-safe manner that lets the compiler automate the pro- cess of type instantiation—that is, when a type replaces a type parameter that appeared in the template code. 6.1 Template Class stack Here, we modify the ch_stack type from Section 4.11, A Container Class Example: ch_stack, on page 164, to have a parameterized type. This is a prototypical container class. It is a class whose chief purpose is to hold values. Rather than write a version of this class for each type, we can write generic code using the template syntax. In file templateStack.cpp // Template stack implementation template <class TYPE> class stack { public: explicit stack(int size = 100) : max_len(size), top(EMPTY), s(new TYPE[size]) { assert(s != 0); } ~stack() { delete []s; } void reset() { top = EMPTY; } void push(TYPE c) { s[++top] = c; } TYPE pop() { return s[top ]; } TYPE top_of() const { return s[top]; } bool empty() const { return top == EMPTY; } bool full() const { return top == max_len - 1; } private: enum { EMPTY = -1 }; TYPE* s; int max_len; int top; }; The syntax of the class declaration is prefaced by template <class identifier> This identifier is a template argument that essentially stands for an arbitrary type. Throughout the class definition, the template argument can be used as a type name. This argument is instantiated in the declarations. A template declaration usually has 6.1 Ira Pohl’s C++ by Dissection 6.1 Template Class stack 247 global or namespace scope, can be a member of a class, and can be declared within another template class. An example of a stack declaration using this is stack<char> stk_ch; // 100 char stack stack<char*> stk_str(200); // 200 char* stack stack<complex> stk_cmplx(500); // 500 complex stack This mechanism saves us rewriting class declarations in which the only variation would be the type declarations, providing a type-safe, efficient, and convenient way to reuse code. When a template class is used, the code must always use the angle brackets as part of the declaration. In file templateStack.cpp // Reversing an array of char* represented strings void reverse(char* str[], int n) { stack<char*> stk(n); int i; for (i = 0; i < n; ++i) stk.push(str[i]); for (i = 0; i < n; ++i) str[i] = stk.pop(); } // Initialize stack of complex numbers from an array void init(complex c[], stack<complex>& stk, int n) { for (int i = 0; i < n; ++i) stk.push(c[i]); } Polymorphic Genie: Capable of Assuming Any Type Which form do you need, master? [...]... on C: 4th Edition, by Al Kelley and Ira Pohl (Addison Wesley, 2000) pages 372 to 380 Ira Pohl’s C++ by Dissection 6. 4 6. 4 6. 4 Class Templates 260 Class Templates In the stack example given in Section 6. 1, Template Class stack, on page 2 46, we have an ordinary case of class parameterization In this section, we wish to discuss various special features of parameterizing classes 6. 4.1 Friends Template... from the iterator value v.end() (See exercise 6 on page 278.) 6. 6 6. 6 Using STL: string, vector, and complex The C++ standard library makes heavy use of templates It is not necessary to be able to code templates, but it is vital to be able to use template code This section discusses a range of useful template types provided by the standard library and by STL The full use of STL is such an important... assignments and concatenations Note that it is important to check the local system documentation, as different vendors have employed their own specifications Ira Pohl’s C++ by Dissection 6. 6.2 6. 6 Using STL: string, vector, and complex 267 vector in STL We developed a useful generalization of an arraylike container, the vector A fully developed std::vector is found in the STL library vector In most... for a type of generic programming and achieves some of the ideas of polymorphism accomplished by the use of templates in C++ The use of Object in writing generic code is based on inherit- Ira Pohl’s C++ by Dissection 6. 9 C++ Compared with Java 273 ance and is discussed in Java by Dissection (Addison Wesley, 1999), Pohl and McDowell, pages 244 to 249 Java does have string types They are built-in and... 4 4 4 4 3 5 3 5 3 -6 3 -6 3 -6 2 2 2 2 2 5 5 5 1 1 8 8 8 8 2 2 2 2 2 8 1 1 1 5 5 9 -6 -3 9 -6 7 9 5 7 9 5 7 9 5 7 Notice that after the fourth pass, the elements with index 0 to 6 have value less than the pivot and that the remaining elements have value greater than or equal to the pivot The address of a[7] is returned from partition() when it finishes the fourth pass and exits 6. 3.1 Converting to... does not suffice to express the needed character set, as is the case for many foreign languages, such as Chinese, Japanese, Finnish, or Korean The basic_string Ira Pohl’s C++ by Dissection 6. 6 Using STL: string, vector, and complex 266 class represents a sequence of characters It contains all the usual operations of a sequence container, as well as standard string operations such as concatenation There... right; if (left < right) { swap(*left, *right); ++left; right; } } return left; } Ira Pohl’s C++ by Dissection 6. 3 Generic Code Development: Quicksort 2 56 The major work is done by partition() We want to explain in detail how this function works Suppose we have an array a[] of 12 elements: 7 4 3 5 2 5 8 2 1 9 -6 -3 When find_pivot() is invoked, the first, middle, and last elements of the array are compared... prod(vect v); // instantiated ····· }; 6. 4.2 Static Members Static members are not universal but are specific to each instantiation template class foo { public: static int count; ····· }; ····· foo foo a; b; The static variables foo::count and foo::count are distinct Ira Pohl’s C++ by Dissection 6. 4.3 6. 4 Class Templates 261 Class Template Arguments Both classes... type that instantiates the template These types can be native types, such as int in the example, or userdefined types The following code uses these templates Ira Pohl’s C++ by Dissection 6. 6 Using STL: string, vector, and complex 265 In file vect_it.cpp int main() { vector v(5); vector::iterator p; int i = 0; for (p = v.begin(); p != v.end(); ++p) *p = 1.5 + i++; do { p; cout . transfer. It does it array-element by array-element, which is generally more efficient than a byte transfer. Ira Pohl’s C++ by Dissection 6. 1 Template Class stack 2 46 C++ uses the keyword template. argument. my_clock c (60 ); c++; // adds a second c ; // subtracts a second c.operator++(5); // adds 1 + 5 seconds c.operator (5); // subtracts 6 seconds Ira Pohl’s C++ by Dissection Exercises. 50); copy(ptr1, f2, 50); Ira Pohl’s C++ by Dissection 6. 2 Function Templates 250 The last two invocations of copy() fail to