C: البنى — المتقدمة

البنى المتقدمة كتشطيب منزل — لا يتعلق الأمر ببناء الهيكل فقط، بل أيضًا بالاستخدام الفعال للمساحة (المحاذاة)، وتوفير المواد (حقول البت)، والتوسع المرن (المصفوفات المرنة).

1. الاستخدام المتقدم للبنى مع الدوال

(1) دوال تُرجع بنية

تسمح لغة C للدوال بإرجاع بنية مباشرة — المترجم يتولى النسخ:

TEXT 📖 للعرض فقط
struct Point make_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

int main(void) {
    struct Point pt = make_point(3, 4);
    printf("(%d, %d)\n", pt.x, pt.y);
    return 0;
}
💡 نصيحة: إرجاع بنية صغيرة بالقيمة لا بأس به. للبنى الكبيرة، مرر مؤشرًا واملأه لتجنب عبء النسخ.

(2) ملء بنية عبر مؤشر

C
void fill_student(struct Student *s, const char *name, int age, float score) {
    strncpy(s->name, name, 19);
    s->name[19] = '\0';
    s->age = age;
    s->score = score;
}

int main(void) {
    struct Student stu;
    fill_student(&stu, "Zhang", 20, 88.5);
    return 0;
}

(3) حماية const لوسائط البنية

لمنع دالة من تعديل بنية، استخدم const على وسيط المؤشر:

TEXT 📖 للعرض فقط
void print_student(const struct Student *s) {
    printf("%s %d %.1f\n", s->name, s->age, s->score);
}
⚠️ ملاحظة: const struct Student *s تعني أنك لا يمكنك تعديل البنية المُشار إليها عبر s، لكن s نفسه يمكنه الإشارة إلى شيء آخر.


2. typedef لأسماء مستعارة للأنواع

ينشئ typedef اسمًا مستعارًا لنوع، مما يقلل الحاجة لكتابة الكلمة المفتاحية struct مرارًا.

(1) الاستخدام الأساسي

C
typedef struct {
    char name[20];
    int age;
    float score;
} Student;

Student s1 = {"Zhang", 20, 89.5};
Student *ps = &s1;
💡 نصيحة: لا حاجة لكتابة struct Student بعد الآن — استخدم Student مباشرة.

(2) typedef مع مؤشرات البنى

TEXT 📖 للعرض فقط
typedef struct Node {
    int data;
    struct Node *next;
} Node, *NodePtr;

Node n1 = {10, NULL};
NodePtr head = &n1;
⚠️ ملاحظة: عندما تشير البنية إلى نفسها، يجب استخدام struct Node *next لأن الاسم المستعار لم يُفعّل بعد في تلك النقطة.

(3) استخدامات أخرى لـ typedef

C
typedef unsigned char Byte;
typedef int (*Comparator)(const void *, const void *);

Byte flag = 0xFF;
Comparator cmp = my_compare;

▶ مثال

C
#include <stdio.h>
#include <string.h>

typedef struct {
    char title[50];
    int pages;
    float price;
} Book;

Book create_book(const char *title, int pages, float price) {
    Book b;
    strncpy(b.title, title, 49);
    b.title[49] = '\0';
    b.pages = pages;
    b.price = price;
    return b;
}

void discount(Book *b, float rate) {
    b->price *= rate;
}

void print_book(const Book *b) {
    printf("<%s> %d pages $%.2f\n", b->title, b->pages, b->price);
}

int main(void) {
    Book b1 = create_book("C Programming", 320, 59.0);
    print_book(&b1);
    discount(&b1, 0.8);
    print_book(&b1);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
<C Programming> 320 pages $59.00
<C Programming> 320 pages $47.20

3. محاذاة ذاكرة البنية

أعضاء البنية ليسوا مكتظين في الذاكرة — المترجم يُدخل بايتات حشو وفقًا لقواعد المحاذاة.

(1) قواعد المحاذاة

  1. إزاحة كل عضو يجب أن تكون مضاعفًا لحجم ذلك العضو
  2. الحجم الإجمالي للبنية يجب أن يكون مضاعفًا لحجم أكبر عضو
C
struct Align1 {
    char a;
    int b;
    char c;
};

struct Align2 {
    char a;
    char c;
    int b;
};
TEXT 📖 للعرض فقط
printf("%zu\n", sizeof(struct Align1));
printf("%zu\n", sizeof(struct Align2));
TEXT 📖 للعرض فقط
12
8

تخطيط Align1: a (بايت 1) + 3 بايتات حشو + b (4 بايتات) + c (بايت 1) + 3 بايتات حشو = 12.

تخطيط Align2: a (بايت 1) + c (بايت 1) + 2 بايتان حشو + b (4 بايتات) = 8.

💡 نصيحة: تجميع الأعضاء الأصغر معًا يقلل بايتات الحشو — تقنية عملية لتوفير الذاكرة.

(2) #pragma pack

يمكنك تحديد حد المحاذاة لضغط البنية:

C
#pragma pack(push, 1)
struct Packed {
    char a;
    int b;
    char c;
};
#pragma pack(pop)

printf("%zu\n", sizeof(struct Packed));
TEXT 📖 للعرض فقط
6
⚠️ ملاحظة: المحاذاة المضغوطة تقلل كفاءة الوصول وقد تسبب أخطاء على بعض المنصات. استخدمها فقط في السيناريوهات التي تتطلب محاذاة بالبايت بشكل صارم، مثل تحليل البروتوكولات أو معالجة صيغ الملفات.


4. حقول البت

تُخصص حقول البت مساحة للأعضاء على أساس البت تلو البت، مما يوفر الذاكرة.

C
struct Flags {
    unsigned int ready : 1;
    unsigned int error : 1;
    unsigned int mode  : 3;
    unsigned int       : 0;
    unsigned int count : 12;
};
C
struct Flags f = {1, 0, 5, 1024};
printf("ready=%u error=%u mode=%u count=%u\n", f.ready, f.error, f.mode, f.count);
printf("Struct size: %zu\n", sizeof(f));
TEXT 📖 للعرض فقط
ready=1 error=0 mode=5 count=1024
Struct size: 8
⚠️ ملاحظة: تخطيط حقول البت يعتمد على المترجم وغير قابل للنقل. لا يمكنك أخذ عنوان عضو حقل بت (&f.ready غير قانوني).


5. الأعضاء المصفوفة المرنة

تسمح C99 بأن يكون آخر عضو في البنية مصفوفة بطول 0، ويُسمى عضو المصفوفة المرن.

C
typedef struct {
    int len;
    int data[];
} IntVec;

عند الاستخدام، خُصّص مساحة إضافية حسب الحاجة:

C
int n = 5;
IntVec *v = (IntVec *)malloc(sizeof(IntVec) + sizeof(int) * n);
v->len = n;
for (int i = 0; i < n; i++) {
    v->data[i] = i * 100;
}
for (int i = 0; i < v->len; i++) {
    printf("%d ", v->data[i]);
}
free(v);
TEXT 📖 للعرض فقط
0 100 200 300 400
💡 نصيحة: الأعضاء المصفوفة المرنة تُستخدم عادةً لتنفيذ بنى متغيرة الطول. مقارنةً باستخدام مؤشر لذاكرة منفصلة، البيانات متجاورة ويكفي free واحدة.

⚠️ ملاحظة: المصفوفة المرنة يجب أن تكون العضو الأخير، ويجب أن تحتوي البنية على عضو آخر على الأقل. لا يمكنك استخدام sizeof للحصول على حجم المصفوفة المرنة.


❓ أسئلة شائعة

س ما الفرق بين typedef و #define للأسماء المستعارة للأنواع؟
ج typedef يُعالجه المترجم، يتبع قواعد النطاق، ويتعامل مع أنواع المؤشرات بشكل صحيح. #define هو استبدال نصي للمعالج الأولي وقد يسبب أخطاء دقيقة. يُفضّل typedef.
س لماذا توجد محاذاة الذاكرة؟
ج المعالجات تقرأ الذاكرة بكفاءة أكبر عند حدود محاذاة، وبعض البُنى تُخطئ عند الوصول غير المحاذي. المترجم يضيف حشوًا تلقائيًا للتوافق والأداء.
س هل يمكن أخذ عنوان حقل بت؟
ج لا. عضو حقل البت قد يكون أصغر من بايت وليس له عنوان مستقل. لا يمكن استخدام عامل & مع حقول البت.
س عضو مصفوفة مرنة أم عضو مؤشر — أيهما أفضل؟
ج المصفوفات المرنة تخزن البيانات بشكل متجاور بتخصيص وتحرير واحد، وهي صديقة للذاكرة المخبئية. أعضاء المؤشر يمكنهم الإشارة لأي مكان، مما يوفر مرونة أكبر لكن يتطلب تخصيصين وتحريرين. المصفوفات المرنة هي الخيار الأبسط.

📖 ملخص

📝 تمارين

  1. عرّف بنية تحتوي على char و short و int و double. جرّب ترتيبين مختلفين للأعضاء واستخدم sizeof للتحقق من فرق الحجم
  2. استخدم typedef لتعريف نوع عقدة قائمة مرتبطة، ثم اكتب دوال لإنشاء واجتياز قائمة مرتبطة أحادية
  3. استخدم عضو مصفوفة مرنة لتنفيذ بنية سلسلة ديناميكية (بها len و data[])، مع دعم عملية إلحاق حرف
Web-Tutorial.com

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

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

100%