Menu
flag Ar iconالعربيةdown icon

البنى في لغة C: التعريف والتهيئة والاستخدام

كيف تجمع القيم المترابطة في نوع واحد ببنية C - تعريفها، والوصول إلى أعضائها بعامل النقطة، والمهيّئات المسمّاة، ومصفوفات البنى، والتداخل، وقاعدة النسخ عند التمرير التي تفاجئ الجميع.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

تحمل المصفوفة قيمًا كثيرة من نوع واحد. وتفعل struct العكس: تحمل بضع قيم من أنواع مختلفة وتعاملها كشيء واحد. فالنقطة هي x وy. والموظّف اسم ومعرّف وراتب. وبدون البنى كنت ستحمل تلك حولك كثلاثة متغيّرات سائبة وترجو ألّا تخلط بين رواتب الناس.

تعريف نوع بنية

يسمّي تعريف البنية وسمًا ويُدرِج الأعضاء بين أقواس معقوفة:

struct Point {
    int x;
    int y;
};

والفاصلة المنقوطة بعد القوس المغلق مطلوبة - ونسيانها من أكثر رسائل الأخطاء إرباكًا لمبتدئ C، لأن المترجم يلوم السطر التالي.

وهذا يعرّف نوعًا لا متغيّرًا. وفي C يكون الاسم الكامل للنوع struct Point، متضمّنًا الكلمة المفتاحية:

تُقرأ الأعضاء وتُكتب بـ عامل النقطة، p.x. ومتغيّر البنية متغيّر عادي: يعيش على المكدّس، ويمكن إسناده إلى بنية أخرى من النوع نفسه بـ =، ويختفي في نهاية نطاقه.

وكتابة struct Point في كل مكان تُتعب، ولهذا تغلّف معظم شيفرة C الحقيقية التعريفَ بـtypedef ليصبح النوع ببساطة Point.

تهيئة بنية

يمكنك ملء بنية لحظة تعريفها. وتُدرِج الصيغة الموضعية القيم بترتيب التعريف:

struct Point p = {3, 4};   // x = 3, y = 4

أما المهيّئ المسمّى فيسمّي كل عضو بدلًا من ذلك، وهو أوضح وأكثر أمانًا:

ثلاثة أمور تستحق الملاحظة:

  • تستطيع الصيغة المسمّاة إدراج الأعضاء بأي ترتيب، فإضافة عضو أو إعادة ترتيبه لاحقًا لا تزيح القيم بصمت إلى الخانات الخاطئة.
  • أي عضو تتركه يُهيَّأ بصفر - و{0} هي الطريقة الاصطلاحية لتصفير بنية كاملة.
  • والعضو char name[32] مصفوفة حقيقية داخل البنية، فالسلسلة مخزَّنة في البنية نفسها لا في مكان آخر. وهذا يعني أيضًا أن a.name = "Ada"; بعد ذلك غير مشروعة؛ إذ تحتاج إلى strcpy (انظر دوال السلاسل النصية).

والبنية بلا مهيّئ تكون غير مهيّأة، وقراءة أعضائها قبل الكتابة فيها سلوك غير معرَّف. و= {0} لا تكلّف شيئًا وتزيل هذا الصنف من الأخطاء بأكمله.

الإسناد والمقارنة

إسناد البنى ينسخ كل عضو:

a دون مساس: فقد حصلت b على نسختها. لكن لاحظ ما لا تمنحك إياه C - فلا يوجد == للبنى. وif (a == b) لن تُترجَم. والمقارنة تعني مقارنة الأعضاء:

if (a.x == b.x && a.y == b.y) { /* equal */ }

ولا تلجأ إلى memcmp كاختصار. فالمترجمات تُدرِج بايتات حشو غير مرئية بين الأعضاء للمحاذاة، وقد تحمل تلك البايتات قمامة، فبنيتان بأعضاء متطابقة قد تختلفان بايتًا ببايت.

مصفوفات البنى

ولأن البنية نوع كأي نوع آخر، يمكنك إنشاء مصفوفة منها - وهي الطريقة المعيارية للاحتفاظ بجدول من السجلّات.

تُقرأ staff[i].salary هكذا: "العنصر i، ثم عضوه salary" - فالفهرسة ترتبط أوثق من النقطة، فلا حاجة إلى أقواس.

تُنسَخ البنى إلى الدوال

هذه هي القاعدة التي توقع بالناس. فحين تمرّر بنية إلى دالة، تنسخها C. وتعمل الدالة على نسختها الخاصة:

p ما تزال (3, 4). وقراءة بنية بالقيمة، كما تفعل distance_from_origin، مقبولة تمامًا وكثيرًا ما تكون الخيار الأوضح للبنى الصغيرة. لكن إن كان على الدالة تغيير بنية المستدعي - أو إن كانت البنية كبيرة إلى حدّ يجعل نسخها إهدارًا - فمرّر مؤشّرًا بدلًا من ذلك. وذلك موضوع البنى والمؤشّرات.

وإرجاع بنية بالقيمة يعمل بالطريقة نفسها وهو مشروع تمامًا:

struct Point make_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

البنى المتداخلة

يمكن أن يكون عضو البنية بنية بدوره. اربط النقاط للوصول إلى الداخل:

تدخل b.published.year مستوى واحدًا في كل مرة. والتداخل هو كيف تبني نماذج بيانات حقيقية في C - والبنية الداخلية مخزَّنة داخل الخارجية لا مشارًا إليها، فـ Book كلها كتلة ذاكرة متّصلة واحدة.

الحجم والحشو وترتيب الأعضاء

تخبرك sizeof بحجم البنية، والإجابة أكبر غالبًا من مجموع أعضائها:

على جهاز نموذجي تكون Wasteful اثني عشر بايتًا وTidy ثمانية، رغم أن كلتيهما تحمل الأعضاء الثلاثة نفسها. فالمترجم يُدرِج حشوًا ليقع كل عضو في عنوان يحبّه نوعه - فـ int يريد عادةً مضاعفًا لأربعة. وتجميع الأعضاء الأكبر قبل الأصغر يميل إلى رصّ أفضل.

ويهمّ هذا نادرًا (مصفوفة بمليون عنصر، أو توزيع حزمة شبكة) ولا يهمّ أبدًا لصحّة شيفرتك. وما يفسّره فعلًا هو سبب مفاجأة sizeof لك، وسبب كون memcmp على البنى فكرة سيئة.

الأسئلة الشائعة

كيف تعرّف بنية في لغة C؟

اكتب struct متبوعة باسم وسم وقائمة أعضاء بين أقواس معقوفة: struct Point { int x; int y; };. وهذا يعرّف نوعًا لا متغيّرًا. ولإنشاء متغيّر تكتب بعدها struct Point p; - فكلمة struct جزء من اسم النوع في C، ولهذا تقرنها معظم الشيفرة بـtypedef.

ما الفرق بين . و-> مع البنى؟

استخدم . حين تملك البنية نفسها (p.x) و-> حين تملك مؤشّرًا إليها (ptr->x). والسهم اختصار لـ (*ptr).x. انظر البنى والمؤشّرات للتفاصيل.

هل تُمرَّر البنى بالقيمة أم بالمرجع في لغة C؟

بالقيمة دائمًا. فتمرير بنية إلى دالة ينسخ كل عضو، فالتغييرات داخل الدالة لا تؤثّر في متغيّر المستدعي. ولتعديل الأصل - أو لتجنّب نسخ بنية كبيرة - مرّر مؤشّرًا بدلًا من ذلك.

كيف أهيّئ بنية في لغة C؟

إما موضعيًا، struct Point p = {3, 4};، وإما بمهيّئات مسمّاة، struct Point p = {.y = 4, .x = 3};. والصيغة المسمّاة تسمّي كل عضو، فتصمد أمام إعادة أحدهم ترتيب البنية لاحقًا وتترك الأعضاء غير المسمّاة مصفّرة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن