C: الاتحادات والتعدادات
الاتحاد ككابل شحن متعدد الاستخدامات — يمكنك استخدام واجهة واحدة فقط في كل مرة، لكنه يوفر المساحة. التعداد كرقم قائمة — استخدام الأسماء بدل الأرقام الخام يجعل الكود أكثر قابلية للقراءة.
1. تعريف الاتحاد والذاكرة المشتركة
جميع أعضاء union يتشاركون نفس الذاكرة. حجمه يساوي حجم أكبر عضو. يمكن استخدام عضو واحد فقط في كل مرة.
union Data {
int i;
float f;
char str[4];
};
printf("%zu\n", sizeof(union Data));
4
بعد الإسناد للعضو int، قراءة العضو float تعطي تفسيرًا مختلفًا لنفس الذاكرة:
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
i = 42
f = 0.000000
2. الاتحاد مقابل البنية
| الميزة | البنية (struct) | الاتحاد (union) |
|---|---|---|
| الذاكرة | كل عضو له مساحته الخاصة | جميع الأعضاء يتشاركون نفس المساحة |
| الحجم | مجموع أحجام جميع الأعضاء (مع حشو المحاذاة) | حجم أكبر عضو |
| الوصول المتزامن | يمكن الوصول لجميع الأعضاء في نفس الوقت | عضو واحد فقط في كل مرة |
| التعديل | تعديل عضو لا يؤثر على البقية | تعديل عضو يؤثر على الجميع |
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));
struct: 12
union: 4
3. الاتحادات الموسومة
عمليًا، يُقرن الاتحاد عادةً بحقل وسم يُشير إلى النوع المخزن حاليًا:
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;
}
}
▶ مثال
#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;
}
int: 42
double: 3.14
4. تعريف التعداد واستخدامه
التعدادات تستخدم الأسماء بدل الثوابت الصحيحة، مما يحسّن قابلية قراءة الكود.
enum Color {
RED,
GREEN,
BLUE
};
enum Color favorite = GREEN;
if (favorite == GREEN) {
printf("Green selected\n");
}
Green selected
(1) تحديد قيم التعداد
enum Weekday {
MON = 1,
TUE,
WED,
THU,
FRI,
SAT = 100,
SUN
};
printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
MON=1 SAT=100 SUN=101
(2) التعداد مع switch
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.
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
200 404 500
متغيرات التعداد يمكن إسنادها من وإلى الأعداد الصحيحة (في C)، لكن من الأفضل استخدام قيم التعداد لتناسق النوع:
enum Status s = OK;
int code = s;
#define للثوابت: التعدادات لها معلومات نوع، تعرض أسماء رمزية في المصححات، ولها نطاق أكثر تحكمًا.
6. استخدام الاتحاد لكشف ترتيب البايتات
ترتيب البايتات يشير إلى تسلسل تخزين بيانات متعددة البايتات في الذاكرة. الاتحاد يجعل الكشف سهلاً:
int is_little_endian(void) {
union {
int i;
char c;
} u;
u.i = 1;
return u.c == 1;
}
إذا خُزن البايت الأدنى في العنوان الأدنى (u.c == 1)، فهو ترتيب البايتات الصغير؛ وإلا فهو ترتيب البايتات الكبير.
▶ مثال
#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;
}
Memory byte order: 78 56 34 12
Little Endian
7. الاتحاد لتوفير الذاكرة عمليًا
عندما تحتوي بنية على عدة حقول يستبعد بعضها بعضًا، استخدم اتحادًا لتوفير المساحة:
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;
واحدة فقط من الأشكال الثلاثة تُستخدم في كل مرة. الاتحاد يتشارك الذاكرة، وحجم البنية يحدده أكبر شكل.
❓ أسئلة شائعة
const char *names[] = {"RED","GREEN","BLUE"}.📖 ملخص
- جميع أعضاء الاتحاد يتشاركون الذاكرة؛ الحجم يساوي أكبر عضو؛ يُستخدم عضو واحد فقط في كل مرة
- عمليًا، يُقرن الاتحاد بحقل وسم (اتحاد موسوم) للتمييز بين النوع الصالح حاليًا
- enum يستبدل الأرقام السحرية بثوابت مُسمّاة، مما يحسّن القراءة؛ النوع الكامن هو int
- قيم التعداد تزداد من 0 افتراضيًا؛ يمكنك تحديد أي قيمة صحيحة يدويًا
- الاتحاد يمكنه كشف ترتيب بايتات النظام — تسلسل تخزين البيانات متعددة البايتات
📝 تمارين
- عرّف
union IPAddressيحتويunsigned int addrوunsigned char bytes[4]. أدخل قيمة IP صحيحة وأخرجها كعدد صحيح وأربع بايتات منفصلة - عرّف تعداد
enum Season { SPRING, SUMMER, AUTUMN, WINTER }واكتب دالة تُرجع اسم الموسع المقابل كسلسلة نصية بناءً على قيمة التعداد - استخدم اتحادًا موسومًا لتنفيذ بنية شكل بسيطة مع وسم نوع واتحاد (مستطيل/دائرة)، ثم اكتب دالة لحساب المساحة