Menu
flag Ar iconالعربيةdown icon

حلقة for في لغة C: الصياغة والأمثلة والأخطاء الشائعة

كيفية تكرار الشيفرة بحلقة for في لغة C - الترويسة المكوّنة من ثلاثة أجزاء، العدّ تصاعديًا وتنازليًا، المرور على المصفوفات بحيلة sizeof، الحلقات المتداخلة، الحلقات اللانهائية، وأخطاء الانزياح بمقدار واحد والأنواع غير الموقّعة التي تطال الجميع.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

لماذا نحتاج حلقة for

تختار if وswitch فرعًا واحدًا وتشغّله مرة واحدة. لكن البرامج الحقيقية تحتاج إلى فعل الأشياء بشكل متكرر: طباعة كل درجة، جمع قائمة من الأرقام، رسم عشرة صفوف من شبكة، تجربة كل محرف في سلسلة نصية. حلقة for هي حصان العمل في لغة C لتكرار الشيفرة عددًا معروفًا من المرات، مع عدّاد تتحكّم به بنفسك.

كل ما تحتاجه حلقة for يقع في ترويسة واحدة موجزة، بحيث تبقى إجابة "كم مرة، وكيف" مرئية في سطر واحد.

الترويسة المكوّنة من ثلاثة أجزاء

تتكوّن ترويسة for من ثلاثة أجزاء مفصولة بفواصل منقوطة: مُهيِّئ، وشرط، وتحديث.

for (initializer; condition; update) {
    // الجسم - يُنفَّذ ما دام الشرط غير صفري
}

تُنفَّذ هذه الأجزاء بترتيب محدّد، ومعرفته تفسّر كل حلقة for ستقرؤها يومًا:

  1. يُنفَّذ المُهيِّئ مرة واحدة، قبل أي شيء آخر.
  2. يُفحَص الشرط. إذا كان صفرًا، تنتهي الحلقة فورًا.
  3. يُنفَّذ الجسم.
  4. يُنفَّذ التحديث.
  5. عودة إلى الخطوة 2.

تتبّع الحلقة أعلاه بهذه القائمة: يُنفَّذ int i = 0 مرة واحدة. يتحقّق 0 < 5، فيطبع الجسم i = 0، ثم يجعل i++ قيمة i تساوي 1. يُفحَص الشرط مجددًا، وهكذا. عندما تبلغ i القيمة 5 يفشل الفحص، فيُتخطّى الجسم وتُطبع done. نُفِّذ الجسم خمس مرات بالضبط، حيث أخذت i القيم من 0 إلى 4.

ينتج عن ذلك أمران مباشرةً. يُفحَص الشرط قبل المرور الأول، لذا فإن حلقة يبدأ شرطها غير محقّق تُنفَّذ صفر مرة - for (int i = 10; i < 5; i++) لا تدخل الجسم إطلاقًا. ويُنفَّذ التحديث بعد الجسم، لذا تحتفظ i بقيمتها القديمة طوال الجسم.

تعريف العدّاد داخل الترويسة (int i = 0) صار مسموحًا منذ C99 وهو الخيار الافتراضي الصحيح: عندئذٍ توجد i داخل الحلقة فقط، فلا يمكن أن تتصادم مع أي شيء بعدها، ويمكن لكل حلقة في الدالة إعادة استخدام الاسم بحرية.

العدّ تصاعديًا وتنازليًا وبخطوات

جزء التحديث ليس مقصورًا على i++. عُدّ تنازليًا، أو تقدّم بأي مقدار، أو ضاعف في كل مرور:

وافِق بين الشرط والتحديث. العدّ التصاعدي يقترن بـ < أو <=؛ والعدّ التنازلي يقترن بـ > أو >=. الخطأ في هذا الاقتران هو الطريقة التي تكتب بها حلقة لا تنتهي أبدًا.

لاحظ أن الحلقة الثانية تستخدم <= 10 لأن 10 قيمة نريد تضمينها، بينما استخدم المثال الأول < 5 لأن 5 ليست كذلك. هذا الاختيار هو مولد أخطاء الانزياح بمقدار واحد، فاتّخذه بوعي: i < n تُنفَّذ n مرة بدءًا من الصفر؛ وi <= n تُنفَّذ n + 1 مرة.

المرور على مصفوفة

أكثر استخدامات حلقة العدّ شيوعًا هو المرور على مصفوفة بالفهرس. يؤدّي العدّاد أيضًا دور الموضع الذي تقرؤه.

هناك أمران يستحقان الالتقاط من هذا المثال.

حيلة sizeof. sizeof(scores) هو حجم المصفوفة كاملة بالبايت، وsizeof(scores[0]) هو حجم عنصر واحد، فيكون ناتج قسمتهما عدد العناصر. تتكيّف الحيلة تلقائيًا عندما تضيف عنصرًا، وهو ما لا يفعله الرقم 5 المكتوب يدويًا. أما المأخذ - وهو كبير - فهو أنها تعمل فقط حيث تكون المصفوفة نفسها ضمن النطاق. مرّر المصفوفة إلى دالة فتتحوّل إلى مؤشّر، فيعطي sizeof هناك حجم المؤشّر بدلًا من ذلك. داخل الدالة، خُذ الطول دائمًا كوسيط منفصل.

الشرط هو i < n، وليس i <= n أبدًا. مصفوفة من خمسة عناصر فهارسها الصالحة من 0 إلى 4. قراءة scores[5] سلوك غير معرَّف: قد تطبع قيمًا مهملة، أو تتعطّل، أو تبدو وكأنها تعمل بينما تُفسد شيئًا آخر بصمت. لا تجري لغة C أي فحص للحدود إطلاقًا، فالمسؤولية كلها عليك.

المرور على مصفوفة بترتيب معكوس هو الفكرة نفسها بالمقلوب:

ابدأ عند n - 1 (آخر فهرس صالح)، وتوقّف عند 0 متضمنًا إياه - ومن هنا >= وليس >. أبقِ i من نوع int موقّع هنا؛ والقسم التالي يشرح السبب.

الحلقات المتداخلة

ضع حلقة for داخل أخرى للعمل مع الشبكات أو الجداول أو كل زوج من العناصر. تُنفَّذ الحلقة الداخلية بالكامل عند كل خطوة مفردة من الحلقة الخارجية.

تثبّت الحلقة الخارجية قيمة row؛ وتمسح الحلقة الداخلية كل col لذلك الصف؛ وينهي printf("\n") بعد الحلقة الداخلية السطر. انقل ذلك السطر الجديد إلى داخل الحلقة الداخلية فيصبح الجدول كله عمودًا واحدًا - وهي تجربة مفيدة، لأنها تجعل بنية التداخل ملموسة.

امنح العدّادات أسماء مميّزة. row/col أو i/j خيارات جيدة؛ أما إعادة استخدام i للاثنين فتحجب العدّاد الخارجي وتُنتج نتائج محيّرة. وانتبه للتكلفة: حلقة بحجم n داخل حلقة بحجم n تُنفّذ الجسم n * n مرة، فزوج من حلقتين بـ 1,000 تكرار يعني مليون مرور.

وإليك حلقة متداخلة تؤدّي عملًا حقيقيًا - مثلّث من النجوم، حيث يعتمد حدّ الحلقة الداخلية على العدّاد الخارجي:

break وcontinue

كلمتان مفتاحيّتان تغيّران المسار في منتصف الحلقة. تترك break الحلقة فورًا؛ وتتخطّى continue بقية المرور الحالي وتقفز إلى التحديث.

تتوقّف الحلقة الأولى لحظة عثورها على 7 ولا تفحص القيم الـ 93 المتبقّية أبدًا. وتستخدم الثانية continue لتخطّي الطباعة للأعداد الزوجية؛ ومع ذلك يُنفَّذ i++ الموجود في الترويسة، فتواصل الحلقة التقدّم. هناك الكثير عن كليهما في break وcontinue، بما في ذلك كيفية الهروب من حلقة متداخلة، وهو ما لا تستطيعه break واحدة.

الحلقات اللانهائية والأجزاء الفارغة

الأجزاء الثلاثة كلها في الترويسة اختيارية. احذف الشرط فيُعامَل على أنه محقّق دائمًا:

for (;;) {
    /* يعمل إلى الأبد - اخرج بـ break أو return */
}

for (;;) هي الحلقة اللانهائية الاصطلاحية في C، والفواصل المنقوطة تبقى مطلوبة. وهي ليست خطأً ما دام هناك شيء بالداخل يمكنه تنفيذ break أو return أو exit - فحلقات الأحداث وحلقات "استمر بالسؤال حتى يصحّ الإدخال" تُكتب بهذه الطريقة تمامًا.

يمكن حذف الأجزاء الأخرى كذلك. إن كان العدّاد موجودًا سلفًا، تخطَّ المُهيِّئ؛ وإن كان الجسم هو من يقدّمه، تخطَّ التحديث:

int i = 0;
for (; i < n; ) {
    /* ... */
    i += step;
}

هذا مسموح لكنه يُقرأ أسوأ من حلقة while التي هي في الحقيقة. فضّل for عندما ينتمي العدّاد وحدّه وخطوته معًا إلى الترويسة، وحلقة while عندما لا يكون الأمر كذلك.

عامل الفاصلة في الترويسة

يمكن أن يحمل كل من المُهيِّئ والتحديث عدة تعبيرات مفصولة بفواصل، وهي الطريقة التي تشغّل بها عدّادين في آن واحد:

يعرّف int i = 0, j = len - 1 العدّادين كليهما، ويقدّم i++, j-- كليهما. هذا هو عامل الفاصلة، الذي يقيّم معاملاته بالترتيب واحدًا تلو الآخر. استخدمه لعدّادات متوازية فعليًا؛ أما حشر عمل غير مرتبط في الترويسة فيخفيه فقط.

مزالق شائعة

حفنة من الفخاخ تفسّر معظم أخطاء حلقات for في لغة C.

الانزياح بمقدار واحد. i <= n على مصفوفة تبدأ من الصفر يقرأ عنصرًا واحدًا بعد النهاية. استخدم i < n من أجل "n مرة بدءًا من 0".

فاصلة منقوطة بعد الترويسة. هذا يُترجَم بنجاح ولا يكون تقريبًا أبدًا ما قصدته:

/* BUG: the loop body is the empty statement; the printf runs once */
for (int i = 0; i < 5; i++);
{
    printf("%d\n", i);
}

تصبح ; هي الجسم، فتدور الحلقة خمس مرات دون فعل شيء، وتُنفَّذ الأقواس المعقوفة أدناه مرة واحدة ككتلة عادية. ينبّهك gcc -Wall إلى ذلك.

عدّادات غير موقّعة تعدّ تنازليًا. هذه حلقة لا نهائية:

/* BUG: an unsigned value is never < 0 */
for (size_t i = n - 1; i >= 0; i--) {
    process(a[i]);
}

i >= 0 محقّق دائمًا لنوع غير موقّع. عندما تبلغ i القيمة 0، يلتفّ i-- إلى عدد موجب ضخم وتمضي الحلقة إلى ذاكرة لا تملكها. استخدم int موقّعًا للعدّ التنازلي، أو اكتب الشرط بالصيغة for (size_t i = n; i-- > 0; )، التي تُنقص وتفحص في خطوة واحدة وتتوقّف عند الصفر بشكل صحيح.

تعديل العدّاد في موضعين. تغيير i داخل الجسم إضافةً إلى الترويسة يجعل عدد التكرارات غير قابل للتنبّؤ. اختر موضعًا واحدًا.

عدّادات الفاصلة العائمة. قد لا تنتهي for (double x = 0.0; x != 1.0; x += 0.1) أبدًا، لأن 0.1 ليس لها تمثيل ثنائي دقيق ويتجاوز المجموع 1.0 دون أن يصيبها. كرّر بعدّ صحيح واحسب القيمة في الداخل:

for (int i = 0; i <= 10; i++) {
    double x = i / 10.0;
    /* ... */
}

تغيير طول مصفوفة أثناء المرور عليها. يعيد الشرط i < n قراءة n في كل مرور، لذا إن قلّص الجسم المجموعة، فعدّل الفهرس عن قصد بدلًا من ترك الاثنين ينحرفان عن بعضهما.

الأسئلة الشائعة

كيف تكتب حلقة for في لغة C؟

ضع ثلاثة أجزاء في الترويسة مفصولة بفواصل منقوطة - مُهيِّئ وشرط وتحديث: for (int i = 0; i < 5; i++) { printf("%d\n", i); }. يُنفَّذ الجسم خمس مرات بينما تأخذ i القيم من 0 إلى 4، ويتوقّف بمجرّد أن يصبح الشرط غير محقّق.

كيف أمرّ على مصفوفة في لغة C؟

عُدّ فهرسًا من 0 حتى الطول دون تضمينه: for (int i = 0; i < n; i++) { sum += a[i]; }. بالنسبة إلى مصفوفة معرّفة في الدالة نفسها يمكنك حساب الطول بـ sizeof(a) / sizeof(a[0])؛ لكن بمجرّد تمرير المصفوفة إلى دالة تتوقّف هذه الحيلة عن العمل، لذا مرّر الطول كوسيط مستقل.

لماذا تُنفَّذ حلقة for عندي مرة زائدة؟

هذا هو خطأ الانزياح بمقدار واحد. في مصفوفة تبدأ من الصفر وحجمها n، الفهارس الصالحة من 0 إلى n - 1، لذا يُنفّذ i <= n تكرارًا إضافيًا ويقرأ ما بعد النهاية - وهو سلوك غير معرَّف. الخيار الافتراضي الآمن هو i < n.

هل يمكن تعريف متغيّر الحلقة داخل ترويسة for في C؟

نعم، منذ معيار C99: for (int i = 0; i < n; i++). عندئذٍ يوجد المتغيّر داخل الحلقة فقط، وهو ما تريده. ترجم بـ gcc -std=c99 أو أحدث (إصدارات GCC وclang الحديثة تعتمد C17 افتراضيًا، فالأمر يعمل مباشرة)؛ أما أسلوب C89 القديم فكان يعرّف i قبل الحلقة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن