C: تطبيق: الأنواع المخصصة

لقد تعلمت ما يكفي — حان وقت البناء. كتعلم الطبخ: قراءة الوصفات لا تكفي؛ يجب أن تدخل المطبخ. ادمج البنى والذاكرة الديناميكية وإدخال/إخراج الملفات لإنشاء برامج تعمل.

1. المشروع 1: نظام إدارة درجات الطلاب

(1) المتطلبات

(2) تصميم هيكل البيانات

C
typedef struct {
    char name[32];
    char id[16];
    int chinese;
    int math;
    int english;
} Student;

typedef struct {
    Student *data;
    int count;
    int capacity;
} StudentList;

يُدير StudentList الطلاب بمصفوفة ديناميكية — السعة الأولية 4، تتوسع عند الامتلاء.

(3) التنفيذ الأساسي

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

typedef struct {
    char name[32];
    char id[16];
    int chinese;
    int math;
    int english;
} Student;

typedef struct {
    Student *data;
    int count;
    int capacity;
} StudentList;

void list_init(StudentList *list) {
    list->capacity = 4;
    list->count = 0;
    list->data = (Student *)malloc(sizeof(Student) * list->capacity);
}

void list_free(StudentList *list) {
    free(list->data);
    list->data = NULL;
    list->count = 0;
    list->capacity = 0;
}

void list_expand(StudentList *list) {
    if (list->count < list->capacity) return;
    list->capacity *= 2;
    Student *tmp = (Student *)realloc(list->data, sizeof(Student) * list->capacity);
    if (tmp) list->data = tmp;
}

void list_add(StudentList *list, const Student *s) {
    list_expand(list);
    list->data[list->count++] = *s;
}

int total_score(const Student *s) {
    return s->chinese + s->math + s->english;
}

double avg_score(const Student *s) {
    return total_score(s) / 3.0;
}

Student *find_by_id(StudentList *list, const char *id) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].id, id) == 0) {
            return &list->data[i];
        }
    }
    return NULL;
}

int cmp_by_total(const void *a, const void *b) {
    int ta = total_score((const Student *)a);
    int tb = total_score((const Student *)b);
    return tb - ta;
}

void sort_by_total(StudentList *list) {
    qsort(list->data, list->count, sizeof(Student), cmp_by_total);
}

▶ مثال

TEXT 📖 للعرض فقط
int main(void) {
    StudentList list;
    list_init(&list);

    Student s1 = {"001", "Zhang", 85, 92, 78};
    Student s2 = {"002", "Li", 90, 88, 95};
    Student s3 = {"003", "Wang", 72, 65, 80};

    list_add(&list, &s1);
    list_add(&list, &s2);
    list_add(&list, &s3);

    sort_by_total(&list);

    Student *found = find_by_id(&list, "001");
    if (found) {
        printf("Found 001: %s Total=%d\n", found->name, total_score(found));
    }

    list_free(&list);
    return 0;
}
TEXT 📖 للعرض فقط
Found 001: Zhang Total=255

2. المشروع 2: إدارة جهات الاتصال

(1) المتطلبات

(2) تصميم هيكل البيانات

C
typedef struct {
    char name[32];
    char phone[16];
    char email[40];
    char group[16];
} Contact;

typedef struct {
    Contact *data;
    int count;
    int capacity;
} ContactList;

(3) التنفيذ الأساسي

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

typedef struct {
    char name[32];
    char phone[16];
    char email[40];
    char group[16];
} Contact;

typedef struct {
    Contact *data;
    int count;
    int capacity;
} ContactList;

void clist_init(ContactList *list) {
    list->capacity = 4;
    list->count = 0;
    list->data = (Contact *)malloc(sizeof(Contact) * list->capacity);
}

void clist_free(ContactList *list) {
    free(list->data);
    list->data = NULL;
    list->count = 0;
    list->capacity = 0;
}

void clist_expand(ContactList *list) {
    if (list->count < list->capacity) return;
    list->capacity *= 2;
    Contact *tmp = (Contact *)realloc(list->data, sizeof(Contact) * list->capacity);
    if (tmp) list->data = tmp;
}

void clist_add(ContactList *list, const Contact *c) {
    clist_expand(list);
    list->data[list->count++] = *c;
}

int clist_remove(ContactList *list, const char *name) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].name, name) == 0) {
            list->data[i] = list->data[list->count - 1];
            list->count--;
            return 0;
        }
    }
    return -1;
}

Contact *clist_find(ContactList *list, const char *name) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].name, name) == 0) {
            return &list->data[i];
        }
    }
    return NULL;
}

▶ مثال

C
int main(void) {
    ContactList list;
    clist_init(&list);

    Contact c1 = {"Zhang", "13800001111", "zhangsan@mail.com", "Coworker"};
    Contact c2 = {"Li", "13900002222", "lisi@mail.com", "Friend"};
    Contact c3 = {"Wang", "15000003333", "wangwu@mail.com", "Coworker"};
    Contact c4 = {"Zhao", "18600004444", "zhaoliu@mail.com", "Family"};

    clist_add(&list, &c1);
    clist_add(&list, &c2);
    clist_add(&list, &c3);
    clist_add(&list, &c4);

    Contact *found = clist_find(&list, "Li");
    if (found) {
        printf("Found Li: %s %s\n", found->phone, found->email);
    }

    clist_remove(&list, "Wang");

    clist_free(&list);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Found Li: 13900002222 lisi@mail.com

3. مراجعة الأنماط الرئيسية

(1) نمط المصفوفة الديناميكية

كلا المشروعين يستخدمان نفس نمط المصفوفة الديناميكية:

  1. البنية تحتوي مؤشر بيانات + عداد + سعة
  2. init تُخصص الذاكرة الأولية
  3. add تفحص السعة وتستخدم realloc للتوسيع عند الامتلاء
  4. free تُحرّر الذاكرة وتضبط إلى NULL

هذه هي الطريقة الأكثر شيوعًا لتنفيذ المجموعات الديناميكية في C. أتقنها مرة، وأعد استخدامها في أي سيناريو.

(2) نمط إدخال/إخراج الملفات

C
fprintf(fp, "%d\n", count);
for (int i = 0; i < count; i++) {
    fprintf(fp, "field1 field2 ...\n", ...);
}

عند القراءة، اقرأ العدد أولًا، ثم مر عبر السجلات. لاحظ أن %s تتوقف عند المسافة — إما تأكد أن الحقول لا تحتوي مسافات، أو استخدم fscanf بتنسيق ثابت.

(3) حيلة الحذف

عند حذف عنصر وسط من مصفوفة، اكتب فوقه بالعنصر الأخير، ثم count--. هذا يتجنب نقل عناصر كثيرة لكن لا يحافظ على الترتيب. إذا كان الترتيب مهمًا، استخدم memmove لإزاحة العناصر اللاحقة للأمام.


❓ أسئلة شائعة

س: لماذا مضاعفة السعة بدلًا من إضافة 1؟ ج: المضاعفة استراتيجية بمعدل استهلاك O(1) — إجمالي عدد النسخ أقل بكثير من الزيادة بواحد في كل مرة. هذه نتيجة كلاسيكية من تحليل الخوارزميات، وvector في C++ يستخدم نفس الاستراتيجية.

س: هل أحفظ الملفات بصيغة ثنائية أم نصية؟ ج: الصيغة النصية قابلة للقراءة والتعديل يدويًا وعبر المنصات. الثنائية أكثر إحكامًا وأسرع في القراءة/الكتابة لكنها غير مقروءة بشريًا. النصية أكثر بديهية لمشاريع التعلم.

س: ماذا لو احتجت الحفاظ على الترتيب عند حذف بتبديل الأخير؟ ج: استخدم memmove لإزاحة العناصر للأمام: memmove(&data[i], &data[i+1], (count-i-1)*sizeof(T)). أو انتقل لبنية قائمة مرتبطة.

س: ماذا لو كان الاسم يحتوي مسافات؟ ج: scanf/fscanf بـ %s تتوقف عند المسافة. استخدم fgets لقراءة سطر كامل، أو اتفق على استخدام شرطات سفلية بدل المسافات، أو استخدم تنسيقًا ذو عرض ثابت مثل %31[^\n].

📖 ملخص

📝 تمارين

  1. أضف ميزة "تعديل الدرجات" لنظام إدارة درجات الطلاب: أدخل رقمًا جامعيًا ودرجات جديدة لتحديث سجل الطالب المُقابل
  2. أضف ميزة "البحث برقم الهاتف" لمدير جهات الاتصال، وغيّر التخزين ليحتفظ بجهات الاتصال مرتبة حسب الاسم (حافظ على الترتيب عند الإدراج)
  3. قسّم كلا المشروعين لهيكل متعدد الملفات: يجب أن يكون لكل مشروع ملف إعلان .h وملف تنفيذ .c على الأقل، مع Makefile مُقابل
Web-Tutorial.com

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

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

100%