430 likes | 451 Views
Operator overloading II. Output, input and other operators. ‘this’ example. bool Employee::operator>(const Employee& e) const { return(this->seniority > e->seniority); } called from the program like this: if (emp1 > emp2) emp1 accounts for ‘this’, emp2 becomes e.
E N D
Operator overloading II Output, input and other operators
‘this’ example • bool Employee::operator>(const Employee& e) const • { • return(this->seniority > e->seniority); • } • called from the program like this: • if (emp1 > emp2) • emp1 accounts for ‘this’, emp2 becomes e
example without ‘this’ • bool Employee::operator>(const Employee& e) const • { • return(seniority > e->seniority); • } • called from the program like this: • if (emp1 > emp2) • ‘this’ is more self-documenting, but more verbose
Invoking objects • If the operator is binary but there is only one explicit argument, the ‘invoking instance’ is assumed to be the one on the left hand side of the expression. Class Date { public: // member functions Date& operator=(const Date&); }; void assign(Date& s1, Date& s2) { s1 = s2; // instead of s1.operator=(s2); }
Overloading output operators • iostream.h defines the cout object as an instance of the ostream class. • The iostream.h file overloads << with functions handling each of the native data types. • Example: • ostream& operator<<(ostream& out, int n);
operator<< overloading • ostream& operator<<(ostream& out, int n); • This is a non-member function (not called as part of a particular class) therefore it needs two arguments (not one like member function operators) • It returns an ostream so that it will work in multiple call settings. • cout << int1 << int2; • //see last lecture for details on chaining problems.
Advantages • Rather than • Clerk.showData(); • we can do the following: • cout << Clerk;
Problems • If operator<< is a non-member function, • 1. What does it look like? • 2. How can it work on private data members of an Employee object?
What the non-member function looks like ostream& operator<<(ostream& out, const Employee& emp) { out << “Employee number is “ << emp.idNum; out << “ Salary is “ << emp.salary << endl; return(out) } a non-member function (not tied to any class)
How it can access private data • To do this we must place the prototype of the non-member function in the public section of the class definition. • Then, we must identify it as a ‘friend’ of the class. • A ‘friend function’ has direct access to the private data members.
The original employee class class Employee { private: int idNum; double salary; public: Employee(const int id, const double salary); double addTwo(const Employee& emp); double operator+(const Employee& emp); }
Non-member functions can have access to private data class Employee { private: int idNum; double salary; public: Employee(const int id, const double salary); double addTwo(const Employee& emp); double operator+(const Employee& emp); friend ostream& operator << (ostream &out, const Employee& emp); // the prototype for a friend function }
Overloading input >> • You can construct an overloaded extraction operator in a manner similar to that used for the insertion operator << • One additional feature could be implemented as well, you could screen for invalid data as it was entered. • This would also need to be a friend function if it was not a non-member of the class.
Extraction operator >> istream& operator>>(istream& in, Employee& emp) { cout << endl; // to clear the buffer cout << “Enter the employee id number “; in >> emp.idNum; cout << “Enter the salary “; in >> emp.salary; // data verification here return(in) } to use it from client code: cin >> Clerk;
Overloading ++ and -- • It makes a big difference whether you are overloading the prefix (++i) or the postfix (i++) versions of this operator. • Prefix makes the change, then processes the variable. • Postfix processes the variable, then makes the change.
Overloaded prefix ++ Class Inventory { private: int stockNum; int numSold; public: Inventory(const int stknum, const int sold); friend ostream& out, const Inventory& item); Inventory* operator++(); } Inventory& Inventory::operator++() { ++numsold; // or ++this->numsold; return(*this); }
Use of the prefix ++ Inventory someItem(789, 84); // the stockNum is 789 // the numSold is 84 ++someItem; cout << someItem; // output says 85 items sold
Problem • The definition of the prefix operator is easy enough. It increments the value before any other operation. • But how will C++ be able to tell the difference between a prefix ++ operator and a postfix ++ operator • Answer: overloaded postfix operators take a dummy argument.
Postfix operator Inventory& Inventory::operator++() // prefix version { ++numsold; // or ++this->numsold; return(*this); } Inventory& Inventory::operator++(int) // postfix version { numsold++; // or this->numsold++; return(*this); } dummy argument
Example of member functions for Fraction class // the prototype in the class definition Fraction& operator++(); // prefix Fraction& operator++(int); // postfix Fraction& Fraction::operator++() { numerator = numerator + denominator; return(*this); } Fraction& Fraction::operator++(int n) { numerator = numerator + denominator; return(*this); }
Overloading relational operators (==) // the prototype in the class definition int operator==(Fraction const& f2); int Fraction::operator==(Fraction& f2) { if (numerator == f2.numerator && denominator == f2.denominator) return(1); else return(0); }
Assignment operator= • Similar to the copy constructor, but • Re-initializes already constructed objects Date today; // copy constructor ... today = “9/20/1999”; • Need assignment operator accepting char*
Assignment operator Class Date { Date& operator=(const char* dCptr); ... } Date::operator=(const char* dCptr) { // parse dateCptr into m, d, y assign(m, d, y); }
Assignment operator • Compiler generates a default assignment operator if you do not define one • bitwise copy only (‘shallow copy’) • If you have dynamically allocated memory in your objects then you will need to write an assignment operator to create ‘deep copies’
Assignment operator • Bitwise copy ok for classes like Date • members of simple types only • no pointers, therefore no remote ownership • What happens if we bitwise copy an object owning a resource? • Same problem as with default copy constructors
Assignment Declaration class Set { public: //Constructors... Set& operator=(const Set &s); private: int *data; int size; };
Set& Set::operator=(const Set &s) { if (this != &s) // no assignment to self { if (data != 0) delete [] data; size = s.size; data = new int[size]; for (int i=0; i<size; ++i) data[i] = s.data[i]; } return *this; }
Overloading restrictions • At least one of the arguments to an overloaded operator MUST be an instance of the class to which the operator belongs. Class Date { public: // member functions Date operator+(const Date&); // OK! Date operator+(); // OK, due to ‘this’ }; // non-member functions friend Date operator+(int, int); // ERROR, no Dates friend Date operator-(int, const Date&); // OK
Overloading unary operators • If the unary operator is a member function, then • Empty argument list (no explicit arguments) • Single argument passed implicitly (this) • If the unary operator is not a member function than there will be one argument
Subscript operator[] • Defines array style syntax for accessing individual elements of “container” classes • Usage examples Set s1; s1[0] = 5; int value = s1[0]; • MUST be made a class member • Implementation Example
class Set { public: //Constructors... int& operator[](const int index); private: int *data; int size; };
int& Set::operator[](const int index) { if (index < 0 || index >= size) return data[0]; return data[index]; }
class Set { public: //Constructors… int& operator[](const int index); private: friend ostream& operator<<(ostream &stream, const Set &s); };
ostream& operator<<(ostream &stream, const Set &s) { for (int i=0; i<s.size(); ++i) stream << s[i] << “ “; stream << endl; return stream; }
Const Version of operator[] • Must also add a const version of [] Back to class definition int operator[](const int size) const
class Set { public: //Constructors... int& operator[](const int index); int operator[](const int index) const; private: int *data; int size; };
Operator[] Summary non-const object const object Set s; const Set s; assignment retrieval assignment retrieval s[0] = 5; cout << s[0]; s[0] = 5; cout << s[0]; non-const version of [] const version of [] may need lvalue, must return reference does not need lvalue -> no reference
Operators - Global or Member ? • Choice impacts the usage of the type • Decision based on how you think type will be used • Addition (Operator+) for Set • Operator+ for sets creates union of both arguments • Assume we made it a member function
Addition (Operator+) Example • Example: Set s1, s2, s3; s3 = s1 + s2; s3 = s1 + 2; s3 = 2 + s1; • Why not create a constructor that takes an integer as an argument. Then it would be implicitly converted and the + operator could be called. • Good idea, but won’t work as a general rule ! Why not ? • 2 = s1; // would then become legal
Better Solution - Global • Make addition (operator+) a global function • How many global operator functions needed ? • Capture s1 + s2, s1 + 2, 2 + s1 • For each global operator function, add the friend specifier to the class declaration • How could I reduce it to 1 global op. function? • Now use an implicit conversion ! • C++ implicitly converts lhs arguments ONLY when it applies to a global operator function, NOT member functions, thus can do 2 + s1, not 2 = s1
Operators - Rules of thunb • Member operator functions • Ones that are required to be members ([]) • Generally have a “=“ in them • Global operators • Generally, +, -, /, * • Decision based on use !
Constructors as type conversions • What is a constructor is passed a value it does not expect? It may promote it. MyType::MyType(AnotherType); MyType::MyType(const AnotherType&); • implicitly convert AnotherType object into MyType object
Constructors as type conversions • If an object of MyType is expected, but object of AnotherType is specified class string { string(int len); … }; string s1(“C”), s2(“++”); cout << s1 + 10 + s2; // equivalent to: cout << s1 + tempString(10) + s2;