MyCodeSchool
Supraincărcarea operatorilor în C++
mycodeschool.ro

🚀 Supraîncărcarea Operatorilor în C++

Învață să personalizezi comportamentul operatorilor pentru clasele tale

📚 Ce este supraîncărcarea operatorilor?

Supraîncărcarea operatorilor (operator overloading) este o caracteristică puternică a C++ care permite definirea comportamentului personalizat pentru operatori standard (+, -, *, /, ==, etc.) când sunt aplicați obiectelor claselor definite de utilizator.

💡 De ce este utilă?
  • Permite scrierea unui cod mai intuitiv și mai lizibil
  • Face obiectele să se comporte ca tipurile primitive
  • Simplifică operațiile complexe cu obiecte
  • Îmbunătățește expresivitatea codului

⚙️ Sintaxa de bază

Operatorii pot fi supraîncărcați ca funcții membre sau funcții non-membre (prietene):

// Sintaxa generală pentru funcție membră
ReturnType operatorop(ParameterList);

// Sintaxa generală pentru funcție non-membră
friend ReturnType operatorop(ParameterList);

Exemplu simplu - Clasa Complex

class Complex {
private:
    double real, imag;
    
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // Supraîncărcarea operatorului + ca funcție membră
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
    
    // Supraîncărcarea operatorului << ca funcție prieten
    friend ostream& operator<<(ostream& os, const Complex& c) {
        os << c.real << " + " << c.imag << "i";
        return os;
    }
};

📋 Operatori care pot fi supraîncărcați

Categorie Operatori Exemple
Aritmetici + - * / % Adunare, scădere, înmulțire
Relaționali == != < > <= >= Comparații între obiecte
Logici && || ! Operații logice
Atribuire = += -= *= /= Atribuire și operații combinate
Increment/Decrement ++ -- Prefix și postfix
Acces [] () -> Acces la elemente, apel funcție
I/O << >> Input și output streams
⚠️ Operatori care NU pot fi supraîncărcați:
  • . (acces membru)
  • .* (pointer la membru)
  • :: (rezoluție domeniu)
  • ?: (operator ternar)
  • sizeof
  • typeid

🤝 Funcții și Clase Friend

Cuvântul cheie friend permite funcțiilor sau claselor externe să acceseze membrii privați și protejați ai unei clase. Aceasta este esențială pentru supraîncărcarea anumitor operatori.

De ce avem nevoie de friend?

💡 Funcțiile friend sunt necesare când:
  • Operatorul binar necesită operandul stâng de alt tip (ex: 2 * vector)
  • Operatorii de I/O (<< și >>) necesită stream-ul ca prim argument
  • Dorim să accesăm membri privați din afara clasei pentru eficiență
  • Implementăm operatori simetrici (comutativitate)

1. Friend Function pentru Operatori

Operator cu friend function
class Complex {
private:
    double real, imag;
    
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // Friend function pentru operator+ (permite 2 + obj)
    friend Complex operator+(const Complex& c1, const Complex& c2);
    
    // Friend function pentru operator* cu scalar
    friend Complex operator*(double scalar, const Complex& c);
    friend Complex operator*(const Complex& c, double scalar);
    
    // Friend function pentru operator<< (output)
    friend ostream& operator<<(ostream& os, const Complex& c);
    
    // Friend function pentru operator>> (input)
    friend istream& operator>>(istream& is, Complex& c);
};

// Implementarea friend functions în afara clasei
Complex operator+(const Complex& c1, const Complex& c2) {
    return Complex(c1.real + c2.real, c1.imag + c2.imag);
}

// Permite: 5.0 * complex_number
Complex operator*(double scalar, const Complex& c) {
    return Complex(scalar * c.real, scalar * c.imag);
}

// Permite: complex_number * 5.0
Complex operator*(const Complex& c, double scalar) {
    return Complex(c.real * scalar, c.imag * scalar);
}

ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real;
    if(c.imag >= 0) os << " + " << c.imag << "i";
    else os << " - " << -c.imag << "i";
    return os;
}

istream& operator>>(istream& is, Complex& c) {
    cout << "Partea reală: ";
    is >> c.real;
    cout << "Partea imaginară: ";
    is >> c.imag;
    return is;
}

2. Friend Class

Clasă friend pentru acces complet
class Matrix;  // Forward declaration

class Vector {
private:
    double* data;
    size_t size;
    
public:
    Vector(size_t n) : size(n) {
        data = new double[size];
    }
    
    // Matrix este friend class - poate accesa toți membrii privați
    friend class Matrix;
    
    // Friend function pentru înmulțire matrice-vector
    friend Vector operator*(const Matrix& m, const Vector& v);
};

class Matrix {
private:
    double** data;
    size_t rows, cols;
    
public:
    Matrix(size_t r, size_t c) : rows(r), cols(c) {
        data = new double*[rows];
        for(size_t i = 0; i < rows; ++i)
            data[i] = new double[cols];
    }
    
    // Poate accesa direct data[] din Vector pentru eficiență
    void setColumn(size_t col, const Vector& v) {
        if(col < cols && v.size == rows) {
            for(size_t i = 0; i < rows; ++i)
                data[i][col] = v.data[i];  // Acces direct la membri privați
        }
    }
    
    friend Vector operator*(const Matrix& m, const Vector& v);
};

3. Comparație: Member vs Friend Function

Când să folosim fiecare abordare
class Rational {
private:
    int numerator, denominator;
    
public:
    Rational(int n = 0, int d = 1) : numerator(n), denominator(d) {}
    
    // MEMBER FUNCTION - operandul stâng trebuie să fie Rational
    // Apel: rational1.operator+(rational2)
    Rational operator+(const Rational& other) const {
        return Rational(
            numerator * other.denominator + other.numerator * denominator,
            denominator * other.denominator
        );
    }
    
    // FRIEND FUNCTION - permite conversii implicite pentru ambii operanzi
    // Apel: operator+(rational1, 2) sau operator+(2, rational1)
    friend Rational operator*(const Rational& r1, const Rational& r2) {
        return Rational(
            r1.numerator * r2.numerator,
            r1.denominator * r2.denominator
        );
    }
    
    // Constructor de conversie - permite int -> Rational
    Rational(int n) : numerator(n), denominator(1) {}
};

// Utilizare:
// Rational r1(3, 4);
// Rational r2 = r1 + Rational(2);  // OK cu member function
// Rational r3 = 2 + r1;             // EROARE cu member, OK cu friend!
// Rational r4 = r1 * 2;             // OK cu friend
// Rational r5 = 2 * r1;             // OK cu friend
⚠️ Reguli importante pentru Friend:
  • Friend-ship nu este simetrică: dacă A e friend cu B, B nu e automat friend cu A
  • Friend-ship nu este tranzitivă: dacă A e friend cu B și B cu C, A nu e friend cu C
  • Friend-ship nu se moștenește: clasele derivate nu moștenesc friend-ship
  • Folosiți friend cu moderație - încalcă încapsularea
  • Preferați funcții membre când e posibil, friend doar când e necesar

4. Exemplu complet: Clasa Matrix cu operatori friend

Implementare completă cu multiple operații friend
class Matrix2x2 {
private:
    double m[2][2];
    
public:
    Matrix2x2(double a = 0, double b = 0, double c = 0, double d = 0) {
        m[0][0] = a; m[0][1] = b;
        m[1][0] = c; m[1][1] = d;
    }
    
    // Friend pentru adunare matrice
    friend Matrix2x2 operator+(const Matrix2x2& m1, const Matrix2x2& m2) {
        return Matrix2x2(
            m1.m[0][0] + m2.m[0][0], m1.m[0][1] + m2.m[0][1],
            m1.m[1][0] + m2.m[1][0], m1.m[1][1] + m2.m[1][1]
        );
    }
    
    // Friend pentru înmulțire cu scalar (ambele direcții)
    friend Matrix2x2 operator*(double k, const Matrix2x2& mat) {
        return Matrix2x2(
            k * mat.m[0][0], k * mat.m[0][1],
            k * mat.m[1][0], k * mat.m[1][1]
        );
    }
    
    friend Matrix2x2 operator*(const Matrix2x2& mat, double k) {
        return k * mat;  // Reutilizăm operatorul de mai sus
    }
    
    // Friend pentru comparație
    friend bool operator==(const Matrix2x2& m1, const Matrix2x2& m2) {
        for(int i = 0; i < 2; ++i)
            for(int j = 0; j < 2; ++j)
                if(m1.m[i][j] != m2.m[i][j])
                    return false;
        return true;
    }
    
    // Friend pentru afișare formatată
    friend ostream& operator<<(ostream& os, const Matrix2x2& mat) {
        os << "[" << mat.m[0][0] << " " << mat.m[0][1] << "]" << endl;
        os << "[" << mat.m[1][0] << " " << mat.m[1][1] << "]";
        return os;
    }
};
Criteriu Funcție Membră Funcție Friend
Sintaxă apel obj1.operator+(obj2) operator+(obj1, obj2)
Acces la membri privați Direct (este membră) Necesită declarare friend
Conversii implicite stânga ❌ Nu permite ✅ Permite
Operatori I/O (<<, >>) ❌ Nu pot fi membri ✅ Trebuie să fie friend
Încapsulare ✅ Păstrează încapsularea ⚠️ Încalcă parțial
Comutativitate ❌ Doar obj * scalar ✅ scalar * obj și obj * scalar

📊 Exemple pe nivele de dificultate

🎯 Progresie de învățare:

Exemplele sunt organizate pe trei nivele pentru a facilita înțelegerea graduală a conceptelor.

🟢 NIVEL UȘOR - Concepte de bază

1. Clasa Point - Operatori aritmetici simpli
class Point {
private:
    int x, y;
    
public:
    Point(int x = 0, int y = 0) : x(x), y(y) {}
    
    // Operator + simplu
    Point operator+(const Point& p) const {
        return Point(x + p.x, y + p.y);
    }
    
    // Operator == pentru comparație
    bool operator==(const Point& p) const {
        return (x == p.x && y == p.y);
    }
    
    // Afișare simplă
    void display() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

// Utilizare:
Point p1(3, 4), p2(1, 2);
Point p3 = p1 + p2;  // (4, 6)
if(p1 == p2) { /* ... */ }
2. Clasa Counter - Operatori increment/decrement
class SimpleCounter {
private:
    int count;
    
public:
    SimpleCounter(int c = 0) : count(c) {}
    
    // Prefix increment
    SimpleCounter& operator++() {
        ++count;
        return *this;
    }
    
    // Operator de conversie la int
    operator int() const {
        return count;
    }
    
    // Operator unar - (negare)
    SimpleCounter operator-() const {
        return SimpleCounter(-count);
    }
};

SimpleCounter c(5);
++c;                    // count = 6
int val = c;          // conversie implicită la int
SimpleCounter neg = -c; // count = -6
3. Clasa Box - Operatori de comparație
class Box {
private:
    double length, width, height;
    
public:
    Box(double l = 1, double w = 1, double h = 1) 
        : length(l), width(w), height(h) {}
    
    double volume() const {
        return length * width * height;
    }
    
    // Comparare pe bază de volum
    bool operator<(const Box& b) const {
        return volume() < b.volume();
    }
    
    bool operator>(const Box& b) const {
        return volume() > b.volume();
    }
    
    bool operator==(const Box& b) const {
        return volume() == b.volume();
    }
};

Box box1(2, 3, 4), box2(1, 5, 5);
if(box1 < box2) {
    cout << "Box1 este mai mică" << endl;
}

🟡 NIVEL MEDIU - Concepte avansate

4. Clasa Fraction - Set complet de operatori
class Fraction {
private:
    int numerator, denominator;
    
    int gcd(int a, int b) const {
        return b == 0 ? a : gcd(b, a % b);
    }
    
    void simplify() {
        int g = gcd(abs(numerator), abs(denominator));
        numerator /= g;
        denominator /= g;
        if(denominator < 0) {
            numerator = -numerator;
            denominator = -denominator;
        }
    }
    
public:
    Fraction(int n = 0, int d = 1) : numerator(n), denominator(d) {
        if(d == 0) throw runtime_error("Denominator zero!");
        simplify();
    }
    
    // Operatori aritmetici
    Fraction operator+(const Fraction& f) const {
        return Fraction(
            numerator * f.denominator + f.numerator * denominator,
            denominator * f.denominator
        );
    }
    
    Fraction operator-(const Fraction& f) const {
        return Fraction(
            numerator * f.denominator - f.numerator * denominator,
            denominator * f.denominator
        );
    }
    
    Fraction operator*(const Fraction& f) const {
        return Fraction(
            numerator * f.numerator,
            denominator * f.denominator
        );
    }
    
    Fraction operator/(const Fraction& f) const {
        if(f.numerator == 0) throw runtime_error("Împărțire cu zero!");
        return Fraction(
            numerator * f.denominator,
            denominator * f.numerator
        );
    }
    
    // Operatori compuși
    Fraction& operator+=(const Fraction& f) {
        *this = *this + f;
        return *this;
    }
    
    // Operatori de comparație
    bool operator==(const Fraction& f) const {
        return numerator == f.numerator && denominator == f.denominator;
    }
    
    bool operator<(const Fraction& f) const {
        return numerator * f.denominator < f.numerator * denominator;
    }
    
    // Conversie la double
    operator double() const {
        return static_cast<double>(numerator) / denominator;
    }
    
    // Friend pentru I/O
    friend ostream& operator<<(ostream& os, const Fraction& f) {
        os << f.numerator;
        if(f.denominator != 1) os << "/" << f.denominator;
        return os;
    }
};
5. Clasa String - Operatori pentru șiruri
class MyString {
private:
    char* str;
    size_t len;
    
public:
    MyString(const char* s = "") {
        len = strlen(s);
        str = new char[len + 1];
        strcpy(str, s);
    }
    
    MyString(const MyString& other) {
        len = other.len;
        str = new char[len + 1];
        strcpy(str, other.str);
    }
    
    ~MyString() { delete[] str; }
    
    // Operator de atribuire
    MyString& operator=(const MyString& other) {
        if(this != &other) {
            delete[] str;
            len = other.len;
            str = new char[len + 1];
            strcpy(str, other.str);
        }
        return *this;
    }
    
    // Operator + pentru concatenare
    MyString operator+(const MyString& other) const {
        char* temp = new char[len + other.len + 1];
        strcpy(temp, str);
        strcat(temp, other.str);
        MyString result(temp);
        delete[] temp;
        return result;
    }
    
    // Operator [] pentru acces la caractere
    char& operator[](size_t index) {
        if(index >= len) throw out_of_range("Index invalid!");
        return str[index];
    }
    
    const char& operator[](size_t index) const {
        if(index >= len) throw out_of_range("Index invalid!");
        return str[index];
    }
    
    // Operatori de comparație
    bool operator==(const MyString& other) const {
        return strcmp(str, other.str) == 0;
    }
    
    bool operator<(const MyString& other) const {
        return strcmp(str, other.str) < 0;
    }
    
    // Friend pentru output
    friend ostream& operator<<(ostream& os, const MyString& s) {
        os << s.str;
        return os;
    }
    
    // Friend pentru input
    friend istream& operator>>(istream& is, MyString& s) {
        char buffer[1000];
        is >> buffer;
        s = MyString(buffer);
        return is;
    }
};

🔴 NIVEL AVANSAT - Concepte complexe

6. Template Matrix - Operatori generici
template<typename T>
class Matrix {
private:
    vector<vector<T>> data;
    size_t rows, cols;
    
public:
    Matrix(size_t r, size_t c, T val = T()) 
        : rows(r), cols(c), data(r, vector<T>(c, val)) {}
    
    // Operator () pentru acces element
    T& operator()(size_t i, size_t j) {
        if(i >= rows || j >= cols) 
            throw out_of_range("Index în afara matricei!");
        return data[i][j];
    }
    
    const T& operator()(size_t i, size_t j) const {
        if(i >= rows || j >= cols) 
            throw out_of_range("Index în afara matricei!");
        return data[i][j];
    }
    
    // Operator + pentru adunare matrice
    Matrix operator+(const Matrix& other) const {
        if(rows != other.rows || cols != other.cols)
            throw invalid_argument("Dimensiuni incompatibile!");
        
        Matrix result(rows, cols);
        for(size_t i = 0; i < rows; ++i)
            for(size_t j = 0; j < cols; ++j)
                result(i, j) = data[i][j] + other.data[i][j];
        return result;
    }
    
    // Operator * pentru înmulțire matrice
    Matrix operator*(const Matrix& other) const {
        if(cols != other.rows)
            throw invalid_argument("Dimensiuni incompatibile pentru înmulțire!");
        
        Matrix result(rows, other.cols, T());
        for(size_t i = 0; i < rows; ++i)
            for(size_t j = 0; j < other.cols; ++j)
                for(size_t k = 0; k < cols; ++k)
                    result(i, j) += data[i][k] * other.data[k][j];
        return result;
    }
    
    // Operator * pentru înmulțire cu scalar
    template<typename Scalar>
    friend Matrix operator*(const Scalar& s, const Matrix& m) {
        Matrix result(m.rows, m.cols);
        for(size_t i = 0; i < m.rows; ++i)
            for(size_t j = 0; j < m.cols; ++j)
                result(i, j) = s * m.data[i][j];
        return result;
    }
    
    // Transpusă
    Matrix operator~() const {
        Matrix result(cols, rows);
        for(size_t i = 0; i < rows; ++i)
            for(size_t j = 0; j < cols; ++j)
                result(j, i) = data[i][j];
        return result;
    }
};
7. Smart Pointer - Operator -> și *
template<typename T>
class SmartPtr {
private:
    T* ptr;
    size_t* ref_count;
    
    void release() {
        if(ref_count && --(*ref_count) == 0) {
            delete ptr;
            delete ref_count;
        }
    }
    
public:
    // Constructor
    explicit SmartPtr(T* p = nullptr) : ptr(p) {
        ref_count = ptr ? new size_t(1) : nullptr;
    }
    
    // Copy constructor
    SmartPtr(const SmartPtr& other) 
        : ptr(other.ptr), ref_count(other.ref_count) {
        if(ref_count) ++(*ref_count);
    }
    
    // Destructor
    ~SmartPtr() { release(); }
    
    // Operator de atribuire
    SmartPtr& operator=(const SmartPtr& other) {
        if(this != &other) {
            release();
            ptr = other.ptr;
            ref_count = other.ref_count;
            if(ref_count) ++(*ref_count);
        }
        return *this;
    }
    
    // Operator -> pentru acces la membri
    T* operator->() const {
        if(!ptr) throw runtime_error("Dereferențiere pointer null!");
        return ptr;
    }
    
    // Operator * pentru dereferențiere
    T& operator*() const {
        if(!ptr) throw runtime_error("Dereferențiere pointer null!");
        return *ptr;
    }
    
    // Operator bool pentru verificare validitate
    operator bool() const {
        return ptr != nullptr;
    }
    
    // Operator == pentru comparație
    bool operator==(const SmartPtr& other) const {
        return ptr == other.ptr;
    }
    
    // Get reference count
    size_t use_count() const {
        return ref_count ? *ref_count : 0;
    }
};

// Utilizare:
SmartPtr<int> sp1(new int(42));
SmartPtr<int> sp2 = sp1;  // Share ownership
cout << *sp1 << endl;     // Dereferențiere
if(sp1) {                  // Verificare validitate
    // ...
}
8. Iterator personalizat - Operator ++ și comparație
template<typename T>
class DynamicArray {
private:
    T* data;
    size_t size;
    
public:
    // Clasa Iterator internă
    class Iterator {
    private:
        T* ptr;
        
    public:
        Iterator(T* p) : ptr(p) {}
        
        // Operator * pentru dereferențiere
        T& operator*() const { return *ptr; }
        
        // Operator -> pentru acces membri
        T* operator->() const { return ptr; }
        
        // Operator ++ prefix
        Iterator& operator++() {
            ++ptr;
            return *this;
        }
        
        // Operator ++ postfix
        Iterator operator++(int) {
            Iterator temp = *this;
            ++ptr;
            return temp;
        }
        
        // Operator -- prefix
        Iterator& operator--() {
            --ptr;
            return *this;
        }
        
        // Operator + pentru aritmetică pointer
        Iterator operator+(int n) const {
            return Iterator(ptr + n);
        }
        
        // Operator - pentru diferență
        ptrdiff_t operator-(const Iterator& other) const {
            return ptr - other.ptr;
        }
        
        // Operatori de comparație
        bool operator==(const Iterator& other) const {
            return ptr == other.ptr;
        }
        
        bool operator!=(const Iterator& other) const {
            return ptr != other.ptr;
        }
        
        bool operator<(const Iterator& other) const {
            return ptr < other.ptr;
        }
    };
    
    DynamicArray(size_t n) : size(n) {
        data = new T[size];
    }
    
    ~DynamicArray() { delete[] data; }
    
    // Funcții begin și end pentru range-based for
    Iterator begin() { return Iterator(data); }
    Iterator end() { return Iterator(data + size); }
    
    // Operator [] pentru acces indexat
    T& operator[](size_t i) { return data[i]; }
};

// Utilizare:
DynamicArray<int> arr(5);
for(auto it = arr.begin(); it != arr.end(); ++it) {
    *it = 42;
}
// Sau cu range-based for:
for(auto& elem : arr) {
    elem = 100;
}
9. Functor avansat - Operator ()
template<typename T>
class Polynomial {
private:
    vector<T> coefficients;  // a0 + a1*x + a2*x^2 + ...
    
public:
    Polynomial(initializer_list<T> coeffs) : coefficients(coeffs) {}
    
    // Operator () pentru evaluare polinom
    T operator()(T x) const {
        T result = T();
        T power = T(1);
        
        for(const auto& coeff : coefficients) {
            result += coeff * power;
            power *= x;
        }
        return result;
    }
    
    // Operator + pentru adunare polinoame
    Polynomial operator+(const Polynomial& other) const {
        size_t maxSize = max(coefficients.size(), 
                               other.coefficients.size());
        vector<T> result(maxSize, T());
        
        for(size_t i = 0; i < coefficients.size(); ++i)
            result[i] += coefficients[i];
        for(size_t i = 0; i < other.coefficients.size(); ++i)
            result[i] += other.coefficients[i];
        
        return Polynomial(initializer_list<T>(
            result.begin(), result.end()));
    }
    
    // Operator * pentru înmulțire polinoame
    Polynomial operator*(const Polynomial& other) const {
        vector<T> result(coefficients.size() + 
                           other.coefficients.size() - 1, T());
        
        for(size_t i = 0; i < coefficients.size(); ++i)
            for(size_t j = 0; j < other.coefficients.size(); ++j)
                result[i + j] += coefficients[i] * other.coefficients[j];
        
        return Polynomial(initializer_list<T>(
            result.begin(), result.end()));
    }
    
    // Derivată
    Polynomial derivative() const {
        if(coefficients.size() <= 1) 
            return Polynomial({T()});
        
        vector<T> result;
        for(size_t i = 1; i < coefficients.size(); ++i)
            result.push_back(coefficients[i] * T(i));
        
        return Polynomial(initializer_list<T>(
            result.begin(), result.end()));
    }
    
    friend ostream& operator<<(ostream& os, const Polynomial& p) {
        bool first = true;
        for(size_t i = 0; i < p.coefficients.size(); ++i) {
            if(p.coefficients[i] != T()) {
                if(!first) os << " + ";
                os << p.coefficients[i];
                if(i > 0) os << "x^" << i;
                first = false;
            }
        }
        return os;
    }
};

// Utilizare:
Polynomial<double> p1{2, 3, 1};  // 2 + 3x + x^2
Polynomial<double> p2{1, -1};    // 1 - x
auto p3 = p1 * p2;                  // Înmulțire
double val = p1(2.0);              // Evaluare: 2 + 6 + 4 = 12
auto dp = p1.derivative();         // Derivată: 3 + 2x

🎯 Exemple practice clasice

1. Clasa Vector2D

Vector matematic 2D cu operații complete
class Vector2D {
private:
    double x, y;
    
public:
    Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
    
    // Operator + (adunare vectori)
    Vector2D operator+(const Vector2D& v) const {
        return Vector2D(x + v.x, y + v.y);
    }
    
    // Operator - (scădere vectori)
    Vector2D operator-(const Vector2D& v) const {
        return Vector2D(x - v.x, y - v.y);
    }
    
    // Operator * (înmulțire cu scalar)
    Vector2D operator*(double scalar) const {
        return Vector2D(x * scalar, y * scalar);
    }
    
    // Operator == (egalitate)
    bool operator==(const Vector2D& v) const {
        return (x == v.x && y == v.y);
    }
    
    // Operator += (adunare cu atribuire)
    Vector2D& operator+=(const Vector2D& v) {
        x += v.x;
        y += v.y;
        return *this;
    }
    
    // Operator << pentru afișare
    friend ostream& operator<<(ostream& os, const Vector2D& v) {
        os << "(" << v.x << ", " << v.y << ")";
        return os;
    }
};

2. Clasa Array cu operator []

Array dinamic cu verificare limite
class SafeArray {
private:
    int* data;
    size_t size;
    
public:
    SafeArray(size_t n) : size(n) {
        data = new int[size];
        for(size_t i = 0; i < size; ++i)
            data[i] = 0;
    }
    
    ~SafeArray() { delete[] data; }
    
    // Operator [] pentru acces la elemente
    int& operator[](size_t index) {
        if(index >= size) {
            throw out_of_range("Index în afara limitelor!");
        }
        return data[index];
    }
    
    // Versiune const pentru operator []
    const int& operator[](size_t index) const {
        if(index >= size) {
            throw out_of_range("Index în afara limitelor!");
        }
        return data[index];
    }
};

3. Operatori ++ și -- (prefix și postfix)

Clasă Counter cu increment/decrement
class Counter {
private:
    int value;
    
public:
    Counter(int v = 0) : value(v) {}
    
    // Operator ++ prefix (++counter)
    Counter& operator++() {
        ++value;
        return *this;
    }
    
    // Operator ++ postfix (counter++)
    Counter operator++(int) {
        Counter temp = *this;
        ++value;
        return temp;
    }
    
    // Operator -- prefix (--counter)
    Counter& operator--() {
        --value;
        return *this;
    }
    
    // Operator -- postfix (counter--)
    Counter operator--(int) {
        Counter temp = *this;
        --value;
        return temp;
    }
    
    int getValue() const { return value; }
};

🎨 Bune practici

✅ Reguli importante:
  1. Consistență: Operatorii supraîncărcați trebuie să aibă un comportament intuitiv și consistent cu semantica lor obișnuită
  2. Simetrie: Dacă definești ==, definește și !=
  3. Return by reference: Pentru operatorii de atribuire, returnează referință la obiect
  4. Const correctness: Folosește const pentru operatori care nu modifică obiectul
  5. Friend vs Member:
    • Folosește funcții membre pentru operatori unari și operatorul =
    • Folosește friend pentru operatori I/O (<< și >>)
    • Folosește friend când ai nevoie de comutativitate
    • Folosește friend când primul operand nu e de tipul clasei tale
  6. Minimizează friend: Folosește friend doar când e necesar - compromite încapsularea

Ghid de alegere: Member vs Friend

📌 Reguli de decizie:
  • ÎNTOTDEAUNA Member: =, [], (), ->, operatori unari
  • ÎNTOTDEAUNA Friend/Non-member: <<, >>
  • PREFERABIL Friend: operatori binari simetrici când vrei conversii implicite pe ambele părți
  • PREFERABIL Member: operatori compuși (+=, -=, etc.)

Exemplu de operator de atribuire corect

class String {
private:
    char* str;
    size_t length;
    
public:
    // Operator de atribuire cu verificare auto-atribuire
    String& operator=(const String& other) {
        // Verificare auto-atribuire
        if(this != &other) {
            // Eliberare memorie veche
            delete[] str;
            
            // Copiere date noi
            length = other.length;
            str = new char[length + 1];
            strcpy(str, other.str);
        }
        return *this;
    }
};

⚡ Demo Interactiv

Testează supraîncărcarea operatorilor!

Alege nivelul de dificultate și vezi exemple în acțiune:

📋 Quick Reference - Sintaxă rapidă

⚡ Referință rapidă pentru sintaxa operatorilor

Salvează această secțiune pentru consultare rapidă când implementezi operatori!

Șablon complet pentru o clasă cu toți operatorii comuni
class MyClass {
private:
    Type data;
    
public:
    // === OPERATORI ARITMETICI ===
    MyClass operator+(const MyClass& rhs) const;
    MyClass operator-(const MyClass& rhs) const;
    MyClass operator*(const MyClass& rhs) const;
    MyClass operator/(const MyClass& rhs) const;
    MyClass operator%(const MyClass& rhs) const;
    
    // === OPERATORI COMPUȘI ===
    MyClass& operator+=(const MyClass& rhs);
    MyClass& operator-=(const MyClass& rhs);
    MyClass& operator*=(const MyClass& rhs);
    MyClass& operator/=(const MyClass& rhs);
    
    // === INCREMENT/DECREMENT ===
    MyClass& operator++();        // Prefix ++obj
    MyClass operator++(int);     // Postfix obj++
    MyClass& operator--();        // Prefix --obj
    MyClass operator--(int);     // Postfix obj--
    
    // === OPERATORI UNARI ===
    MyClass operator-() const;     // Negare
    MyClass operator+() const;     // Plus unar
    bool operator!() const;        // NOT logic
    MyClass operator~() const;     // Complement
    
    // === COMPARAȚIE ===
    bool operator==(const MyClass& rhs) const;
    bool operator!=(const MyClass& rhs) const;
    bool operator<(const MyClass& rhs) const;
    bool operator>(const MyClass& rhs) const;
    bool operator<=(const MyClass& rhs) const;
    bool operator>=(const MyClass& rhs) const;
    
    // === C++20 Three-way comparison ===
    auto operator<=>(const MyClass& rhs) const = default;
    
    // === ATRIBUIRE ===
    MyClass& operator=(const MyClass& rhs);        // Copy
    MyClass& operator=(MyClass&& rhs) noexcept;    // Move C++11
    
    // === ACCES ===
    Type& operator[](size_t index);
    const Type& operator[](size_t index) const;
    Type* operator->();
    const Type* operator->() const;
    Type& operator*();
    const Type& operator*() const;
    
    // === APEL FUNCȚIE ===
    RetType operator()(Args... args);
    
    // === CONVERSII ===
    operator bool() const;              // Conversie la bool
    explicit operator int() const;     // Conversie explicită
    
    // === I/O (FRIEND) ===
    friend ostream& operator<<(ostream& os, const MyClass& obj);
    friend istream& operator>>(istream& is, MyClass& obj);
    
    // === OPERATORI BINARI SIMETRICI (FRIEND) ===
    friend MyClass operator+(Type lhs, const MyClass& rhs);
    friend MyClass operator*(Type lhs, const MyClass& rhs);
};
Tip Operator Membru/Friend Return Type Const? Exemplu
Aritmetic binar Membru sau Friend Obiect nou (by value) Da T operator+(const T&) const
Compus (+=, -=) Membru Referință (*this) Nu T& operator+=(const T&)
Prefix ++/-- Membru Referință (*this) Nu T& operator++()
Postfix ++/-- Membru Obiect nou (copie) Nu T operator++(int)
Comparație Membru sau Friend bool Da bool operator==(const T&) const
Atribuire (=) DOAR Membru Referință (*this) Nu T& operator=(const T&)
Subscript [] DOAR Membru Referință element 2 versiuni T& operator[](size_t)
I/O (<< >>) DOAR Friend/Non-membru Referință stream - friend ostream& operator<<

💪 Exerciții practice

📝 Practică pentru consolidare:

Încearcă să rezolvi aceste exerciții pentru a-ți consolida cunoștințele despre supraîncărcarea operatorilor.

🟢 Exerciții Nivel UȘOR

Exercițiu 1: Clasa Time

Creează o clasă Time care reprezintă timpul în ore și minute. Implementează:

  • Constructor cu parametri impliciti
  • Operator + pentru adunare timp
  • Operator == pentru comparație
  • Operator << pentru afișare (format: HH:MM)
💡 Hint

Nu uita să gestionezi depășirea minutelor (60 minute = 1 oră)

Exercițiu 2: Clasa Distance

Implementează o clasă pentru distanțe în metri și centimetri cu:

  • Operatori +, - pentru adunare și scădere
  • Operator < pentru comparație
  • Operator unar - pentru negare

🟡 Exerciții Nivel MEDIU

Exercițiu 3: Clasa Set

Creează o clasă Set pentru mulțimi de întregi cu:

  • Operator + pentru reuniune
  • Operator * pentru intersecție
  • Operator - pentru diferență
  • Operator [] pentru verificare apartenență
  • Operator == pentru egalitate mulțimi
  • Friend operator << pentru afișare
💡 Hint

Folosește std::set sau std::unordered_set intern pentru stocare

Exercițiu 4: Clasa BigInteger

Implementează numere întregi mari (depășesc long long) cu:

  • Operatori aritmetici de bază (+, -, *)
  • Operatori de comparație
  • Constructor din string
  • Friend pentru I/O

🔴 Exerciții Nivel AVANSAT

Exercițiu 5: Expression Tree

Creează clase pentru arbori de expresii matematice cu:

  • Operator () pentru evaluare expresie
  • Operator + pentru combinare expresii
  • Operator * pentru înmulțire expresii
  • Conversie la string pentru afișare
  • Suport pentru variabile și funcții
💡 Hint

Folosește pattern-ul Visitor sau polimorfism pentru diferite tipuri de noduri

Exercițiu 6: Template Graph

Implementează o clasă template Graph cu:

  • Operator [] pentru acces la noduri
  • Operator () pentru verificare muchie
  • Operator + pentru uniune grafuri
  • Iterator personalizat pentru parcurgere BFS/DFS
  • Operator << pentru afișare în format DOT
🎯 Sfaturi pentru rezolvare:
  1. Începe cu operatorii simpli și adaugă gradual funcționalități
  2. Testează fiecare operator individual înainte de a continua
  3. Nu uita de const-correctness și gestionarea erorilor
  4. Pentru nivel avansat, consideră utilizarea template-urilor
  5. Documentează-ți codul pentru claritate

🚨 Erori comune de evitat

❌ Erori frecvente:
  1. Modificarea semanticii standard: Nu faceți ca operatorul + să scadă!
  2. Uitarea de const: Operatorii care nu modifică obiectul trebuie marcați const
  3. Memory leaks: În operatorul =, eliberați întotdeauna memoria veche
  4. Nu verificați auto-atribuirea: obj = obj poate cauza probleme
  5. Return by value când nu e necesar: Poate afecta performanța
  6. Abuz de friend: Folosirea excesivă compromite încapsularea
  7. Friend pentru operatori unari: Nu folosiți friend pentru ++, --, ! (folosiți membre)
  8. Uitarea de friend pentru I/O: << și >> trebuie să fie întotdeauna friend sau non-membre
  9. Nu implementați perechi de operatori: Dacă aveți <, implementați și >, <=, >=

Exemplu de eroare comună cu friend

// ❌ GREȘIT - operator << ca funcție membră
class Wrong {
    ostream& operator<<(ostream& os) {
        // Aceasta ar necesita apel: obj << cout; (invers!)
        return os;
    }
};

// ✅ CORECT - operator << ca friend
class Correct {
    friend ostream& operator<<(ostream& os, const Correct& obj) {
        // Permite apel normal: cout << obj;
        return os;
    }
};

📖 Resurse suplimentare și concluzii

🎓 Rezumat concepte cheie:
  • Supraîncărcarea operatorilor face codul mai intuitiv și expresiv
  • Friend functions sunt esențiale pentru operatori I/O și comutativitate
  • Const-correctness este critică pentru operatori care nu modifică obiectul
  • Return by reference pentru operatori de atribuire și increment
  • Template-uri permit operatori generici reutilizabili

📚 Resurse recomandate pentru studiu avansat

🔑 Checklist pentru implementare corectă

✅ Verifică întotdeauna:
  1. ☐ Semantica operatorului este intuitivă și consistentă?
  2. ☐ Ai implementat toți operatorii înrudiți? (ex: == cu !=)
  3. ☐ Folosești const pentru operatori care nu modifică?
  4. ☐ Returnezi referință pentru =, +=, -=, etc?
  5. ☐ Ai verificat auto-atribuirea în operator=?
  6. ☐ Friend este folosit doar când e necesar?
  7. ☐ Gestionezi corect memoria (dacă e cazul)?
  8. ☐ Ai testat edge cases și erori?
  9. ☐ Documentația este clară pentru utilizatori?
  10. ☐ Performance-ul este optimizat unde contează?

🚀 Următorii pași în învățare

După ce ai înțeles supraîncărcarea operatorilor, explorează:

  1. Move semantics și operator=(&&) - C++11 și optimizări
  2. Expression templates - Optimizări avansate pentru operații matematice
  3. User-defined literals - Operatori literali personalizați
  4. Spaceship operator (<=>) - C++20 three-way comparison
  5. Concepts și constraints - C++20 pentru operatori template
  6. SFINAE și enable_if - Control avansat asupra template-urilor