Operator Overloading and Custom Data Types in C++
Learn how to create your own types using class and make them behave like built-in types by overloading operators like +, ==, and <<.
- How to define custom data types using class
- What operator overloading is and why it's useful
- How to overload operators like +, ==, and <<
- How to build intuitive class-based code
- When to overload operators and when to avoid it
1Why create custom data types?
C++ lets you define your own types using class or struct, which group data and functions together. This allows you to model real-world entities more naturally and write code that is easier to understand and maintain.
Example – modelling a complex number:
class Complex { public: double real; double imag; };
You now have a new data type: Complex. You can create variables of this type, store them in arrays, and pass them to functions.
class and struct are nearly identical. The only difference is that class members are private by default, while struct members are public by default. We use class when we want to encapsulate data and provide a clear interface.
2What is operator overloading?
Operator overloading allows you to redefine how operators work for your custom types. This makes your code cleaner and more intuitive.
Without operator overloading:
Complex result = add(c1, c2);
With operator overloading:
Complex result = c1 + c2;
The second version is much more natural. Anyone reading the code immediately understands that you are adding two complex numbers.
3Step 1: Define the class
Let's walk through creating a simple Complex number class and overloading some basic operators.
Define the class with a constructor and display function:
class Complex { public: double real, imag; // Constructor Complex(double r = 0, double i = 0) { real = r; imag = i; } // Display function void display() { cout << real << " + " << imag << "i" << endl; } };
4Step 2: Overload the + operator
We want to allow c1 + c2 where both are Complex objects. The overloaded operator returns a new Complex object.
Complex operator+(const Complex& c) { return Complex(real + c.real, imag + c.imag); }
Complete class with + overloaded:
class Complex { public: double real, imag; Complex(double r = 0, double i = 0) { real = r; imag = i; } Complex operator+(const Complex& c) { return Complex(real + c.real, imag + c.imag); } void display() { cout << real << " + " << imag << "i" << endl; } };
Usage:
Complex c1(3.5, 2.5); Complex c2(1.5, 4.5); Complex result = c1 + c2; result.display();
5Step 3: Overload the == operator
You can define what it means for two objects to be equal. This makes comparisons intuitive.
bool operator==(const Complex& c) { return real == c.real && imag == c.imag; }
Usage:
if (c1 == c2) { cout << "Equal\n"; } else { cout << "Not Equal\n"; }
6Step 4: Overload the << operator for cout
To use cout << object, you must define operator<< as a friend function outside the class.
friend ostream& operator<<(ostream& out, const Complex& c) { out << c.real << " + " << c.imag << "i"; return out; }
Complete updated class:
class Complex { public: double real, imag; Complex(double r = 0, double i = 0) : real(r), imag(i) {} Complex operator+(const Complex& c) { return Complex(real + c.real, imag + c.imag); } bool operator==(const Complex& c) { return real == c.real && imag == c.imag; } friend ostream& operator<<(ostream& out, const Complex& c) { out << c.real << " + " << c.imag << "i"; return out; } };
Usage:
Complex c1(2, 3), c2(2, 3); cout << c1 << endl; if (c1 == c2) { cout << "Equal\n"; }
7Custom data types: Another example – Fractions
You can define a class for fractions and overload operators like +, *, or == to make them behave naturally.
class Fraction { public: int num, denom; Fraction(int n = 0, int d = 1) { num = n; denom = d; } Fraction operator+(const Fraction& f) { int n = num * f.denom + f.num * denom; int d = denom * f.denom; return Fraction(n, d); } friend ostream& operator<<(ostream& out, const Fraction& f) { out << f.num << "/" << f.denom; return out; } };
Usage:
Fraction f1(1, 2); Fraction f2(1, 3); Fraction result = f1 + f2; cout << result << endl;
8When should you overload operators?
- You are building a custom data type that represents a mathematical or logical entity
- You want your objects to behave like built-in types
- It makes the code more readable and natural
- Your users expect the operator to work in a certain way
- If it makes the code confusing or less clear
- If the operation is not well-defined or intuitive
- If it violates the user's expectations (e.g., overloading + to subtract)
9Common mistakes to avoid
+ must return a new object. Forgetting to return a value causes undefined behavior.
const references for parameters that are not modified. This is a good practice and enables more efficient code.
operator<< must be a non-member function (or friend) because the left operand is ostream, not your class.
10Mini exercises
- Create a
Pointclass withxandycoordinates. Overload+to add two points. - Create a
Distanceclass in meters. Overload==and<to compare distances. - Write a
Fractionclass and overload*and/operators. - Add a
friend ostream& operator<<to any class you have created to print it withcout.
Hint: For the Distance class, store the value in meters as a double and compare directly.
