Menu
Coddy logo textTech

Sanal Fonksiyonlara Yeniden Bakış

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 58 / 104.

Derleme zamanı ve çalışma zamanı polimorfizmi arasındaki farkı anladığımıza göre, sanal fonksiyonlara ve onları kullanmayı daha güvenli hale getiren override anahtar kelimesine daha derinlemesine bir göz atalım.

Bir fonksiyonu temel sınıfta virtual olarak işaretlediğinizde, türetilmiş sınıflar kendi uygulamalarını sağlayabilirler. override belirleyicisi, derleyiciye bir sanal fonksiyonu geçersiz kılmak istediğinizi açıkça belirtir:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

override kullanmak kritiktir çünkü hataları derleme zamanında yakalar. Eğer yanlışlıkla fonksiyon adını yanlış yazarsanız veya yanlış parametreler kullanırsanız, derleyici sessizce yeni bir fonksiyon oluşturmak yerine sizi uyaracaktır:

class Cat : public Animal {
public:
    void speek() override {  // Derleyici hatası: geçersiz kılınacak fonksiyon yok
        std::cout << "Meow!" << std::endl;
    }
};

final belirleyicisi daha fazla geçersiz kılmayı (overriding) engeller. Türetilmiş bir sınıfın belirli bir işlevi geçersiz kılan son sınıf olması gerektiğinde bunu kullanın:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // Hata: final fonksiyon geçersiz kılınamaz
};

Sanal fonksiyonları geçersiz kılarken her zaman override kullanın. Bu, niyetinizi belgeler ve derleyicinin mevcut bir sanal fonksiyonu gerçekten geçersiz kıldığınızı doğrulamasını sağlar.

challenge icon

Görev

Kolay

Sanal fonksiyonlarla birlikte override ve final anahtar kelimelerinin gücünü gösteren bir bildirim sistemi kuralım. Bazı metotların daha fazla özelleştirilebildiği, bazılarının ise değişikliği önlemek için kilitlendiği bir bildirim işleyici hiyerarşisi oluşturacaksınız.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • Notifier.h: Herhangi bir bildirim göndericiyi temsil eden temel bir Notifier sınıfı tanımlayın:
    • Korumalı (protected) bir std::string recipient üyesi
    • Alıcı adını alan ve saklayan bir yapıcı (constructor)
    • Şunu yazdıran sanal bir send(const std::string& message) metodu: Notifying <recipient>: <message>
    • "Generic" dizesini döndüren sanal bir getType() metodu
    • Sanal bir yıkıcı (destructor)
  • EmailNotifier.h: Notifier sınıfından miras alan bir EmailNotifier sınıfı tanımlayın:
    • Özel (private) bir std::string domain üyesi
    • Alıcı ve alan adını (domain) alan, alıcıyı temel sınıfa ileten bir yapıcı
    • send() metodunu şunu yazdıracak şekilde geçersiz kılın (override): Emailing <recipient>@<domain>: <message>
    • getType() metodunu geçersiz kılın ve final olarak işaretleyin — bu metot "Email" döndürmelidir

    Ardından EmailNotifier sınıfından miras alan bir UrgentEmailNotifier sınıfı tanımlayın:

    • Alıcı ve alan adını alan, her ikisini de EmailNotifier sınıfına ileten bir yapıcı
    • send() metodunu şunu yazdıracak şekilde geçersiz kılın: [URGENT] Emailing <recipient>@<domain>: <message>
    • Not: EmailNotifier içinde final olarak işaretlendiği için burada getType() metodunu geçersiz kılamazsınız
  • main.cpp: Üç girdi okuyun (her biri ayrı bir satırda):
    1. Alıcı adı
    2. E-posta alan adı (domain)
    3. Mesaj metni

    Dinamik olarak üç bildirim nesnesi oluşturun: bir temel Notifier, bir EmailNotifier ve bir UrgentEmailNotifier — hepsi aynı alıcıyı (ve uygun olan yerlerde alan adını) kullanmalıdır. Bunları bir Notifier* işaretçi dizisinde saklayın.

    Dizi boyunca döngü kurun ve her bildirim için getType() kullanarak türünü yazdırın, ardından mesajınızla birlikte send() metodunu çağırın. Her girişi şu şekilde biçimlendirin:

    Type: <type>
    <send output>

    Her bildirim arasına boş bir satır yazdırın. İşiniz bittiğinde dinamik olarak ayrılmış nesnelerinizi temizleyin.

Örneğin, Alice, company.com ve Meeting at 3pm girdileriyle:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

UrgentEmailNotifier sınıfının mesaj formatını özelleştirmek için send() metodunu nasıl geçersiz kılabildiğine, ancak getType() metodu final olarak işaretlendiği için "Email" türünü EmailNotifier sınıfından nasıl miras aldığına dikkat edin. Derleme zamanında herhangi bir imza uyuşmazlığını yakalamak için tüm geçersiz kılınan metotlarda override anahtar kelimesini kullanın.

Kopya kağıdı

override belirleyicisi, derleyiciye bir temel sınıftan sanal bir işlevi geçersiz kılma (override) niyetinde olduğunuzu açıkça bildirir:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

override kullanmak, hataları derleme zamanında yakalar. İşlev adını yanlış yazarsanız veya yanlış parametreler kullanırsanız, derleyici sizi uyaracaktır:

class Cat : public Animal {
public:
    void speek() override {  // Derleyici hatası: geçersiz kılınacak işlev yok
        std::cout << "Meow!" << std::endl;
    }
};

final belirleyicisi, türetilmiş sınıflarda daha fazla geçersiz kılma işlemini engeller:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // Hata: final işlevi geçersiz kılınamaz
};

Niyetinizi belgelemek ve derleyici doğrulamasını etkinleştirmek için sanal işlevleri geçersiz kılarken her zaman override kullanın.

Kendin dene

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

int main() {
    // Girdileri oku
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: 3 elemanlı bir Notifier* işaretçi dizisi oluşturun
    
    // TODO: Dinamik olarak oluşturun:
    // - Alıcı (recipient) ile bir temel Notifier
    // - Alıcı (recipient) ve alan adı (domain) ile bir EmailNotifier
    // - Alıcı (recipient) ve alan adı (domain) ile bir UrgentEmailNotifier
    
    // TODO: Dizi boyunca döngü kurun ve her bildirimci (notifier) için:
    // - "Type: " ve ardından getType() sonucunu yazdırın
    // - Mesaj ile send() fonksiyonunu çağırın
    // - Bildirimciler arasına boş bir satır yazdırın (sonuncusundan sonra değil)
    
    // TODO: Dinamik olarak ayrılmış nesneleri temizleyin
    
    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler