التكرار دون عدّ
حلقة for هي الأداة الصحيحة حين تعرف - أو تستطيع حساب - عدد المرّات التي تحتاجها. لكن كثيرًا من الأعمال لا يأتي مع عدد. اقرأ كل سطر من ملف حتى ينتهي. واصل طلب كلمة المرور حتى تصحّ. نصّف عددًا حتى يبلغ 1. في هذه الحالات، السؤال ليس "كم مرة" بل "إلى متى"، وهذا بالضبط ما تعبّر عنه while.
لم يحسب أحد "سبعة" مسبقًا. فالحلقة تمضي ببساطة ما دام الشرط محقّقًا.
الصياغة وقاعدة الفحص أولًا
while (condition) {
// الجسم - يُنفَّذ ما دام الشرط غير صفري
}
يُفحَص الشرط قبل كل مرور، بما في ذلك الأول. وثلاث نتائج تترتّب على ذلك:
- الحلقة التي يبدأ شرطها غير محقّق تُنفَّذ صفر مرة. فـ
while (0) { ... }لا تنفّذ الجسم إطلاقًا. - ما يفحصه الشرط يجب أن يكون مهيّأً قبل الحلقة، وإلا كنت تفحص قيمة مهملة.
- يجب أن يجعل شيء في الجسم الشرطَ غير محقّق في النهاية، وإلا لن تنتهي الحلقة أبدًا.
ومثل if، الأقواس مطلوبة ولا فاصلة منقوطة بعدها. وتلك الفاصلة الشاردة هي نسخة الحلقات من الخطأ نفسه:
/* BUG: the empty statement is the body - this spins forever */
while (i < 10);
{
printf("%d\n", i);
i++;
}
لا تُزاد i إطلاقًا، فلا يتغيّر الشرط أبدًا. وgcc -Wall ينبّه إلى الجسم الفارغ.
وتنطبق قاعدة الصحّة في C هنا تمامًا كما تنطبق في if/else: تستمرّ الحلقة ما دام التعبير غير صفري، وتتوقّف حين يصبح صفرًا.
الشكل المعياري: هيّئ، افحص، حدّث
تكاد كل حلقة while صحيحة أن تتكوّن من ثلاث قطع موزّعة على ثلاثة مواضع:
هذه هي الأجزاء الثلاثة نفسها التي تحملها ترويسة for، لكنها غير مجموعة في سطر واحد. وهذه هي الإجابة الصريحة عن سؤال "for أم while؟": إن كانت القطع الثلاث كلها عن عدّاد واحد، فإن for تضعها حيث يستطيع القارئ فحصها معًا. وإن كان التحديث متناثرًا في جسم طويل، أو كان الشرط عن شيء غير العدّ، فإن while هي الصيغة الأوضح.
ونسيان القطعة الثالثة هو خطأ حلقة while بامتياز. فإن تعلّقت حلقة، فأول ما ينبغي فحصه هو ما إذا كان الجسم يغيّر فعلًا ما يقرؤه الشرط.
حلقات القيمة الحارسة
القيمة الحارسة قيمة خاصة تعني "توقّف". اجمع الأعداد حتى يُدخل المستخدم 0:
لا يُبلَغ 99 الذي بعد 0 إطلاقًا - فقد أنهت القيمة الحارسة الحلقة. ولاحظ أن عدد الأعداد غير معروف مسبقًا إطلاقًا، وهذا بالضبط سبب كون هذه حلقة while.
وسلاسل C النصية تعمل بالطريقة نفسها: فهي تنتهي بقيمة حارسة '\0'، فالمرور عليها حلقة while نموذجية:
قراءة الإدخال حتى نهاية الملف
أشيع حلقة while في العالم الحقيقي تقرأ الإدخال حتى لا يبقى شيء. والقاعدة هي فحص القيمة المُرجَعة من دالة الإدخال، لا القيمة التي خزّنتها.
تُرجِع getchar المحرف التالي، أو الثابت EOF حين ينفد الإدخال:
تفصيلان يهمّان. c من نوع int لأن EOF قيمة سالبة لا تتّسع في char على كثير من الأنظمة - وتعريفها char قد يجعل المقارنة لا تنجح أبدًا. والإسناد يقع داخل الشرط: فـ (c = getchar()) != EOF تقرأ محرفًا وتخزّنه وتقارن في خطوة واحدة. والأقواس الداخلية مطلوبة، لأن != ترتبط أوثق من =؛ فبدونها ستخزّن نتيجة المقارنة في c. وهذا أحد المواضع القليلة التي يكون فيها الإسناد داخل شرط أسلوبًا اصطلاحيًا لا خطأً.
والشكل نفسه يعمل مع scanf، التي تُرجِع عدد العناصر المحوَّلة بنجاح:
int n;
while (scanf("%d", &n) == 1) {
/* n holds a valid number */
}
تنتهي الحلقة عند نهاية الملف و عند أول إدخال ليس عددًا، وهو ما تريده عادةً.
وما لا ينبغي أن تكتبه هو while (!feof(f)). فـ feof تبلّغ عمّا إذا كانت قراءة قد فشلت سلفًا، فهي ما تزال خاطئة وأنت على العنصر الأخير، فيُنفَّذ جسم الحلقة مرة أخرى، وتفشل القراءة - فتعالج بيانات قديمة. افحص القراءة نفسها.
while (1) وbreak
أحيانًا لا يُعرف شرط الخروج إلا في منتصف الجسم - بعد القراءة وقبل المعالجة. والجواب الاصطلاحي حلقة لا نهائية عن قصد مع خروج صريح:
while (1) محقّقة دائمًا، فالمخرج الوحيد هو break (أو return أو exit). وسترى for (;;) مستخدمة للغرض نفسه؛ فهما تُترجَمان إلى الشيء نفسه، وأيّهما تفضّل قاعدة شيفرة مسألة أسلوب.
استخدم هذا الشكل حين ينتمي فحص الخروج فعلًا إلى منتصف الجسم. أما حين يناسب الفحص أعلى الحلقة بطبيعته، فضعه في الشرط حيث يراه القارئ - فـ while (1) التي يكون break الوحيد فيها هو الجملة الأولى ليست إلا شرطًا متنكّرًا.
التحويل بين for وwhile
يمكن كتابة أي حلقة for كحلقة while والعكس. والتقابل آليّ:
/* for */ /* equivalent while */
for (init; cond; update) { init;
body; while (cond) {
} body;
update;
}
فهاتان تطبعان الشيء نفسه:
الموضع الوحيد الذي يتسرّب منه هذا التكافؤ هو continue. ففي حلقة for، تقفز continue إلى التحديث في الترويسة، فيواصل العدّاد تقدّمه. أما في نسخة while فتقفز continue مباشرةً إلى الشرط وتتخطّى i++ في آخر الجسم - فتنشأ حلقة لا نهائية في الحال:
/* BUG: continue skips i++, so the loop spins forever on the first even number */
int i = 0;
while (i < 10) {
if (i % 2 == 0) {
continue;
}
printf("%d ", i);
i++;
}
إن استخدمت حلقة while كلمة continue، فتأكّد من وقوع التحديث قبلها - أو استخدم for، حيث تضمنه الترويسة.
مزالق شائعة
- لا شيء يحدّث الشرط. يجب أن يغيّر الجسم ما يقرؤه الشرط. فإن تعلّق برنامج، فانظر هنا أولًا.
- فاصلة منقوطة بعد الترويسة. تجعل
while (cond);الجملةَ الفارغة هي الجسم. - فحص متغيّر لم يُهيَّأ قط. يُنفَّذ الشرط قبل الجسم، فالقيمة التي يقرؤها يجب أن تكون موجودة سلفًا.
charبدلintمعgetchar. لا يتّسعEOFفيchar؛ فقد لا تصحّ المقارنة أبدًا، أو يُخطأ في اعتبار بايت مشروعEOF.while (!feof(f)). تدور مرة زائدة. افحص القراءة.- افتراض أن الجسم يُنفَّذ مرة واحدة على الأقل. ليس كذلك. وحين تحتاج إلى مرور واحد مضمون - كطباعة قائمة قبل فحص الاختيار - فتلك حلقة do-while.
الأسئلة الشائعة
كيف تكتب حلقة while في لغة C؟
while (condition) { body }. يُفحَص الشرط قبل كل مرور، فيُنفَّذ الجسم ما دام الشرط غير صفري، ولا يُنفَّذ إطلاقًا إن بدأ غير محقّق. ويجب أن يغيّر شيء داخل الجسم الشرطَ في النهاية، وإلا لن تنتهي الحلقة أبدًا.
ما الفرق بين for وwhile في لغة C؟
هما متكافئتان في القدرة - فكل for يمكن إعادة كتابتها كـ while. استخدم for حين ينتمي عدّاد وحدّه وخطوته معًا إلى ترويسة واحدة، وwhile حين لا يكون شرط التوقّف عددًا: القراءة حتى نهاية الملف، أو إعادة المحاولة حتى النجاح، أو التكرار حتى تنقلب راية.
كيف أقرأ الإدخال حتى نهاية الملف في لغة C؟
افحص القيمة المُرجَعة من دالة الإدخال، لا القيمة التي قرأتها: while (scanf("%d", &n) == 1) { ... } تتوقّف عند نهاية الملف أو عند أول إدخال غير رقمي، وwhile ((c = getchar()) != EOF) { ... } تقرأ محرفًا محرفًا. ولا تكرّر أبدًا بـ while (!feof(f)) - فهي تدور مرة إضافية بعد النهاية.
لماذا حلقة while عندي لا نهائية في لغة C؟
عادةً لأن لا شيء في الجسم يغيّر القيمة التي يفحصها الشرط، أو لأن فاصلة منقوطة شاردة بعد الترويسة (while (i < 10);) جعلت الجملة الفارغة هي الجسم. وكلاهما يُترجَم دون مشكلة، فتحقّق من أن الجسم يحدّث فعلًا متغيّر الشرط.