Code that works for many types.
The version executed is determined dynamically. (Savitch 14,15; Stroustrup 12; Horstmann 14)
The version executed is determined statically from the types of the arguments (Savitch 8.1; Stroustrup 7.4,11; Horstmann 13.4)
A single version, parameterized by types, is used (Savitch 16.1-2; Stroustrup 13.2-3; Horstmann 13.5)
A single symbol has multiple meanings. The meaning of a particular use is statically determined by the types of its arguments.
The following may be overloaded in C++:
Operator overloading makes for concise programs, but overuse may impair readability.
void f(double x) { ... }
it is legal to write f(1), because 1, of type int
can be implicitly converted to double.
(Later: similar situation with inheritance of class types.)
void f(int n) { ... }
If we call f(1), the best (most specific) definition is selected,
i.e. the one closest to the call type.
void f(int i, double y) { ... }
void f(double x, int j) { ... }
the following is rejected by the the compiler:
f(1, 2); // ambiguous!
We could get around this by also defining
void f(int i, int j) { ... }
Then every application would have a best match.
int i;
if (i == 3) ...
We can also compare objects:
string s1, s2;
if (s1 == s2) ...
and similarly for vectors.
The == operator is overloaded: special definitions have been given for string, vector and many other types.
An operator can be either an independent function or a member function, in each case with a special name starting with operator:
class Point {
int _x, _y;
public:
Point(int x, int y) : _x(x), _y(y) { }
int x() const { return _x; }
int y() const { return _y; }
bool operator==(const Point &p) const {
return _x == p.x() && _y == p.y();
}
};
bool operator==(const Point &p1, const Point &p2) {
return p1.x() == p2.x() && p1.y() == p2.y();
}
In either case we can then write
Point p1, p2;
...
if (p1 == p2)
...
if (p1 == Point(0, 0)) // temporary object
...
Only built-in operators can be overloaded:
| unary | ~ ! + - & * ++ -- |
||
| binary | + - * / % ^ & | << >> |
||
+= -= *= /= %= ^= &= |= <<= >>= |
|||
== != < > <= >= && || |
|||
= , ->* -> () [] |
a + b + c * d (a + b) + (c * d)
are always equivalent, no matter how the operators are overloaded.
Consider
cout << "Total = " << sum << '\n';
This is equivalent to
((cout << "Total = ") << sum) << '\n';
class ostream {
public:
ostream& operator<<(char c);
ostream& operator<<(unsigned char c);
ostream& operator<<(int n);
ostream& operator<<(unsigned int n);
ostream& operator<<(long n);
ostream& operator<<(float n);
ostream& operator<<(double n);
...
};
In the string header file:
ostream& operator<<(ostream &out, const string &s);
class Point {
int _x, _y;
public:
Point(x, y) : _x(x), _y(y) { }
int x() const { return _x; }
int y() const { return _y; }
};
The output operator for Points is defined as a non-member function:
ostream& operator<<(ostream &s, Point p) {
return s << '(' << p.x() << ", " << p.y() << ')';
}
Suppose we have an expression a « b, where a has type A, and b has type B. Then the relevant definition of « could be either
ReturnType A::operator<<(B x)
ReturnType operator<<(A x, B y)
Point p(2,3);
cout << "The point is " << p << '\n';
Input is almost the mirror image of output:
int x, y, z;
cout << "Please type three numbers: ";
cin >> x >> y >> z;
class istream : virtual public ios {
public:
istream& operator>>(char &c);
istream& operator>>(unsigned char &c);
istream& operator>>(int &n);
istream& operator>>(unsigned int &n);
istream& operator>>(long &n);
istream& operator>>(float &n);
istream& operator>>(double &n);
...
};
In the string header file:
istream& operator>>(istream &in, string &s);
The following methods of istream test its state:
istream& operator>>(istream &s, Point &p) {
int x, y;
char lpar, comma, rpar;
if (s >> lpar) {
if ((s >> x >> comma >> y >> rpar) &&
(lpar == '(' && comma == ',' && rpar == ')'))
p = Point(x, y);
else
s.set(ios::badbit); // read failed
}
return s;
}