تعريف الـ Opaque Structs
جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 18 من 61.
الآن بعد أن فهمت مفهوم المؤشرات المعتمة (opaque pointers)، دعنا نرى كيفية إكمال هذا النمط. يصرح ملف الترويسة (header) بوجود نوع ما، ولكن ملف المصدر (source file) هو المكان الذي تحدد فيه ما يوجد بداخله فعلياً.
إليك الإعداد الكامل. أولاً، ملف الترويسة (header) الذي يحتوي فقط على التصريح المسبق (forward declaration):
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // نوع غير مكتمل (Incomplete type)
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
بعد ذلك، يحتوي ملف المصدر على التعريف الفعلي للـ struct:
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // مخفي عن الخارج!
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
النقطة الأساسية: struct Counter { ... } يظهر فقط في ملف .c. أي ملف يتضمن counter.h يعرف أن Counter موجود ولكنه لا يستطيع رؤية value أو step. محاولة كتابة c->value في main.c ستؤدي إلى خطأ في المترجم (compiler error) - فالأعضاء غير مرئيين حقاً.
هذا الفصل هو أساس التغليف (encapsulation) في لغة C. الخطوة التالية هي توفير وصول متحكم فيه من خلال دوال الـ getter والـ setter.
التحدي
سهللنقم ببناء وحدة Timer توضح نمط المؤشر المعتم (opaque pointer pattern). ستتدرب على إخفاء تعريف الهيكل (struct) داخل ملف المصدر، مما يجعل أعضاءه الداخلية غير مرئية تماماً لأي كود يستخدم وحدتك.
ستقوم بإنشاء ثلاثة ملفات:
timer.h: هذه هي واجهتك العامة. قم بالتصريح عن نوعTimerمعتم باستخدام تصريح مسبق (forward declaration) فقط — لا تضع جسم الهيكل هنا! قم أيضاً بالتصريح عن دالتين:create_timer(تأخذ عدداً صحيحاً للثواني وتعيد مؤشر Timer) وfree_timer(تأخذ مؤشر Timer وتقوم بالتنظيف). استخدم حراس التضمين (include guards) مع الرمزTIMER_H.timer.c: هذا هو المكان الذي يعيش فيه السر. قم بتعريفstruct Timerالفعلي مع عضوين مخفيين:int secondsوint running(استخدم 1 للتشغيل، و 0 للإيقاف). قم بتنفيذ المنشئ (constructor) لتخصيص الهيكل، وتهيئةsecondsبالقيمة المقدمة، وتعيينrunningإلى 1. قم بتنفيذ المدمر (destructor) لتحرير الذاكرة.main.c: استخدم وحدة Timer الخاصة بك لإنشاء مؤقت والتأكد من إنشائه بنجاح. نظراً لأن أعضاء الهيكل مخفية، فلا يمكنك الوصول إليها مباشرة — وهذا هو الهدف الأساسي! ببساطة اطبع رسالة تؤكد الإنشاء، ثم قم بتحرير المؤقت واطبع رسالة تنظيف.
ستتلقى مدخلاً واحداً: عدد الثواني (عدد صحيح) لتهيئة المؤقت به.
في ملفك الرئيسي، قم بإنشاء مؤقت باستخدام قيمة الثواني المقدمة. اطبع رسالة إنشاء، ثم قم بتحرير المؤقت واطبع تأكيداً.
اطبع المخرجات بهذا التنسيق:
Timer created
Timer freedالبصيرة الأساسية هنا هي أن main.c يمكنه العمل مع مؤشرات Timer ولكنه لا يملك طريقة للوصول إلى seconds أو running مباشرة. محاولة كتابة t->seconds في main.c ستؤدي إلى خطأ في المترجم لأن جسم الهيكل محدد فقط في timer.c. هذا هو التغليف (encapsulation) الحقيقي — تفاصيل التنفيذ مخفية تماماً عن مستخدمي وحدتك.
ورقة مرجعية
يفصل نمط المؤشر المعتم (opaque pointer pattern) بين التصريح عن النوع وتعريفه، مما يخفي تفاصيل التنفيذ.
يحتوي ملف الترويسة (Header file) على تصريح مسبق فقط:
// counter.h
#ifndef COUNTER_H
#define COUNTER_H
typedef struct Counter Counter; // نوع غير مكتمل
Counter *create_counter(int start);
void free_counter(Counter *c);
#endif
يحتوي ملف المصدر (Source file) على التعريف الفعلي للهيكل (struct):
// counter.c
#include "counter.h"
#include <stdlib.h>
struct Counter {
int value; // مخفي عن الخارج
int step;
};
Counter *create_counter(int start) {
Counter *c = malloc(sizeof(Counter));
c->value = start;
c->step = 1;
return c;
}
void free_counter(Counter *c) {
free(c);
}
يظهر تعريف الهيكل (struct) فقط في ملف .c. الملفات التي تتضمن الترويسة تعرف بوجود النوع ولكن لا يمكنها الوصول إلى أعضائه مباشرة. يوفر هذا تغليفاً (encapsulation) حقيقياً في لغة C.
جرّب بنفسك
#include <stdio.h>
#include "timer.h"
int main() {
int seconds;
scanf("%d", &seconds);
// TODO: إنشاء مؤقت بالقيمة المعطاة لـ seconds
// TODO: طباعة "Timer created"
// TODO: تحرير المؤقت
// TODO: طباعة "Timer freed"
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مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة