Menu

לולאת while ב-C: תחביר, דוגמאות וטעויות נפוצות

איך לולאת while ב-C חוזרת על עצמה עד שתנאי משתנה: כלל התנאי-קודם, לולאות עם ערך זקיף, קריאת קלט עד EOF, while(1) ומעבר בין for ל-while.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

חזרה בלי מספר קבוע

לולאת for היא הכלי הנכון כשאתם יודעים, או יכולים לחשב, כמה סיבובים צריך. הרבה עבודה לא מגיעה עם מספר. לקרוא כל שורה בקובץ עד שהוא נגמר. להמשיך לבקש סיסמה עד שהיא נכונה. לחלק מספר בחצי עד שהוא מגיע ל-1. במקרים כאלה השאלה היא לא "כמה פעמים" אלא "עד מתי", ובדיוק את זה while מבטאת.

אף אחד לא חישב "שבע" מראש. הלולאה פשוט ממשיכה כל עוד התנאי מתקיים.

תחביר וכלל התנאי-קודם

while (condition) {
    // הגוף: רץ כל עוד התנאי שונה מאפס
}

התנאי נבדק לפני כל סיבוב, כולל הראשון. שלוש השלכות:

  • לולאה שהתנאי שלה שקר מההתחלה רצה אפס פעמים. while (0) { ... } אף פעם לא מריצה את הגוף.
  • מה שהתנאי בודק חייב להיות מאותחל לפני הלולאה, אחרת אתם בודקים ערך זבל.
  • משהו בגוף חייב בסופו של דבר להפוך את התנאי לשקר, אחרת הלולאה לא נגמרת.

כמו ב-if, הסוגריים חובה ואין אחריהם נקודה-פסיק. נקודה-פסיק תועה כזו היא גרסת הלולאה של אותו באג:

/* באג: הפקודה הריקה היא הגוף, וזה מסתובב לנצח */
while (i < 10);
{
    printf("%d\n", i);
    i++;
}

i אף פעם לא גדל, ולכן התנאי אף פעם לא משתנה. gcc -Wall מסמן את הגוף הריק.

כלל האמת של C חל כאן בדיוק כמו ב-if/else: הלולאה ממשיכה כל עוד הביטוי שונה מאפס, ועוצרת כשהוא אפס.

הצורה הסטנדרטית: אתחול, בדיקה, עדכון

כמעט לכל לולאת while נכונה יש שלושה חלקים שמפוזרים בשלושה מקומות:

אלה אותם שלושה חלקים שכותרת של for מחזיקה, רק לא מרוכזים בשורה אחת. וזו התשובה הכנה לשאלה "for או while?": אם שלושת החלקים עוסקים במונה אחד, for שם אותם במקום שבו מי שקורא יכול לבדוק אותם יחד. אם העדכון מפוזר לאורך גוף ארוך, או שהתנאי עוסק במשהו שאינו ספירה, while היא הצורה הברורה יותר.

לשכוח את חלק 3 זה הבאג של לולאות while. אם לולאה נתקעת, הדבר הראשון לבדוק הוא האם הגוף באמת משנה את מה שהתנאי קורא.

לולאות עם ערך זקיף

זקיף (sentinel) הוא ערך מיוחד שפירושו "עצור". סוכמים מספרים עד שהמשתמש מזין 0:

ל-99 שאחרי ה-0 לא מגיעים אף פעם: הזקיף סיים את הלולאה. שימו לב שמספר המספרים לא ידוע מראש אף פעם, ובדיוק בגלל זה זו while.

מחרוזות ב-C עובדות באותה דרך: הן מסתיימות בזקיף '\0', ולכן מעבר על מחרוזת הוא while קלאסית:

קריאת קלט עד EOF

ה-while הנפוצה ביותר בעולם האמיתי קוראת קלט עד שאין יותר. הכלל הוא לבדוק את ערך ההחזרה של פונקציית הקלט, לא את הערך שהיא שמרה.

getchar מחזירה את התו הבא, או את הקבוע EOF כשהקלט נגמר:

שני פרטים חשובים. c הוא int כי EOF הוא ערך שלילי שלא נכנס ב-char במערכות רבות, והצהרה עליו כ-char עלולה לגרום להשוואה לא להצליח אף פעם. וההשמה יושבת בתוך התנאי: (c = getchar()) != EOF קורא תו, שומר אותו ומשווה בצעד אחד. הסוגריים הפנימיים חובה, כי != נקשר חזק יותר מ-=; בלעדיהם הייתם שומרים ב-c את תוצאת ההשוואה. זה אחד המקומות הבודדים שבהם השמה בתוך תנאי היא ביטוי מקובל ולא באג.

אותה צורה עובדת עם scanf, שערך ההחזרה שלה הוא מספר הפריטים שהומרו בהצלחה:

int n;
while (scanf("%d", &n) == 1) {
    /* n מחזיק מספר תקין */
}

הלולאה נגמרת בסוף הקובץ וגם בקלט הראשון שאינו מספר, ובדרך כלל זה מה שרוצים.

מה שלא כדאי לכתוב הוא 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++ שבסוף הגוף, וזו לולאה אינסופית מיידית:

/* באג: continue מדלג על i++, אז הלולאה מסתובבת לנצח על המספר הזוגי הראשון */
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 כשתנאי העצירה אינו ספירה: קריאה עד סוף הקובץ, ניסיון חוזר עד הצלחה, או לולאה עד שדגל מתהפך.

איך קוראים קלט עד EOF ב-C?

בודקים את ערך ההחזרה של פונקציית הקלט, לא את הערך שהיא קראה: while (scanf("%d", &n) == 1) { ... } עוצרת בסוף הקובץ או בקלט הראשון שאינו מספר, ו-while ((c = getchar()) != EOF) { ... } קוראת תו אחרי תו. אף פעם אל תכתבו לולאה על while (!feof(f)): היא רצה פעם אחת נוספת אחרי הסוף.

למה לולאת ה-while שלי אינסופית ב-C?

בדרך כלל כי שום דבר בגוף לא משנה את הערך שהתנאי בודק, או כי נקודה-פסיק תועה אחרי הכותרת (while (i < 10);) הפכה את הפקודה הריקה לגוף. שני המקרים מתקמפלים בלי תלונות, אז בדקו שהגוף באמת מעדכן את המשתנה של התנאי.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל