目次

1. المقدمة

عند تعلم لغة C، يُعَدّ التعامل مع “الإدخال القياسي” (Standard Input) من الوظائف الأساسية التي لا يمكن تجاهلها. من خلال فهم الإدخال القياسي بشكل صحيح والتعامل معه بأمان، يمكنك تحسين مرونة البرامج وزيادة موثوقيتها بشكل كبير.

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

أهمية الإدخال القياسي

الإدخال القياسي هو الآلية الأساسية التي يستخدمها البرنامج لاستقبال البيانات من الخارج. على سبيل المثال، يتم استخدامه في الحالات التالية:

  • تطبيقات الحساب التي تعتمد على الأرقام التي يدخلها المستخدم
  • وظائف البحث باستخدام سلاسل نصية
  • البرامج الديناميكية التي تستجيب لأوامر سطر الأوامر

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

ما الذي ستتعلمه في هذا المقال

سنتناول في هذا المقال النقاط التالية:

  1. الآلية الأساسية للإدخال القياسي وكيفية تنفيذه في لغة C
  2. استخدام الدوال مثل scanf و fgets
  3. طرق آمنة وموثوقة للتعامل مع الإدخال القياسي
  4. تقنيات متقدمة لمعالجة البيانات
  5. المشاكل الشائعة وحلولها

الفئة المستهدفة

  • المبتدئون الذين يتعلمون لغة C لأول مرة
  • المبرمجون الذين ليست لديهم خبرة كافية مع الإدخال القياسي
  • كل من يرغب في تنفيذ معالجة إدخال آمنة وفعّالة

2. ما هو الإدخال القياسي؟

في لغة C، الإدخال القياسي هو الآلية التي يستخدمها البرنامج لاستقبال البيانات من الخارج. يُعتَبَر جزءًا من “الإدخال/الإخراج القياسي” حيث يقدّم المستخدم البيانات للبرنامج عبر الطرفية أو سطر الأوامر. في هذا القسم سنشرح المفهوم الأساسي ودور الإدخال القياسي.

تعريف الإدخال القياسي

الإدخال القياسي (Standard Input، stdin) هو تدفق بيانات (Data Stream) يستخدمه البرنامج لاستقبال البيانات من الخارج. في لغة C، يمكن التعامل مع الإدخال القياسي بسهولة عبر مكتبة stdio.h.

  • عادةً ما يتم إدخال البيانات عبر لوحة المفاتيح.
  • يتم معالجة البيانات المدخلة داخل البرنامج، ثم عرض النتائج عبر الإخراج القياسي.

آلية الإدخال/الإخراج القياسي

في لغة C، هناك ثلاثة تدفقات قياسية أساسية:

  1. الإدخال القياسي (stdin): لاستقبال البيانات من الخارج.
  2. الإخراج القياسي (stdout): لعرض نتائج البرنامج.
  3. إخراج الأخطاء القياسي (stderr): لعرض رسائل الخطأ.

مثال عملي

المثال التالي يوضّح برنامجًا بسيطًا يستقبل عددًا صحيحًا من الإدخال القياسي ويعرضه عبر الإخراج القياسي:

#include <stdio.h>

int main() {
    int number;
    printf("أدخل رقمًا: ");
    scanf("%d", &number); // قراءة عدد صحيح من الإدخال القياسي
    printf("الرقم الذي أدخلته هو: %d\n", number); // عرض النتيجة
    return 0;
}
侍エンジニア塾

3. أساسيات الإدخال القياسي في لغة C

في لغة C، توجد عدة دوال للحصول على البيانات من المستخدم عبر الإدخال القياسي. من أبرز هذه الدوال scanf و fgets. في هذا القسم سنشرح كيفية استخدام كل دالة مع أمثلة عملية.

كيفية استخدام دالة scanf

scanf هي دالة تُستخدم لتحليل البيانات المدخلة استنادًا إلى محددات التنسيق (Format Specifiers) وتخزينها في متغيرات.

البنية الأساسية

int scanf(const char *format, ...);
  • format: سلسلة تحدد نوع البيانات المطلوب إدخالها.
  • ...: عناوين المتغيرات التي سيتم تخزين البيانات فيها.

محددات التنسيق الأساسية

المحددالوصفمثال
%dعدد صحيح (int)42
%fعدد عشري (float)3.14
%cحرف واحدA
%sسلسلة نصيةHello

مثال على الاستخدام

#include <stdio.h>

int main() {
    int age;
    printf("أدخل عمرك: ");
    scanf("%d", &age); // قراءة عدد صحيح
    printf("عمرك هو: %d\n", age);
    return 0;
}

ملاحظات مهمة

  • إذا لم يُدخل المستخدم البيانات بالشكل الصحيح، قد يحدث خطأ.
  • لتجنب تجاوز سعة الذاكرة (Buffer Overflow)، يجب تحديد حجم الإدخال عند استخدام %s.

كيفية استخدام دالة fgets

fgets تُستخدم لقراءة سلسلة نصية كاملة (حتى نهاية السطر). وهي أكثر أمانًا من scanf لأنها تسمح بتحديد عدد الحروف.

البنية الأساسية

char *fgets(char *str, int n, FILE *stream);
  • str: مصفوفة لتخزين النص المُدخل.
  • n: الحد الأقصى لعدد الحروف (بما فيها رمز النهاية \0).
  • stream: مصدر الإدخال (عادةً stdin).

مثال على الاستخدام

#include <stdio.h>

int main() {
    char name[50];
    printf("أدخل اسمك: ");
    fgets(name, 50, stdin); // قراءة حتى 49 حرفًا
    printf("اسمك هو: %s", name);
    return 0;
}

ملاحظات مهمة

  • إذا تجاوز الإدخال حجم المصفوفة، سيتم اقتطاع البيانات الزائدة.
  • قد يحتوي الإدخال على رمز السطر الجديد \n ويجب حذفه يدويًا.

طريقة إزالة رمز السطر الجديد

name[strcspn(name, "\n")] = '\0';

مقارنة بين scanf و fgets

الميزةscanffgets
الاستخدامقراءة أعداد أو نصوص حسب التنسيققراءة سلسلة نصية كاملة (سطر واحد)
الأمانخطر تجاوز السعة (Buffer Overflow)إمكانية التحكم في حجم الإدخال
المرونةإمكانية تحديد صيغة البياناتقراءة النصوص كما هي

أي دالة يجب أن تستخدم؟

  • استخدم scanf: إذا كنت تريد قراءة أعداد أو بيانات بتنسيق محدد.
  • استخدم fgets: إذا كنت تريد قراءة نصوص طويلة أو تبحث عن أمان أكبر في الإدخال.

4. تنفيذ آمن للإدخال القياسي

عند التعامل مع الإدخال القياسي في لغة C، من المهم جدًا ضمان الأمان. أي معالجة غير صحيحة قد تؤدي إلى تجاوز الذاكرة (Buffer Overflow) أو سلوك غير متوقع، مما يقلل من موثوقية البرنامج. في هذا القسم سنشرح طرقًا عملية لتنفيذ إدخال آمن.

منع تجاوز الذاكرة (Buffer Overflow)

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

مثال مع تحديد حجم الإدخال

#include <stdio.h>

int main() {
    char input[10];
    printf("أدخل نصًا بحد أقصى 9 حروف: ");
    scanf("%9s", input); // قراءة حتى 9 حروف فقط
    printf("النص المدخل: %s\n", input);
    return 0;
}

النقاط المهمة

  • استخدام %9s يمنع إدخال بيانات تتجاوز حجم المصفوفة.

استخدام fgets للإدخال الآمن

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

مثال باستخدام fgets

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

int main() {
    char buffer[20];
    printf("أدخل نصًا: ");
    fgets(buffer, sizeof(buffer), stdin); // قراءة حتى 19 حرفًا
    buffer[strcspn(buffer, "\n")] = '\0'; // إزالة رمز السطر الجديد
    printf("النص المدخل: %s\n", buffer);
    return 0;
}

النقاط المهمة

  • باستخدام sizeof(buffer) يتم تحديد الحجم تلقائيًا.
  • الدالة strcspn تساعد في إزالة رمز السطر الجديد.

التحقق من صحة البيانات المدخلة

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

مثال على التحقق من إدخال عدد صحيح

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    char buffer[20];
    long number;
    char *endptr;

    printf("أدخل عددًا صحيحًا: ");
    fgets(buffer, sizeof(buffer), stdin);

    errno = 0; // إعادة تعيين حالة الخطأ
    number = strtol(buffer, &endptr, 10); // تحويل النص إلى عدد صحيح

    if (errno != 0 || endptr == buffer || *endptr != '\0') {
        printf("الرجاء إدخال عدد صحيح صالح.\n");
    } else {
        printf("العدد المدخل هو: %ld\n", number);
    }

    return 0;
}

النقاط المهمة

  • باستخدام strtol يمكن اكتشاف الأخطاء أثناء التحويل.
  • الجمع بين errno و endptr يسمح بالتحقق من صحة الإدخال.

أفضل الممارسات لمعالجة الأخطاء

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

مثال على التحقق من الأخطاء

#include <stdio.h>

int main() {
    int value;
    printf("أدخل عددًا صحيحًا: ");

    if (scanf("%d", &value) != 1) { // إذا فشل الإدخال
        printf("إدخال غير صالح.\n");
        return 1; // إنهاء بالخطأ
    }

    printf("العدد المدخل: %d\n", value);
    return 0;
}

النقاط المهمة

  • من المهم التحقق من قيمة الإرجاع لدالة scanf لمعرفة ما إذا كان الإدخال صالحًا.

ملخص حول الإدخال الآمن

  • عند استخدام scanf يجب دائمًا تحديد حجم الإدخال.
  • للتعامل مع نصوص طويلة أو إدخال متعدد الأسطر، يُفضل fgets.
  • يجب عدم الثقة بإدخال المستخدم، بل التحقق منه دائمًا وتنفيذ معالجة الأخطاء.

5. المعالجة المتقدمة للإدخال القياسي

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

إدخال عدة بيانات دفعة واحدة

غالبًا ما نحتاج إلى إدخال عدة قيم ومعالجتها في البرنامج. يمكن ذلك باستخدام الفواصل أو المسافات للفصل بين القيم.

مثال: إدخال أعداد مفصولة بمسافة

#include <stdio.h>

int main() {
    int numbers[5];
    printf("أدخل 5 أعداد صحيحة مفصولة بمسافة: ");
    for (int i = 0; i < 5; i++) {
        scanf("%d", &numbers[i]); // إدخال الأعداد مفصولة بمسافة
    }

    printf("الأعداد المدخلة: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    return 0;
}

مثال: إدخال أعداد مفصولة بفاصلة

#include <stdio.h>

int main() {
    int numbers[3];
    printf("أدخل 3 أعداد مفصولة بفاصلة: ");
    scanf("%d,%d,%d", &numbers[0], &numbers[1], &numbers[2]); // إدخال مفصول بفاصلة

    printf("الأعداد المدخلة: %d, %d, %d\n", numbers[0], numbers[1], numbers[2]);
    return 0;
}

التعامل مع المسافات والأسطر الجديدة

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

مثال: إدخال يحتوي على سطر جديد

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

int main() {
    char input[100];
    printf("أدخل جملة: ");
    fgets(input, sizeof(input), stdin);

    input[strcspn(input, "\n")] = '\0'; // إزالة السطر الجديد
    printf("النص المدخل: %s\n", input);

    return 0;
}

معالجة إدخال متعدد الأسطر

عند التعامل مع نصوص أو بيانات طويلة، قد تحتاج إلى قراءة عدة أسطر بشكل متكرر.

مثال: قراءة عدة أسطر حتى انتهاء الإدخال

#include <stdio.h>

int main() {
    char line[100];

    printf("أدخل عدة أسطر (للإنهاء اضغط Ctrl+D):\n");
    while (fgets(line, sizeof(line), stdin) != NULL) {
        printf("السطر المدخل: %s", line);
    }

    return 0;
}

معالجة بيانات إدخال ديناميكية

في بعض الأحيان، لا نعرف مسبقًا عدد القيم التي سيدخلها المستخدم. يمكننا التعامل مع ذلك عبر الحلقات الشرطية.

مثال: حساب مجموع أعداد غير محددة

#include <stdio.h>

int main() {
    int number, sum = 0;
    printf("أدخل أعدادًا صحيحة (للإنهاء اضغط Ctrl+D):\n");

    while (scanf("%d", &number) == 1) {
        sum += number;
    }

    printf("مجموع الأعداد المدخلة: %d\n", sum);
    return 0;
}

الخلاصة

باستخدام تقنيات الإدخال المتقدمة، يمكنك بناء برامج قادرة على التعامل مع بيانات معقدة أو متغيرة بشكل ديناميكي. التمرّن على إدخال متعدد القيم والتعامل مع الأسطر الجديدة يساعدك على تطوير برامج أكثر مرونة وكفاءة.

6. المشاكل الشائعة وحلولها

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

مشكلات scanf وحلولها

المشكلة 1: تخطي الإدخال

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

مثال
#include <stdio.h>

int main() {
    int number;
    char letter;

    printf("أدخل عددًا صحيحًا: ");
    scanf("%d", &number);

    printf("أدخل حرفًا: ");
    scanf("%c", &letter); // يقرأ محرف السطر الجديد بدلاً من الحرف

    printf("العدد: %d, الحرف: %c\n", number, letter);
    return 0;
}
الحل

يمكن تجاهل المحارف الزائدة بإضافة مسافة قبل محدد التنسيق:

scanf(" %c", &letter); // المسافة تتجاهل السطر الجديد

المشكلة 2: تجاوز الذاكرة (Buffer Overflow)

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

مثال
char input[10];
scanf("%s", input); // إذا تجاوز الإدخال 10 محارف يحدث خطأ
الحل

تحديد حجم الإدخال في صيغة scanf:

scanf("%9s", input); // يسمح بإدخال 9 محارف كحد أقصى

أو استخدام fgets كبديل أكثر أمانًا.

مشكلات fgets وحلولها

المشكلة 1: محرف السطر الجديد

دالة fgets تحتفظ بمحرف السطر الجديد \n في النص، مما قد يؤدي إلى نتائج غير متوقعة عند المقارنة.

مثال
char input[20];
fgets(input, sizeof(input), stdin);
if (strcmp(input, "yes") == 0) {
    printf("النص هو yes\n");
}

في هذا المثال النص المقروء سيكون "yes\n" وليس "yes".

الحل

إزالة محرف السطر الجديد يدويًا:

input[strcspn(input, "\n")] = '\0';

التعامل مع إدخال غير صالح

المشكلة: إدخال نص بدل عدد

إذا تم إدخال نص بينما البرنامج يتوقع عددًا، قد يتوقف التنفيذ بشكل غير صحيح.

مثال
int number;
scanf("%d", &number); // إدخال نص يؤدي إلى خطأ
الحل

التحقق من صحة الإدخال:

if (scanf("%d", &number) != 1) {
    printf("إدخال غير صالح.\n");
    while (getchar() != '\n'); // تنظيف المخزن
}

مشكلة عند الجمع بين scanf و fgets

السبب

عند استخدام scanf يظل محرف السطر الجديد في المخزن، مما يؤدي إلى أن fgets يقرأ سطرًا فارغًا.

الحل

تنظيف المخزن بعد scanf:

while (getchar() != '\n');

الخلاصة

الإدخال القياسي في لغة C يحتوي على الكثير من التحديات، لكن من خلال فهم أسباب المشاكل وتطبيق الحلول المناسبة يمكنك بناء برامج أكثر أمانًا وفعالية. المفتاح هو معرفة متى تستخدم scanf ومتى تستخدم fgets وفقًا لاحتياجات البرنامج.