الهياكل في C: struct و typedef و enum
حتى الآن كل متغير عندك يخزن معلومة واحدة: رقم أو حرف. لكن «الطالب» له اسم وعمر ودرجة معاً. هل ستنشئ ثلاث مصفوفات منفصلة وتحاول إبقاءها متطابقة؟ الحل الأنظف هو الهيكل (struct): نوع جديد تصممه أنت، يجمع عدة معلومات مترابطة تحت اسم واحد. وهو أقرب شيء في C إلى الكلاسات التي ستراها في اللغات الأحدث.
تعريف هيكل
Section titled “تعريف هيكل”struct Student { char name[30]; int age; float grade;};هذا التعريف قالب فقط ولا يحجز ذاكرة. يُكتب عادة في أعلى الملف قبل الدوال. والمعلومات بداخله تسمى الحقول (Fields)، ولا تنسَ الفاصلة المنقوطة بعد القوس الأخير.
الهيكل مثل استمارة تسجيل فارغة: فيها خانات محددة، وكل طالب يملأ نسخته الخاصة.
struct Student s1 = {"Sara", 14, 91.5f};
printf("%s %d %.1f\n", s1.name, s1.age, s1.grade);
s1.age = 15;strcpy(s1.name, "Sara Ali");printf("%s %d\n", s1.name, s1.age);Sara 14 91.5Sara Ali 15struct Student s1: متغير من النوع الجديد.{"Sara", 14, 91.5f}: القيم الأولية بترتيب الحقول.- النقطة
s1.age: الوصول إلى حقل للقراءة أو التعديل. - الحقل
nameمصفوفة حروف، فتعديله بـstrcpyوليس بعلامة=.
يمكنك أيضاً تسمية الحقول عند التهيئة، وهذا أوضح ولا يعتمد على الترتيب:
struct Student s2 = {.name = "Omar", .grade = 78.0f};printf("%s %d %.1f\n", s2.name, s2.age, s2.grade);Omar 0 78.0الحقل الذي لم تذكره (age) أخذ القيمة صفراً تلقائياً.
typedef: اسم أقصر
Section titled “typedef: اسم أقصر”كتابة struct Student في كل مرة متعبة. typedef تعطي النوع اسماً مختصراً:
typedef struct { char title[40]; int pages; float price;} Book;Book b = {"C Basics", 250, 120.0f};printf("%s: %d صفحة\n", b.title, b.pages);C Basics: 250 صفحةالآن Book اسم نوع كامل مثل int، وتكتبه بدون كلمة struct.
نسخ الهياكل
Section titled “نسخ الهياكل”بعكس المصفوفات، الهياكل تُنسخ بعلامة = مباشرة، وتُنسخ معها كل الحقول:
Book original = {"C Basics", 250, 120.0f};Book copy = original;
copy.pages = 300;printf("%d %d\n", original.pages, copy.pages);250 300النسخة مستقلة تماماً عن الأصل. لكن المقارنة بـ == بين هيكلين غير مسموحة في C: قارن الحقول واحداً واحداً.
مصفوفة هياكل
Section titled “مصفوفة هياكل”هنا تظهر القوة الحقيقية: قائمة كاملة من السجلات في متغير واحد.
Book library[3] = { {"C Basics", 250, 120.0f}, {"Pointers", 180, 95.5f}, {"Algorithms", 420, 210.0f},};
float total = 0;int longest = 0;for (int i = 0; i < 3; i++) { total += library[i].price; if (library[i].pages > library[longest].pages) { longest = i; }}printf("المجموع: %.2f\n", total);printf("الأطول: %s\n", library[longest].title);المجموع: 425.50الأطول: Algorithmslibrary[i].price: العنصر رقم i ثم الحقل price. مثل جدول بيانات: كل صف كتاب، وكل عمود حقل.
الهياكل والدوال: المؤشر والسهم
Section titled “الهياكل والدوال: المؤشر والسهم”عند تمرير هيكل لدالة تُنسخ كل حقوله، فأي تعديل داخل الدالة يضيع. وإذا كان الهيكل كبيراً فالنسخ يضيّع وقتاً وذاكرة. الحل الذي تعلمته: مرّر عنوانه.
void applyDiscount(Book *book, float percent) { book->price = book->price * (1 - percent / 100);}
void printBook(const Book *book) { printf("%s - %d صفحة - %.2f\n", book->title, book->pages, book->price);}Book b = {"C Basics", 250, 120.0f};printBook(&b);applyDiscount(&b, 25);printBook(&b);C Basics - 250 صفحة - 120.00C Basics - 250 صفحة - 90.00Book *book: الدالة تستقبل مؤشراً إلى هيكل.- السهم
book->price: الوصول إلى حقل عبر مؤشر. هو اختصار للكتابة الثقيلة(*book).price. const Book *book: وعد بأن الدالة ستقرأ فقط ولن تعدّل. استخدمها مع كل دالة لا تغيّر الهيكل.
هيكل داخل هيكل
Section titled “هيكل داخل هيكل”حقل الهيكل يمكن أن يكون هيكلاً آخر:
typedef struct { int day; int month; int year;} Date;
typedef struct { char name[30]; Date birthday;} Person;Person p = {"Laila", {12, 5, 2011}};printf("%s: %d/%d/%d\n", p.name, p.birthday.day, p.birthday.month, p.birthday.year);Laila: 12/5/2011enum: قيم لها أسماء
Section titled “enum: قيم لها أسماء”التعداد (enum) نوع قيمه قائمة محددة من الأسماء. بدل أن تتذكر أن 0 تعني «جديد» و 1 تعني «قيد الشحن»، تكتب الأسماء مباشرة:
typedef enum { NEW, SHIPPED, DELIVERED} Status;
typedef struct { int id; Status status;} Order;Order order = {1024, NEW};order.status = SHIPPED;
switch (order.status) { case NEW: printf("الطلب %d جديد\n", order.id); break; case SHIPPED: printf("الطلب %d في الطريق\n", order.id); break; case DELIVERED: printf("الطلب %d وصل\n", order.id); break;}printf("%d\n", DELIVERED);الطلب 1024 في الطريق2الأسماء في الحقيقة أعداد صحيحة تبدأ من صفر: NEW تساوي 0 و SHIPPED تساوي 1 و DELIVERED تساوي 2. لكن الكود صار يُقرأ كجملة مفهومة.
جرّب بنفسك
Section titled “جرّب بنفسك”- عرّف
typedef structباسمProductفيه الاسم والسعر والكمية. - أنشئ مصفوفة من أربعة منتجات، واطبع القيمة الإجمالية للمخزون (السعر × الكمية لكل منتج).
- اكتب دالة
void restock(Product *p, int amount)تزيد الكمية، وجرّبها. - تحدٍّ: اكتب دالة
const Product *cheapest(const Product items[], int size)تُرجع مؤشراً إلى أرخص منتج في المصفوفة.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان الفاصلة المنقوطة: بعد القوس الأخير في تعريف الهيكل
};. نسيانها يعطي أخطاء غريبة في السطور التالية. - النقطة مع المؤشر:
book.priceوbookمؤشر خطأ ترجمة. استخدم السهمbook->price. - إسناد نص لحقل مصفوفة:
s1.name = "Ali";خطأ. استخدمstrcpyأوsnprintf. - تمرير الهيكل بالقيمة وانتظار التعديل: الدالة تعدّل نسختها فقط. مرّر العنوان بـ
&.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
إذا كان p مؤشراً إلى هيكل فيه الحقل age، كيف نصل إلى الحقل؟
السهم -> يصل إلى حقل عبر مؤشر، وهو اختصار للكتابة (*p).age.
سؤال
ماذا يحدث عند تنفيذ b2 = b1; بين هيكلين من النوع نفسه؟
الهياكل تُنسخ بالإسناد حقلاً حقلاً، وتعديل النسخة بعدها لا يمس الأصل.
الخلاصة
Section titled “الخلاصة”structيجمع حقولاً مترابطة في نوع واحد، والوصول إليها بالنقطة.typedefيعطي الهيكل اسماً مختصراً يُستخدم بدون كلمةstruct.- الهياكل تُنسخ بـ
=، وتوضع في مصفوفات لتمثيل قوائم السجلات. - مرّر الهيكل للدوال بعنوانه، واستخدم السهم
->وconstعند القراءة فقط. enumيعطي الأعداد أسماء واضحة تجعل الكود مقروءاً.