C: البنى — المتقدمة
البنى المتقدمة كتشطيب منزل — لا يتعلق الأمر ببناء الهيكل فقط، بل أيضًا بالاستخدام الفعال للمساحة (المحاذاة)، وتوفير المواد (حقول البت)، والتوسع المرن (المصفوفات المرنة).
1. الاستخدام المتقدم للبنى مع الدوال
(1) دوال تُرجع بنية
تسمح لغة C للدوال بإرجاع بنية مباشرة — المترجم يتولى النسخ:
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) ملء بنية عبر مؤشر
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 على وسيط المؤشر:
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) الاستخدام الأساسي
typedef struct {
char name[20];
int age;
float score;
} Student;
Student s1 = {"Zhang", 20, 89.5};
Student *ps = &s1;
struct Student بعد الآن — استخدم Student مباشرة.
(2) typedef مع مؤشرات البنى
typedef struct Node {
int data;
struct Node *next;
} Node, *NodePtr;
Node n1 = {10, NULL};
NodePtr head = &n1;
struct Node *next لأن الاسم المستعار لم يُفعّل بعد في تلك النقطة.
(3) استخدامات أخرى لـ typedef
typedef unsigned char Byte;
typedef int (*Comparator)(const void *, const void *);
Byte flag = 0xFF;
Comparator cmp = my_compare;
▶ مثال
#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;
}
<C Programming> 320 pages $59.00
<C Programming> 320 pages $47.20
3. محاذاة ذاكرة البنية
أعضاء البنية ليسوا مكتظين في الذاكرة — المترجم يُدخل بايتات حشو وفقًا لقواعد المحاذاة.
(1) قواعد المحاذاة
- إزاحة كل عضو يجب أن تكون مضاعفًا لحجم ذلك العضو
- الحجم الإجمالي للبنية يجب أن يكون مضاعفًا لحجم أكبر عضو
struct Align1 {
char a;
int b;
char c;
};
struct Align2 {
char a;
char c;
int b;
};
printf("%zu\n", sizeof(struct Align1));
printf("%zu\n", sizeof(struct Align2));
12
8
تخطيط Align1: a (بايت 1) + 3 بايتات حشو + b (4 بايتات) + c (بايت 1) + 3 بايتات حشو = 12.
تخطيط Align2: a (بايت 1) + c (بايت 1) + 2 بايتان حشو + b (4 بايتات) = 8.
(2) #pragma pack
يمكنك تحديد حد المحاذاة لضغط البنية:
#pragma pack(push, 1)
struct Packed {
char a;
int b;
char c;
};
#pragma pack(pop)
printf("%zu\n", sizeof(struct Packed));
6
4. حقول البت
تُخصص حقول البت مساحة للأعضاء على أساس البت تلو البت، مما يوفر الذاكرة.
struct Flags {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 3;
unsigned int : 0;
unsigned int count : 12;
};
readyيشغل بتًا واحدًا (0 أو 1)errorيشغل بتًا واحدًاmodeيشغل 3 بتات (0-7): 0يجبر المحاذاة على وحدة التخزين التاليةcountيشغل 12 بتة
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));
ready=1 error=0 mode=5 count=1024
Struct size: 8
&f.ready غير قانوني).
5. الأعضاء المصفوفة المرنة
تسمح C99 بأن يكون آخر عضو في البنية مصفوفة بطول 0، ويُسمى عضو المصفوفة المرن.
typedef struct {
int len;
int data[];
} IntVec;
عند الاستخدام، خُصّص مساحة إضافية حسب الحاجة:
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);
0 100 200 300 400
sizeof للحصول على حجم المصفوفة المرنة.
❓ أسئلة شائعة
& مع حقول البت.📖 ملخص
- الدوال يمكنها إرجاع بنى؛ للبنى الكبيرة، يُفضّل الملء عبر مؤشر للكفاءة
typedefينشئ أسماء مستعارة للأنواع، مبسّطًا أسماء أنواع البنى- البنى لها محاذاة ذاكرة — ترتيب الأعضاء يؤثر على الحجم الإجمالي
#pragma packيمكنه تجاوز قواعد المحاذاة، مفيد لتحليل البروتوكولات والسيناريوهات المشابهة- حقول البت تُخصص مساحة بت ببت، توفر الذاكرة لكنها غير قابلة للعنونة وغير قابلة للنقل
- الأعضاء المصفوفة المرنة تُمكّن البنى متغيرة الطول مع بيانات متجاورة وتخصيص/تحرير بسيط
📝 تمارين
- عرّف بنية تحتوي على char و short و int و double. جرّب ترتيبين مختلفين للأعضاء واستخدم sizeof للتحقق من فرق الحجم
- استخدم typedef لتعريف نوع عقدة قائمة مرتبطة، ثم اكتب دوال لإنشاء واجتياز قائمة مرتبطة أحادية
- استخدم عضو مصفوفة مرنة لتنفيذ بنية سلسلة ديناميكية (بها len و data[])، مع دعم عملية إلحاق حرف