محتويات الدورة

C / المتقدم: حدود اللغة والأنظمة / الدرس 15

هياكل البيانات والثوابت

اشتق العمليات من ثوابت التمثيل وأظهر الترابط والملكية والتعقيد في التصميم.

وقت القراءة التقريبي: 4 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • صياغة ثوابت المكدس والقائمة المرتبطة.
  • معالجة الفراغ والامتلاء وفشل التخصيص.
  • مقارنة المحلية والكلفة التقاربية للعمليات.

الثابت تعريف البنية

هيكل البيانات أكثر من مجموعة حقول. يحدد ثابت التمثيل تراكيب الحقول الصالحة. في مكدس محدود، صفر <= الطول <= السعة، والعناصر تحت الطول مهيأة. يجب أن يفحص الدفع السعة قبل الكتابة، وأن يفحص السحب عدم الفراغ قبل الإنقاص أو القراءة.

حدد الملكية والإبطال بجانب الترابط. هل تنقل إزالة العنصر موارده أم تدمرها؟ هل يجوز للمستدعين حفظ مؤشرات العناصر بعد التوسع أو الإزالة؟ تحدد الإجابات سلامة استخدام خوارزمية صحيحة في ذاتها. وضح سلوك الفشل: ينبغي للدفع الفاشل عموما حفظ المكدس الصالح السابق.

مكدس صغير بتحديث حالة متكامل

يفحص مثال المكدس كل الشروط قبل تغيير الطول أو كتابة الإخراج. يعني وصف التحديث المتكامل هنا عدم تجزئته منطقيا على مستوى الواجهة في مثال أحادي الخيط؛ وليس عملية ذرية C11 بين الخيوط. يمنع فصل المعنيين ادعاءات تزامن عرضية.

بعد دفع ثلاثة ثم ثمانية يعيد السحب الأول ثمانية لأن المكدس يعمل بالأخير أولا. لا يُقرأ التخزين غير المستخدم. تتجنب السعة الثابتة التخصيص وتجعل الامتلاء متوقعا، لكن واجهة بسعة خارجية ما زالت تحتاج فحص التخزين والحساب.

c بنية محدودة للأخير أولا
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>

struct stack { int data[4]; size_t length; };

static bool push(struct stack *s, int value)
{
    if (s->length == 4) return false;
    s->data[s->length++] = value;
    return true;
}

static bool pop(struct stack *s, int *out)
{
    if (s->length == 0) return false;
    *out = s->data[--s->length];
    return true;
}

int main(void)
{
    struct stack s = {.length = 0};
    int value = 0;
    if (!push(&s, 3) || !push(&s, 8) || !pop(&s, &value)) return 1;
    printf("popped=%d left=%zu\n", value, s.length);
    return 0;
}
الناتج المتوقع
popped=8 left=1

تحافظ main على مكدس صالح وكائن إخراج حي؛ ويعيد السحب أحدث قيمة.

الروابط والمالكون دوران مختلفان

تخزن القائمة الأحادية مؤشرا next في كل عقدة. تتطلب القائمة المالكة البسيطة سلسلة غير دورية تنتهي بصفري، وتصل إلى كل عقدة مرة واحدة وتملكها. يستغرق الإدراج في الرأس عملا ثابتا على المؤشرات بعد نجاح التخصيص. ويتطلب العثور على موضع عشوائي اجتيازا.

يخصص المثال الكامل قبل نشر الرأس الجديد، فيبقي الفشل السلسلة السابقة دون تغيير. ويحفظ التدمير next قبل تحرير العقدة الحالية؛ فقراءة current->next بعده تصل إلى كائن انتهى عمره. لا يجوز للمستدعي الاحتفاظ بمؤشرات عقد مستعارة بعد التدمير.

c سلسلة مالكة من عقدتين
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

struct node { int value; struct node *next; };

static bool prepend(struct node **head, int value)
{
    struct node *item = malloc(sizeof *item);
    if (item == NULL) return false;
    item->value = value; item->next = *head;
    *head = item;
    return true;
}

static void clear(struct node **head)
{
    while (*head != NULL) {
        struct node *next = (*head)->next;
        free(*head);
        *head = next;
    }
}

int main(void)
{
    struct node *head = NULL;
    if (!prepend(&head, 3) || !prepend(&head, 8)) { clear(&head); return 1; }
    printf("head=%d next=%d\n", head->value, head->next->value);
    clear(&head);
    return 0;
}
الناتج المتوقع
head=8 next=3

عند نجاح التخصيص يعكس الإدراج الرأسي ترتيب الإدخال ويحرر clear كل عقدة مرة واحدة.

عد العمل ومراعاة الوصول للذاكرة

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

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

اختبار الانتقالات لا القيم المخزنة فقط

اختبر الانتقال من الفراغ وإليه ورفض الامتلاء وفشل البناء الجزئي. في المصفوفة الديناميكية اختبر التوسع مع إبطال المؤشرات المستعارة؛ وفي القائمة اختبر إزالة الأول والوسط والأخير وفق سياسة الملكية. قد يكشف فاحص الثابت في بناء التنقيح الفساد قرب مصدره.

لا تجتز بلا حد قائمة فاسدة قد تكون دورية كاختصار للتشخيص. استخدم فحصا محدودا أو كشف دورات مناسبا في اختبار مضبوط. يفترض التدمير ثابت ملكية البنية؛ وقد يتطلب الفساد تشخيص التعديل السابق الخاطئ بدلا من محاولات تحرير تخمينية.

الأخطاء الشائعة وإصلاحها

يقرأ سحب مكدس فارغ خارج المتتالية المنطقية.

لماذا يحدث؟
أُنقص الطول قبل فحص الفراغ.
كيف تصلحه؟
ارفض الفراغ قبل تغيير الحالة.
كيف تتجنب تكراره؟
اختبر السحب الفارغ وبقاء الإخراج دون تغيير.

يقرأ تدمير القائمة تخزينا محررا.

لماذا يحدث؟
جُلب الرابط التالي بعد free.
كيف تصلحه؟
احفظ next والعقدة حية ثم حررها.
كيف تتجنب تكراره؟
راجع العمر في كل خطوة من حلقات الإزالة.

يفقد فشل التخصيص الرأس القديم.

لماذا يحدث؟
عُدلت البنية قبل وجود العقدة الجديدة.
كيف تصلحه؟
خصص وهيئ بصورة خاصة قبل نشر الرابط.
كيف تتجنب تكراره؟
اشترط عدم التغيير عند فشل الإدراج.

اختبر فهمك

1. حدد ضمانات clear لقائمة فارغة أصلا وبعد مسح عقدتين.

اعرض الحل المشروح

في القائمة الفارغة لا تنفذ الحلقة ويبقى الرأس صفريا. ولعقدتين تحفظ مؤشر الثانية وتحرر الأولى وتقدم المالك ثم تكرر وتنتهي بصفري. تتحرر كل عقدة مملوكة أصلا مرة واحدة ولا يُقرأ رابط بعد انتهاء عمر عقدته. وتبطل بعدها المؤشرات المستعارة لكلتا العقدتين.

2. يمسح حمل كل القيم كثيرا ونادرا ما يدرج. اشرح تفوق متجه متجاور المحتمل رغم التوسع العرضي.

اعرض الحل المشروح

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

أهم ما تعلمته

  • حدد الحالات الصالحة قبل تنفيذ العمليات.
  • انشر التعديلات بعد نجاح التحضير القابل للفشل.
  • يؤثر التعقيد والمحلية والملكية وإبطال المؤشرات كلها في اختيار الحاوية.

المراجع الرسمية