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:
| Belirleyici | Sınıf İçi | Türetilmiş Sınıf | Dış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?
Veri bütünlüğü: Geçersiz değer atanmasını önler
İç detayları gizleme: Kullanıcı nasıl çalıştığını bilmek zorunda değil
Değiştirme özgürlüğü: İç implementasyonu dışarıyı etkilemeden değiştirebilirsin
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 gerekebilirSı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.
AI Asistan
Sorularını yanıtlamaya hazır