Menu
Coddy logo textTech

Polimorfizm czasu kompilacji i wykonania

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

Polimorfizm oznacza „wiele form” i jest podstawową koncepcją programowania obiektowego, która pozwala traktować obiekty w jednolity sposób, mimo że zachowują się różnie. C++ obsługuje dwa odrębne typy polimorfizmu, z których każdy jest rozstrzygany na innym etapie wykonywania programu.

Polimorfizm czasu kompilacji (nazywany również polimorfizmem statycznym) jest rozstrzygany przez kompilator przed uruchomieniem programu. Kompilator dokładnie określa, którą funkcję wywołać na podstawie sygnatury funkcji. Obejmuje to przeciążanie funkcji i szablony:

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // Kompilator wybiera print(int)
print(3.14);   // Kompilator wybiera print(double)

Polimorfizm czasu wykonania (zwany także polimorfizmem dynamicznym) jest rozstrzygany podczas działania programu. Decyzja o tym, którą funkcję wywołać, zależy od rzeczywistego typu obiektu, a nie typu wskaźnika lub referencji. Osiąga się to za pomocą funkcji wirtualnych:

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle" << std::endl; }
};

Shape* s = new Circle();
s->draw();  // Ustalane w czasie wykonywania: "Drawing circle"

Kluczowy kompromis: polimorfizm statyczny nie powoduje żadnego narzutu w czasie działania, ponieważ decyzje są podejmowane podczas kompilacji, natomiast polimorfizm dynamiczny wiąże się z niewielkim kosztem (wyszukiwaniem w tabeli metod wirtualnych), ale zapewnia większą elastyczność podczas pracy z obiektami, których typy nie są znane aż do momentu wykonania programu.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system kalkulatora, który pokazuje obok siebie oba rodzaje polimorfizmu. Utworzysz system, w którym polimorfizm czasu kompilacji obsługuje różne typy danych wejściowych za pomocą przeciążania funkcji, a polimorfizm czasu wykonania pozwala dynamicznie wymieniać różne strategie obliczeń.

Rozmieścisz kod w trzech plikach:

  • Calculator.h: Zdefiniuj bazową klasę Calculator, która reprezentuje dowolną strategię obliczeń:
    • Wirtualna metoda calculate(int a, int b), która zwraca wartość int i wypisuje: Base calculation: <a> ? <b> (zwracając 0)
    • Wirtualny destruktor
  • Operations.h: Zdefiniuj dwie klasy kalkulatorów pochodne, które nadpisują sposób wykonywania obliczeń:
    • Adder: Nadpisz metodę calculate(), aby wypisywała Adding: <a> + <b> i zwracała sumę
    • Multiplier: Nadpisz metodę calculate(), aby wypisywała Multiplying: <a> * <b> i zwracała iloczyn
    Obie klasy powinny używać słowa kluczowego override.
  • main.cpp: Utwórz system, który pokazuje oba rodzaje polimorfizmu. Wczytaj dwie liczby całkowite (każdą w osobnym wierszu).

    Najpierw pokaż polimorfizm czasu kompilacji, tworząc trzy przeciążone funkcje display():

    • display(int x) wypisuje: Integer value: <x>
    • display(double x) wypisuje: Double value: <x>
    • display(const std::string& x) wypisuje: String value: <x>

    Następnie pokaż polimorfizm czasu wykonania, tworząc tablicę wskaźników Calculator*, zawierającą obiekt bazowy Calculator, obiekt Adder i obiekt Multiplier. Przejdź przez tablicę w pętli i wywołaj calculate() dla każdego obiektu, używając wczytanych wartości, a po każdym obliczeniu wypisz wynik.

    Ułóż dane wyjściowe w następujący sposób:

    === Compile-Time Polymorphism ===
    <display outputs for int, double, string>
    
    === Runtime Polymorphism ===
    <calculate outputs with results>

    W sekcji dotyczącej czasu kompilacji wywołaj display() najpierw z pierwszą wartością jako liczbą całkowitą, następnie jako liczbą typu double (ta sama wartość z dodanym .5), a na końcu z ciągiem znaków "Result". Po zakończeniu zwolnij pamięć zajmowaną przez dynamicznie utworzone obiekty kalkulatorów.

Na przykład dla danych wejściowych 10 i 3:

=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result

=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30

Zwróć uwagę, jak kompilator wybiera właściwe przeciążenie display() na podstawie typu argumentu (decyzja podejmowana w czasie kompilacji), podczas gdy właściwa metoda calculate() jest określana w czasie wykonania na podstawie rzeczywistego typu obiektu, za pomocą mechanizmu vtable.

Spróbuj swoich sił

#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"

// TODO: Utwórz trzy przeciążone funkcje display():
// 1. display(int x) - wypisuje "Integer value: <x>"
// 2. display(double x) - wypisuje "Double value: <x>"
// 3. display(const std::string& x) - wypisuje "String value: <x>"



int main() {
    // Wczytaj dwie liczby całkowite
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Compile-Time Polymorphism ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: Wywołaj display() z następującymi argumentami:
    // - a jako liczba całkowita
    // - a jako wartość typu double (dodaj do niej 0.5)
    // - napis "Result"
    
    
    std::cout << std::endl;
    
    // === Runtime Polymorphism ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: Utwórz tablicę wskaźników Calculator* zawierającą 3 elementy:
    // - obiekt klasy bazowej Calculator
    // - obiekt klasy Adder
    // - obiekt Multiplier
    
    // TODO: Przejdź przez tablicę, wywołując calculate(a, b) dla każdego elementu,
    // i wypisz "Result: <return_value>" po każdym obliczeniu
    
    
    // TODO: Zwolnij dynamicznie przydzieloną pamięć
    
    
    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