Sanal Fonksiyonlara Yeniden Bakış
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası. Ders 58 / 104.
Artık derleme zamanı ve çalışma zamanı çok biçimliliği arasındaki farkı anladığımıza göre, sanal fonksiyonlara ve onları kullanmayı daha güvenli hâle getiren override anahtar sözcüğüne daha yakından bakalım.
Bir işlevi virtual olarak base sınıfta işaretlediğinizde, türetilmiş sınıflar kendi uygulamalarını sağlayabilir. override belirteci, derleyiciye bir sanal işlevi geçersiz kılmayı amaçladığınızı 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 çok önemlidir, çünkü hataları derleme zamanında yakalar. İşlev adını yanlışlıkla yazım hatasıyla yazarsanız veya yanlış parametreler kullanırsanız, derleyici sessizce yeni bir işlev oluşturmak yerine sizi uyarır:
class Cat : public Animal {
public:
void speek() override { // Derleyici hatası: override edilecek fonksiyon yok
std::cout << "Meow!" << std::endl;
}
};final belirteci, daha fazla geçersiz kılmayı engeller. Türetilmiş sınıfın belirli bir function için geçersiz kılma işlemi yapacak 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 override edilemez
};Sanal işlevleri geçersiz kılarken her zaman override kullanın. Bu, amacınızı belgelendirir ve derleyicinin gerçekten mevcut bir sanal işlevi geçersiz kıldığınızı doğrulamasını sağlar.
Görev
KolaySanal işlevlerle birlikte override ve final anahtar sözcüklerinin gücünü gösteren bir bildirim sistemi oluşturalım. Bazı yöntemlerin daha fazla özelleştirilebildiği, bazılarının ise değiştirilmelerini önlemek için kilitlendiği bir bildirim işleyicileri hiyerarşisi oluşturacaksın.
Kodunu üç dosya arasında düzenleyeceksin:
Notifier.h: Herhangi bir bildirim göndericisini temsil eden temel birNotifiersınıfı tanımla:- Korumalı bir
std::string recipientüyesi - Alıcı adını alan ve saklayan bir kurucu
- Şunu yazdıran sanal bir
send(const std::string& message)yöntemi:Notifying <recipient>: <message> getType()dizesini döndüren sanal bir"Generic"yöntemi- Sanal bir yok edici
- Korumalı bir
EmailNotifier.h:EmailNotifiersınıfından kalıtım alan birNotifiersınıfı tanımla:- Özel bir
std::string domainüyesi - Alıcıyı ve alan adını alan, alıcıyı temel sınıfa aktaran bir kurucu
- Şunu yazdıracak şekilde
send()yöntemini geçersiz kıl:Emailing <recipient>@<domain>: <message> getType()yöntemini geçersiz kıl vefinalolarak işaretle."Email"döndürmelidir
Ardından
UrgentEmailNotifiersınıfından kalıtım alan birEmailNotifiersınıfı tanımla:- Alıcıyı ve alan adını alan, her ikisini de
EmailNotifiersınıfına aktaran bir kurucu - Şunu yazdıracak şekilde
send()yöntemini geçersiz kıl:[URGENT] Emailing <recipient>@<domain>: <message> - Not:
getType()içindefinalolarak işaretlendiği için buradaEmailNotifieryöntemini geçersiz kılamazsın
- Özel bir
main.cpp: Üç girdi oku (her biri ayrı bir satırda):- Alıcı adı
- E-posta alan adı
- Mesaj metni
Dinamik olarak üç bildirim nesnesi oluştur: temel bir
Notifier, birEmailNotifierve birUrgentEmailNotifier; tümünde aynı alıcıyı (uygun olanlarda aynı alan adını) kullan. BunlarıNotifier*işaretçilerinden oluşan bir dizide sakla.Dizi üzerinde döngü kur ve her bildirim için önce
getType()kullanarak türünü yazdır, ardından mesajınla birliktesend()yöntemini çağır. Her girdiyi şu biçimde düzenle:Type: <type> <send output>Her bildirim arasında boş bir satır yazdır. İşin bittiğinde dinamik olarak ayrılmış nesneleri temizle.
Örneğin girdiler Alice, company.com ve Meeting at 3pm olduğunda:
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 3pmUrgentEmailNotifier sınıfının mesaj biçimini özelleştirmek için send() yöntemini nasıl geçersiz kılabildiğine, ancak "Email" yöntemi EmailNotifier olarak işaretlendiği için getType() sınıfından final türünü devraldığına dikkat et. Derleme zamanında imza uyuşmazlıklarını yakalamak için geçersiz kılınan tüm yöntemlerde override anahtar sözcüğünü kullan.
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ı Notifier* işaretçi dizisi oluştur
// TODO: Dinamik olarak oluştur:
// - recipient ile temel bir Notifier
// - recipient ve domain ile bir EmailNotifier
// - recipient ve domain ile bir UrgentEmailNotifier
// TODO: Diziyi döngüyle gez ve her notifier için:
// - "Type: " yazdır, ardından getType() sonucunu
// - message ile send() çağır
// - notifier'lar arasında boş bir satır yazdır (sonuncudan sonra değil)
// TODO: Dinamik olarak ayrılmış nesneleri temizle
return 0;
}
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
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic KonteynerKendi başına pratik yap: Online C++ derleyicisi