C: الاتحادات والتعدادات

الاتحاد ككابل شحن متعدد الاستخدامات — يمكنك استخدام واجهة واحدة فقط في كل مرة، لكنه يوفر المساحة. التعداد كرقم قائمة — استخدام الأسماء بدل الأرقام الخام يجعل الكود أكثر قابلية للقراءة.

1. تعريف الاتحاد والذاكرة المشتركة

جميع أعضاء union يتشاركون نفس الذاكرة. حجمه يساوي حجم أكبر عضو. يمكن استخدام عضو واحد فقط في كل مرة.

C
union Data {
    int i;
    float f;
    char str[4];
};

printf("%zu\n", sizeof(union Data));
TEXT 📖 للعرض فقط
4

بعد الإسناد للعضو int، قراءة العضو float تعطي تفسيرًا مختلفًا لنفس الذاكرة:

C
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
TEXT 📖 للعرض فقط
i = 42
f = 0.000000
⚠️ ملاحظة: قراءة عضو غير آخر عضو كُتب فيه هي سلوك يعتمد على التنفيذ (يعتمد على المنصة والأنواع). عمليًا، استخدم حقل وسم للتمييز بين العضو الصالح حاليًا.


2. الاتحاد مقابل البنية

الميزة البنية (struct) الاتحاد (union)
الذاكرة كل عضو له مساحته الخاصة جميع الأعضاء يتشاركون نفس المساحة
الحجم مجموع أحجام جميع الأعضاء (مع حشو المحاذاة) حجم أكبر عضو
الوصول المتزامن يمكن الوصول لجميع الأعضاء في نفس الوقت عضو واحد فقط في كل مرة
التعديل تعديل عضو لا يؤثر على البقية تعديل عضو يؤثر على الجميع
C
struct SData {
    int i;
    float f;
    char str[4];
};

union UData {
    int i;
    float f;
    char str[4];
};

printf("struct: %zu\n", sizeof(struct SData));
printf("union:  %zu\n", sizeof(union UData));
TEXT 📖 للعرض فقط
struct: 12
union:  4

3. الاتحادات الموسومة

عمليًا، يُقرن الاتحاد عادةً بحقل وسم يُشير إلى النوع المخزن حاليًا:

C
typedef struct {
    int type;
    union {
        int int_val;
        float float_val;
        char str_val[32];
    } value;
} Variant;

void print_variant(const Variant *v) {
    switch (v->type) {
        case 0: printf("int: %d\n", v->value.int_val); break;
        case 1: printf("float: %f\n", v->value.float_val); break;
        case 2: printf("str: %s\n", v->value.str_val); break;
    }
}

▶ مثال

C
#include <stdio.h>

typedef struct {
    int kind;
    union {
        int ival;
        double dval;
    } u;
} Number;

void print_number(const Number *n) {
    if (n->kind == 0) {
        printf("int: %d\n", n->u.ival);
    } else {
        printf("double: %.2f\n", n->u.dval);
    }
}

int main(void) {
    Number a = {0, .u.ival = 42};
    Number b = {1, .u.dval = 3.14};
    print_number(&a);
    print_number(&b);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
int: 42
double: 3.14

4. تعريف التعداد واستخدامه

التعدادات تستخدم الأسماء بدل الثوابت الصحيحة، مما يحسّن قابلية قراءة الكود.

C
enum Color {
    RED,
    GREEN,
    BLUE
};

enum Color favorite = GREEN;
if (favorite == GREEN) {
    printf("Green selected\n");
}
TEXT 📖 للعرض فقط
Green selected

(1) تحديد قيم التعداد

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
TEXT 📖 للعرض فقط
MON=1 SAT=100 SUN=101
💡 نصيحة: ثوابت التعداد بدون قيمة صريحة تأخذ تلقائيًا القيمة السابقة + 1. الأولى تساوي 0 افتراضيًا.

(2) التعداد مع switch

C
enum Direction { UP, DOWN, LEFT, RIGHT };

void move(enum Direction d) {
    switch (d) {
        case UP:    printf("Move up\n"); break;
        case DOWN:  printf("Move down\n"); break;
        case LEFT:  printf("Move left\n"); break;
        case RIGHT: printf("Move right\n"); break;
    }
}
⚠️ ملاحظة: التعدادات مدعومة بـ int — يمكنك إسناد أي قيمة صحيحة، وقد لا يحذّر المترجم. لا تُسيء استخدام هذه الميزة.


5. القيم الصحيحة الكامنة للتعدادات

ثوابت التعداد هي في جوهرها ثوابت مُسمّاة من النوع int.

C
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
TEXT 📖 للعرض فقط
200 404 500

متغيرات التعداد يمكن إسنادها من وإلى الأعداد الصحيحة (في C)، لكن من الأفضل استخدام قيم التعداد لتناسق النوع:

C
enum Status s = OK;
int code = s;
💡 نصيحة: يُفضّل استخدام التعدادات بدل #define للثوابت: التعدادات لها معلومات نوع، تعرض أسماء رمزية في المصححات، ولها نطاق أكثر تحكمًا.


6. استخدام الاتحاد لكشف ترتيب البايتات

ترتيب البايتات يشير إلى تسلسل تخزين بيانات متعددة البايتات في الذاكرة. الاتحاد يجعل الكشف سهلاً:

C
int is_little_endian(void) {
    union {
        int i;
        char c;
    } u;
    u.i = 1;
    return u.c == 1;
}

إذا خُزن البايت الأدنى في العنوان الأدنى (u.c == 1)، فهو ترتيب البايتات الصغير؛ وإلا فهو ترتيب البايتات الكبير.

▶ مثال

C
#include <stdio.h>

typedef union {
    unsigned int i;
    unsigned char bytes[4];
} EndianTest;

void print_byte_order(void) {
    EndianTest t;
    t.i = 0x12345678;
    printf("Memory byte order: ");
    for (int i = 0; i < 4; i++) {
        printf("%02X ", t.bytes[i]);
    }
    printf("\n");
    if (t.bytes[0] == 0x78) {
        printf("Little Endian\n");
    } else {
        printf("Big Endian\n");
    }
}

int main(void) {
    print_byte_order();
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Memory byte order: 78 56 34 12
Little Endian
🔥 خطأ شائع: معالجات x86/ARM عادةً بترتيب البايتات الصغير، بينما النقل الشبكي يستخدم ترتيب البايتات الكبير (ترتيب بايتات الشبكة). فهم ترتيب البايتات ضروري لتحليل البروتوكولات ومعالجة صيغ الملفات.


7. الاتحاد لتوفير الذاكرة عمليًا

عندما تحتوي بنية على عدة حقول يستبعد بعضها بعضًا، استخدم اتحادًا لتوفير المساحة:

C
typedef struct {
    int type;
    union {
        struct { int width, height; } rect;
        struct { int radius; } circle;
        struct { int side; } square;
    } shape;
} Figure;

Figure f;
f.type = 1;
f.shape.circle.radius = 10;

واحدة فقط من الأشكال الثلاثة تُستخدم في كل مرة. الاتحاد يتشارك الذاكرة، وحجم البنية يحدده أكبر شكل.


❓ أسئلة شائعة

س هل يمكن للاتحاد استخدام عدة أعضاء في نفس الوقت؟
ج لا. أعضاء الاتحاد يتشاركون الذاكرة — كتابة عضو تُلغي البقية. يجب قراءة العضو الذي كُتب آخرًا فقط.
س هل يمكن طباعة اسم التعداد بـ %s؟
ج لا. قيم التعداد هي أعداد صحيحة. لطباعة الأسماء، تحتاج كتابة دالة تعيين أو مصفوفة، مثل const char *names[] = {"RED","GREEN","BLUE"}.
س ما الفرق بين enum و #define للثوابت؟
ج enum له نوع (int)، يمكن للمصححات عرض الأسماء الرمزية، ونطاقه أكثر تحكمًا. #define استبدال نصي للمعالج بدون معلومات نوع. يُفضّل enum.
س لماذا استخدام الاتحاد لكشف ترتيب البايتات أفضل من تحويل المؤشر؟
ج هما متكافئان أساسًا، لكن نهج الاتحاد أكثر توافقًا مع روح أمان الأنواع في C، يتجنب مشاكل قاعدة الاسم المستعار الصارمة، وأكثر قابلية للقراءة.

📖 ملخص

📝 تمارين

  1. عرّف union IPAddress يحتوي unsigned int addr و unsigned char bytes[4]. أدخل قيمة IP صحيحة وأخرجها كعدد صحيح وأربع بايتات منفصلة
  2. عرّف تعداد enum Season { SPRING, SUMMER, AUTUMN, WINTER } واكتب دالة تُرجع اسم الموسع المقابل كسلسلة نصية بناءً على قيمة التعداد
  3. استخدم اتحادًا موسومًا لتنفيذ بنية شكل بسيطة مع وسم نوع واتحاد (مستطيل/دائرة)، ثم اكتب دالة لحساب المساحة
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%