Menu
Coddy logo textTech

Funkcje wirtualne — powtórka

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 58 z 104.

Skoro rozumiemy już różnicę między polimorfizmem w czasie kompilacji a polimorfizmem w czasie wykonywania, przyjrzyjmy się bliżej funkcjom wirtualnym i słowu kluczowemu override, które sprawia, że można ich używać bezpieczniej.

Gdy oznaczysz funkcję jako virtual w klasie bazowej, klasy pochodne mogą dostarczyć własną implementację. Specyfikator override jawnie informuje kompilator, że zamierzasz zastąpić funkcję wirtualną:

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;
    }
};

Używanie override jest kluczowe, ponieważ pozwala wykrywać błędy podczas kompilacji. Jeśli przypadkowo błędnie zapiszesz nazwę funkcji lub użyjesz niewłaściwych parametrów, kompilator Cię o tym powiadomi, zamiast po cichu tworzyć nową funkcję:

class Cat : public Animal {
public:
    void speek() override {  // Błąd kompilatora: brak funkcji do nadpisania
        std::cout << "Meow!" << std::endl;
    }
};

Specyfikator final zapobiega dalszemu przesłanianiu. Użyj go, gdy klasa pochodna powinna być ostatnią, która przesłania daną funkcję:

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

class TinyBulldog : public Bulldog {
    void speak() override {}  // Błąd: nie można przesłonić funkcji final
};

Zawsze używaj override przy przesłanianiu funkcji wirtualnych. Dokumentuje to Twój zamiar i pozwala kompilatorowi sprawdzić, czy rzeczywiście przesłaniasz istniejącą funkcję wirtualną.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system powiadomień, który pokazuje możliwości słów kluczowych override i final w przypadku funkcji wirtualnych. Utworzysz hierarchię programów obsługi powiadomień, w której niektóre metody można dalej dostosowywać, a inne są zablokowane, aby zapobiec ich modyfikacji.

Podzielisz kod na trzy pliki:

  • Notifier.h: Zdefiniuj bazową klasę Notifier, która reprezentuje dowolnego nadawcę powiadomień:
    • Chronione pole std::string recipient
    • Konstruktor, który przyjmuje i przechowuje nazwę odbiorcy
    • Wirtualna metoda send(const std::string& message), która wyświetla: Notifying <recipient>: <message>
    • Wirtualna metoda getType(), która zwraca ciąg znaków "Generic"
    • Wirtualny destruktor
  • EmailNotifier.h: Zdefiniuj klasę EmailNotifier, która dziedziczy po Notifier:
    • Prywatne pole std::string domain
    • Konstruktor, który przyjmuje odbiorcę i domenę oraz przekazuje odbiorcę do klasy bazowej
    • Nadpisz send(), aby wyświetlała: Emailing <recipient>@<domain>: <message>
    • Nadpisz getType() i oznacz ją jako final. Powinna zwracać "Email"

    Następnie zdefiniuj klasę UrgentEmailNotifier, która dziedziczy po EmailNotifier:

    • Konstruktor, który przyjmuje odbiorcę i domenę oraz przekazuje obie wartości do EmailNotifier
    • Nadpisz send(), aby wyświetlała: [URGENT] Emailing <recipient>@<domain>: <message>
    • Uwaga: Nie możesz tutaj nadpisać getType(), ponieważ w EmailNotifier oznaczono ją jako final
  • main.cpp: Odczytaj trzy dane wejściowe (każdą w osobnym wierszu):
    1. Nazwę odbiorcy
    2. Domenę e-mail
    3. Treść wiadomości

    Utwórz dynamicznie trzy obiekty powiadamiające: bazowy Notifier, EmailNotifier oraz UrgentEmailNotifier, wszystkie z tym samym odbiorcą (i domeną, jeśli dotyczy). Zapisz je w tablicy wskaźników Notifier*.

    Przejdź przez tablicę i dla każdego obiektu powiadamiającego wyświetl jego typ za pomocą getType(), a następnie wywołaj send(), przekazując swoją wiadomość. Sformatuj każdy wpis w następujący sposób:

    Type: <type>
    <send output>

    Wyświetl pusty wiersz między poszczególnymi obiektami powiadamiającymi. Po zakończeniu zwolnij dynamicznie przydzielone obiekty.

Na przykład dla danych wejściowych Alice, company.com i Meeting at 3pm:

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

Zwróć uwagę, że UrgentEmailNotifier może nadpisać send(), aby dostosować format wiadomości, ale dziedziczy typ "Email" z EmailNotifier, ponieważ getType() oznaczono jako final. Używaj słowa kluczowego override we wszystkich nadpisanych metodach, aby wykrywać niezgodności sygnatur podczas kompilacji.

Spróbuj swoich sił

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

using namespace std;

int main() {
    // Odczytaj dane wejściowe
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: Utwórz tablicę 3 wskaźników typu Notifier*
    
    // TODO: Utwórz dynamicznie:
    // - Obiekt klasy bazowej Notifier z recipient
    // - Obiekt EmailNotifier z recipient i domain
    // - Obiekt UrgentEmailNotifier z recipient i domain
    
    // TODO: Przejdź przez tablicę i dla każdego obiektu powiadamiającego:
    // - Wypisz "Type: ", a następnie wynik getType()
    // - Wywołaj send() z message
    // - Wypisz pusty wiersz między obiektami powiadamiającymi (ale nie po ostatnim)
    
    // TODO: Zwolnij pamięć przydzieloną dynamicznie obiektom
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online