← Kursa Dön
📄 Text · 12 min

Erişim Belirleyiciler ve friend

Bir önceki derslerde sınıflar oluşturduk ama hep public kullandık — yani dışarıdan her şeye erişilebiliyordu. Gerçek dünyada bu iyi bir fikir değil. Bir banka hesabının bakiyesini herkes doğrudan değiştirebilseydi ne olurdu?

Bu derste sınıf üyelerine erişimi kontrol eden erişim belirleyicileri (access specifiers) ve bu kuralların istisnası olan friend kavramını öğreneceğiz.


Erişim Belirleyiciler

C++'ta üç erişim belirleyici var:

BelirleyiciSınıf İçiTüretilmiş SınıfDışarıdan
public
protected
private

Bunu bir ev gibi düşün:

  • public = Evin bahçesi. Herkes görebilir, herkes kullanabilir.

  • protected = Evin garajı. Aile üyeleri (türetilmiş sınıflar) kullanabilir, ama komşular giremez.

  • private = Evin kasası. Sadece ev sahibi (sınıfın kendisi) açabilir.


public — Herkese Açık

public üyelere sınıf dışından doğrudan erişilebilir:

#include <iostream>
using namespace std;

class Dikdortgen {
public:
    double genislik;
    double yukseklik;

    double alan() {
        return genislik * yukseklik;
    }
};

int main() {
    Dikdortgen d;
    d.genislik = 10.0;  // OK — public
    d.yukseklik = 5.0;   // OK — public
    cout << "Alan: " << d.alan() << endl;

    // Ama sorun: negatif değer atanabilir!
    d.genislik = -5.0;   // Mantıksız ama engellenmiyor
    cout << "Alan: " << d.alan() << endl;  // -25 (anlamsız)

    return 0;
}

Sorun açık: dışarıdan her şeye erişim, geçersiz durumlar (invalid state) oluşturabilir.


private — Sadece Sınıfın Kendisi

private üyelere sadece sınıfın kendi fonksiyonları erişebilir:

#include <iostream>
using namespace std;

class Dikdortgen {
private:
    double genislik;
    double yukseklik;

public:
    Dikdortgen(double g, double y) {
        genislikAyarla(g);
        yukseklikAyarla(y);
    }

    void genislikAyarla(double g) {
        if (g > 0) genislik = g;
        else cout << "Geçersiz genişlik!" << endl;
    }

    void yukseklikAyarla(double y) {
        if (y > 0) yukseklik = y;
        else cout << "Geçersiz yükseklik!" << endl;
    }

    double alan() {
        return genislik * yukseklik;
    }
};

int main() {
    Dikdortgen d(10.0, 5.0);
    // d.genislik = -5.0;  // HATA! private — erişilemez

    d.genislikAyarla(-5.0);  // "Geçersiz genişlik!" — korunuyor
    cout << "Alan: " << d.alan() << endl;  // 50 — hâlâ doğru

    return 0;
}

Artık negatif değer atanamıyor. Sınıf kendini koruyor.


protected — Aile İçi Erişim

protected üyelere sınıfın kendisi ve türetilmiş sınıflar (derived class) erişebilir ama dışarıdan erişilemez:

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

class Hayvan {
protected:
    string isim;
    int yas;

public:
    Hayvan(string i, int y) : isim(i), yas(y) {}
};

class Kedi : public Hayvan {
public:
    Kedi(string i, int y) : Hayvan(i, y) {}

    void miyavla() {
        // protected üyelere türetilmiş sınıftan erişilebilir
        cout << isim << " (" << yas << " yaşında): Miyav!" << endl;
    }
};

int main() {
    Kedi k("Pamuk", 3);
    k.miyavla();

    // k.isim = "Tekir";  // HATA! protected — dışarıdan erişilemez
    // k.yas = 5;          // HATA!

    return 0;
}

protected en çok kalıtım (inheritance) ile birlikte kullanılır. İlerideki OOP derslerinde daha çok göreceksin.


Encapsulation (Kapsülleme) Neden Önemli?

Encapsulation, OOP'nin temel ilkelerinden biri. Veriyi gizle, kontrollü erişim sağla.

Neden?

  1. Veri bütünlüğü: Geçersiz değer atanmasını önler

  2. İç detayları gizleme: Kullanıcı nasıl çalıştığını bilmek zorunda değil

  3. Değiştirme özgürlüğü: İç implementasyonu dışarıyı etkilemeden değiştirebilirsin

  4. Debug kolaylığı: Veri sadece belirli noktalardan değişiyorsa hata bulmak kolay

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

class BankaHesabi {
private:
    string sahip;
    double bakiye;

    // İç yardımcı fonksiyon — dışarıdan çağrılamaz
    void log(string islem, double miktar) {
        cout << "[LOG] " << islem << ": " << miktar << " TL" << endl;
    }

public:
    BankaHesabi(string s, double b) : sahip(s), bakiye(b) {}

    void yatir(double miktar) {
        if (miktar > 0) {
            bakiye += miktar;
            log("Yatırma", miktar);
        }
    }

    bool cek(double miktar) {
        if (miktar > 0 && miktar <= bakiye) {
            bakiye -= miktar;
            log("Çekme", miktar);
            return true;
        }
        cout << "Yetersiz bakiye veya geçersiz miktar!" << endl;
        return false;
    }

    double bakiyeGor() const { return bakiye; }
    string sahipGor() const { return sahip; }
};

int main() {
    BankaHesabi hesap("Ali", 1000);

    hesap.yatir(500);
    hesap.cek(200);
    // hesap.bakiye = 999999;  // HATA! private
    // hesap.log("hack", 0);   // HATA! private

    cout << "Bakiye: " << hesap.bakiyeGor() << " TL" << endl;

    return 0;
}

💡 İpucu: İyi tasarlanmış bir sınıfta üye değişkenler private, fonksiyonlar ise gerektiği kadar public olmalı. "Mümkün olduğunca gizle, gerektiği kadar aç" prensibi.


Getter / Setter Pattern

Private üyelere kontrollü erişim sağlamak için getter (okuma) ve setter (yazma) fonksiyonları kullanılır:

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

class Ogrenci {
private:
    string isim;
    int yas;
    double ortalama;

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

    // Getter'lar — const olmalı (değiştirmiyoruz)
    string getIsim() const { return isim; }
    int getYas() const { return yas; }
    double getOrtalama() const { return ortalama; }

    // Setter'lar — doğrulama yapabilir
    void setIsim(const string& yeniIsim) {
        if (!yeniIsim.empty()) {
            isim = yeniIsim;
        }
    }

    void setYas(int yeniYas) {
        if (yeniYas > 0 && yeniYas < 150) {
            yas = yeniYas;
        }
    }

    void setOrtalama(double yeniOrt) {
        if (yeniOrt >= 0.0 && yeniOrt <= 100.0) {
            ortalama = yeniOrt;
        }
    }
};

int main() {
    Ogrenci ali("Ali", 20, 85.0);

    cout << ali.getIsim() << ", " << ali.getYas() << " yaşında" << endl;

    ali.setYas(200);  // Geçersiz — değişmez
    cout << "Yaş: " << ali.getYas() << endl;  // Hâlâ 20

    ali.setYas(21);   // Geçerli
    cout << "Yaş: " << ali.getYas() << endl;  // 21

    return 0;
}

Getter/Setter Ne Zaman Gerekli?

Her private değişkene otomatik getter/setter yazmak yanlış bir yaklaşım. Bu, private yapmış olmanın amacını yok eder.

// KÖTÜ — getter/setter her şeyi açıyorsa public yapmaktan farkı yok
class Kotu {
    int x;
public:
    int getX() { return x; }
    void setX(int val) { x = val; }  // Hiçbir kontrol yok!
};

// İYİ — setter doğrulama yapıyor
class Iyi {
    int sicaklik;
public:
    int getSicaklik() const { return sicaklik; }
    void setSicaklik(int s) {
        if (s >= -273 && s <= 1000) sicaklik = s;
    }
};

// DAHA İYİ — anlamlı iş mantığı fonksiyonları
class DahaIyi {
    double bakiye;
public:
    double bakiyeGor() const { return bakiye; }
    void yatir(double miktar) { /* doğrulama + loglama */ }
    bool cek(double miktar) { /* doğrulama + loglama */ }
    // setBakiye() yok! Bakiye direkt atanamaz.
};

⚠️ Dikkat: Her private değişkene getter ve setter yazmak zorunda değilsin. Setter yerine anlamlı iş fonksiyonları (yatir, cek, sev, kiralа) yaz. Nesne davranışını model le, veri erişimini değil.


friend Fonksiyon

Bazen bir dış fonksiyonun sınıfın private üyelerine erişmesi gerekebilir. friend anahtar kelimesi bunu sağlar:

#include <iostream>
using namespace std;

class Kutu {
private:
    double uzunluk;
    double genislik;
    double yukseklik;

public:
    Kutu(double u, double g, double y)
        : uzunluk(u), genislik(g), yukseklik(y) {}

    // Bu fonksiyon private üyelere erişebilir
    friend double hacim_hesapla(const Kutu& k);

    // Operatör overloading'de çok kullanılır
    friend ostream& operator<<(ostream& os, const Kutu& k);
};

double hacim_hesapla(const Kutu& k) {
    // private üyelere erişim — friend sayesinde
    return k.uzunluk * k.genislik * k.yukseklik;
}

ostream& operator<<(ostream& os, const Kutu& k) {
    os << k.uzunluk << "x" << k.genislik << "x" << k.yukseklik;
    return os;
}

int main() {
    Kutu k(3, 4, 5);
    cout << "Hacim: " << hacim_hesapla(k) << endl;  // 60
    cout << "Kutu: " << k << endl;                    // 3x4x5

    return 0;
}

İki Sınıf Arasında friend

#include <iostream>
using namespace std;

class Motor;  // Forward declaration

class Araba {
private:
    int hiz;
    string marka;

public:
    Araba(string m, int h) : marka(m), hiz(h) {}

    friend class Motor;  // Motor sınıfı tüm private üyelere erişebilir
};

class Motor {
public:
    void araba_bilgi(const Araba& a) {
        // Araba'nın private üyelerine erişebilir
        cout << a.marka << " - " << a.hiz << " km/h" << endl;
    }

    void hiz_artir(Araba& a, int miktar) {
        a.hiz += miktar;  // private üyeyi değiştiriyor
    }
};

int main() {
    Araba bmw("BMW", 180);
    Motor m;

    m.araba_bilgi(bmw);    // BMW - 180 km/h
    m.hiz_artir(bmw, 20);
    m.araba_bilgi(bmw);    // BMW - 200 km/h

    return 0;
}

friend Class

friend class ile bir sınıfın tamamına erişim verilebilir:

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

class Ogrenci {
private:
    string isim;
    vector<int> notlar;

public:
    Ogrenci(string i) : isim(i) {}

    void notEkle(int not_) {
        notlar.push_back(not_);
    }

    // Sinav sınıfı private üyelere erişebilir
    friend class Sinav;
};

class Sinav {
public:
    static void rapor_yazdir(const Ogrenci& o) {
        cout << "=== " << o.isim << " ===" << endl;
        double toplam = 0;
        for (int n : o.notlar) {
            cout << "  Not: " << n << endl;
            toplam += n;
        }
        if (!o.notlar.empty()) {
            cout << "  Ortalama: " << toplam / o.notlar.size() << endl;
        }
    }
};

int main() {
    Ogrenci ali("Ali");
    ali.notEkle(90);
    ali.notEkle(85);
    ali.notEkle(78);

    Sinav::rapor_yazdir(ali);

    return 0;
}

Ne Zaman friend Kullanılmalı?

friend kapsüllemeyi zayıflatır — dikkatli kullan.

Uygun durumlar:

  • Operatör overloading: <<, >> gibi operatörler sınıf dışı fonksiyon olarak tanımlanmalı ama private üyelere erişim gerekebilir

  • Sıkı ilişkili sınıflar: Birlikte çalışması gereken iki sınıf (Iterator ve Container gibi)

  • Test sınıfları: Unit test'lerde private üyelere erişim

Kaçınılması gereken durumlar:

  • Sadece "getter yazmak istemiyorum" diye friend kullanma

  • Her yere friend serpmek kapsüllemenin anlamını yok eder

  • friend ilişkisi tek yönlüdür ve geçişli değildir (A, B'nin friend'i ise B otomatik olarak A'nın friend'i olmaz)

⚠️ Dikkat: friend'i "kapsüllemenin kontrollü istisnası" olarak düşün, "kapsüllemenin düşmanı" olarak değil. Gerçekten gerektiğinde kullan, her yere serpme.


Pratik Örnek: Koordinat Sınıfı

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

class Nokta {
private:
    double x, y;

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

    // Getter'lar
    double getX() const { return x; }
    double getY() const { return y; }

    // Mesafe hesaplama — friend fonksiyon
    friend double mesafe(const Nokta& a, const Nokta& b);

    // Yazdırma — friend operatör
    friend ostream& operator<<(ostream& os, const Nokta& n);
};

double mesafe(const Nokta& a, const Nokta& b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

ostream& operator<<(ostream& os, const Nokta& n) {
    os << "(" << n.x << ", " << n.y << ")";
    return os;
}

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

    cout << "A: " << a << endl;
    cout << "B: " << b << endl;
    cout << "Mesafe: " << mesafe(a, b) << endl;

    return 0;
}

Özet

  • public üyelere her yerden erişilebilir, private sadece sınıf içinden, protected sınıf ve türetilmiş sınıflardan erişilebilir.

  • Encapsulation veriyi private yapıp kontrollü erişim sağlamaktır; veri bütünlüğünü korur, iç detayları gizler ve değiştirme özgürlüğü verir.

  • Getter/setter pattern private üyelere erişim sağlar; setter'da doğrulama yapılabilir. Ama her private değişkene otomatik getter/setter yazmak yerine anlamlı iş fonksiyonları tercih edilmelidir.

  • friend fonksiyon veya sınıf, private üyelere erişim istisnası tanır. En yaygın kullanım operatör overloading'dir.

  • friend kullanımı kapsüllemeyi zayıflatır; gerçekten gerektiğinde (operatör overloading, sıkı ilişkili sınıflar) kullan, her yere serpme.

  • Genel kural: "Mümkün olduğunca gizle, gerektiği kadar aç." Üye değişkenler private, fonksiyonlar gerektiği kadar public olsun.