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.
Görev
KolayHer 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 temelCalculatorsınıfı tanımlayın:- Bir
intdöndüren ve şunu yazdıran sanal bircalculate(int a, int b)metodu:Base calculation: <a> ? <b>(0 döndürerek) - Sanal bir yıkıcı (destructor)
- Bir
Operations.h: Hesaplama davranışını geçersiz kılan (override) iki türetilmiş hesap makinesi sınıfı tanımlayın:Adder:calculate()metodunuAdding: <a> + <b>yazdıracak ve toplamı döndürecek şekilde geçersiz kılınMultiplier:calculate()metodunuMultiplying: <a> * <b>yazdıracak ve çarpımı döndürecek şekilde geçersiz kılın
overrideanahtar 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, birAdderve birMultiplieriçeren birCalculator*işaretçi dizisi oluşturarak çalışma zamanı polimorfizmini gösterin. Bir döngü içinde her biri üzerinde girdi değerlerinizlecalculate()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: 30Derleyicinin 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;
}
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 Konteyner