לולאת while חוזרת על הגוף שלה כל עוד תנאי נשאר אמיתי. השתמשו בה כשאתם לא יודעים מראש כמה איטרציות צריך: להמשיך עד שיתרה מגיעה ליעד, עד שתור מתרוקן או עד שהקלט נגמר.
פלט:
Year 1: 1070.00
Year 2: 1144.90
Year 3: 1225.04
Year 4: 1310.80
Year 5: 1402.55
Year 6: 1500.73
Goal reached after 6 years
התנאי נבדק לפני כל איטרציה, כולל הראשונה. אם savings כבר היה מגיע ליעד, הגוף לא היה רץ אף פעם והתוכנית הייתה מדפיסה "after 0 years". הלולאה תלויה בכך שהגוף ישנה משהו שהתנאי קורא; כאן savings גדל בכל שנה עד ש-savings < goal הופך לשקר.
do while: ריצה לפחות פעם אחת
לולאת do while מעבירה את הבדיקה לסוף, ולכן הגוף תמיד רץ לפחות פעם אחת:
פלט:
Attempt 1: got 7
Attempt 2: got 4
Attempt 3: got 1
Done after 3 attempts
הערך הראשון של code קיים רק אחרי שהגוף רץ פעם אחת, ובדיוק אז מתאימה do while. שני פרטי תחביר מכשילים רבים:
- הנקודה-פסיק אחרי
while (code != 1)היא חובה. השמטתה היא שגיאת קומפילציה. - משתנים שהתנאי משתמש בהם חייבים להיות מוצהרים לפני
do. משתנה שמוצהר בתוך הסוגריים המסולסלים כבר מחוץ ל-scope כשהתנאי רץ, ולכןdo { int code = ...; } while (code != 1);לא מתקמפל.
אותה לולאה שנכתבת עם while צריכה קריאה ראשונה כפולה לפני הלולאה, או ערך התחלתי מלאכותי שמכריח את המעבר הראשון. do while מבטאת ישירות את הרעיון "נסו, ואז החליטו אם לנסות שוב".
קריאת קלט עד ערך עצירה
קריאת שורות עד מילת עצירה, או עד שהקלט נגמר, היא לולאת while קלאסית. Console.ReadLine() מחזירה null כשאין עוד קלט, ולכן בדקו את שני המקרים:
פלט:
skipping "abc": not a number
3 amounts, total 49.50
(line = Console.ReadLine()) != null מציב את השורה הבאה ובודק אותה בביטוי אחד; הסוגריים סביב ההשמה הם חובה, כי הקדימות של != גבוהה מזו של =. הלולאה נעצרת ב-done, ולכן ה-99 שאחריו לעולם לא נקרא. decimal.TryParse דוחה את abc בלי לזרוק חריגה, ו-continue מדלג לשורה הבאה. בלי בדיקת ה-null, הגעה לסוף הקלט הייתה מעבירה null ל-TryParse לנצח, כי ReadLine ממשיכה להחזיר null.
do while מתאימה לתבנית של בקשה ובדיקה, שבה הבקשה חייבת להופיע לפחות פעם אחת:
פלט:
Enter your age:
"-4" is not a valid age
Enter your age:
"fifteen" is not a valid age
Enter your age:
Age accepted: 15
הבקשה נמצאת בתוך הלולאה, ולכן היא חוזרת אחרי כל תשובה שנדחתה, ו-age מוצהר לפני do כדי שהקוד שאחרי הלולאה יוכל להשתמש בו. כשמשתמש יושב מול המקלדת, כל תשובה מופיעה בקונסולה אחרי הבקשה שלה; כשהקלט מנותב, כמו כאן, מוצג רק הפלט של התוכנית עצמה.
while (true) עם break
לפעמים נקודת היציאה הטבעית נמצאת באמצע הגוף, אחרי חלק מהעבודה ולפני השאר. כתבו את הלולאה כ-while (true) וצאו ממנה עם break:
פלט:
processing: resize photo
processing: send email
stop signal received
1 job(s) left in the queue
Dequeue מוציא את המשימה הבאה מראש התור, והלולאה יוצאת ברגע שהיא רואה את סימן העצירה, ולכן המשימה האחרונה נשארת בתור. while (true) היא לא code smell כשהיציאה ברורה וקרובה לתחילת הגוף. היא הופכת לכזו כשיש כמה פקודות break מפוזרות בגוף ארוך; אז תנאי bool עם שם משמעותי או מתודה עם return קלים יותר למעקב. הדף על break ו-continue מסביר איך יוצאים מלולאות מקוננות.
לולאות שלא נגמרות
ללולאה אינסופית לא מכוונת יש אחת משלוש סיבות.
הגוף אף פעם לא משנה את התנאי. המונה אף פעם לא מקודם, או שמקדמים את המשתנה הלא נכון:
int i = 0;
while (i < 5)
{
Console.WriteLine(i); // i++ forgotten: prints 0 forever
}
נקודה-פסיק אחרי התנאי. הנקודה-פסיק היא כל הגוף של הלולאה, והבלוק שמתחתיה רץ אחרי הלולאה (שלעולם לא מסתיימת):
while (retries < 3); // warning CS0642: Possible mistaken empty statement
{
retries++;
}
תנאי שמדלג מעבר ליעד. בדיקה עם != כשהמשתנה מתקדם בקפיצות עלולה לדלג על ערך העצירה. while (x != 10) x += 3; עובר דרך 0, 3, 6, 9, 12 וממשיך הלאה מעבר ל-10 (הוא נוחת על 10 רק במקרה, אחרי שה-int גולש ומתגלגל חזרה מיליארדי צעדים אחר כך). בדקו עם < או > במקום != בכל פעם שהקפיצה אינה 1. אותו דבר נכון לערכי double, שכמעט אף פעם לא פוגעים ביעד מדויק: while (x != 1.0) x += 0.1; לא נגמרת אף פעם.
בחירה בין while, do while ו-for
| מצב | לולאה |
|---|---|
| מספר איטרציות ידוע, או אינדקס | for |
| כל איבר באוסף | foreach |
| חזרה כל עוד תנאי מתקיים, אולי אפס פעמים | while |
| הגוף חייב לרוץ פעם אחת לפני שלתנאי יש משמעות | do while |
| בדיקת יציאה באמצע הגוף | while (true) עם break |
כל לולאת for אפשר לכתוב מחדש כלולאת while ולהפך; הבחירה היא באיזו מהן ההיגיון של הלולאה נמצא במקום שבו הקורא מצפה לו. כשהמונה, הבדיקה והקידום קיימים כולם, for שומרת אותם בשורה אחת. כשהלולאה מונעת ממצב שמשתנה בתוך הגוף, while אומרת את זה במפורש.
שאלות נפוצות
מה ההבדל בין while ל-do while ב-C#?
לולאת while בודקת את התנאי שלה לפני כל איטרציה, ולכן הגוף שלה יכול לרוץ אפס פעמים. לולאת do { } while (condition); בודקת אותו אחרי כל איטרציה, ולכן הגוף שלה תמיד רץ לפחות פעם אחת. השתמשו ב-do while כשהמעבר הראשון מייצר את הערך שהתנאי בודק, למשל קריאת קלט ואז בדיקת תקינותו.
מה התחביר של לולאת do while ב-C#?
do { body } while (condition);. שימו לב לנקודה-פסיק אחרי הסוגריים הסוגרים: היא חובה, והשמטתה היא שגיאת קומפילציה. התנאי יכול להשתמש במשתנים שמקבלים ערך בגוף, אבל צריך להצהיר עליהם לפני ה-do, כי הסוגריים המסולסלים של הגוף הם scope נפרד.
איך כותבים while (true) ב-C#?
while (true) { ... } חוזרת לנצח עד שמשהו בתוכה יוצא ממנה עם break, return או חריגה. זו הצורה הטבעית כשבדיקת היציאה שייכת לאמצע הגוף, למשל קריאת שורה ועצירה כשהיא ריקה.
איך קוראים קלט ב-C# עד שהמשתמש מקליד מילה מסוימת או עד שהקלט נגמר?
הריצו לולאה על Console.ReadLine(), שמחזירה null בסוף הקלט: string line; while ((line = Console.ReadLine()) != null && line != "done") { ... }. ההשמה בתוך התנאי קוראת את השורה הבאה ובודקת אותה בצעד אחד.
למה לולאת ה-while שלי ב-C# לא נגמרת אף פעם?
בדרך כלל הגוף אף פעם לא משנה את מה שהתנאי בודק (i++ שנשכח), או שיש נקודה-פסיק מיד אחרי התנאי: while (count < 10); היא לולאה עם גוף ריק שמסתובבת לנצח. הקומפיילר מזהיר על המקרה השני עם CS0642, Possible mistaken empty statement.