Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley
Chapter 11 Friends and Overloaded Operators 1. Solutions to and Remarks on Selected Programming Problems 1. Modify Money class. No solution is provided for this exercise. 2. Implement constructors and overload >>, <<, +, and * for class Pair. //Chapter 11 Programming Problem #2 //Enhance Self Test Problem 17 by completing the // overloading of operator<< and >> for class Pair // Implement: // The default constructor // A one int parameter constructor that // sets the first member to the parameter // and the second to 0. // A two int parameter constructor. // Overload * on a Pair and an int // (x, y)*c = (x*c, y*c) // Overload + on two objects of type Pair. // (a, b) + (c, d) = (a + c, b + d)
#include <iostream> using namespace std;
class Pair { public: Pair(); Pair(int first, int second); Pair(int first);
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
2 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley int accessFirst(); int accessSecond(); // other members and friends friend Pair operator+(const Pair&, const Pair&); friend Pair operator*(const Pair&, int);
int Pair::accessFirst(){ return f; } int Pair::accessSecond(){ return s; } Sample run 0 0 2 3 4 0
x (0, 0) y (2, 3) z (4, 0)
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
5 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley y + z (6, 3) y * 2 (4, 6)
34. No Solutions Provided
5. Rational Number Class This class implements rational number of the type 2/3. Requirements: class Rational;
private data: int n, (fraction numerator) and int d (fraction denominator).
public interface: constructors: two int args, to allow setting rational to any legal value one int arg, to construct rationals with arg numerator and denominator 1. overload << and >> to allow writing to screen in form 325/430 and reading it from the keyboard in the same format.
Notes: either n or d may contain a negative quantity. overload + - * / < <= > >= == Put definitions in separate file for separate compilation
//NOTE: //Doing input changes the input stream state. This seems //obvious, but I have students who didn't realize this.
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
10 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley //This code, along with iostream library, goes into an //infinite loop if you make istream a const reference. There //are no error messages, only an infinite loop, involving //the single parameter constructor. This can be quite //disconcerting to the naive student. // //Bottom line: The first param MUST NOT be const. The //second one is written, so it cannot be const either.
istream& operator >>(istream& in_str, Rational& right) { char ch; in_str >> right.n >> ch >> right.d; if (ch != '/') // properly done, we would set iostream //state {// to fail here in case of error. cout << "bad input format for operator >>. Aborting!" << endl; exit (1); } normalize(right.n, right.d); return in_str; }
//This, like the above case, along with g++ iostream //library, goes into an infinite loop when you attempt to //make ostream a const reference. //There are no error messages, only an infinite loop, //involving the single parameter constructor. // //Bottom line: The first parameter should not be const, the //second is read only and should be const.
ostream& operator <<(ostream& out_str, const Rational& right) { Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
//postcondition: return value is gcd of the absolute values //of m and n depends on function int abs(int); declared in //cstdlib
int gcd(int m, int n) { int t; m = abs (m); // don't care about signs. n = abs (n); if(n < m) // make m >= n so algorithm will work! { t = m; m = n; n = t; } int r; r = m % n; while(r != 0) { r = m%n; m = n; n = r; } return m; }
//postcondition: n and d (to be numerator and denominator //of a fraction)have all common factors removed, and d > 0.
void normalize(int& n, int& d) { Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
12 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley // remove common factors: int g = gcd(n, d); n = n/g; d = d/g;
//fix things so that if the fraction is 'negative' //it is n that carries the sign. If both n and d are //negative, each is made positive. if(n > 0 && d < 0 || n < 0 && d < 0) { n = -n; d = -d; } // assert: d > 0 }
//end file ch11prb4.cc
//File: ch11prb4.tst.cc //File: test program for Rational class
#include <iostream> #include "rational.h" using namespace std; int main() { cout << "Testing declarations" << endl; cout << "Rational x, y(2), z(-5,-6), w(1,-3);" << endl; Rational x, y(2), z(-5,-6), w(1,-3); cout << "z = " << z << ", y = " << y << ", z = " << z << ", w = " << w << endl;
cout << "Testing >> overloading: \nEnter " << "a fraction in the format " << "integer_numerator/integer_denominator" << endl; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
13 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley cin >> x; cout << "You entered the equivalent of: " << x << endl; cout << z << " - (" << w << ") = " << z - w << endl;
cout << "Testing the constructor and normalization routines: " << endl; y =Rational(-128, -48); cout << "y =Rational(-128, -48) outputs as " << y << endl; y =Rational(-128, 48); cout << "y =Rational(-128, 48)outputs as " << y << endl; y = Rational(128,-48); cout << "y = Rational(128, -48) outputs as " << y << endl; Rational a(1,1); cout << "Rational a(1,1); a outputs as: " << a << endl; Rational ww = y*a; cout << y << " * " << a << " = " << ww << endl;
w = Rational(25,9); z = Rational(3,5); cout << "Testing arithmetic and relational " << " operator overloading" << endl; cout << w << " * " << z << " = " << w * z << endl; cout << w << " + " << z << " = " << w + z << endl; cout << w << " - " << z << " = " << w - z << endl; cout << w << " / " << z << " = " << w / z << endl;
cout << w << " < " << z << " = " << (w < z) << endl; cout << w << " < " << w << " = " << (w < w) << endl; cout << w << " <= " << z << " = " << (w <= z) << endl; cout << w << " <= " << w << " = " << (w <= w) << endl;
cout << w << " > " << z << " = " << (w > z) << endl; cout << w << " > " << w << " = " << (w > w) << endl; cout << w << " >= " << z << " = " << (w >= z) << endl; cout << w << " >= " << w << " = " << (w >= w) << endl;
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
14 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley w = Rational(-21,9); z = Rational(3,5); cout << w << " * " << z << " = " << w * z << endl; cout << w << " + " << z << " = " << w + z << endl; cout << w << " - " << z << " = " << w - z << endl; cout << w << " / " << z << " = " << w / z << endl; cout << w << " < " << z << " = " << (w < z) << endl; cout << w << " < " << w << " = " << (w < w) << endl; cout << w << " <= " << z << " = " << (w <= z) << endl; cout << w << " <= " << w << " = " << (w <= w) << endl;
cout << w << " > " << z << " = " << (w > z) << endl; cout << w << " > " << w << " = " << (w > w) << endl; cout << w << " >= " << z << " = " << (w >= z) << endl; cout << w << " >= " << w << " = " << (w >= w) << endl; return 0; } // end file ch11prb4.tst.cc
A typical run follows
21:08:22:~/AW$ rational > rational.out 45/35 21:08:34:~/AW$ cat rational.out Testing declarations Rational x, y(2), z(-5,-6), w(1,-3); z = 5/6, y = 2/1, z = 5/6, w = -1/3 Testing << overloading: Enter a fraction in the format integer_numerator/integer_denominator You entered the equivalent of: 9/7 5/6 - (-1/3) = 7/6 Testing the constructor and normalization routines: y =Rational(-128, -48) outputs as 8/3 y =Rational(-128, 48)outputs as -8/3 y =Rational(128, -48) outputs as -8/3 Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
15 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Rational a(1,1); a outputs as: 1/1 -8/3 * 1/1 = -8/3 Testing arithmetic and relational operator overloading 25/9 * 3/5 = 5/3 25/9 + 3/5 = 152/45 25/9 - 3/5 = 98/45 25/9 / 3/5 = 125/27 25/9 < 3/5 = 0 25/9 < 25/9 = 0 25/9 <= 3/5 = 0 25/9 <= 25/9 = 1 25/9 > 3/5 = 1 25/9 > 25/9 = 0 25/9 >= 3/5 = 1 25/9 >= 25/9 = 1 -7/3 * 3/5 = -7/5 -7/3 + 3/5 = -26/15 -7/3 - 3/5 = -44/15 -7/3 / 3/5 = -35/9 -7/3 < 3/5 = 1 -7/3 < -7/3 = 0 -7/3 <= 3/5 = 1 -7/3 <= -7/3 = 1 -7/3 > 3/5 = 0 -7/3 > -7/3 = 0 -7/3 >= 3/5 = 0 -7/3 >= -7/3 = 1 6. Complex Numbers Define an ADT for complex numbers. The problem specifies a form of a + i*b where a and b are of type double, and i is the complex unit, square root of -1. Implement operator overloading for ==, +, -< *, >>, and <<. I have implemented a bit more than is required. The problem is to produce an ADT for complex numbers. The intention is not to produce a complex class that is particularly usable, rather the intent is to produce a class that is reasonably complete yet still Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
16 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley understandable to the student. Even so, I acknowledge some overkill for the problem as specified in the text. I have used an external form different from this specification. The ISO/ANSI C++ Standard says that the complex inserter and extractor should read and write a complex numbers of the form: re, (re), or (re, im), where re is the real part and im is the imaginary part. This is the external form that the C++ compilers will required by the ISO Standard for compliant compilers. I am only allowing the external form (re, im). I check only the input format, and I do not check for a good stream state at each read from the input stream. Robust software requires checking the stream state at each fetch. The student should not be expected to have a knowledge of the requirements of the ISO C++ Standard. Before assigning this exercise, the instructor should provide the student with information about the external form of a complex the C++ Standard expects. Not coincidentally, the ISO Standard requires a fully implemented <complex> type with overloaded operators, transcendental functions, a complex arrays including array slices, as part of the Numerics library. //file: complex.h // Chapter 11, problem 5: Define an ADT for complex numbers.
#ifndef _COMPLEX_H #define _COMPLEX_H
#include <cmath> #include <iostream> using namespace std; class complex { public: complex (double r = 0, double i = 0): re (r), im (i) { }
double real () const { return re; } Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
17 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley double imag () const { return im; } private: double re, im;
//file: complex.cpp //This is the implementation file for members of the //class complex. The class interface is given in complex.h
#include "complex.h" #include <iostream>
ostream& operator<< (ostream& o, const complex& c) { Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
18 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley o << "(" << c.re << ", " << c.im << ")"; return o; }
//limited checking of format is done here. istream& operator>> (istream& ins, complex& z) { double r, i; char ch;
ins >> ch;
if ('(' != ch) //if the complex number isn't in //required form, complain and exit. { cout << "\nBad complex form: found " << ch << ", need(for complex input; \n" << "A complex must be of the form (re, im) \n"; exit(1); }
//We have '(' -- now get the real part ins >> r;
//and get the comma ins >>ch;
if (',' != ch) //complex number must have a comma next, //if not, complain and exit. { cout << "\nBad complex form: found " << ch << ", need comma for complex input; \n" << "A complex must be of the form (re, im) \n"; exit(1); }
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
19 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley //now get the imaginary part ins >> i;
//and get the close parenthesis ins >> ch;
if(')' != ch)//complex number must have a ')' last, //If not, complain and exit. { cout << "\nBad complex form: found " << ch << ", need)for complex input; \n" << "A complex must be of the form (re, im) \n"; exit(1); }
double norm (const complex& x) Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
21 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley { return real (x) * real (x) + imag (x) * imag (x); }
//Divide overloading: There is a possible bug here. //The usual tool for complex division, num/den = //num*conj(den)*(1/(den * conj(den)), causes an infinite //recursion. //Exercise: How and why?
int main() { // test constructors complex x, y(3), z(-3.2, 2.1); cout <<"x = " << x << " y = " << y << " z = " << z << endl << endl;
x = complex(3, -4);
cout << "testing members and support functions as " << " well as output operator:\n" Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
22 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley << "complex number x = " << x << endl << "real part: " << x.real() << endl << "real part from friend real(x): " << real(x) << endl << "imaginary part: " << x.imag() << endl << "imaginary part from friend imag(x) : " << imag(x) << endl << "norm: " << norm(x) << endl << endl;
cout << "test complex arithmetic and output" << " routines: \n\n"; y = complex (1, -1); cout << "x = " << x << " y = " << y << " z = " << z << endl << endl;
z = x + y; cout << "z = x + y = " << z << endl;
z = x * y; cout << "z = x * y = " << z << endl;
z = x - y; cout << "z = x - y = " << z << endl;
z = x / y; cout << "z = x / y = " << z << endl << endl;
//test of automatic conversion double -> complex by the //constructor.
double d(2.0); cout << "d: " << d << " x: " << x <<endl; cout << "x+d: " ; z = x + d; cout << z << endl; z = x - d; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
23 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley cout << "x-d: " ; cout << z << endl; z = x * d; cout << "x*d: "; cout << z << endl; z = x / d; cout << "x/d: " ; cout << z << endl; z = d + x; cout << "d+x: " ; cout << z << endl; z = d - x; cout << "d-x: " ; cout << z << endl; z = d * x; cout << "d*x: " ;; cout << z << endl; z = d / x; cout << "d/x: " ;; cout << z << endl;
//test whether double/complex and complex/complex //give same result: complex two(2,0); cout << "two/x: "; cout << two/x << endl;
cout << "\nGetting data from standard input: \n"; cin >> x >> y; cout << "data read is: x = " << x << " y = " << y << endl << endl; return 0; } Test data and a test run: The content of the file, good-data, is: Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
24 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley (1,1) (2,3) Here is the output from our input routine when given bad data. x = (0, 0) y = (3, 0) z = (-3.2, 2.1)
testing members and support functions as well as output operator: complex number x = (3, -4) real part: 3 real part from friend real(x): 3 imaginary part: -4 imaginary part from friend imag(x) : -4 norm: 25
We test complex arithmetic and output routines.
x = (3, -4) y = (1, -1) z = (-3.2, 2.1)
z = x + y = (4, -5) z = x * y = (-1, -7) z = x - y = (2, -3) z = x / y = (3.5, -0.5)
Getting data from standard input: Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
25 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley data read is: x = (1, 1) y = (2, 3) Messages from the input routine when given bad data. The content of the file, bad-data1, is: (1 2) The messages from the input routine with this file for data follow. [...snip...]
Getting data from standard input:
Bad complex form: found 2, need comma for complex input; A complex must be of the form (re, im)
The content of the file, bad-data2, is: (1,2) (1,2_ The messages from the input routine with this file for data follow. [...snip...] Getting data from standard input:
Bad complex form: found _, need )for complex input; A complex must be of the form (re, im)
7. No Solution Provided 8. No Solution Provided 9. No Solution Provided 10. No Solution Provided 11. No Solution Provided 12.
// **************************************************************** // // Ch11Proj12.cpp Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
26 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley // // This program defines a class for storing a set of STL strings. // The + operator unions two sets and the * operator intersects // two sets. // // ****************************************************************
// ====================== // StringSet::StringSet // This constructor initializes the string set to those // strings in the input array. // ====================== StringSet::StringSet(const string initialStrings[], int arraysize) { int i; for (i=0; i < arraysize; i++) { data.push_back(initialStrings[i]); } }
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
27 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley // ====================== // StringSet::Output // This method simply outputs all strings to the console. // ====================== void StringSet::output() { int i; for (i=0; i < data.size(); i++) { cout << " " << data[i] << endl; } }
// ====================== // StringSet::clear // Erases all entries in the stringset. // ====================== void StringSet::clear() { data.clear(); // Erases all elements from the vector }
// ====================== // StringSet::size // Number of entries in the stringset. // ====================== int StringSet::size() { return data.size(); }
// ====================== // StringSet::search // This private member function searches the vector for // the target string. If found, the index is returned, // otherwise -1 is returned. // ====================== int StringSet::search(const string s) { int i; for (i=0; i < data.size(); i++) { if (data[i]==s) return i; } return -1; }
// ====================== // StringSet::add // Adds a new entry to the vector. // If the entry already exists, then "false" is returned, // otherwise "true" is returned. // ====================== bool StringSet::add(const string s) { int i; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
28 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley
i = search(s); if (i>=0) { return false; // s already in the set } data.push_back(s); return true; }
// ====================== // StringSet::remove // Removes an entry from the vector. // If the entry doesnt exist, then "false" is returned, // otherwise "true" is returned and the string is removed. // ====================== bool StringSet::remove(const string s) { int i;
i = search(s); if (i>=0) { // To remove the string from the vector, we make a new // copy of the vector without the string, then copy that // back to the original vector. // Chapter 18 describes a better technique using the "erase" // method and iterators. vector<string> temp; for (int j=0; j<data.size(); j++) { // Copy all items that are not the target to delete if (data[j] != s) { temp.push_back(data[j]); } } // Clear original vector data.clear(); // Copy temp to original for (int j=0; j<temp.size(); j++) { data.push_back(temp[j]); } return true; } return false; }
// ====================== // StringSet::operator * // Intersects the current StringSet and otherSet, and returns // a new StringSet that is the intersection of the two. // This code loops and compares all strings in each and adds the // common words to the new StringSet. Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
29 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley // ====================== StringSet operator *(const StringSet &set1, const StringSet &set2) { StringSet temp; int i,j;
for (i=0; i<set1.data.size(); i++) { for (j=0; j<set2.data.size(); j++) { if (set1.data[i]==set2.data[j]) { temp.add(set1.data[i]); } } } return temp; }
// ====================== // StringSet::operator + // Unions the current StringSet and otherSet, and returns // a new StringSet that is the union of the two. // This code adds all the strings from the first set to temp, // then adds any words from the second set as well. The add // function will weed out any duplicates. // ====================== StringSet operator +(const StringSet &set1, const StringSet &set2) { StringSet temp; int i;
// Add everything from set 1 for (i=0; i<set1.data.size(); i++) { temp.add(set1.data[i]); } // Add everything from set 2 for (i=0; i<set2.data.size(); i++) { temp.add(set2.data[i]); } return temp; }
// ====================== // main function // ====================== int main() { // Some simple StringSet tests string initialStrings[] = {"foo","bar","zot"}; StringSet sset(initialStrings,3);
cout << "Initial set:" << endl; sset.output(); Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
30 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley cout << "Remove bar:" << endl; sset.remove("bar"); sset.output(); cout << "Add alpha:" << endl; sset.add("alpha"); sset.output(); cout << "Size of set is " << sset.size() << endl;
// **************************************************************** // // Ch11Proj13.cpp // // This program represents a document and query as a set of keywords. // It uses the binary cosine metric to then compute the similarity // between the query and document and outputs the similarity as a // value between 0 and 1. // // This program uses the StringSet class from the previous project. // ****************************************************************
// ====================== // StringSet::StringSet // This constructor initializes the string set to those // strings in the input array. // ====================== StringSet::StringSet(const string initialStrings[], int arraysize) { int i; for (i=0; i < arraysize; i++) { data.push_back(initialStrings[i]); } }
// ====================== // StringSet::Output // This method simply outputs all strings to the console. // ====================== void StringSet::output() { int i; for (i=0; i < data.size(); i++) { cout << " " << data[i] << endl; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
32 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley } }
// ====================== // StringSet::clear // Erases all entries in the stringset. // ====================== void StringSet::clear() { data.clear(); // Erases all elements from the vector }
// ====================== // StringSet::size // Number of entries in the stringset. // ====================== int StringSet::size() { return data.size(); }
// ====================== // StringSet::search // This private member function searches the vector for // the target string. If found, the index is returned, // otherwise -1 is returned. // ====================== int StringSet::search(const string s) { int i; for (i=0; i < data.size(); i++) { if (data[i]==s) return i; } return -1; }
// ====================== // StringSet::add // Adds a new entry to the vector. // If the entry already exists, then "false" is returned, // otherwise "true" is returned. // ====================== bool StringSet::add(const string s) { int i;
i = search(s); if (i>=0) { return false; // s already in the set } data.push_back(s); return true; }
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
33 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley // ====================== // StringSet::remove // Removes an entry from the vector. // If the entry doesnt exist, then "false" is returned, // otherwise "true" is returned and the string is removed. // ====================== bool StringSet::remove(const string s) { int i;
i = search(s); if (i>=0) { // To remove the string from the vector, we make a new // copy of the vector without the string, then copy that // back to the original vector. // Chapter 18 describes a better technique using the "erase" // method and iterators. vector<string> temp; for (int j=0; j<data.size(); j++) { // Copy all items that are not the target to delete if (data[j] != s) { temp.push_back(data[j]); } } // Clear original vector data.clear(); // Copy temp to original for (int j=0; j<temp.size(); j++) { data.push_back(temp[j]); } return true; } return false; }
// ====================== // StringSet::operator * // Intersects the current StringSet and otherSet, and returns // a new StringSet that is the intersection of the two. // This code loops and compares all strings in each and adds the // common words to the new StringSet. // ====================== StringSet operator *(const StringSet &set1, const StringSet &set2) { StringSet temp; int i,j;
for (i=0; i<set1.data.size(); i++) { for (j=0; j<set2.data.size(); j++) { Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
34 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley if (set1.data[i]==set2.data[j]) { temp.add(set1.data[i]); } } } return temp; }
// ====================== // StringSet::operator + // Unions the current StringSet and otherSet, and returns // a new StringSet that is the union of the two. // This code adds all the strings from the first set to temp, // then adds any words from the second set as well. The add // function will weed out any duplicates. // ====================== StringSet operator +(const StringSet &set1, const StringSet &set2) { StringSet temp; int i;
// Add everything from set 1 for (i=0; i<set1.data.size(); i++) { temp.add(set1.data[i]); } // Add everything from set 2 for (i=0; i<set2.data.size(); i++) { temp.add(set2.data[i]); } return temp; }
// ******************************* End StringSet Class
// ************************ // InputKeywords: // Inputs strings from the keyboard // and stores them into the set until // enter is pressed. // ************************ void inputKeywords(StringSet &setKeywords) { string s; cout << "Enter keywords, one per line. Enter a blank line when finished." << endl; do { Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
35 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley getline(cin,s); if (s != string("")) { setKeywords.add(s); } } while (s != string("")); }
// ************************ // ReadFileKeywords: // Opens the specified file for reading and inputs its words // into the StringSet. // ************************ void readFileKeywords(StringSet &setDocument, const char filename[]) { ifstream in_stream; string s;
// Open the file in_stream.open(filename); if (in_stream.fail()) { cout << "Input file opening failed." << endl; exit(-1); } // Read each word from the file until there are no more while (!in_stream.eof()) { // Input word, ideally we should filter out // punctuation and other non-letters but will skip now // for purposes of this assignment in_stream >> s; setDocument.add(s); } in_stream.close(); }
// ====================== // main function // ====================== int main() { // Variable declarations StringSet doc1; StringSet doc2; StringSet query;
readFileKeywords(doc1, "Ch11Proj13Doc1.txt"); readFileKeywords(doc2, "Ch11Proj13Doc2.txt"); cout << "Enter set of keywords for the query." << endl; inputKeywords(query);
// Calculate intersection StringSet intersect1 = doc1 * query; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
36 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley StringSet intersect2 = doc2 * query;
// Calculate similarity to each double sim1 = intersect1.size() / (sqrt(doc1.size()) * sqrt(query.size())); cout << "The similarity to document 1 is " << sim1 << endl;
double sim2 = intersect2.size() / (sqrt(doc2.size()) * sqrt(query.size())); cout << "The similarity to document 2 is " << sim2 << endl;
return 0; }
chocolate ice cream and chocolate candy bars are my favorite things to eat
I like to eat broccoli and fish along with tofu and brussel sprouts all day long
2. Outline of Topics in the Chapter 11.1 Friend Function An Equality Function Friend Functions Define Both Accessor Functions and Friend Functions Use Both Member and Nonmember Functions Money Class (Version 1) Implementation of digit_to_int (Optional) Leading Zeros in Number Constants The const Parameter Modifier Inconsistent Use of const 11.2 Overloading Operators Overloading Operators Constructors for Automatic Type Conversion Overloading Unary Operators Overloading >> and <<
3. General Comments on the Chapter Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
37 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Here many of the tools necessary for defining abstract data types are presented. The notion of friend, the const keyword, operator overloading, user-defined casts, and separate compilation are all important to the construction of Abstract Data types. Namespaces are treated, since the standard libraries require them. 11.1 Friend Functions This section starts by revisiting the class DayOfYear. Recall that few of the operations (+, /, -, ==, <, etc.) apply to ADTs directly. We do have assignment, = , that results in member wise copy. This means the right hand objects members are each assigned to the left-hand objects members. We will see later that frequently this should be called member-UN-wise copy, since under circumstances frequently encountered, this results in a disaster. (Thanks to John Gibson for the expression "member UN-wise copy".) While revisiting the DayOfYear class, the text implements == as an ordinary function, bool equal(DayOfYear date1, DayOfYear date2); The text points out that this requires access functions, but even access functions turns out to provide insufficient (i.e., inefficient) access. We need friend access. Friend Functions Enter the notion "friend of a class". A friend of a class is a function that is not a member, but the class grants the function access to all members of the class whether the members are public or private. friend is a C++ reserved keyword. A function is a friend of a class if that function is declared within the class, preceded by the friend keyword. A friend function is as much a part of the interface of a class as a member. The property of being a friend is granted by the class by the friend function being declared 1 within the class of which it is a friend, prefixed with the keyword friend. The class grants this access, so there is no violation of the protection afforded by the private section of the class. The friend function is not a member of the class.
1 The ANSI/ISO C++ language refers to a function declaration where the C language refers to a function prototype. This usage has found its way into earlier C++ texts. Earlier in this IRM, we have used declaration (prototype). Since the author uses function declaration consistently, we will use declaration in the sequel. Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
38 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Consequently, the friend function is not called with member access operators, unless it is a member of some other class. The text does not discuss this. We emphasize the class grants friendship. A function cannot unilaterally seize friendship of a class. The declaration must be made in the class definition. It is pointed out in the text that friend status can result in increased efficiency by allowing bypassing access functions that are otherwise necessary. A function that is a friend of a class, unless it is a function member of another class, will be defined outside the scope of any class, making the function global, that is, accessible in any function definition. Like any programming effort, there is a balance to be struck between use of friend functions and use of accessor functions. If a function needs to write to a private member of a class, one can have an accessor return a reference to the private member, but this makes the private member globally writeable. In this case, we might as well make the variable public. (This is where you need a friend :). 2
Example: //File: Friend.cc //To illustrate return by reference. #include <iostream> using namespace std;
class F { public: F(); //various constructors int& rw_accessor_i(); friend int& rw_friend_accessor_i(F& w); int read_accessor_i(); private:
2 The symbol :)is called a smiley. This was originally inserted into email to represent intent to be amusing. There are many, many of these. See Smileys, David Sanderson, OReilly, 1993. Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
39 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley int i; };
F::F() { i = 10; } //we could have written F::F():i(10) {}
int F::read_accessor_i(){ return i; }
int& F::rw_accessor_i() { return i; // not &i, which is the address of i. } The argument in the following function must be pass-by-reference otherwise only the local variable x is changed when the variable that is returned is assigned, and the caller's argument is not changed. This is a classic error. There must be a chain of references from the returned value to the caller's argument. int& rw_friend_accessor_i(F& x) { return x.i; // }
return 0; } Debugging Notes I chose to type in the money ADT in Display 11.3 rather than download from the Addison Wesley web site, www.aw.com/savitch, I found that I made two typographical errors worth a remark. Some errors in typing do indeed compile and run to a successful if not correct conclusion. In the routine Money::output(ostream&), instead of positive_cents = labs(all_cents); I had positive_cents - labs(all_cents); I had typed '-' instead of a '='. This expression is evaluated and the value is ignored. This adjacent-key typo caused interesting output. The error was caused by never setting positive_cents. While this error is evident from a quick look at the code, there are typos of this kind that are very hard to find, hence are worth mentioning to the students. The - for = error produces this output: enter an amount of money:$10.09 Your amount is $6070067.52 My amount is $25.92 We have the same amount. $245.84 + $246.08 equals $246.08 Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
41 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley This suggests that the error is in the input or early in the output routines, since my_amount is set by constructor. The value reported for my_amount is wrong, as well as a ridiculous result for your_amount. Placing output statements in Money::input determined that this routine was working correctly. As soon as I looked at the output routine the lack of an = sign in the second line jumped out at me. The errors in output for my_amount and your_amount are caused by not setting positive_cents in that routine. The other error was leaving out the last two lines of Money::input(istream&). The program worked fine until tested with negative values, which were accepted, and recorded as positive entries. These seem like trivial errors, but my students make errors without having any idea about what the possibilities are, nor how to test to find the errors, let alone how to do efficient testing. A debugger can be a great help but it is not a panacea. Frequently it is faster to insert output statements at places in the code that are suggested by the errors produced. Under any circumstances, whether a debugger is used or output statements are added, planning is necessary. Again, I point out the need for the student to create her own exercises like those in Chapter 1 of the text that help students see what error messages are issued for particular questions about syntax. Many errors are more subtle. Fixing the typo fixed the routine, until I tried it for negative money amounts, as noted above. The const Parameter Modifier Remember these three things about usage of the const keyword. One: Use of the keyword const is a promise to the compiler that you wont write code that changes something. Two: This use is a request that the compiler enforce these promises. Three: The use of const is difficult or impossible to retrofit. If you write code without const correctness, that code almost certainly will have to be rewritten from the ground up to get the code right. The declaration Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
42 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley const int x = 3; is a promise to the compiler that you wont write anything that has the potential to change x, such as using x on the left of an assignment statement or as an argument to a non- const reference parameter. A const member function promises not to change any class data using that function. A use of const in a class object declaration promises that you wont call any function with the potential to change that objects data members. As mentioned, the compiler tries to enforce these promises. The text points out that use of the const keyword requires consistency. If you declare a const parameter for a function member declaration in a class, you must use the const keyword in your definition of that function member. If you use a non-const object for an argument, and do not declare the function with a const parameter, you should get an error or a warning from your compiler. If you fail to declare a member function to be const, then call this member on behalf of a const object of this class, as below, you should get an error. The reason is in C++ the compilers encode all the information about a function: name of the function, the sequence of types in the argument list, including const qualifiers, and for member functions of a class, any const qualifiers that follow the declaration and uses this as the function name. If the const qualifier is in the definition but not in the class declaration, as far as the compiler is concerned, they are different functions, and you get an error. It is worth noting that in the following example, the Borland compiler is more forgiving of the const errors than the GNU compiler. The point is that each compiler has things it is fussy about. Know your compiler. //File: test.const.cc //to test attempt use of non-const arg for nonconst //reference parameter. and to apply a nonconst member //to a const object #include <iostream> using namespace std;
class C { Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
43 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley public: C(); C(int x); // other members void output(); //need void output() const; to avoid error below private: int i; }; C::C(){} //does nothing C::C(int x) //could write C::C(int x):i(x){} { i = x; } const C x(1); const int xx = 3;
void t(int& i){}
int main() { x.output(); //error: nonconst member called on a const //object t(xx); //error: const argument used for reference //parameter return 0; }
/* bcc32 -c test.const.cpp Borland C++ 5.4 for Win32 Warning 25: Parameter 'i' is never used in function t(int &) Warning 29: Non-const function C::output() called for const object in function main() Warning 30: Temporary used for parameter 'i' in call to 't(int &)' in function main() Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
44 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Compilation finished at Sun Dec 31 16:38:51
g++ -c test.const.cpp In function 'int main()': 29: passing 'const C' as 'this' argument of 'void C::output()' discards qualifiers 30: conversion from 'const int' to 'int &' discards qualifiers 25: in passing argument 1 of 't(int &)'
Compilation exited abnormally with code 1 at Sun Dec 31 16:37:54 Overloading Operators Suppose the compiler sees x and y in the context x op y where one of x or y is an object of class Some_Class . For the sake of definiteness, suppose it is x that is of class Some_Class. Further, suppose 'op' is one of the many C++ operators capable of being overloaded. The compiler then looks for a function: return_type operator op(Some_Class x, type_name y); or for a member function return_type Some_Class::operator op(type_name y); to which the compiler generates a call. For example, consider the infix expression x + y where for the variables x and y have been defined to have the int type. If the operator op were overloaded as a standalone function, the infix expression is translated into the direct call to the operator function, operator+ (x, y); Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
45 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley If the operator op were overloaded as a standalone function, the infix expression is translated into this direct call to the operator function, x.operator+(y); The programmer can actually write direct calls to the operator functions, but there is no good reason to do so other than to verify that this is possible. Nevertheless, my students want to call the operator functions directly instead of the infix operators. I found it necessary to spend some time emphasizing that the compiler does that for them. Some Rules 1. Precedence and arity (the number of arguments) of the operator cannot be changed by overloading. For example, a binary / cannot be made to be a unary operator. 2. Operator functions for =, ->, [] and () must be non-static members of the class to which they apply. (Overloading of the operators =, [] and -> is discussed later in the text.) The student must know and understand the rules listed on page 471 of the text. Friends Vs Member overloading (for the instructor) Please note that the text does not discuss overloading operators as members until Chapter 11. There is a compelling argument in favor of overloading operators using friends rather than overloading as members. Professor Savitch pointed out to me that he did not put both in the text at this point because presumably the students only know about the friend overloading method. It is worth pointing out to the instructor that if both operands are arguments, then C++ will do automatic type conversion of either argument to the class type from simple types such as int (provided you have supplied an appropriate constructor). This allows you to use 2 as a Rational or an amount of money in an expression such as object + 2 or 2 + object Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
46 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley which are converted to operator+(object, 2); or operator+(2, object); where the non class type arguments will be converted to an object using the constructor having an int argument. However, if operator+ is a member, then the first argument must always be an object, as in: object + 2 and expressions such as 2 + object are always illegal. This is the reason for presenting the friend rather than member operator overloading first. More detail on operator overloading (for the instructor) The decision on whether to write an overloading operator function as a stand-alone function (most likely a friend of the class) or to write the operator function as a member depends on several things. Is the operator expected to behave symmetrically between the arguments? If so, the operator should be a standalone function (and probably a friend of the class). If the asymmetry is desired or is no problem, then a member operator function may be used. Here is an example, stripped of all details except the minimum to illustrate the language behavior. This code illustrates the essential asymmetry of operators overloaded as members. //File: overload1.cc //to test behavior of member operator overloading. //What conversions and when the conversions are made.
#include <iostream> Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
47 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley using namespace std;
class F { public: F(){}; void operator +(Fx); F(int); operator int (); };
F::F(){} //does nothing
F::F(int) { cout << "promoting int to F" << endl; }
{ cout << "demoting F to int " << endl; return 1; }
int main() {
3 The usage operator int(...) is not discussed in the text. The operator function member used here is for conversion from a class F object to an int value. The keywords for all built-in types can be overloaded to convert a class object to the built-in type in a way appropriate for the requirements of our class.
Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
48 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley F x; int i = 2; cout << "F x; int i; x+i; Here i is promoted, and " << "operator+(F)is called." << endl; x + i; // only calls operator +, returned value ignored cout << "cout << i+x << endl; x is demoted, and " << "the + adds two ints here." << endl; cout << i + x << endl; return 0; } The output of this is: F x; int i; x+i; Here i is promoted, and operator+(F)is called. promoting int to F operator+ called cout << i+x << endl; x is demoted, and the + adds two ints here. demoting F to int 3 Note the asymmetric behavior between int_object + F_object and F_object + int_object. If this had been overloaded as a friend function, the behavior would have been symmetric. The following example illustrates the idea. It requires a few more details to illustrate the point than the earlier exercise. //File: overload2.cc //to investigate exactly what conversions are made in a //call to operator overloadings, and when these conversions //are made.
#include <iostream> Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
49 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley using namespace std;
class F { public: friend F operator +(F u, F v); F(int q); operator int (); private: int p; };
F::F(int q) { p = q; cout << "promoting int to F" << endl; } F::operator int () // See the earlier note on this overloading. { cout << "demoting F to int " << endl; return p; }
F operator +(F u, F v) { cout << "operator+(F,F) called " << endl; F x(u.p + v.p); cout << "still inside the operator+ function " << endl; return x; }
int main() { cout << "starting main, F x = 1;" << endl; F x = 1; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
50 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley int i = 2; cout << "F x; int i; x+i; Here i is promoted, and " << "operator+(F, F)is called." << endl; cout << x + i << endl; // converts from F to int
cout << "after first section" << endl << endl; cout << "cout << i+x << endl; i is promoted, and " << "the operator+(F, F) is called here." << endl; cout << i + x << endl; // converts from F to int. return 0; } Output from this code is: starting main, F x = 1; promoting int to F F x; int i; x+i; Here i is promoted, and operator+(F, F)is called. promoting int to F operator+(F,F) called promoting int to F still inside the operator+ function demoting F to int 3 after first section
cout << i+x << endl; i is promoted, and the operator+(F, F) is called here. promoting int to F operator+(F,F) called promoting int to F still inside the operator+ function demoting F to int 3 Note that in each case the arguments are promoted to F objects. Overloading >> and << (the extractors and inserters) Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
51 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley If we are the class author, we can add a member overloading operator <<(ourclass object) to our class definition. However, the istream and ostream classes are not ours to modify. It would not be desirable for us to do so, even if we could, since every one else will want her (or his) class to have io in the streams library. Our I/O overloading would not be in anyone else's I/O library. How then do we obtain stream I/O for a user defined class? The answer is to write a stand-alone operator function that overloads our inserter or extractor, where the iostream is the first argument, and our class object is the second argument. This provides us with what we need: in_stream >> our_class_object; and out_stream << our_class_object; Operator Overloading using void return type While the text always overloads the << and >> operators to return a stream reference, some instructors, in fact, some programmers, prefer to arrange an operator overloading to output to a stream from a class to return a void than to return a stream reference. While this provides somewhat simpler code in the implementation, the result violates the philosophy of the language design. It is the intent of the language design that an operator overloaded for a class behaves as it does for predefined types. Whether the operator function returns a stream reference or a void is a matter of whether we want to have the following style of i/o available to us: our_class x, y, z; in_stream >> x >> y >> z; If we are content to write instead, in_stream >> x; in_stream >> y; in_stream >> z; Savitch Instructors Resource Guide Problem Solving w/ C++, 6e Chapter 11
52 Copyright 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley The difference in coding amounts to replacing the return type with void, and removing the return statement from the function. The sole advantage is not having to return the stream. // returns istream&: istream& operator >> (istream& ins, Money& amount) { //all the necessary code to fetch the amount //from the istream and do some format checking //are in Display 11.8 in the text. return ins; }
void operator >> (istream& ins, Money& amount) { //all the necessary code to fetch the amount //from the istream and do some format checking //are in Display 11.8 in the text. //no return statement } Similar arrangements work for overloading operator <<.