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

C / الأساسي: قراءة البرامج وبناؤها / الدرس 3

الإدخال والإخراج والتحقق

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

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

أهداف الدرس

  • قراءة النص بسعة صريحة.
  • فحص نهاية التحويل ومداه.
  • مطابقة معاملات الإخراج المنسق مع أنواعها المعلنة.

الدفق ليس قيمة متحققا منها

الدفقات القياسية stdin وstdout وstderr واجهات مكتبية للإدخال والإخراج المعتاد والتشخيصات. توفر القراءة الناجحة محارف، لا عددا موثوقا. حدد سياسة السجل أولا: هل تقبل الأسطر الفارغة، وما أقصى طول للسطر، وهل النص اللاحق خطأ. تحدد هذه السياسة ما يجب أن يفحصه المحلل.

تقرأ fgets حتى السعة ناقص واحد من المحارف وتضيف نهاية صفرية عند النجاح. وقد تحتفظ بالسطر الجديد، وقد يعني غيابه نهاية الملف أو جزءا من سطر طويل. يجب على قارئ أسطر إنتاجي تمييز الحالتين والتخلص من البقية أو تجميعها عمدا.

عرض قراءة محدودة

يستخدم المثال الكامل دفقا مؤقتا من C القياسية يحتوي سجلا قصيرا معروفا. يجعل ذلك المدخل قابلا لإعادة الإنتاج مع اختبار عقد fgets نفسه المستخدم مع stdin. تُفحص كل عملية قد تفشل، ولا يُكتب فوق ملف خارجي. وقد يتعذر tmpfile أثناء التشغيل؛ وعندها يبلغ البرنامج الفشل بحالة خروجه.

يتسع المخزن لسبعة محارف ونهاية صفرية. يلائم المدخل red مع السطر الجديد السعة، وتعيد fputs طباعة السطر الجديد المحفوظ دون إضافة آخر. للاستخدام التفاعلي استبدل الدفق بـstdin مع إبقاء السعة وسياسة اكتمال السجل. لا تستبدل هذا بـgets التي أزيلت من C11.

c قراءة سجل قصير قابل لإعادة الإنتاج
#include <stdio.h>

int main(void)
{
    char line[8];
    FILE *input = tmpfile();
    if (input == NULL) return 1;
    if (fputs("red\n", input) == EOF) { fclose(input); return 1; }
    rewind(input);
    if (fgets(line, sizeof line, input) == NULL) { fclose(input); return 1; }
    if (fputs(line, stdout) == EOF) { fclose(input); return 1; }
    return fclose(input) == 0 ? 0 : 1;
}
الناتج المتوقع
red

عند نجاح الإدخال والإخراج تحتفظ fgets بالسطر الجديد وتكتبه fputs مرة واحدة بالضبط.

تحليل العدد مع أدلة

تفصل strtol التحويل عن جلب الإدخال. صفّر errno واطلب الأساس العشري وافحص مؤشر النهاية وERANGE معا. إذا ساوى مؤشر النهاية البداية فلم تتحول أية أرقام. وإذا بقيت محارف فقد يحتوي السجل لاحقة مرفوضة؛ وقبول الفراغات المحيطة سياسة مستقلة تحتاج فحصا صريحا.

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

c تحويل سجل عشري وفحصه
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    const char *text = "42";
    char *end;
    errno = 0;
    long value = strtol(text, &end, 10);
    if (end == text || *end != '\0' || errno == ERANGE || value < 0 || value > 100) {
        puts("invalid");
        return 1;
    }
    printf("accepted=%ld\n", value);
    return 0;
}
الناتج المتوقع
accepted=42

تُستهلك كل المحارف ولا يقع خطأ مدى، ويطابق %ld النتيجة من long.

التنسيق واجهة ذات أنواع

تستخدم printf نص التنسيق لتفسير المعاملات متغيرة العدد. يتوقع %d النوع int و%ld النوع long و%zu النوع size_t و%f النوع double بعد ترقيات المعاملات. عدم تطابق المحدد ليس اختيار عرض بريئا؛ بل خرق لعقد المكتبة وقد يسبب سلوكا غير معرّف. يستطيع المترجم غالبا تشخيص أخطاء التنسيقات الحرفية عند تفعيل التحذيرات.

لا تجعل النص الخارجي نص التنسيق: تعامل printf("%s", text) علامات النسبة كبيانات. افحص أخطاء الإخراج عندما يهم نجاح التسليم. تفيد fprintf(stderr, ...) للتشخيصات، لكن إرسال رسالة إلى stderr وحده لا يجعل العملية قابلة للاسترداد ولا يغير حالة إرجاعها.

بناء خط تحقق

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

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

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

يُقبل المدخل 12x بوصفه اثني عشر.

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

يتحول السطر الطويل إلى سجلين للتطبيق.

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

يتصرف الإخراج بصورة غير متوقعة مع معامل long.

لماذا يحدث؟
يطلب التنسيق int بينما القيمة long.
كيف تصلحه؟
استخدم %ld وفعّل تشخيصات التنسيق.
كيف تتجنب تكراره؟
راجع التنسيق والمعاملات معا كواجهة واحدة.

اختبر فهمك

1. صنف السجلات "" و"42" و"42x" و"101" وفق محلل المثال، وحدد فحص رفض كل سجل غير صالح.

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

يفشل النص الفارغ في end == text لعدم تحويل أرقام. يستهلك "42" السجل كاملا ويقع ضمن المجال فيُقبل. يترك "42x" المحرف x عند مؤشر النهاية فيفشل فحص اللاحقة. يمتلك "101" صياغة عشرية صحيحة لكنه يخالف حد التطبيق الأعلى. فشل الصياغة وفشل المجال مختلفان حتى لو أُبلغ عنهما بلفظ غير صالح.

2. صمم اختبارات لمخزن سطر من ثمانية محارف دون إدخال غير محدود، مع حالة فشل إدخال.

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

استخدم دفق إدخال مؤقتا واختبر دفقا فارغا و"a\n" وستة أحرف ثم سطرا جديدا وسبعة ثم سطرا جديدا وثمانية ثم سطرا جديدا. تتطلب الحالتان الأخيرتان فحص التقاط نهاية السطر ومعالجة البقية باتساق. افحص نتيجة القراءة قبل المصفوفة. ومع الدفق الفارغ أبلغ نهاية الإدخال بدلا من تحليل محتويات مخزن قديمة.

أهم ما تعلمته

  • الجلب والصياغة والمدى والمجال مسؤوليات مستقلة.
  • الإدخال المحدود يحتاج أيضا سياسة للسجلات المبتورة.
  • نص التنسيق عقد؛ ومكان النص الخارجي معامل البيانات.

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