con-In file transferArray.cpp // Simple array assignment function int transferint from[], int to[], int size For the following declarations: int a[10], b[10]; double c[20], d[20]; C++ ha
Trang 1Ira 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 theinternal representation to its smallest a and q value
5 Overload the equality and comparison operators for rational Notice that tworationals 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));}
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 tract Each should return complex Write each as a friend function Why not writethem as member functions?
sub-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 areneeded to allow completely mixed expressions of complex and double Explain whywriting one with an int parameter is unnecessary when these friend functions areavailable
Trang 2Ira 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 assignmentthat 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 arecomplex Overload operators + and * to mean, respectively, element-by-elementcomplex 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 bechanged 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 ofmy_string
// strcmp is negative if s < s1,
// and is positive if s > s1
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 onobjects of type my_string (See Section 5.5, Strings Using Reference Semantics, onpage 201.) Rewrite my_string by adding a conversion member function operatorchar*() This now allows << to output objects of type my_string Discuss thissolution
14 What goes wrong with the following client code when the overloaded definition ofoperator=() is omitted from my_string? (See Section 5.5, Strings Using Reference
Semantics, on page 201.)
Trang 3Ira Pohl’s C++ by Dissection Exercises 240
// Swapping my_strings that are reference counted
overload-my_string overload-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)
Trang 4Ira 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 stringfor a given character sequence and to return true if the subsequence is found Tofurther test your understanding, recode this function to test that the positions arewithin the actual string This means that they cannot have negative values and theycannot go outside the null character terminator of the string
19 Code a class int_stack Use this to write out integer subsequences in increasingorder 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 nextsequence value is no longer increasing Keep in mind that the stack pops values inreverse 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 ator+(), and del() should change to operator () Also, overload operator[]()
oper-to return the ith element in the list
21 The postfix operators ++ and can be overloaded distinct from their prefix ings Postfix can be distinguished by defining the postfix overloaded function ashaving a single unused integer argument, as in
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 oradded as an additional argument
my_clock c(60);
c.operator++(5); // adds 1 + 5 seconds
c.operator (5); // subtracts 6 seconds
Trang 5Ira Pohl’s C++ by Dissection Exercises 242
22 (Uwe F Mayer) Rewrite istream& operator>>(istream& in, rational& x).Youcan 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 poly nomial class with
overloaded operators + and * for polynomial addition and multiplication You canbase 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, andcomplex numbers There are several ways to improve the rational implementation.You can try to improve the precision of going from double to rational Also, manyalgorithms are more convenient when the rational is in a canonical form in whichthe quotient and divisor are relatively prime This can be accomplished by adding agreatest common division algorithm to reduce the representation to the canonicalform (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
Trang 6A 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 InC++, 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 Thislets 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 tents of one array to a second array
con-In file transferArray.cpp
// Simple array assignment function
int transfer(int from[], int to[], int size)
For the following declarations:
int a[10], b[10];
double c[20], d[20];
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
Trang 7Ira 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
Trang 8Ira Pohl’s C++ by Dissection 245
C++ has template functions that can be used to create generic code Template functionsare 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, wewrite the transfer() function using templates
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
Trang 9Ira Pohl’s C++ by Dissection 6.1 Template Class stack 246
C++ uses the keyword template to provide parametric polymorphism, which allows thesame code to be used with respect to various types, in which the type is a parameter ofthe code body This is a form of generic programming Many of the classes used in thetext so far contained data of a particular type, although the data have been processed inthe 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 ofthis 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; }
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
Trang 10Ira 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 withinanother 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 reusecode
When a template class is used, the code must always use the angle brackets as part ofthe declaration
In file templateStack.cpp
// Reversing an array of char* represented strings
void reverse(char* str[], int n)
// Initialize stack of complex numbers from an array
void init(complex c[], stack<complex>& stk, int n)
do you need, master?
Trang 11Ira Pohl’s C++ by Dissection 6.2 Function Templates 248
Member functions, when declared and defined inside the class, are, as usual, inline.When defining them externally, you must use the full angle bracket declaration So,when defined outside the template class,
TYPE top_of() const { return s[top]; }
template<class TYPE> stack<TYPE>::~stack()
for (i = 0; i < n; ++i)
a[i] = b[i];
Many programmers automate this with a simple macro:
Now that’s what I call a generic waiter - he can balance anything!
6.2
Trang 12Ira Pohl’s C++ by Dissection 6.2 Function Templates 249
#define COPY(A, B, N) \
{ int i; for (i=0; i < (N); ++i) (A)[i] = (B)[i]; }
Programming that works regardless of type is a form of generic programming The use
of define macros is a form of generic programming Its advantages are several, ing simplicity, familiarity, and efficiency There is familiarity because of a long tradition
includ-in C programminclud-ing of usinclud-ing such macros It is very efficient There is no function calloverhead
The disadvantages of using macros include type-safety, unanticipated evaluations, andscoping problems Using define macros can often work, but doing so is not type-safe.Macro substitution is a preprocessor textual substitution that is not syntacticallychecked until later Another problem with define macros is that they can lead torepeated evaluation of a single parameter Definitions of macros are tied to their posi-tion in a file and not to the C++ language rules for scope The code
#define CUBE(X) ((X)*(X)*(X))
behaves differently from the code
template<class T> T cube (T x) { return x * x * x;}
When cube(sqrt(7)) is invoked, the function sqrt(7) is called once, not three times
as with the CUBE define macro
Templates are safer when types can be mixed in an expression and conversions areinappropriate
Trang 13Ira Pohl’s C++ by Dissection 6.2 Function Templates 250
The last two invocations of copy() fail to compile because their types cannot be
matched to the template type This is called a unification error The types of the
argu-ments do not conform to the template How the compiler generates this matching is cussed in the next section If we were to cast f2 as
dis-copy(i1, static_cast<int* >(f2), 50);
compilation would occur However, the result would be an inappropriate form of ing Instead, we need to have a generic copying procedure that accepts two distinctclass type arguments
6.2.1 Signature Matching and Overloading
A generic routine often cannot work for special case The following form of swappingtemplate works on basic types, such as int or char, but will not work as expected on anarray of int or char In order for a template function to work on a particular type, weneed each operation to be defined for that type Without this condition, the code willfail to compile for that type Even when the template compiles, the resulting code needs
to be correct for that type The following form of swapping template works on basictypes:
See, any cookie shape is possible!
Trang 14Ira Pohl’s C++ by Dissection 6.2 Function Templates 251
swap(str1[50], str2[33]); // both char variables-okay
swap(s1, s2); // legal- but may not be intention
In the first three cases, the template compiles and runs as expected The case ofswap(i, ch) yields a syntax error, as the two arguments are not the same type In thecase of swap(str1, str2), str1 and str2 are array names They are pointer valuesthat cannot be modified Therefore, for this type, the code x = y; cannot compile Inthe last case, swap(s1, s2), the arguments are the same type and are modifiable Theswap(s1, s2) function compiles and executes, but the contents of the arrays that thepointers represent are not copied; only the pointers themselves are swapped
To have swap() work for strings represented as character arrays, we write the followingspecial case:
void swap(char* s1, char* s2)
Trang 15Ira Pohl’s C++ by Dissection 6.2 Function Templates 252
This specific version of swap() swaps the two strings represented by pointer values.With this specialized case added, an exact match of this nontemplate version to the sig-nature of a swap() invocation takes precedence over the exact match found by a tem-plate substitution This is a dangerous swap routine, as the longer string mightoverflow the memory that had been allocated for the shorter string When multiplefunctions are available, the overloaded function-selection algorithm given below deter-mines which to use
Overloaded Function-Selection Algorithm
1 Exact match with some trivial conversions on nontemplate functions
2 Exact match using function templates
3 Ordinary argument resolution on nontemplate functions
6.2.2 How to Write a Simple Function: square()
Let us review what we know about writing a simple function We choose the functionsquare() as our test case
// Hand-coding genericity by overloading the function
inline int square(int n)
func-// Macro square
#define SQUARE(X) ((X)*(X))
Here, we use a preprocessor to inline substitute code where necessary, independent oftype The macro is also error-prone, because textual substitution occurs without lan-guage rules being checked It is only after the substitution that the compiler checks forsyntax errors
Trang 16Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 253
// Poor attempt at genericity using void*
inline double square(void* p)
{
double* temp = reinterpret_cast<double*>(p);
return (*temp) * (*temp);
}
Here, we use a void* as the argument type, which is error-prone because it uses a tem-dependent cast It works through conversion to double, which may be inappropri-ate
sys-And the winner is template coding:
univer-6.3 Generic Code Development: Quicksort
Sorting is an important algorithm for many applications Sorting needs to be plished on many different types Sorting needs to be compactly coded and highly effi-cient Thus, sorting functions are a prime candidate for generic coding We develop thecode for quicksort—a highly efficient, well-known sorting method Quicksort was cre-
accom-ated by C Anthony R Hoare and described in his 1962 paper “Quicksort” (Computer
Journal, vol 5, no 1) Of all the various sorting techniques, quicksort is perhaps the
most widely used internal sort An internal sort is one in which all the data to be sortedfit entirely within main memory
First, we program the quicksort for a specific type Then we show how easy it is to vert it to generic code Our quicksort code is as follows:
con-6.3
Trang 17Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 254
int* partition(int *left, int *right, int pivot);
void quicksort(int *left, int *right)
{
int *p, pivot;
if (find_pivot(left, right, &pivot)) {
p = partition(left, right, pivot);
quicksort(left, p - 1);
quicksort(p, right);
}
}
Quicksort is usually implemented recursively The underlying idea is to divide and
con-quer Suppose that in main() we have declared a to be an array of size N After the arrayhas been filled, we can sort it with the call
quicksort(a, a + N - 1);
The first argument is a pointer to the first element of the array; the second argument is
a pointer to the last element of the array In the function definition for quicksort(), it
is convenient to think of these pointers as being on the left and right side of the array,respectively The function find_pivot() chooses, if possible, one of the elements of
the array to be a pivot element The function partition() is used to rearrange the
array so that the first part consists of elements all of whose values are less than thepivot, and the remaining part consists of elements all of whose values are greater than
or equal to the pivot In addition, partition() returns a pointer to an element in thearray Elements to the left of the pointer all have value less than the pivot, and elements
to the right of the pointer, as well as the element pointed to, all have value greater than
or equal to the pivot Once the array has been rearranged with respect to the pivot,quicksort() is invoked on each subarray
Trang 18Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 255
bool find_pivot(int *left, int *right, int *pivot_ptr)
{
int a, b, c, *p;
b = *(left + (right - left) / 2); // middle value
order(a, b);
order(a, c);
order(b, c); // order these 3 values
if (a < b) { // pivot is higher of 2 values
int *partition(int *left, int *right, int pivot)
{
while (left <= right) {
while (*left < pivot)
Trang 19Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 256
The major work is done by partition() We want to explain in detail how this functionworks Suppose we have an array a[] of 12 elements:
When find_pivot() is invoked, the first, middle, and last elements of the array arecompared The middle value is 5, and because this is larger than the smallest of thethree values, this value is chosen for the pivot value The following simulation showsthe values of the elements of the array after each pass of the outer while loop in thepartition() function The elements that were swapped in that pass are underlined.Unordered data: 7 4 3 5 2 5 8 2 1 9 -6 -3
6.3.1 Converting to a Generic quicksort()
Now let us convert the quicksort to work with generic data The key is to identify anytypes that need to be generalized The original algorithm works with int data Let uschange this step by step to work with data of type T:
Trang 20Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 257
Once the algorithm and its general coding scheme are understood, it is relatively easy tofind the specific type, in this case int, that needs to be parameterized and change it to
a parameterized type T We show the remaining code parameterized without comment
Dissection of the swap() and order() Functions
■ template <class T>
inline void order(T& x, T& y)
The key to writing templates is to preface the code with template
and the appropriate number of type identifiers, usually identifiers T1,
T2, and so on Normally, only one type is parameterized, and for this
case, class T is usual The function normally uses T as part of its
sig-nature Later, when order() is called with a given type, as in
order(x, y), with these being doubles, the compiler generates code
to specifically handle that type One downside is that for each use of
order() with different types, a code body is generated Schemes for
generic coding using void* can avoid these multiple code bodies,
also referred to as code bloat.
■ {
if (x > y) swap(x, y);
}
The code is no different from for the int case The compiler gives
you a syntax error if the operator > is not defined for a particular type
This code should work universally because operator= is defined for
any type However, the user needs to check that semantics for
opera-tor= should be a deep copy, that is, a copy of the data itself, not just
a copy of a pointer to the data
Trang 21Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 258
// Forward declarations of auxiliary functions
if (find_pivot(left, right, &pivot)) {
p = partition(left, right, pivot);
while (left <= right) {
while (*left < pivot)
b = *(left + (right - left) / 2); // middle value
order(a, b);
order(a, c);
order(b, c); // order these 3 values
if (a < b) { // pivot is higher of 2 values
*pivot_ptr = b;
return true;
}
Trang 22Ira Pohl’s C++ by Dissection 6.3 Generic Code Development: Quicksort 259
The standard C library provides qsort(), a generic quicksort routine using void* An
extended discussion of this technique can be found in A Book on C: 4th Edition, by Al
Kelley and Ira Pohl (Addison Wesley, 2000) pages 372 to 380
Trang 23Ira Pohl’s C++ by Dissection 6.4 Class Templates 260
In the stack<T> example given in Section 6.1, Template Class stack, on page 246, wehave an ordinary case of class parameterization In this section, we wish to discuss var-ious special features of parameterizing classes
6.4.1 Friends
Template classes can contain friends A friend function that does not use a templatespecification is universally a friend of all instantiations of the template class A friendfunction that incorporates template arguments is specifically a friend of its instantiatedclass
template <class T>
class matrix {
public:
friend vect<T> prod(vect<T> v); // instantiated
Trang 24Ira Pohl’s C++ by Dissection 6.4 Class Templates 261
6.4.3 Class Template Arguments
Both classes and functions can have several class template arguments Let us write afunction that converts one type of value to a second type, provided the first type is atleast as wide as the second type:
This template function has two possibly distinct types as template arguments
Other template arguments include constant expressions, function names, and characterstrings
x = y; // should work efficiently
The benefits of this parameterization include allocation off the stack, as opposed toallocation from free store On many systems, the former is more efficient The type isbound to the particular integer constant; thus, operations involving compatible-lengtharrays are type-safe and checked at compile time (See exercise 1 on page 278.)
6.4.4 Default Template Arguments
A template provider can decide that there is a common case that should be provided as
Trang 25Ira Pohl’s C++ by Dissection 6.5 Parameterizing the Class vector 262
This template can be used with an explicit parameter or with the parameter omitted asfollows:
point <int> i_pt; // the coordinates are int
point <double> d_pt; // the coordinates are double
point < > d_pt2; // the coordinates are double
documenta-6.5 Parameterizing the Class vector
Let us improve on the native C++ array by creating a container class A defect of thearray as found in C and C++ is that it is easy to have out-of-bounds errors resulting indifficult-to-find runtime bugs We parameterize the class, naming it vector in anticipa-tion of discussing and understanding the Standard Template Library (STL) classstd::vector The new class is used in conjunction with iterators and algorithms An
iterator is a pointer or a pointerlike variable used for traversing and accessing container
elements
6.5