Operator Overloading
In C++, built-in operators such as + and == can be redefined for classes. This is called operator overloading, which enables natural notation for numeric calculations, coordinate operations, string processing, and other operations that match the class's semantics.
Syntax
class MyClass {
public:
// As a member function: left operand is this, right operand is the argument
MyClass operator+(const MyClass& rhs) const {
// rhs: right-hand side operand
}
// Comparison operator example
bool operator==(const MyClass& rhs) const {
}
// Unary operator (negation, etc.)
MyClass operator-() const {
}
// Subscript operator
int& operator[](int index) {
}
// Compound assignment operator (+=, etc.)
MyClass& operator+=(const MyClass& rhs) {
return *this;
}
};
// As a friend function (used when the left operand is not a class type)
// The << operator (output stream) typically uses this form
class MyClass2 {
public:
friend std::ostream& operator<<(std::ostream& os, const MyClass2& obj) {
os << "output content";
return os;
}
};
Operator Reference
| Operator | Definition Method | Description |
|---|---|---|
| +, -, *, / | Member or non-member function | Defines arithmetic operations. Returns a new object without modifying the original. |
| +=, -=, *=, /= | Member function (returns *this) | Defines compound assignment. Modifies self and returns a reference to *this. |
| ==, != | Member or non-member function | Defines equality comparison. Returns bool. |
| <, <=, >, >= | Member or non-member function | Defines ordering. Used by algorithms like std::sort. |
| Unary - (negation) | Member function (no arguments) | Returns a new object with the sign reversed. |
| [] (subscript) | Member function | Defines element access by index. Returning a reference allows assignment. |
| <<, >> (stream) | Friend non-member function | Defined as a friend function since the left operand is std::ostream or std::istream. |
| () (function call) | Member function | Allows calling the object like a function. Used for functors (function objects). |
Sample Code
kof_vector.cpp
#include <iostream>
#include <cmath>
#include <string>
// Vec2 class: represents a 2D vector
class Vec2 {
public:
double x;
double y;
Vec2(double x = 0.0, double y = 0.0) : x(x), y(y) {}
// operator+: adds two vectors, returns a new vector
Vec2 operator+(const Vec2& rhs) const {
return Vec2(x + rhs.x, y + rhs.y);
}
// operator-: subtracts two vectors
Vec2 operator-(const Vec2& rhs) const {
return Vec2(x - rhs.x, y - rhs.y);
}
// operator*: scalar multiplication
Vec2 operator*(double scalar) const {
return Vec2(x * scalar, y * scalar);
}
// Unary operator-: reverses the direction of the vector
Vec2 operator-() const {
return Vec2(-x, -y);
}
// operator+=: adds rhs to self and returns reference to self
Vec2& operator+=(const Vec2& rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
// operator==: checks if two vectors are equal
bool operator==(const Vec2& rhs) const {
return x == rhs.x && y == rhs.y;
}
// operator!=: negation of ==
bool operator!=(const Vec2& rhs) const {
return !(*this == rhs);
}
double length() const {
return std::sqrt(x * x + y * y);
}
// Friend function: output stream operator
friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
// Fighter class: represents a KOF character
class Fighter {
public:
std::string name;
Vec2 pos;
int hp;
Fighter(const std::string& name, Vec2 pos, int hp)
: name(name), pos(pos), hp(hp) {}
void showStatus() const {
std::cout << name
<< " Pos: " << pos
<< " HP: " << hp
<< std::endl;
}
void move(const Vec2& delta) {
pos += delta;
}
void takeDamage(int dmg) {
hp -= dmg;
if (hp < 0) hp = 0;
}
};
int main() {
Fighter iori ("Yagami Iori", Vec2(150.0, 0.0), 1000);
Fighter kyo ("Kusanagi Kyo", Vec2( 50.0, 0.0), 1000);
Fighter terry("Terry Bogard", Vec2(100.0, 50.0), 1000);
std::cout << "=== KOF Battle Simulation ===" << std::endl << std::endl;
std::cout << "--- Initial positions ---" << std::endl;
iori.showStatus();
kyo.showStatus();
terry.showStatus();
std::cout << std::endl;
// operator+: combines movement vectors
Vec2 kyoMove (20.0, 0.0);
Vec2 kyoJump ( 0.0, 5.0);
Vec2 combined = kyoMove + kyoJump;
std::cout << "--- Kyo jumps and advances ---" << std::endl;
std::cout << "Advance vector: " << kyoMove << std::endl;
std::cout << "Jump vector: " << kyoJump << std::endl;
std::cout << "Combined vector: " << combined << std::endl;
kyo.move(combined);
std::cout << "After Kyo moves: ";
kyo.showStatus();
std::cout << std::endl;
// operator-: direction vector calculation
Vec2 dirKyoToIori = iori.pos - kyo.pos;
std::cout << "--- Direction vector from Kyo to Iori ---" << std::endl;
std::cout << "Direction: " << dirKyoToIori << std::endl;
std::cout << "Distance: " << dirKyoToIori.length() << std::endl << std::endl;
// operator*: scalar multiplication (knockback)
Vec2 knockbackDir(-1.0, 0.5);
Vec2 knockback = knockbackDir * 12.0;
std::cout << "--- Kyo hits Terry with knockback ---" << std::endl;
std::cout << "Knockback direction: " << knockbackDir << std::endl;
std::cout << "Knockback vector: " << knockback << std::endl;
terry.move(knockback);
terry.takeDamage(200);
std::cout << "Terry after hit: ";
terry.showStatus();
std::cout << std::endl;
// Unary operator-: reverse knockback direction
Vec2 reverseKnockback = -knockback;
std::cout << "--- Iori hit with reverse knockback ---" << std::endl;
iori.move(reverseKnockback);
iori.takeDamage(180);
std::cout << "Iori after hit: ";
iori.showStatus();
std::cout << std::endl;
// operator== and operator!=
Vec2 origin(0.0, 0.0);
std::cout << "--- Position equality check ---" << std::endl;
std::cout << "Is Kyo at origin? "
<< (kyo.pos == origin ? "yes" : "no") << std::endl;
std::cout << "Are Kyo and Iori at different positions? "
<< (kyo.pos != iori.pos ? "yes" : "no") << std::endl;
std::cout << std::endl;
std::cout << "=== Final State ===" << std::endl;
iori.showStatus();
kyo.showStatus();
terry.showStatus();
return 0;
}
g++ -std=c++11 kof_vector.cpp -o kof_vector && ./kof_vector === KOF Battle Simulation === --- Initial positions --- Yagami Iori Pos: (150, 0) HP: 1000 Kusanagi Kyo Pos: (50, 0) HP: 1000 Terry Bogard Pos: (100, 50) HP: 1000 --- Kyo jumps and advances --- Advance vector: (20, 0) Jump vector: (0, 5) Combined vector: (20, 5) After Kyo moves: Kusanagi Kyo Pos: (70, 5) HP: 1000 --- Direction vector from Kyo to Iori --- Direction: (80, -5) Distance: 80.1561 --- Kyo hits Terry with knockback --- Knockback direction: (-1, 0.5) Knockback vector: (-12, 6) Terry after hit: Terry Bogard Pos: (88, 56) HP: 800 --- Iori hit with reverse knockback --- Iori after hit: Yagami Iori Pos: (162, -6) HP: 820 --- Position equality check --- Is Kyo at origin? no Are Kyo and Iori at different positions? yes === Final State === Yagami Iori Pos: (162, -6) HP: 820 Kusanagi Kyo Pos: (70, 5) HP: 1000 Terry Bogard Pos: (88, 56) HP: 800
Common mistake 1: Defining + without +=, or vice versa
Defining only operator+ without operator+= (or vice versa) means only one form is usable. A common approach is to implement operator+= first and then implement operator+ using it.
// NG: Only + is defined, += cannot be used
class Vec2 {
public:
double x, y;
Vec2(double x, double y) : x(x), y(y) {}
Vec2 operator+(const Vec2& rhs) const {
return Vec2(x + rhs.x, y + rhs.y);
}
// operator+= is undefined, so v1 += v2 causes a compile error
};
OK: Implement += first, then implement + using it.
// OK: Implement += first, implement + using +=
class Vec2 {
public:
double x, y;
Vec2(double x, double y) : x(x), y(y) {}
Vec2& operator+=(const Vec2& rhs) {
x += rhs.x;
y += rhs.y;
return *this;
}
Vec2 operator+(const Vec2& rhs) const {
Vec2 result = *this;
result += rhs; // Reuses operator+=
return result;
}
};
Common mistake 2: Trying to define the << operator as a member function
The left operand of the << (stream output) operator is std::ostream. As a member function, the left operand would be this, which does not match the intended usage.
// NG: Defining << as a member function makes the left operand Vec2
class Vec2 {
public:
double x, y;
// This would make it Vec2 << ostream (reversed)
// std::ostream& operator<<(std::ostream& os) const { ... }
};
// Usage would be: v << std::cout; (unintended)
OK: Define as a friend non-member function.
// OK: Define as a friend non-member function
class Vec2 {
public:
double x, y;
Vec2(double x, double y) : x(x), y(y) {}
friend std::ostream& operator<<(std::ostream& os, const Vec2& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
// Usage: std::cout << vec;
Overview
Operator overloading allows writing operators like + and == for class objects, improving code readability and expressiveness. When defined as a member function, the left operand is this and the right operand is the argument. For stream operators (<<) where the left operand is std::ostream (a non-class type), define them as friend non-member functions. Compound assignment operators like += enable chained assignment by returning a reference to *this. Some operators cannot be overloaded (::, ., .*, ?:). Designing operators so that their behavior aligns with the natural, intuitive meaning of the class is important to avoid confusion.
If you find any errors or copyright issues, please contact us.