לולאת for חוזרת על בלוק כל עוד תנאי מתקיים, כשההכנה והקידום כתובים בכותרת הלולאה. זו הלולאה לשימוש כשסופרים: מספר קבוע של חזרות, או כל אינדקס של מערך.
פלט:
Lap 1 of 5
Lap 2 of 5
Lap 3 of 5
Lap 4 of 5
Lap 5 of 5
איך שלושת החלקים רצים
for (initializer; condition; iterator) מתבצעת בסדר קבוע:
- האתחול (
int i = 1) רץ פעם אחת, לפני כל דבר אחר. - התנאי (
i <= 5) נבדק. אם הוא שקרי, הלולאה מסתיימת והגוף לא רץ יותר. - הגוף רץ.
- הקידום (
i++) רץ, ואז השליטה חוזרת לשלב 2.
מכיוון שהתנאי נבדק לפני כל איטרציה, לולאת for יכולה לרוץ אפס פעמים: for (int i = 10; i < 5; i++) אף פעם לא נכנסת לגוף שלה. הלולאה שלמעלה מתנהגת כמו לולאת ה-while הזו:
int i = 1;
while (i <= 5)
{
Console.WriteLine($"Lap {i} of 5");
i++;
}
שתי הצורות שונות בפרט אחד: continue בתוך גוף של for עדיין מריץ את הקידום, בעוד שבגרסת ה-while הזו הוא היה מדלג על i++, ולכן הלולאה הייתה בודקת שוב את אותו i ועלולה להסתובב לנצח. משתנה שמוצהר באתחול קיים רק בתוך הלולאה. אם צריך את הערך הסופי שלו אחר כך, הצהירו עליו לפני הלולאה: int i; for (i = 0; ...; i++) { }.
ספירה לאחור וקפיצות
הקידום יכול להיות כל ביטוי. הורידו כדי לספור לאחור, הוסיפו יותר מאחד כדי לקפוץ, או הכפילו בשביל סדרה הנדסית:
פלט:
3... 2... 1... liftoff
00 15 30 45
1 10 100 1000
ודאו שהקידום מתקדם לכיוון שבו התנאי יהפוך לשקרי. for (int i = 0; i < 10; i--) סופרת הרחק מ-10 ועוצרת רק כש-i מתגלגל מסביב אחרי שני מיליארד איטרציות.
מעבר על מערך לפי אינדקס
למערכים יש Length, לרשימות יש Count, והאינדקסים התקינים רצים מ-0 עד אחד פחות מזה. האינדקס נותן משהו ש-foreach לא נותנת: גישה למיקום, לשכנים, והאפשרות לכתוב בחזרה.
פלט:
#1: 17.99
#2: 4.50
#3: 38.25
#4: 7.43
item 1 vs item 0: down
item 2 vs item 1: up
item 3 vs item 2: down
הלולאה האחרונה מתחילה ב-1 כי היא משווה כל איבר לזה שלפניו. העמוד על מערכים מכסה יצירה, מיון והעתקה של מערכים עצמם.
כמה משתני לולאה
האתחול יכול להצהיר על כמה משתנים מאותו טיפוס, והקידום יכול לעדכן כמה מהם, מופרדים בפסיקים. השימוש הקלאסי הוא שני אינדקסים שהולכים זה לקראת זה:
פלט:
racecar is a palindrome: True
9, 1, 8, 3
בהצהרה אחת שני המשתנים חייבים להיות מאותו טיפוס: int left = 0, right = 10 עובד, int i = 0, string s = "" לא.
לולאות מקוננות
לולאה בתוך לולאה מריצה את כל הטווח שלה פעם אחת בכל איטרציה של הלולאה החיצונית. טבלאות ורשתות הן המקרה הטיפוסי:
פלט:
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
הגוף הפנימי רץ 16 פעמים (4 × 4). קינון מכפיל את העבודה מהר: שתי לולאות מקוננות על 10,000 פריטים כל אחת הן 100 מיליון איטרציות. break בלולאה הפנימית יוצא רק מהלולאה הפנימית; העמוד על break ו-continue מראה איך לצאת משתיהן.
לולאות אינסופיות: for(;;)
כל שלושת החלקים של הכותרת אופציונליים. בלי תנאי, הלולאה רצה עד שמשהו בתוכה יוצא:
פלט:
Month 1: 70 left
Month 2: 40 left
Month 3: 10 left
Overdrawn in month 4: -20
for (;;) ו-while (true) מתקמפלות לאותו דבר; בחרו את מה שבסיס הקוד שלכם משתמש בו.
טעויות באחד (off-by-one)
הבאג הנפוץ ביותר ב-for הוא תנאי שהולך צעד אחד רחוק מדי:
פלט:
Ada
Grace
Linus
IndexOutOfRangeException at index 3
למערך של 3 איברים יש אינדקסים 0, 1 ו-2, ולכן i < names.Length הוא התנאי הנכון. הבאג ההפוך הוא ספירה לאחור עם i > 0, שאף פעם לא מבקרת באינדקס 0. ב-List<T>, אותה טעות זורקת במקום זאת ArgumentOutOfRangeException.
מחיקת פריטים תוך כדי לולאה
מחיקה מ-List<T> בתוך לולאה קדימה מזיזה כל איבר שאחריה מקום אחד אחורה, ולכן הלולאה מדלגת על האיבר שבא מיד אחרי כל מחיקה:
פלט:
forward: 5, 0, 3, 7
backward: 5, 3, 7
הלולאה קדימה השאירה 0 מאחור: אחרי מחיקת אינדקס 1, האפס השני זז לאינדקס 1, אבל i++ כבר התקדם לאינדקס 2. מעבר מהסוף להתחלה מונע את זה כי מחיקות מזיזות רק איברים שכבר ביקרתם בהם. כחלופה בשורה אחת, stock.RemoveAll(x => x == 0) עושה את אותו הדבר. לולאת foreach לא מאפשרת מחיקה בכלל; היא זורקת InvalidOperationException.
מונים של נקודה צפה
הימנעו ממוני לולאה מטיפוס double שנבדקים בשוויון. ל-0.1 אין ייצוג בינארי מדויק, ולכן חיבור שלו עשר פעמים לא נותן בדיוק 1.0:
for (double x = 0; x != 1.0; x += 0.1) // never ends: x skips past 1.0
{
}
ספרו עם מספר שלם וחשבו ממנו את הערך: for (int i = 0; i <= 10; i++) { double x = i / 10.0; }.
for מול foreach
| צורך | השתמשו ב |
|---|---|
| לקרוא כל איבר לפי הסדר | foreach |
האינדקס, או שכנים (i - 1, i + 1) | for |
| השמה לאיבר של מערך | for |
| קפיצה ב-2, מעבר אחורה | for |
מחיקה מ-List תוך כדי לולאה | for, מהסוף להתחלה |
אוסף בלי אינדקסר (HashSet, Dictionary, תוצאות LINQ) | foreach |
| לחזור על משהו N פעמים, בלי אוסף | for |
על מערכים, שתי הלולאות מתקמפלות למעשה לאותו קוד, אז בחרו לפי מה שקריא יותר ולא לפי מהירות.
שאלות נפוצות
מה התחביר של לולאת for ב-C#?
for (initializer; condition; iterator) { body }, למשל for (int i = 0; i < 5; i++) { ... }. האתחול רץ פעם אחת, התנאי נבדק לפני כל איטרציה, והקידום רץ אחרי כל איטרציה. הלולאה מסתיימת בפעם הראשונה שהתנאי שקרי.
איך עוברים בלולאה מהסוף להתחלה ב-C#?
מתחילים באינדקס האחרון ומורידים: for (int i = items.Length - 1; i >= 0; i--). שימו לב ל->= 0 ולא > 0, אחרת האיבר הראשון מדולג. מעבר מהסוף להתחלה הוא גם הדרך הבטוחה למחוק פריטים מ-List תוך כדי מעבר.
מתי כדאי להשתמש ב-for במקום foreach ב-C#?
השתמשו ב-for כשצריך את האינדקס (כדי להשוות שכנים, לכתוב בחזרה למערך או לקפוץ ביותר מאחד), כשסופרים במקום לעבור על אוסף, או כשמוחקים פריטים מרשימה. השתמשו ב-foreach כדי לקרוא כל איבר לפי הסדר; היא קצרה יותר ואין בה אינדקס שאפשר לטעות בו.
איך כותבים לולאה אינסופית ב-C#?
for (;;) { ... } או while (true) { ... }. כל שלושת החלקים של כותרת for אופציונליים, ותנאי חסר נחשב אמת. צאו מהלולאה עם break או return כשתנאי היציאה מתקיים.
למה אני מקבל IndexOutOfRangeException בלולאת for?
התנאי מאפשר לאינדקס להגיע לאורך: i <= array.Length מבקר ב-array[array.Length], אחד אחרי האיבר האחרון. האינדקסים התקינים רצים מ-0 עד Length - 1, ולכן התנאי צריך להיות i < array.Length (או i < list.Count עבור List, שזורקת במקום זאת ArgumentOutOfRangeException).