Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

مغلف عام

جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 50 من 61.

يمكن لمؤشر void* بمفرده تخزين أي بيانات، ولكن لديه عيب جوهري: وهو أنك تفقد تتبع النوع الذي يحمله. لبناء حاويات عامة (generic containers) مفيدة حقاً، نحتاج إلى إقران مؤشر البيانات بمعلومات النوع. وهنا يأتي دور نمط Generic Wrapper.

الفكرة بسيطة: قم بإنشاء struct يجمع بين void* و enum يحدد النوع المخزن. بهذه الطريقة، يعرف الـ wrapper دائماً كيفية تفسير محتوياته.

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;

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

عند تخزين البيانات، تقوم بتخصيص مساحة في الذاكرة للقيمة الفعلية وتسجيل نوعها. وعند الاسترجاع، تتحقق من النوع قبل إجراء عملية التحويل (casting):

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);
    }
}

هذا النمط هو الأساس لبناء حاويات عامة آمنة من حيث النوع في لغة C. يعمل الـ enum كعلامة نوع في وقت التشغيل (runtime type tag)، مما يسمح للكود الخاص بك باتخاذ قرارات آمنة حول كيفية التعامل مع البيانات المخزنة.

challenge icon

التحدي

سهل

لنقم ببناء نظام تغليف عام (generic wrapper) آمن النوع يمكنه تخزين إما عدد صحيح أو سلسلة نصية، مع تتبع النوع الذي يحمله حتى نتمكن من استرداد البيانات وطباعتها بأمان لاحقاً.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • wrapper.h: قم بتعريف التعداد DataType بالقيم TYPE_INT و TYPE_STRING. ثم قم بتعريف هيكل Wrapper الذي يجمع بين مؤشر بيانات void* وحقل DataType. قم بالتصريح عن النماذج الأولية للدوال (function prototypes) لإنشاء الأغلفة وطباعة محتوياتها.
  • wrapper.c: قم بتنفيذ وظائف الغلاف:
    • wrap_int — يأخذ عدداً صحيحاً، ويخصص ذاكرة له، ويخزن القيمة، ويعيد Wrapper مع علامة النوع المناسبة.
    • wrap_string — يأخذ سلسلة نصية (char*)، ويخصص ذاكرة ويسخ السلسلة، ويعيد Wrapper مع علامة نوع السلسلة النصية.
    • print_wrapper — يتحقق من علامة النوع ويطبع البيانات بشكل مناسب (العدد الصحيح كما هو، والسلسلة النصية كما هي).
    • free_wrapper — يحرر الذاكرة المخصصة ديناميكياً داخل الغلاف.
  • main.c: اجمع كل شيء معاً. اقرأ مؤشر النوع (i للعدد الصحيح، s للسلسلة النصية) متبوعاً بالقيمة. أنشئ الغلاف المناسب، واطبع محتوياته، ثم حرر الذاكرة.

سيتلقى برنامجك مدخلين:

  1. مؤشر النوع: i للعدد الصحيح أو s للسلسلة النصية
  2. القيمة المراد تغليفها

مثال على المخرجات عندما تكون المدخلات i و 42:

42

مثال على المخرجات عندما تكون المدخلات s و Hello:

Hello

مثال على المخرجات عندما تكون المدخلات i و -100:

-100

مثال على المخرجات عندما تكون المدخلات s و Generic Programming:

Generic Programming

تذكر استخدام حراس التضمين (include guards) في ملف الرأس الخاص بك. لنسخ السلسلة النصية، خصص مساحة كافية للسلسلة بالإضافة إلى النهاية الصفرية (null terminator) باستخدام strlen و strcpy.

ورقة مرجعية

يقرن نمط المغلف العام (Generic Wrapper) مؤشر void* بمعلومات النوع لإنشاء حاويات عامة آمنة من حيث النوع. يتيح لك ذلك تخزين أي نوع من البيانات مع الحفاظ على معرفة النوع المخزن.

عرّف تعداداً (enum) لتحديد الأنواع المخزنة:

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;

أنشئ هيكلاً (struct) يجمع مؤشر البيانات مع وسم النوع:

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

عند تخزين البيانات، قم بتخصيص الذاكرة وتسجيل النوع:

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

عند استرداد البيانات، تحقق من وسم النوع قبل إجراء التحويل (casting):

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) كوسم للنوع في وقت التشغيل، مما يتيح التحقق الآمن من النوع والتعامل المناسب مع البيانات المخزنة.

جرّب بنفسك

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

int main() {
    char type;
    scanf("%c", &type);
    getchar(); // استهلاك سطر جديد
    
    Wrapper w;
    
    if (type == 'i') {
        int value;
        scanf("%d", &value);
        // TODO: إنشاء غلاف عدد صحيح باستخدام wrap_int
        
    } else if (type == 's') {
        char str[256];
        fgets(str, sizeof(str), stdin);
        // إزالة السطر الجديد الزائد إذا وجد
        str[strcspn(str, "\n")] = '\0';
        // TODO: إنشاء غلاف نصي باستخدام wrap_string
        
    }
    
    // TODO: طباعة محتويات الغلاف باستخدام print_wrapper
    
    // TODO: تحرير ذاكرة الغلاف باستخدام free_wrapper
    
    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming