Menu
Coddy logo textTech

Derleme ve Çalışma Zamanı Çok Biçimliliği

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

Polimorfizm "çok biçimlilik" anlamına gelir ve nesnelerin farklı davranırken tek tip olarak ele alınmasına olanak tanıyan temel bir OOP kavramıdır. C++, her biri program yürütülmesinin farklı bir aşamasında çözümlenen iki farklı polimorfizm türünü destekler.

Derleme zamanı polimorfizmi (statik polimorfizm olarak da adlandırılır), program çalışmadan önce derleyici tarafından çözümlenir. Derleyici, fonksiyon imzasına dayanarak tam olarak hangi fonksiyonun çağrılacağını belirler. Bu, fonksiyon aşırı yüklemesini ve şablonları içerir:

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

print(5);      // Derleyici print(int) fonksiyonunu seçer
print(3.14);   // Derleyici print(double) fonksiyonunu seçer

Çalışma zamanı polimorfizmi (dinamik polimorfizm olarak da adlandırılır), program çalışırken çözümlenir. Hangi fonksiyonun çağrılacağına dair karar, işaretçi veya referans türüne değil, gerçek nesne türüne bağlıdır. Bu, sanal fonksiyonlar (virtual functions) aracılığıyla gerçekleştirilir:

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();  // Çalışma zamanında karar verildi: "Drawing circle"

Temel ödünleşim: derleme zamanı polimorfizmi, kararlar derleme sırasında verildiği için sıfır çalışma zamanı yüküne sahiptir; çalışma zamanı polimorfizmi ise küçük bir maliyet (vtable araması) ekler ancak türleri yürütme anına kadar bilinmeyen nesnelerle çalışmak için daha fazla esneklik sağlar.

challenge icon

Görev

Kolay

Her iki polimorfizm türünü yan yana gösteren bir hesap makinesi sistemi kuralım. Derleme zamanı (compile-time) polimorfizminin fonksiyon aşırı yükleme (overloading) yoluyla farklı girdi türlerini işlediği, çalışma zamanı (runtime) polimorfizminin ise farklı hesaplama stratejilerinin dinamik olarak değiştirilmesine izin verdiği bir sistem oluşturacaksınız.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • Calculator.h: Herhangi bir hesaplama stratejisini temsil eden bir temel Calculator sınıfı tanımlayın:
    • Bir int döndüren ve şunu yazdıran sanal bir calculate(int a, int b) metodu: Base calculation: <a> ? <b> (0 döndürerek)
    • Sanal bir yıkıcı (destructor)
  • Operations.h: Hesaplama davranışını geçersiz kılan (override) iki türetilmiş hesap makinesi sınıfı tanımlayın:
    • Adder: calculate() metodunu Adding: <a> + <b> yazdıracak ve toplamı döndürecek şekilde geçersiz kılın
    • Multiplier: calculate() metodunu Multiplying: <a> * <b> yazdıracak ve çarpımı döndürecek şekilde geçersiz kılın
    Her iki sınıf da override anahtar kelimesini kullanmalıdır.
  • main.cpp: Her iki polimorfizm türünü de sergileyen bir sistem oluşturun. İki tam sayı girdisi okuyun (her biri ayrı bir satırda).

    İlk olarak, üç adet aşırı yüklenmiş display() fonksiyonu oluşturarak derleme zamanı polimorfizmini gösterin:

    • display(int x) şunu yazdırır: Integer value: <x>
    • display(double x) şunu yazdırır: Double value: <x>
    • display(const std::string& x) şunu yazdırır: String value: <x>

    Ardından, bir temel Calculator, bir Adder ve bir Multiplier içeren bir Calculator* işaretçi dizisi oluşturarak çalışma zamanı polimorfizmini gösterin. Bir döngü içinde her biri üzerinde girdi değerlerinizle calculate() metodunu çağırın ve her hesaplamadan sonra sonucu yazdırın.

    Çıktınızı şu şekilde yapılandırın:

    === Compile-Time Polymorphism ===
    <int, double, string için display çıktıları>
    
    === Runtime Polymorphism ===
    <sonuçlarla birlikte calculate çıktıları>

    Derleme zamanı bölümü için, display() fonksiyonunu ilk girdiyi bir tam sayı olarak, ardından bir double olarak (0.5 eklenmiş aynı değer) ve son olarak "Result" dizesi olarak çağırın. İşiniz bittiğinde dinamik olarak ayrılmış hesap makinelerinizi temizleyin.

Örneğin, 10 ve 3 girdileriyle:

=== 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

Derleyicinin argüman türüne göre doğru display() aşırı yüklemesini nasıl seçtiğine (derleme zamanı kararı), doğru calculate() metodunun ise vtable mekanizması aracılığıyla çalışma zamanındaki gerçek nesne türü tarafından nasıl belirlendiğine dikkat edin.

Kopya kağıdı

C++ farklı aşamalarda çözümlenen iki tür polimorfizmi (çok biçimlilik) destekler:

Derleme zamanı polimorfizmi (statik polimorfizm), yürütmeden önce derleyici tarafından çözümlenir. Fonksiyon aşırı yüklemesi (function overloading) ve şablonları (templates) içerir:

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

print(5);      // Compiler chooses print(int)
print(3.14);   // Compiler chooses print(double)

Çalışma zamanı polimorfizmi (dinamik polimorfizm), sanal fonksiyonlar (virtual functions) kullanılarak yürütme sırasında çözümlenir. Hangi fonksiyonun çağrılacağını gerçek nesne türü belirler:

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();  // Decided at runtime: "Drawing circle"

Dengeleme (Trade-off): Derleme zamanı polimorfizminin çalışma zamanı maliyeti sıfırdır; çalışma zamanı polimorfizmi ise küçük bir maliyet (vtable araması) ekler ancak daha fazla esneklik sağlar.

Kendin dene

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

// TODO: Üç adet aşırı yüklenmiş display() fonksiyonu oluşturun:
// 1. display(int x) - "Integer value: <x>" yazdırır
// 2. display(double x) - "Double value: <x>" yazdırır
// 3. display(const std::string& x) - "String value: <x>" yazdırır



int main() {
    // İki tam sayı girdisi oku
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Derleme Zamanı Çok Biçimliliği ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: display() fonksiyonunu şunlarla çağırın:
    // - tam sayı olarak a
    // - double olarak a (üzerine 0.5 ekleyin)
    // - "Result" dizesi
    
    
    std::cout << std::endl;
    
    // === Çalışma Zamanı Çok Biçimliliği ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: 3 elemanlı bir Calculator* işaretçi dizisi oluşturun:
    // - bir temel Calculator
    // - bir Adder
    // - bir Multiplier
    
    // TODO: Dizi üzerinde döngü kurun, her biri için calculate(a, b) fonksiyonunu çağırın,
    // ve her hesaplamadan sonra "Result: <return_value>" yazdırın
    
    
    // TODO: Dinamik olarak ayrılmış belleği 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