Menu
Coddy logo textTech

Jenerik Sarmalayıcı

Coddy'nin C Journey'sinin Object Oriented Programming bölümünün bir parçası — ders 50 / 61.

Tek başına bir void* herhangi bir veriyi saklayabilir, ancak kritik bir kusuru vardır: hangi türü tuttuğunun takibini kaybedersiniz. Gerçekten kullanışlı genel kapsayıcılar oluşturmak için, veri işaretçisini tür bilgisiyle eşleştirmemiz gerekir. İşte burada Generic Wrapper deseni devreye girer.

Fikir basit: Bir void* ile depolanan türü tanımlayan bir enum'u bir araya getiren bir struct oluşturun. Bu sayede, sarmalayıcı (wrapper) içeriğini nasıl yorumlayacağını her zaman bilir.

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;

typedef struct {
    void* data;
    DataType type;
} Wrapper;

Veri depolarken, gerçek değer için bellek ayırır ve türünü kaydedersiniz. Veriyi geri alırken, tür dönüştürme (casting) işleminden önce türü kontrol edersiniz:

Wrapper wrap_int(int value) {
    int* p = malloc(sizeof(int));
    *p = value;
    return (Wrapper){ .data = p, .type = TYPE_INT };
}

void print_wrapper(Wrapper* w) {
    if (w->type == TYPE_INT) {
        printf("%d\n", *(int*)w->data);
    } else if (w->type == TYPE_STRING) {
        printf("%s\n", (char*)w->data);
    }
}

Bu desen, C'de tip güvenli generic kapsayıcılar oluşturmanın temelidir. enum, bir çalışma zamanı tip etiketi görevi görerek kodunuzun saklanan veriyi nasıl işleyeceği konusunda güvenli kararlar vermesini sağlar.

challenge icon

Görev

Kolay

Daha sonra verileri güvenli bir şekilde alıp yazdırabilmemiz için hangi türü tuttuğunu takip eden, bir tam sayı veya bir dize saklayabilen tür açısından güvenli (type-safe) bir genel sarmalayıcı (generic wrapper) sistemi oluşturalım.

Kodunuzu üç dosya halinde düzenleyeceksiniz:

  • wrapper.h: TYPE_INT ve TYPE_STRING değerlerine sahip bir DataType enum tanımlayın. Ardından, bir void* veri göstericisi ile bir DataType alanını birleştiren bir Wrapper struct tanımlayın. Sarmalayıcılar oluşturmak ve içeriklerini yazdırmak için fonksiyon prototiplerini bildirin.
  • wrapper.c: Sarmalayıcı işlevselliğini uygulayın:
    • wrap_int — bir tam sayı alır, onun için bellek ayırır, değeri saklar ve uygun tür etiketiyle bir Wrapper döndürür
    • wrap_string — bir dize (char*) alır, bellek ayırır ve dizeyi kopyalar ve dize türü etiketiyle bir Wrapper döndürür
    • print_wrapper — tür etiketini kontrol eder ve verileri uygun şekilde yazdırır (tam sayı olduğu gibi, dize olduğu gibi)
    • free_wrapper — sarmalayıcı içindeki dinamik olarak ayrılmış belleği serbest bırakır
  • main.c: Hepsini bir araya getirin. Bir tür göstergesi (tam sayı için i, dize için s) ve ardından değeri okuyun. Uygun sarmalayıcıyı oluşturun, içeriğini yazdırın ve belleği serbest bırakın.

Programınız iki girdi alacaktır:

  1. Bir tür göstergesi: tam sayı için i veya dize için s
  2. Sarmalanacak değer

Girdiler i ve 42 olduğunda örnek çıktı:

42

Girdiler s ve Hello olduğunda örnek çıktı:

Hello

Girdiler i ve -100 olduğunda örnek çıktı:

-100

Girdiler s ve Generic Programming olduğunda örnek çıktı:

Generic Programming

Başlık dosyanızda include guard kullanmayı unutmayın. Dize kopyalama için, strlen ve strcpy kullanarak dize artı null sonlandırıcı için yeterli alan ayırın.

Kopya kağıdı

Generic Wrapper (Genel Sarmalayıcı) deseni, tür açısından güvenli genel kapsayıcılar oluşturmak için bir void* işaretçisini tür bilgisiyle eşleştirir. Bu, hangi türün saklandığı bilgisini korurken herhangi bir veri türünü saklamanıza olanak tanır.

Saklanan türleri tanımlamak için bir enum tanımlayın:

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;

Veri işaretçisini tür etiketiyle birleştiren bir struct oluşturun:

typedef struct {
    void* data;
    DataType type;
} Wrapper;

Veri saklarken, bellek ayırın ve türü kaydedin:

Wrapper wrap_int(int value) {
    int* p = malloc(sizeof(int));
    *p = value;
    return (Wrapper){ .data = p, .type = TYPE_INT };
}

Veriyi geri alırken, tür dönüşümü (casting) yapmadan önce tür etiketini kontrol edin:

void print_wrapper(Wrapper* w) {
    if (w->type == TYPE_INT) {
        printf("%d\n", *(int*)w->data);
    } else if (w->type == TYPE_STRING) {
        printf("%s\n", (char*)w->data);
    }
}

Enum, çalışma zamanı tür etiketi (runtime type tag) görevi görerek güvenli tür kontrolü ve saklanan verilerin uygun şekilde işlenmesini sağlar.

Kendin dene

#include <stdio.h>
#include <string.h>
#include "wrapper.h"

int main() {
    char type;
    scanf("%c", &type);
    getchar(); // yeni satır karakterini tüket
    
    Wrapper w;
    
    if (type == 'i') {
        int value;
        scanf("%d", &value);
        // TODO: wrap_int kullanarak bir tamsayı (integer) sarmalayıcı oluşturun
        
    } else if (type == 's') {
        char str[256];
        fgets(str, sizeof(str), stdin);
        // Eğer varsa sondaki yeni satır karakterini kaldırın
        str[strcspn(str, "\n")] = '\0';
        // TODO: wrap_string kullanarak bir dize (string) sarmalayıcı oluşturun
        
    }
    
    // TODO: print_wrapper kullanarak sarmalayıcı içeriğini yazdırın
    
    // TODO: free_wrapper kullanarak sarmalayıcı belleğini serbest bırakın
    
    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.

Object Oriented Programming bölümündeki tüm dersler