مغلف عام
جزء من قسم البرمجة كائنية التوجه في رحلة C على Coddy. الدرس 50 من 61.
يمكن لـ void* وحده تخزين أي بيانات، لكنه يعاني من عيب جوهري: تفقد معرفة نوع البيانات التي يحتوي عليها. ولإنشاء حاويات Generic مفيدة حقًا، نحتاج إلى إقران pointer البيانات بمعلومات النوع. وهنا يأتي دور نمط Generic Wrapper.
الفكرة بسيطة: أنشئ struct يجمع بين void* وenum يحدد النوع المخزّن. بهذه الطريقة، يعرف الغلاف دائمًا كيفية تفسير contents.
typedef enum {
TYPE_INT,
TYPE_STRING
} DataType;
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 };
}
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);
}
}هذا النمط هو الأساس لبناء حاويات generic آمنة من حيث النوع في لغة C. يعمل enum كوسم نوع وقت التشغيل، مما يتيح لكودك اتخاذ قرارات آمنة حول كيفية التعامل مع data المخزنة.
التحدي
سهللنَبْنِ نظام تغليف Generic آمن من حيث النوع يمكنه تخزين إما قيمة integer أو قيمة string، مع تتبّع نوع القيمة التي يحتوي عليها حتى نتمكن من استرجاع data وطباعتها بأمان لاحقًا.
ستنظّم الكود عبر ثلاثة ملفات:
wrapper.h: عرّف تعدادDataTypeبالقيمTYPE_INTوTYPE_STRING. ثم عرّف بنيةWrapperالتي تجمع مؤشر data من النوعvoid*مع field من النوعDataType. صرّح بـ function prototypes الخاصة بإنشاء الأغلفة وطباعة contents الخاصة بها.wrapper.c: Implement وظيفة التغليف:wrap_int: تستقبل integer، وتخصص memory له، وتخزن القيمة، ثم تعيد Wrapper مع وسم النوع appropriatelywrap_string: تستقبل string (char*)، وتخصص memory وتنسخ string، ثم تعيد Wrapper مع وسم نوع stringprint_wrapper: تتحقق من وسم النوع وتطبع data appropriately (integer كما هي، وstring كما هي)free_wrapper: تحرر data المخصصة dynamically داخل الغلاف
main.c: اجمع كل ذلك معًا. اقرأ مؤشر نوع (iلـ integer أوsلـ string) متبوعًا بالقيمة. Create الغلاف appropriately، واطبع contents الخاصة به، ثم حرر memory.
سيستقبل برنامجك مدخلين:
- مؤشر نوع:
iلـ integer أوsلـ string - القيمة المراد تغليفها
مثال على المخرجات عندما تكون المدخلات i و42:
42مثال على المخرجات عندما تكون المدخلات s وHello:
Helloمثال على المخرجات عندما تكون المدخلات i و-100:
-100مثال على المخرجات عندما تكون المدخلات s وGeneric Programming:
Generic Programmingتذكّر استخدام include guards في ملف الترويسة. لنسخ string، خصص مساحة كافية لـ string بالإضافة إلى null terminator باستخدام strlen وstrcpy.
جرّب بنفسك
#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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرةتنفيذ المستطيلاستخدام تعدد الأشكالحاوية الأشكالتدرّب بنفسك: مترجم C عبر الإنترنت