Polimorfik İterasyon
Coddy'nin C Journey'sinin Object Oriented Programming bölümünün bir parçası — ders 42 / 61.
Arayüzler oluşturmayı ve bunları farklı somut fonksiyonlarla uygulamayı öğrendiniz. Asıl kazanç, farklı nesnelerden oluşan bir koleksiyona tek tip bir şekilde davranabildiğinizde — içlerinde hangi özel davranışın tanımlandığına bakılmaksızın, her birinin üzerinde dönerek aynı fonksiyon işaretçisini çağırdığınızda — ortaya çıkar.
Her birine kendi execute fonksiyonu atanmış Action struct'larından oluşan bir diziniz olduğunu hayal edin:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Bu eylemlerden oluşan bir dizi oluşturabilir, her birini farklı bir fonksiyona bağlayabilir ve ardından basit bir for döngüsü ile bunlar üzerinde dönebilirsiniz:
int main() {
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}
return 0;
}Döngü, her bir eylemin dahili olarak ne yaptığını bilmez veya umursamaz. Sadece her bir öğe üzerinde execute() metodunu çağırır.
Bu, uygulamadaki polimorfizmdir; aynı kod, tamamen farklı davranışlara sahip nesneleri işler. Yeni bir eylem türü eklemek, yineleme mantığında herhangi bir değişiklik gerektirmez; sadece diziye başka bir öğe eklersiniz.
Görev
KolayOrtak bir arayüzü paylaşan ancak farklı davranışlar sergileyen bir nesne koleksiyonunu işlemeyi, yani polimorfik yinelemeyi (polymorphic iteration) gösteren bir Task yönetim sistemi kuralım.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
task.h: Görev arayüzünüzü burada tanımlayın. Parametre almayan ve hiçbir şey döndürmeyenTaskFuncadında bir fonksiyon göstericisi (function pointer) türü oluşturun. Ardından, birname(birconst char*) veTaskFunctüründe birrunfonksiyon göstericisi içeren birTaskyapısı (struct) tanımlayın.task.c: Farklı iş türlerini temsil eden üç farklı görev fonksiyonu uygulayın:backup_task—Backing up data...yazdırır.cleanup_task—Cleaning up files...yazdırır.report_task—Generating report...yazdırır.
main.c: Her şeyi burada birleştirin. Her biri farklı bir görev fonksiyonuna bağlanmış birTaskyapısı dizisi oluşturun. Ardından dizi boyunca ilerleyin ve her görevi polimorfik olarak çalıştırın — her görev için adını yazdırın ve ardındanrunfonksiyonunu çağırın.
Programınız tek bir girdi alacaktır: çalıştırılacak görev sayısı (1, 2 veya 3).
Tam olarak şu sırayla üç görev içeren bir dizi oluşturun: Backup adında bir yedekleme görevi, Cleanup adında bir temizleme görevi ve Report adında bir rapor görevi. Girdi olarak alınan sayıya göre, dizinin başından itibaren sadece o kadar görev üzerinde ilerleyin.
Yinelemenizdeki her görev için, görevin adını, ardından bir iki nokta üst üste ve boşluk yazdırın, sonra run fonksiyonunu çağırın.
Girdi 2 olduğunda örnek çıktı:
Backup: Backing up data...
Cleanup: Cleaning up files...Girdi 3 olduğunda örnek çıktı:
Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...Buradaki temel nokta, döngünüzün her bir görevin ne yaptığını bilmesine gerek olmamasıdır — sadece adı yazdırır ve her öğe üzerinde run() fonksiyonunu çağırır. Aynı yineleme kodu tüm görev türlerini tek tip olarak işler. Başlık dosyanızda include guard kullanmayı unutmayın.
Kopya kağıdı
Ortak bir arayüzü paylaşan nesne koleksiyonları üzerinde döngü kurabilir ve her bir öğenin özel uygulamasından bağımsız olarak her öğe üzerinde aynı fonksiyon işaretçisini çağırabilirsiniz. Bu polimorfizmdir (çok biçimlilik) — farklı nesnelere tek tip davranmak.
Bir fonksiyon işaretçisi türü ve bunu kullanan bir struct tanımlayın:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;İmzaya uygun somut fonksiyonlar oluşturun:
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Her biri farklı bir fonksiyona bağlanmış bir struct dizisi oluşturun, ardından bunlar üzerinde döngü kurun:
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}Döngünün her bir eylemin dahili olarak ne yaptığını bilmesine gerek yoktur — sadece her öğe üzerindeki fonksiyon işaretçisini çağırır. Yeni türler eklemek, döngü mantığında herhangi bir değişiklik gerektirmez.
Kendin dene
#include <stdio.h>
#include "task.h"
// task.c dosyasında uygulanan görev fonksiyonlarını bildirin
void backup_task(void);
void cleanup_task(void);
void report_task(void);
int main() {
int count;
scanf("%d", &count);
// TODO: Bu sırayla 3 Task struct'ından oluşan bir dizi oluşturun:
// 1. backup_task kullanan "Backup" adlı yedekleme görevi
// 2. cleanup_task kullanan "Cleanup" adlı temizleme görevi
// 3. report_task kullanan "Report" adlı raporlama görevi
// TODO: Dizideki 'count' kadar görev boyunca döngü kurun
// Her görev için şunu yazdırın: "<name>: " ardından run fonksiyonunu çağırın
return 0;
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Object Oriented Programming bölümündeki tüm dersler
1Modüler Programlama Temelleri
Başlık DosyalarıInclude Guard'larKaynak DosyalarıStatik FonksiyonlarÖzet: Modüler Hesap Makinesi4Kapsülleme
Opaque Pointer KavramıOpaque Struct TanımlamaGetter ve Setter MetotlarıSetter Metotlarında DoğrulamaÖzet: Gizli Kutu2Nesneler ve Metotlar
Nesne Olarak Struct'lar'Self' İşaretçisiConst Doğruluğuİşaretçi vs DeğerYardımcı MetotlarÖzet: Point Manager5Proje: Basit Banka Hesabı
Proje KurulumuHesabın Oluşturulması8Polimorfizm
Struct'larda Fonksiyon İşaretçileriMetotları Simüle EtmeArayüz KavramıArayüzleri UygulamaPolimorfik İterasyonÖzet: Greeter3Nesne Yaşam Döngüsü
Constructor DeseniDestructor DeseniStack İlklendirmeDerin KopyalamaÖzet: String Wrapper6Kompozisyon Yoluyla Kalıtım
Struct Gömmeİlk Üye KuralıÜst Üyelere ErişimUpcastingÖzet: Şekil Hiyerarşisi