Operator overloading allows operators like +
and <<
to have additional meanings beyond their standard numerical operations, such as concatenating or printing strings.
The expression a * x + b
can be written as sum(product(a, x), b)
without operators, but function calls are less intuitive.
Operator overloading allows you to define how operators work with your classes.
It's recommended to maintain the traditional meaning of operators when overloading them (e.g., +
for concatenation, which relates to the sum of numbers).
The Time
class represents a time with hours and minutes.
The header file time.h
declares the Time
class with private members hours
and minutes
, and public methods like Time()
, Time(int h, int m)
, get_hours()
, and get_minutes()
.
time.h
)#ifndef TIME_H
#define TIME_H
class Time {
public:
Time();
Time(int h, int m);
int get_hours() const;
int get_minutes() const;
private:
int hours;
int minutes;
};
#endif
time.cpp
)#include "time.h"
Time::Time() {
hours = 0;
minutes = 0;
}
Time::Time(int h, int m) {
hours = h;
minutes = m;
}
int Time::get_hours() const {
return hours;
}
int Time::get_minutes() const {
return minutes;
}
It's possible to compute the difference between two Time
objects if you define the operator-
.
Example:cpp Time morning(9, 30); Time lunch(12, 0); int minutes_to_lunch = lunch - morning;
operator-
int operator-(Time a, Time b) {
return a.get_hours() * 60 + a.get_minutes() - b.get_hours() * 60 - b.get_minutes();
}
You can't create new operators in C++ or change operator precedence.
The general syntax for defining an overloaded operator is: return_type operatoroperator_symbol(parameters) { statements }
Example:cpp int operator-(Time a, Time b) { return a.get_hours() * 60 + a.get_minutes() - b.get_hours() * 60 - b.get_minutes(); }
operator_symbol
can be any of the following: +
, -
, *
, /
, %
, ^
, <
, >
, <<
, >>
, |
, ||
, &
, &&
, ==
, !=
, <=
, >=
, !
, --
, ++
, ~
, and a few more.
The operator-
function can be a non-member function or an accessor member function.
Non-member function:cpp int operator-(Time a, Time b) { return a.get_hours() * 60 + a.get_minutes() - b.get_hours() * 60 - b.get_minutes(); }
Member function:cpp int Time::operator-(Time b) const { return (hours - b.get_hours()) * 60 + minutes - b.get_minutes(); }
It might not make sense to add two Time
objects because they represent clock times, not durations.
However, it makes sense to add minutes to a Time
object.
Time operator+(Time a, int minutes) {
int result_minutes = a.get_hours() * 60 + a.get_minutes() + minutes;
return Time((result_minutes / 60) % 24, result_minutes % 60);
}
Time too_early(6, 0);
Time wake_up = too_early + 45; // Using the operator
To compare two Time
objects, overload the ==
operator.
==
, !=
, and <
Operatorsbool operator==(Time a, Time b) {
return a - b == 0;
}
bool operator!=(Time a, Time b) {
return a - b != 0;
}
bool operator<(Time a, Time b) {
return a - b < 0;
}
If you want to sort an array of Time
objects, you need to define the <
operator.
Overload the <<
output operator for the Time
class:cpp ostream& operator<<(ostream& out, Time a) { out << a.get_hours() << ":" << std::setw(2) << std::setfill('0') << a.get_minutes(); return out; }
The <<
operator returns the output stream to enable chaining.
Chaining example:cpp cout << noon << "\n"; // Equivalent to (cout << noon) << "\n";
operator<<(cout, noon)
prints the time and returns cout
, which then prints a newline.
Define the operator>>
to read a Time
object from an input stream:cpp istream& operator>>(istream& in, Time& a) { int hours; char separator; int minutes; in >> hours; in.get(separator); // Read ':' character in >> minutes; a = Time(hours, minutes); return in; }
The >>
operator also returns the input stream reference istream&
to allow chaining.
The >>
operator must have a parameter of type Time&
because it modifies the object.
Example main()
function:cpp int main() { Time too_early(6, 0), noon(12, 0); Time wake_up = too_early + 45; std::cout << "Wake up at: " << wake_up << std::endl; std::cout << "Current time: "; Time now; std::cin >> now; if (now < wake_up) { std::cout << "Too early!" << std::endl; } if (now == noon) { std::cout << "Time for lunch!" << std::endl; } std::cout << "Minutes to lunch: " << noon - now << std::endl; return 0; }
Example Program Run:
Wake up at: 6:45
Current time: 7:25
Minutes to lunch: 275
When defining an operator function whose first argument is a class, you can make it a member function to access private data directly.
Example:
class Time {
public:
...
Time operator+(int min) const;
};
Time Time::operator+(int min) const {
int result_minutes = hours * 60 + minutes + min;
return Time((result_minutes / 60) % 24, result_minutes % 60);
}
Complete the code for a -
operator that is a member function of the Time
class to return the total minutes between two Time
objects:cpp int Time::operator-(const Time& other) const { return (hours - other.hours) * 60 + (minutes - other.minutes); }
Implement an operator*
function to repeat strings a given number of times:cpp std::string operator*(std::string s, int n) { std::string result = ""; for (int i = 0; i < n; ++i) { result += s; } return result; }
Example:cpp std::string greeting = "hi"; std::string result = greeting * 3; // result is "hihihi"
Overloading ++
and --
operators has some challenges due to prefix (++x
) and postfix (x++
) forms.
Both forms increment x
, but they return different values.
++x
returns the value of x
after incrementing.
x++
returns the value of x
before incrementing.
The difference between prefix and postfix matters when combining the increment with another expression.
Example:cpp int x = 4; std::cout << x++ << std::endl; // Prints 4, then x becomes 5 std::cout << x << std::endl; // Prints 5 std::cout << ++x << std::endl; // Increments x to 6, then prints 6
Avoid using the return value of increment operators in complex expressions.
Use ++
only to increment a variable and ignore the return value.
To overload the prefix and postfix forms, the compiler needs to distinguish between them.
Prefix form:cpp void operator++(Time& a); // Prefix
Postfix form:cpp void operator++(Time& a, int dummy); // Postfix
The int dummy
parameter is not used; it only serves to differentiate the two operator++
functions.
C++ converts operand types to match before performing arithmetic.
Example: 6 * 3.1415926
(integer times a floating-point number). The 6
will be converted to a floating point number for the calculation.
You can define implicit type conversions for your own classes.
In Worked Example 13.1
:cpp class Fraction { public: Fraction(int n, int d); // The fraction n/d Fraction(int n); // The fraction n/1 Fraction operator+(Fraction other) const; ... };
Two categories of type conversions:
From a standard type to the new class.
From the class to another type.
With the Fraction
class example given earlier, conversions to the class are done by constructors.
Fraction a(3, 4);
a = a + 2; // Translated to a = a.operator+(Fraction(2))
Sometimes, constructors shouldn't be type converters. The vector
class's constructor with an integer parameter (for initial size) is declared as explicit
. This prevents implicit conversions.
To enable conversion from a custom class to another type, use a conversion operator.
Fraction::operator double() const {
// Convert numerator to double, then do division
return numerator * 1.0 / denominator;
}
Conversion operator syntax: a type as the operator name, no arguments, and no result type (the type is implicit in the name).
Usage:
Fraction a(3, 4);
double root = sqrt(a); // Translates as sqrt(a.operator double())
The istream
class has an operator bool
for use in conditional statements.
Example:cpp while (cin >> x) { ... }
The >>
operator returns type istream
, which isn't a legal condition type, but istream
defines operator bool
to return true
if input is successful and false
at the end of input or on failure.
Represent a ratio of two integers, with three constructor formats:
Fraction a(3, 4); // Represents 3/4
Fraction b(7); // Represents 7/1
Fraction c; // Represents 0/1
Fraction
objects should behave like numbers:
if(a < b)
c = b - a;
else
c = a - b;
cout << "Value is " << c << endl;
Should be able to mix with other number types such as in this example:
c = a + 3; // Should read as the addition of a + 3/1
Reduce arithmetic results to lowest common terms.
Example: 1/2 + 3/2 = (1\cdot2 + 2\cdot3)/(2\cdot2) = 8/4 = 2/1
Reduction occurs in the constructor:
Fraction::Fraction(int n, int d) {
int g = gcd(n, d); // The greatest common divisor
if (g == 0) {
numerator = 0;
denominator = 1;
} else if (d > 0) {
numerator = n / g;
denominator = d / g;
} else {
numerator = -n / g;
denominator = -d / g;
}
}
The greatest common divisor is computed using Euclid’s algorithm:
int gcd(int a, int b) {
int m = abs(a);
int n = abs(b);
while (n != 0) {
int r = m % n;
m = n;
n = r;
}
return m;
}
Two subtraction operators exist.
Unary: -f
Binary: f - g
Unary version:
Fraction operator-(Fraction f) {
return f * -1;
}
Binary version:
Fraction operator-(Fraction f, Fraction g) {
return f + g * -1;
}
Express new operators in terms of existing ones for simplicity.
Division using the same strategy as subtraction is ideal, but because there is no existing multiplicative inverse, it simplifies the code to implement the member function shown:
Fraction Fraction::inverse() const {
return Fraction(denominator, numerator);
}
Fraction operator/(Fraction f, Fraction g) {
return f * g.inverse();
}
#ifndef FRACTION_H
#define FRACTION_H
#include <iostream>
using namespace std;
class Fraction {
public:
Fraction(int n, int d);
Fraction(int n);
Fraction();
/**
* Maes the inverse of this fraction.
*
* @return the inverse (denominator / numerator)
*/
Fraction inverse() const;
/**
* Prints this fraction to a stream.
*
* @param out the output stream
*/
void print(ostream& out) const;
/**
* Reads a fraction from a stream and stores the input in this fraction
*
* @param in the input stream
*/
void read(istream& in);
Fraction operator+(Fraction other) const;
Fraction operator*(Fraction other) const;
bool operator<(Fraction other) const;
private:
int numerator;
int denominator;
};
Fraction operator-(Fraction f);
Fraction operator-(Fraction f, Fraction g);
Fraction operator*(int n, Fraction f);
Fraction operator/(Fraction f, Fraction g);
bool operator>(Fraction f, Fraction g);
bool operator<=(Fraction f, Fraction g);
bool operator>=(Fraction f, Fraction g);
bool operator==(Fraction f, Fraction g);
bool operator!=(Fraction f, Fraction g);
ostream& operator<<(ostream& out, Fraction f);
istream& operator>>(istream& in, Fraction& a);
#endif
See workedexample1/fraction.cpp for function implementations
Here is fractiondemo.cpp
:
#include <iostream>
#include "fraction.h"
using namespace std;
int main() {
Fraction f(1, 2);
Fraction g(1, 3);
cout << "f + g: " << f + g << endl;
cout << "f * g: " << f * g << endl;
cout << "f / g: " << f / g << endl;
cout << "Enter a fraction: ";
Fraction h;
cin >> h;
if (f == h) {
cout << "Your input equals 1/2" << endl;
} else {
cout << "f - h: " << f - h << endl;
}
return 0;
}