İçeriğe geç
academia.sh

Ders 02 / 14

Sınıflar ve Erişim Denetimi

Bir üye işlevin hangi nesneyle çalıştığını ve erişim denetiminin nesne değil sınıf düzeyinde işlediğini ölçer.

Yayın Güncelleme

Bu derste

  • Bir üye işlev çağrısında this işaretçisinin hangi nesneyi gösterdiğini ölçer
  • class ile struct'ın varsayılan erişim farkını ve public/private/protected'i uygular
  • Erişim denetiminin nesne değil sınıf düzeyinde çalıştığını koşturarak gösterir
İçindekiler

Önceki ders bir serbest işlevin parametresine bakarak hangi nesneyle çalıştığını ölçtü: kaydir(Nokta n) kendi kopyasıyla çalışıyordu, tasi(Nokta* p) çağıranın nesnesine adresle ulaşıyordu. Bir üye işlevin de bir parametresi vardır, ama derleyici onu örtük geçirir: çağrının yapıldığı nesnenin adresi. Bu örtük parametrenin adı this’tir ve bir üye işlevin gövdesinde her zaman kullanılabilir — yazılmasa bile oradadır. Bu ders o örtük parametreyi aynı çekirdekle ölçer: bir üye işlev çağrıldığında this hangi nesneyi gösteriyor, ve bu bilgi erişim denetimiyle nasıl bir araya geliyor?

Sınıf ve Erişim Denetimi

class ile struct C++‘ta aynı şeydir; ikisi de üye değişken ve üye işlev taşıyabilen bir tür tanımlar, ikisi de kalıtım alabilir, ikisi de yapıcı ve yıkıcı çalıştırır. Aralarındaki tek fark varsayılan erişimdir. struct üyeleri varsayılan olarak public, class üyeleri varsayılan olarak private‘tır — başka hiçbir davranış farkı yoktur. Bu, C’den gelen bir mirastır: C’de struct yalnız veri taşıyan bir kayıttı, işlev taşımazdı; C++ struct’a üye işlev ekleme yeteneğini verirken geriye dönük uyumluluk için varsayılan erişimi public bıraktı. Bir C++ kod tabanında struct’ın genellikle yalnız veri taşıyan, davranışı az türler için, class’ın ise davranışı erişim denetimiyle koruyan türler için kullanılması bir gelenektir — dilin kendisi bunu zorlamaz.

Bir sınıf üç erişim düzeyi bildirir:

  • public — sınıfın dışından, herhangi bir kodun herhangi bir yerinden erişilebilir. Bir sınıfın dışarıya sunduğu arayüz buradadır.
  • private — yalnız sınıfın kendi üye işlevlerinden (ve friend olarak bildirilmiş işlev ya da sınıflardan) erişilebilir. Bir sınıfın kendi iç durumunu tuttuğu veri üyeleri genellikle buradadır.
  • protected — kendi üye işlevlerinden ve türeyen sınıflardan erişilebilir; private‘tan farkı yalnız kalıtımda ortaya çıkar. Kalıtım M11/K04’ün ikinci konusuna kadar bu kursta kullanılmaz, bu yüzden protected burada yalnız adıyla anılır.

Bir erişim düzeyi bildirimi (public:, private: ya da protected:) bir sınıf gövdesinde o noktadan sonraki tüm üyelere uygulanır, ta ki bir sonraki erişim düzeyi bildirimine kadar. Hesap sınıfında public: altındaki üç üye işlev dışarıdan çağrılabilir, private: altındaki bakiye_ çağrılamaz — aynı sınıf gövdesinde birden çok public:/private: bloğu da bulunabilir, derleyici hepsini birleştirir.

Bir sınıf veri üyesi (nesnenin her örneğinde ayrı bir kopyası olan değişken — bakiye_ her Hesap nesnesinde kendi değerini taşır) ve üye işlev (sınıfın tüm örnekleri arasında paylaşılan, çağrıldığı örneğin veri üyelerine this üzerinden erişen kod — yatir’in makine kodu bir kez üretilir, her Hesap nesnesi için ayrı ayrı değil) taşır. Bir üye işlev const işaretliyse this’in kendisi const’tur: işlev veri üyelerini değiştiremez, yalnız okuyabilir. bakiye() ve esit_mi bu yüzden const’tur — ikisi de bakiye_’yi okur ama değiştirmez; yatir const değildir çünkü bakiye_’yi değiştirir. Bir const nesne üzerinden yalnız const üye işlevler çağrılabilir; const olmayan bir üye işlevi const bir nesne üzerinden çağırmak derleme hatasıdır, çünkü derleyici this’in işlev içinde değiştirilmeyeceğini garanti edemez.

friend bildirimi bir işleve ya da sınıfa, üyesi olmadığı bir sınıfın private üyelerine erişim tanır — erişim denetiminin tek istisnasıdır ve sınıfın kendi bildirimiyle açıkça verilir, dışarıdan alınamaz. Bu kursta friend yalnız bir kez, operator<<’ın bir sınıfın private veri üyesini okuyabilmesi için kullanılır (İşleç Aşırı Yükleme dersinde); burada yalnız adıyla anılır.

Kapsülleme — bir sınıfın iç durumunu dışarıdan gizleyip yalnız işlevler üzerinden erişim sunması — M08’de zaten işlendi; burada yeniden öğretilmez, yalnız C++’ın public/private sözdizimiyle uygulanır. Hesap bu fikri uygular: bakiye_’ye dışarıdan doğrudan yazılamaz, yalnız yatir üzerinden değiştirilebilir.

Üye İşlevin this’i

this, bir üye işlevin gövdesinde örtük olarak tanımlı bir işaretçidir; tipi çağrıldığı sınıfa göre belirlenir. Hesap::yatir içinde this’in tipi Hesap* const’tur — işaretçinin kendisi değiştirilemez (başka bir Hesap’ı göstermeye başlayamaz), ama gösterdiği nesne değiştirilebilir. const işaretli bir üye işlevde — Hesap::bakiye() const gibi — this’in tipi const Hesap* const’tur: hem işaretçi hem gösterdiği nesne değiştirilemez, bu yüzden const bir üye işlev veri üyelerine yalnız okuma erişimi kazanır. Kod bu işaretçiyi elle yazmaz — bakiye_ += miktar derleyici tarafından this->bakiye_ += miktar olarak okunur — ama aşağıdaki ölçüm this’in gerçekten çağrılan nesneyi gösterdiğini, başka bir nesneyi değil, açıkça gösterir: h.yatir(50) çağrısından önce alınan &h adresiyle, işlev içinde üstü kapalı kullanılan this’in eşdeğeri karşılaştırılır.

// hesap.hpp — bakiyesi private olan bir hesap
#ifndef HESAP_HPP
#define HESAP_HPP

class Hesap {
public:
    void yatir(int miktar) { bakiye_ += miktar; }
    int bakiye() const { return bakiye_; }
    bool esit_mi(const Hesap& digeri) const { return bakiye_ == digeri.bakiye_; }

private:
    int bakiye_ = 0;
};

#endif
// this-olcumu.cpp — uye islevin this'i cagrilan nesneyi mi gosteriyor
#include <cstdio>

#include "hesap.hpp"

int main() {
    Hesap h;
    Hesap* onceki = &h;

    h.yatir(50);
    std::printf("this cagrilan nesneyi gosteriyor mu %s\n", onceki == &h ? "evet" : "hayir");
    std::printf("bakiye %d\n", h.bakiye());
    return 0;
}
this cagrilan nesneyi gosteriyor mu evet
bakiye 50

SK3. h.yatir(50) çağrısında derleyici this’e &h’yi geçirir; yatir içindeki bakiye_ += miktar satırı h.bakiye_’yi değiştirir, başka hiçbir Hesap’ı değil. Bu ifade kendisi sınıfındadır: h’nin kendisiyle çalışır.

Erişim Denetimi Nesneyi Değil Sınıfı Sorar

bakiye_ private’tır; Hesap’ın dışından h.bakiye_ yazılamaz — main içinde böyle bir satır denense derleyici reddeder, çünkü main Hesap sınıfının bir üyesi değildir. Ama Hesap’ın kendi bir üye işlevi, başka bir Hesap nesnesinin bakiye_’sine doğrudan erişebilir — erişim denetimi nesneyi değil, sınıfı sorar. esit_mi bunu gösterir: digeri.bakiye_ çağıranın kendi bakiye_’si değildir, ama aynı sınıftan olduğu için derlenir. Bu, birçok dilde geçerli olmayan bir C++ kuralıdır — bazı dillerde erişim denetimi nesne düzeyinde işler ve bir nesne yalnız kendi verisine dokunabilir; C++’ta sınır sınıfın kendisidir, tek bir nesne değil.

// esitlik-olcumu.cpp — bir uye islev baska bir nesnenin private uyesine erisiyor
#include <cstdio>

#include "hesap.hpp"

int main() {
    Hesap a;
    Hesap b;
    a.yatir(30);
    b.yatir(30);

    std::printf("a ile b esit mi %s\n", a.esit_mi(b) ? "evet" : "hayir");

    b.yatir(1);
    std::printf("a ile b esit mi %s\n", a.esit_mi(b) ? "evet" : "hayir");
    return 0;
}
a ile b esit mi evet
a ile b esit mi hayir

SK4. a.esit_mi(b) içindeki digeri.bakiye_ erişimi b’nin private üyesine a’nın üye işlevinden yapılır; derleyici bunu yalnız digeri’nin tipine (const Hesap&) bakarak kabul eder, a ile b’nin aynı nesne olup olmadığına bakmaz. Erişim kararı derleme zamanında, sınıf bilgisine göre verilir.

Bu, birçok C++ öğrencisinin ilk şaşırdığı noktalardan biridir: private’ın “yalnız bu nesne kendi verisine erişebilir” anlamına geldiği sanılır, ama kural budur — “yalnız bu sınıfın üye işlevleri erişebilir.” esit_mi’nin gövdesi derlenirken derleyici yalnız şunu bilir: digeri bir const Hesap&’tır, ve o an derlenen işlev Hesap sınıfının bir üyesidir; bu iki bilgi digeri.bakiye_ erişimine izin vermek için yeterlidir. a ile digeri’nin (yani b’nin) aynı çalışma zamanı nesnesi olup olmadığı derleme zamanında bilinmez ve erişim kararına hiç katılmaz — nitekim ikinci çağrıda a ile b ayrı nesnelerdir ve erişim yine de derlenir.

private Neden Nesneyi Değil Sınıfı Korur

Bu kural rastgele değildir; sınıf yazarının değişmezlerini (invariant — bir nesnenin her zaman doğru kalması gereken durumu, örneğin bakiye_’nin hiçbir zaman negatife düşmemesi) korumasını mümkün kılar. Eğer private yalnız “başka nesnelerden değil, kendi nesnenden erişilebilir” anlamına gelseydi, esit_mi gibi iki nesneyi karşılaştıran ya da bir nesneden diğerine veri kopyalayan işlevler yazılamazdı — her üye işlev yalnız kendi this’ine bakabilirdi. Sınıf düzeyinde erişim, bir sınıfın kendi üye işlevleri arasında güvenle veri paylaşmasını sağlarken, sınıfın dışındaki koda aynı veriyi kapatır. Sınırı çizen nesne değil, sınıfın kaynak dosyasındaki bildirimdir.

Bunun karşıtı, alanı dışarıya açık bırakılmış bir yapıdır: böyle bir kayıtta çağıran kod o alanı herhangi bir denetimden geçirmeden değiştirebilir. Hesap aynı hatayı yapmaz — bakiye_’ye dışarıdan yazma yolu yoktur, yalnız yatir üzerinden değiştirilebilir. yatir bugün bakiye_ += miktar dışında bir şey yapmıyor, ama private sayesinde ileride negatif bir miktar’ı reddetmek ya da bir üst sınırı denetlemek gibi bir kural eklense, bu kural her çağıranı değiştirmeden yalnız yatir’in içine yazılabilir — çünkü bakiye_’ye giden tek yol yatir’dir. Bu, private’ın sağladığı asıl mühendislik değeridir: sınıfın iç temsilini değiştirme özgürlüğü, dışarıdaki koda dokunmadan.

const Nesne Üzerinden Çağrı Seçimi

bakiye() ve esit_mi neden const işaretlidir, yatir neden değildir sorusunun bir pratik sonucu vardır: bir const Hesap& parametre alan bir işlev, o parametre üzerinden yalnız const üye işlevleri çağırabilir. esit_mi’nin imzası bool esit_mi(const Hesap& digeri) const’tur; gövdesi içinde digeri.bakiye_’ye doğrudan erişir, ama digeri.bakiye() çağırsaydı da derlenirdi — çünkü bakiye() const’tur ve const bir referans üzerinden çağrılabilir. digeri.yatir(10) denenmiş olsaydı derlenmezdi: yatir const değildir, ve const bir nesne (ya da const bir referansın gösterdiği nesne) üzerinden const olmayan bir üye işlev çağrılamaz. Bu kural, bir işlevin imzasına const yazmasının yalnız belgeleyici bir not olmadığını, derleyicinin gerçekten denetlediği bir sözleşme olduğunu gösterir: esit_mi’nin digeri’yi değiştirmeyeceği, işlevin kendi imzasından derleme zamanında doğrulanır.

Aynı kural this için de geçerlidir. Hesap::esit_mi const işaretli olduğu için kendi this’i de const Hesap* const’tur; bu işlevin gövdesi içinde bakiye_ += 1 gibi bir değişiklik denense — kendi nesnesi üzerinde bile — derleyici reddeder. const bir üye işlev, ne kendi nesnesini ne de const bir referansla aldığı başka bir nesneyi değiştirebilir; değiştirebileceği tek şey yerel değişkenler ve mutable işaretli veri üyeleridir (mutable, önbellekleme gibi nadir durumlar için ayrılmış bir işaretleyicidir ve bu kursta kullanılmaz).

Bildirim Sırası ve Erişim Düzeyi Blokları

Hesap sınıfında erişim düzeyi bildirimleri belli bir yerleşim sırası izler: önce public: altında dışarıya sunulan üye işlevler, sonra private: altında veri üyesi. Bu sıra dilin bir kuralı değildir — derleyici için private: üstte, public: altta olsaydı da aynı sonucu verirdi — ama okunabilirlik için yaygın bir alışkanlıktır: bir sınıfı okuyan kişi önce onun sunduğu arayüzü, sonra iç temsilini görür. Ters sıra (önce private veri, sonra public işlevler) de geçerlidir ve bazı kod tabanlarında tercih edilir; bu ders public-önce sırasını izler ve kursun geri kalanında tutarlı kalır.

Bir erişim düzeyi bildirimi yalnız bir kez kullanılmak zorunda değildir — aynı sınıf gövdesi içinde public: ve private: birden fazla kez tekrarlanabilir, her biri kendinden sonraki bloğu kapsar. Hesap bunu yapmaz; sınıf küçük olduğu için tek bir public: ve tek bir private: yeterlidir. Daha büyük bir sınıfta, ilgili üyeleri bir arada tutmak için birden çok blok kullanmak — örneğin yapıcılar için ayrı bir public:, sorgulayıcı işlevler için ayrı bir public: — okunabilirliği artırabilir; bu bir stil tercihidir, derleyicinin davranışını değiştirmez.

Sınırlayıcı Ölçüm

Bu dersin ölçtüğü ifade kendisi sınıfındadır: h.yatir(50) h’nin kendisine yazar. Ama erişim denetiminin sınırı da burada görülür: private bir üyeye “kimin eriştiği” nesne düzeyinde değil sınıf düzeyinde sorulur. esit_mi içindeki digeri.bakiye_ çağrısı bunu kanıtlar — aynı sınıftan farklı bir nesnenin private üyesine, o nesnenin kendi işlevi olmadan, yalnız aynı sınıfın bir başka üye işlevinden erişilebilir. private, nesneyi değil sınıfın sınırını korur.

Bu sınırın bir sonucu daha vardır: private bir üyeye erişim, sınıfın kendi kaynak kodundaki her üye işlevden mümkündür — bu üye işlevlerin sayısı sınıf büyüdükçe artabilir ve her biri aynı değişmezi bozma riski taşır. Hesap üç üye işlevle küçüktür ve hepsi bu derste görülebilir; büyük bir sınıfta private veriye erişen onlarca üye işlev olabilir, ve bu derste ölçülen kural — erişimin nesneye değil sınıfa bağlı olması — o ölçekte de değişmeden kalır. private, “kaç yerden erişilebilir” sorusuna nesne sayısıyla değil, sınıfın üye işlev sayısıyla cevap verir.

Bu iki nokta — hangi ifadenin hangi nesneyle çalıştığı ve erişim denetiminin nesneyi değil sınıfı sorduğu — birlikte okunduğunda bir sınıfın neden güvenilir bir sınır çizebildiğini açıklar. this her çağrıda doğru nesneyi gösterir, bu yüzden bir üye işlev kendi nesnesini başka bir nesneyle karıştırmaz; private her üye işleve aynı sınıftan gelen her nesneye erişim tanır, bu yüzden sınıf kendi içinde veri paylaşımı gerektiren işlemleri (karşılaştırma, kopyalama, birleştirme) yazabilir. Bu iki kural bir arada, bir sonraki dersin konusu olan yapıcıya zemin hazırlar: bir yapıcı da bir üye işlevdir, kendi this’i vardır, ve private veri üyelerine aynı serbestlikle erişir — ama onu farklı kılan, çağrıldığı nesnenin henüz var olmayan bir nesne olmasıdır.

Özet

  • Bu dersin ölçtüğü ifade kendisi sınıfındadır: h.yatir(50) çağrısında this, &h’ye eşittir.
  • class ile struct’ın tek farkı varsayılan erişimdir; public/private/protected üç erişim düzeyini bildirir.
  • const işaretli bir üye işlevde this de const’tur; veri üyeleri değiştirilemez.
  • Erişim denetimi derleme zamanında ve sınıf düzeyinde çalışır: bir üye işlev, aynı sınıftan başka bir nesnenin private üyesine erişebilir.
  • Kapsülleme M08’de tanımlandı; burada yalnız class/private sözdizimiyle uygulandı.

Sonraki Adım

Bu ders bir üye işlevin hangi nesneyle çalıştığını gösterdi; bir sonraki ders aynı soruyu bir yapıcıya sorar — int’ten örtük kurulan bir argümanın adsız, geçici bir nesne olduğunu ve bu nesnenin tam olarak ne zaman yok olduğunu ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat