← Kursa Dön
📄 Text · 15 min

Operatör Overloading

C++'ın en güçlü özelliklerinden biri operatör overloading (operatör aşırı yükleme). +, -, ==, << gibi operatörlerin kendi sınıflarınla nasıl çalışacağını sen tanımlarsın. Böylece nesnelerin tıpkı temel türler gibi doğal bir sözdizimi ile kullanılmasını sağlarsın.

Bunu bir evrensel adaptör gibi düşün. Prizin şekli değişmiyor (operatör aynı) ama arkadaki kablolama (implementasyon) cihaza göre farklı. + operatörü iki int toplamayı bilir ama senin Vektor sınıfın için ne yapacağını bilmez — onu sen tanımlarsın.


Neden Operatör Overloading?

Operatör overloading olmadan:

Vektor a(1, 2);
Vektor b(3, 4);
Vektor c = a.topla(b);       // Çirkin
if (a.esitMi(b)) { ... }     // Doğal değil
cout << a.toString() << endl; // Zahmetli

Operatör overloading ile:

Vektor a(1, 2);
Vektor b(3, 4);
Vektor c = a + b;          // Doğal
if (a == b) { ... }        // Sezgisel
cout << a << endl;          // Temiz

İkinci versiyon çok daha okunabilir. Kod matematiksel ifadeler gibi okunur.


Temel Kurallar

Overloading yaparken uyulması gereken kurallar:

  1. Yeni operatör icat edemezsin — sadece mevcut C++ operatörlerini overload edebilirsin

  2. Bazı operatörler overload edilemez: ::, ., .*, ?:, sizeof

  3. Öncelik ve birleşim yönü değişmez* her zaman +'dan önce gelir

  4. En az bir operand kullanıcı tanımlı tür olmalıint + int'i değiştiremezsin

  5. Anlamlı ol+ ile silme işlemi yapma, sezgiselliği koru


Aritmetik Operatörler (+, -, *, /)

Üye Fonksiyon Olarak

#include <iostream>
using namespace std;

class Vektor2D {
private:
    double x, y;

public:
    Vektor2D(double x = 0, double y = 0) : x(x), y(y) {}

    // + operatörü (üye fonksiyon olarak)
    Vektor2D operator+(const Vektor2D& diger) const {
        return Vektor2D(x + diger.x, y + diger.y);
    }

    // - operatörü
    Vektor2D operator-(const Vektor2D& diger) const {
        return Vektor2D(x - diger.x, y - diger.y);
    }

    // Skaler çarpım (vektor * sayi)
    Vektor2D operator*(double skaler) const {
        return Vektor2D(x * skaler, y * skaler);
    }

    // Tekli eksi (negation): -v
    Vektor2D operator-() const {
        return Vektor2D(-x, -y);
    }

    void yazdir() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

int main() {
    Vektor2D a(3, 4);
    Vektor2D b(1, 2);

    Vektor2D c = a + b;    // operator+ çağrılır
    Vektor2D d = a - b;    // operator- çağrılır
    Vektor2D e = a * 2.0;  // operator*(double) çağrılır
    Vektor2D f = -a;       // tekli eksi

    c.yazdir();  // (4, 6)
    d.yazdir();  // (2, 2)
    e.yazdir();  // (6, 8)
    f.yazdir();  // (-3, -4)

    return 0;
}

+= ve -= Operatörleri (Compound Assignment)

#include <iostream>
using namespace std;

class Vektor2D {
private:
    double x, y;

public:
    Vektor2D(double x = 0, double y = 0) : x(x), y(y) {}

    // += kendini değiştirip referans döndürür
    Vektor2D& operator+=(const Vektor2D& diger) {
        x += diger.x;
        y += diger.y;
        return *this;
    }

    // + operatörünü += üzerinden tanımla (DRY prensibi)
    Vektor2D operator+(const Vektor2D& diger) const {
        Vektor2D sonuc = *this;  // kopyala
        sonuc += diger;           // += kullan
        return sonuc;
    }

    void yazdir() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

int main() {
    Vektor2D a(1, 2);
    Vektor2D b(3, 4);

    a += b;
    a.yazdir();  // (4, 6)

    Vektor2D c = Vektor2D(1, 1) + Vektor2D(2, 2);
    c.yazdir();  // (3, 3)

    return 0;
}

💡 İpucu: + operatörünü tanımlarken, önce += yaz ve +'yı onun üzerine kur. Bu kod tekrarını önler ve tutarlılık sağlar.


Karşılaştırma Operatörleri (==, <, <=>)

== ve != Operatörleri

#include <iostream>
using namespace std;

class Nokta {
private:
    double x, y;

public:
    Nokta(double x = 0, double y = 0) : x(x), y(y) {}

    bool operator==(const Nokta& diger) const {
        return x == diger.x && y == diger.y;
    }

    bool operator!=(const Nokta& diger) const {
        return !(*this == diger);  // == üzerinden tanımla
    }
};

int main() {
    Nokta a(3, 4);
    Nokta b(3, 4);
    Nokta c(1, 2);

    cout << (a == b) << endl;  // 1 (true)
    cout << (a != c) << endl;  // 1 (true)

    return 0;
}

< Operatörü ve Sıralama

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

class Ogrenci {
private:
    string isim;
    double ortalama;

public:
    Ogrenci(string i, double o) : isim(i), ortalama(o) {}

    // Ortalamaya göre sırala (büyükten küçüğe)
    bool operator<(const Ogrenci& diger) const {
        return ortalama > diger.ortalama;  // büyük olan önce gelsin
    }

    bool operator==(const Ogrenci& diger) const {
        return isim == diger.isim && ortalama == diger.ortalama;
    }

    friend ostream& operator<<(ostream& os, const Ogrenci& o) {
        os << o.isim << " (" << o.ortalama << ")";
        return os;
    }
};

int main() {
    vector<Ogrenci> sinif = {
        {"Ali", 85.5}, {"Veli", 92.0}, {"Ayşe", 78.0}, {"Fatma", 95.5}
    };

    sort(sinif.begin(), sinif.end());  // operator< kullanır

    for (const auto& o : sinif) {
        cout << o << endl;
    }
    // Fatma (95.5)
    // Veli (92)
    // Ali (85.5)
    // Ayşe (78)

    return 0;
}

Spaceship Operatörü — <=> (C++20)

C++20 ile gelen three-way comparison operator (<=>), tüm karşılaştırma operatörlerini tek seferde tanımlamanı sağlar. Adı "spaceship" çünkü <=> şekli bir uzay gemisine benzer.

#include <iostream>
#include <compare>  // <=> için
using namespace std;

class Sicaklik {
private:
    double derece;

public:
    Sicaklik(double d) : derece(d) {}

    // <=> tanımla — ==, !=, <, >, <=, >= hepsi otomatik gelir!
    auto operator<=>(const Sicaklik& diger) const = default;

    friend ostream& operator<<(ostream& os, const Sicaklik& s) {
        os << s.derece << "°C";
        return os;
    }
};

int main() {
    Sicaklik a(25.0);
    Sicaklik b(30.0);
    Sicaklik c(25.0);

    cout << (a < b) << endl;   // 1 (true)
    cout << (a > b) << endl;   // 0 (false)
    cout << (a == c) << endl;  // 1 (true)
    cout << (a != b) << endl;  // 1 (true)
    cout << (a <= c) << endl;  // 1 (true)
    cout << (a >= b) << endl;  // 0 (false)

    return 0;
}

Özel <=> Tanımlama

Eğer = default yetmiyorsa (farklı bir karşılaştırma mantığı istiyorsan) kendin yazabilirsin:

#include <iostream>
#include <compare>
#include <string>
using namespace std;

class Versiyon {
private:
    int major, minor, patch;

public:
    Versiyon(int ma, int mi, int p)
        : major(ma), minor(mi), patch(p) {}

    strong_ordering operator<=>(const Versiyon& diger) const {
        if (auto cmp = major <=> diger.major; cmp != 0) return cmp;
        if (auto cmp = minor <=> diger.minor; cmp != 0) return cmp;
        return patch <=> diger.patch;
    }

    // <=> tanımlarsan == otomatik gelmez (= default değilse)
    bool operator==(const Versiyon& diger) const {
        return (*this <=> diger) == 0;
    }

    friend ostream& operator<<(ostream& os, const Versiyon& v) {
        os << v.major << "." << v.minor << "." << v.patch;
        return os;
    }
};

int main() {
    Versiyon v1(2, 1, 0);
    Versiyon v2(2, 3, 1);
    Versiyon v3(2, 1, 0);

    cout << v1 << " < " << v2 << ": " << (v1 < v2) << endl;   // 1
    cout << v1 << " == " << v3 << ": " << (v1 == v3) << endl;  // 1

    return 0;
}

💡 İpucu: C++20 kullanabiliyorsan operator<=> ile başla. Tek tanımlama ile 6 karşılaştırma operatörü elde edersin. Basit sınıflarda = default yeterli olur.


<< Operatörü (ostream ile Yazdırma)

<< operatörünü overload ederek nesneyi cout ile doğrudan yazdırabilirsin. Bu operatör friend fonksiyon olarak tanımlanmalı çünkü sol taraftaki operand ostream nesnesi (sınıfımız değil):

#include <iostream>
#include <string>
using namespace std;

class Tarih {
private:
    int gun, ay, yil;

public:
    Tarih(int g, int a, int y) : gun(g), ay(a), yil(y) {}

    // friend olarak tanımla — sol taraf ostream
    friend ostream& operator<<(ostream& os, const Tarih& t) {
        os << t.gun << "/" << t.ay << "/" << t.yil;
        return os;  // zincirleme çıktı için ostream'i döndür
    }

    // >> ile giriş (istream)
    friend istream& operator>>(istream& is, Tarih& t) {
        char ayrac;
        is >> t.gun >> ayrac >> t.ay >> ayrac >> t.yil;
        return is;
    }
};

int main() {
    Tarih bugun(19, 2, 2026);
    cout << "Bugün: " << bugun << endl;  // Bugün: 19/2/2026

    // Zincirleme çalışır
    Tarih yarin(20, 2, 2026);
    cout << bugun << " -> " << yarin << endl;

    // Giriş
    cout << "Tarih girin (GG/AA/YYYY): ";
    Tarih t(0, 0, 0);
    // cin >> t;  // Kullanıcıdan giriş alır
    // cout << "Girilen: " << t << endl;

    return 0;
}

[] ve () Operatörleri

[] — İndeksleme Operatörü

#include <iostream>
#include <stdexcept>
using namespace std;

class DinamikDizi {
private:
    int* veri;
    int boyut;

public:
    DinamikDizi(int n) : boyut(n) {
        veri = new int[n]();  // sıfırla
    }

    ~DinamikDizi() { delete[] veri; }

    // [] operatörü — referans döndür (okuma + yazma)
    int& operator[](int indeks) {
        if (indeks < 0 || indeks >= boyut) {
            throw out_of_range("Indeks sınır dışı!");
        }
        return veri[indeks];
    }

    // const versiyon — sadece okuma
    const int& operator[](int indeks) const {
        if (indeks < 0 || indeks >= boyut) {
            throw out_of_range("Indeks sınır dışı!");
        }
        return veri[indeks];
    }

    int size() const { return boyut; }
};

int main() {
    DinamikDizi d(5);

    // Yazma
    d[0] = 10;
    d[1] = 20;
    d[2] = 30;

    // Okuma
    for (int i = 0; i < d.size(); i++) {
        cout << "d[" << i << "] = " << d[i] << endl;
    }

    // Sınır kontrolü
    try {
        d[10] = 99;
    } catch (const out_of_range& e) {
        cout << "Hata: " << e.what() << endl;
    }

    return 0;
}

() — Fonksiyon Çağrı Operatörü (Functor)

() operatörünü overload eden sınıflara functor (fonksiyon nesnesi) denir. Nesne bir fonksiyon gibi çağrılabilir:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class CiftMi {
public:
    bool operator()(int sayi) const {
        return sayi % 2 == 0;
    }
};

class Toplayici {
private:
    int toplam;

public:
    Toplayici() : toplam(0) {}

    void operator()(int sayi) {
        toplam += sayi;
    }

    int sonuc() const { return toplam; }
};

int main() {
    CiftMi cift_mi;
    cout << cift_mi(4) << endl;  // 1 (true)
    cout << cift_mi(7) << endl;  // 0 (false)

    // STL ile kullanım
    vector<int> sayilar = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    int cift_sayisi = count_if(sayilar.begin(), sayilar.end(), CiftMi());
    cout << "Çift sayı adedi: " << cift_sayisi << endl;  // 5

    // Toplayıcı
    Toplayici top;
    for (int s : sayilar) {
        top(s);
    }
    cout << "Toplam: " << top.sonuc() << endl;  // 55

    return 0;
}

⚠️ Dikkat: Functor'lar C++11'den önce lambda yerine kullanılıyordu. Bugün basit durumlarda lambda tercih edilir ama functor'lar state tutabilme avantajı sayesinde hâlâ kullanışlıdır.


Üye Fonksiyon vs friend Fonksiyon

Operatörü üye fonksiyon olarak mı yoksa friend olarak mı tanımlayacağın önemli bir tasarım kararı:

#include <iostream>
using namespace std;

class Sayi {
private:
    int deger;

public:
    Sayi(int d) : deger(d) {}

    // ÜYE FONKSİYON: sol taraf her zaman Sayi nesnesi
    Sayi operator+(const Sayi& diger) const {
        return Sayi(deger + diger.deger);
    }

    // FRIEND: sol taraf Sayi olmak zorunda değil
    friend Sayi operator*(int skaler, const Sayi& s);
    friend ostream& operator<<(ostream& os, const Sayi& s);

    int getDeger() const { return deger; }
};

// 3 * sayi şeklinde kullanım — sol taraf int
Sayi operator*(int skaler, const Sayi& s) {
    return Sayi(skaler * s.deger);
}

ostream& operator<<(ostream& os, const Sayi& s) {
    os << s.deger;
    return os;
}

int main() {
    Sayi a(5);
    Sayi b(3);

    Sayi c = a + b;      // üye fonksiyon: a.operator+(b)
    Sayi d = 3 * a;      // friend fonksiyon: operator*(3, a)
    // Sayi e = a * 3;   // Bu üye olarak tanımlanmadı — farklı imza gerekir

    cout << c << endl;   // 8
    cout << d << endl;   // 15

    return 0;
}

Hangi Yolu Seçmeli?

OperatörÖnerilen YolNeden
=, [], (), ->Üye fonksiyonC++ kuralı gereği üye olmalı
+=, -=, *=Üye fonksiyonSol taraf değişiyor
+, -, *, /Her ikisi de olurSimetrik istiyorsan friend
==, <, <=>Üye fonksiyonC++20'de = default desteği
<<, >>friend fonksiyonSol taraf ostream/istream

Tam Örnek: Kesir (Fraction) Sınıfı

#include <iostream>
#include <numeric>  // gcd (C++17)
using namespace std;

class Kesir {
private:
    int pay, payda;

    void sadeles() {
        int ortakBolen = gcd(abs(pay), abs(payda));
        pay /= ortakBolen;
        payda /= ortakBolen;
        if (payda < 0) { pay = -pay; payda = -payda; }
    }

public:
    Kesir(int p = 0, int pd = 1) : pay(p), payda(pd) {
        if (payda == 0) throw invalid_argument("Payda 0 olamaz!");
        sadeles();
    }

    // Aritmetik
    Kesir operator+(const Kesir& d) const {
        return Kesir(pay * d.payda + d.pay * payda, payda * d.payda);
    }

    Kesir operator-(const Kesir& d) const {
        return Kesir(pay * d.payda - d.pay * payda, payda * d.payda);
    }

    Kesir operator*(const Kesir& d) const {
        return Kesir(pay * d.pay, payda * d.payda);
    }

    Kesir operator/(const Kesir& d) const {
        return Kesir(pay * d.payda, payda * d.pay);
    }

    // Karşılaştırma
    bool operator==(const Kesir& d) const {
        return pay == d.pay && payda == d.payda;
    }

    bool operator<(const Kesir& d) const {
        return pay * d.payda < d.pay * payda;
    }

    // Yazdırma
    friend ostream& operator<<(ostream& os, const Kesir& k) {
        if (k.payda == 1) os << k.pay;
        else os << k.pay << "/" << k.payda;
        return os;
    }
};

int main() {
    Kesir a(1, 2);   // 1/2
    Kesir b(1, 3);   // 1/3

    cout << a << " + " << b << " = " << (a + b) << endl;  // 5/6
    cout << a << " - " << b << " = " << (a - b) << endl;  // 1/6
    cout << a << " * " << b << " = " << (a * b) << endl;  // 1/6
    cout << a << " / " << b << " = " << (a / b) << endl;  // 3/2

    cout << a << " == " << b << ": " << (a == b) << endl;  // 0
    cout << a << " < " << b << ": " << (a < b) << endl;    // 0

    Kesir c(2, 4);  // 2/4 = 1/2 (sadeleşir)
    cout << a << " == " << c << ": " << (a == c) << endl;  // 1

    return 0;
}

Overloading'de Dikkat Edilmesi Gerekenler

  1. Sezgisel ol: + toplama yapsın, - çıkarma. Sürpriz olmasın.

  2. Tutarlı ol: + tanımladıysan += da tanımla. == tanımladıysan != da tanımla.

  3. const doğruluğu: Nesneyi değiştirmeyen operatörler const olmalı.

  4. Return tipi: + yeni nesne döndürmeli, += referans (*this).

  5. Self-assignment: = operatöründe if (this != &diger) kontrolü yap.

⚠️ Dikkat: Operatör overloading güçlü bir araç ama kötüye kullanılabilir. + ile veritabanı sorgusu çalıştıran veya * ile dosya silen bir sınıf yazmak mümkün ama büyük hata olur. Operatörler matematiksel veya mantıksal anlamlarına uygun kullanılmalıdır.


Özet

  • Operatör overloading, mevcut C++ operatörlerinin kullanıcı tanımlı türlerle çalışmasını sağlar. Kodun okunabilirliğini artırır.

  • Aritmetik operatörler (+, -, *, /) yeni nesne döndürür; compound assignment (+=, -=) referans döndürür. +'yı += üzerinden tanımlamak DRY prensibidir.

  • Karşılaştırma operatörleri (==, <) bool döndürür. C++20'deki spaceship operatörü (<=>) ile tek tanımlamayla 6 karşılaştırma operatörü elde edilir.

  • << operatörü ostream ile yazdırmak için friend fonksiyon olarak tanımlanır; ostream& döndürerek zincirleme çıktı sağlar.

  • [] operatörü indeksleme, () operatörü ise functor (fonksiyon nesnesi) oluşturmak için kullanılır.

  • Üye fonksiyon olarak tanımlama sol tarafın sınıf nesnesi olmasını gerektirir; friend fonksiyon daha esnek simetrik kullanım sağlar. =, [], (), -> zorunlu olarak üye fonksiyon olmalıdır.