مغلف عام
جزء من قسم 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)، مما يسمح للكود الخاص بك باتخاذ قرارات آمنة حول كيفية التعامل مع البيانات المخزنة.
التحدي
سهللنقم ببناء نظام تغليف عام (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للسلسلة النصية) متبوعاً بالقيمة. أنشئ الغلاف المناسب، واطبع محتوياته، ثم حرر الذاكرة.
سيتلقى برنامجك مدخلين:
- مؤشر النوع:
iللعدد الصحيح أوsللسلسلة النصية - القيمة المراد تغليفها
مثال على المخرجات عندما تكون المدخلات 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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات البرمجة التركيبية
ملفات الترويسةحواجز التضمينملفات المصدرالدوال الساكنةمراجعة: آلة حاسبة تركيبية4التغليف (Encapsulation)
مفهوم الـ Opaque Pointersتعريف الـ Opaque Structsالـ Getters والـ Settersالتحقق من البيانات في الـ Settersملخص: الصندوق السري2الكائنات والأساليب
الـ Structs ككائناتمؤشر 'Self'صحة استخدام Constالمؤشر مقابل القيمةالأساليب المساعدةملخص: Point Manager5مشروع: حساب بنكي بسيط
إعداد المشروعتنفيذ الحساب3دورة حياة الكائن
نمط المنشئ (Constructor Pattern)نمط الهادم (Destructor Pattern)تهيئة الـ Stackالنسخ العميق (Deep Copy)مراجعة: String Wrapper6الوراثة عبر التركيب
تضمين الـ Structقاعدة العضو الأولالوصول إلى أعضاء الأبعملية الـ Upcastingمراجعة: هرمية الأشكال9مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة