Menu

לולאת for ב-Golang: כל ארבע הצורות עם דוגמאות

ל-Go יש מילת מפתח אחת ללולאות, for, והיא מכסה הכול: לולאת מונה עם שלושה חלקים, לולאת while עם תנאי בלבד, לולאה אינסופית, ו-range על slices, maps, מחרוזות, channels, מספרים שלמים ופונקציות. ובנוסף break ו-continue עם תוויות והשינוי במשתני הלולאה ב-Go 1.22.

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

ל-Go יש בדיוק מילת מפתח אחת ללולאות: for. יש לה ארבע צורות, ויחד הן מכסות כל לולאה שהייתם כותבים עם for, while, do while או foreach בשפות אחרות.

אין סוגריים סביב כותרת הלולאה, וסוגריים מסולסלים הם תמיד חובה.

לולאה עם שלושה חלקים

for init; condition; post {
	// body
}
  1. init רץ פעם אחת לפני הלולאה. בדרך כלל זו הצהרת משתנה קצרה כמו i := 0, והמשתנה הזה קיים רק בתוך הלולאה.
  2. condition נבדק לפני כל איטרציה. כשהוא false, הלולאה מסתיימת.
  3. גוף הלולאה רץ.
  4. post רץ אחרי כל איטרציה, בדרך כלל i++.

ב-Go i++ הוא הוראה ולא ביטוי, ולכן post לא יכול להכיל i++, j--. השתמשו במקום זאת בהשמה מקבילה: i, j = i+1, j-1.

כל שלושת החלקים אופציונליים. for ; i < 10; {} חוקי, ו-gofmt משכתב אותו ל-for i < 10 {}, שהיא הצורה הבאה.

לולאה עם תנאי בלבד (ה-while של Go)

עם תנאי בלבד, for מתנהגת כמו while בשפות אחרות:

אין do while נפרד. הדף על לולאת while מראה איך מקבלים התנהגות של "לרוץ לפחות פעם אחת".

לולאות אינסופיות

for בלי שום דבר אחריה רצה לנצח. יוצאים ממנה עם break, return, os.Exit או panic:

for {
	line, err := reader.ReadString('\n')
	if err != nil {
		break
	}
	process(line)
}

זו הצורה הסטנדרטית לשרתים, ל-workers וללולאות קלט. for {} עם גוף ריק שורפת ליבת מעבד; בקוד אמיתי, הגוף נחסם על משהו (קריאה, קבלה מ-channel, select).

לולאות range

range עובר על אוסף ומחזיר ערך אחד או שניים בכל איטרציה. מה הם תלוי במה שעוברים עליו:

עוברים עלערך ראשוןערך שני
slice או מערךאינדקסאיבר (עותק)
מחרוזתאינדקס בבתיםrune (תו Unicode)
mapמפתחערך
channelאיבר(אין)
מספר שלם n (Go 1.22)0 עד n פחות 1(אין)
פונקציית איטרטור (Go 1.23)מה שהפונקציה מניבהמה שהפונקציה מניבה

שלושה פרטים שהפלט מראה:

  • משתנה הערך הוא עותק. v *= 2 בתוך הלולאה לא היה משנה את ה-slice; כדי לשנות איברים, גשו ל-slice דרך האינדקס עם nums[i], כמו שהלולאה השלישית עושה.
  • האינדקס של מחרוזת הוא היסט בבתים. é תופס שני בתים, ולכן האינדקס קופץ מ-1 ל-3.
  • הסדר של map אקראי בכוונה. Go הופכת אותו לאקראי כדי שקוד לא יוכל להסתמך עליו. slices.Sorted(maps.Keys(m)) (Go 1.23) נותן את המפתחות לפי הסדר.

range מוסבר במלואו, כולל channels, פונקציות איטרטור וכללי ההעתקה, בדף על range.

break ו-continue

break מסיים את הלולאה הפנימית ביותר. continue מדלג לאיטרציה הבאה (ובלולאה עם שלושה חלקים, מריץ קודם את post):

break ו-continue עם תווית

break רגיל יוצא רק מהלולאה הפנימית ביותר. כדי לצאת מלולאה חיצונית מתוך לולאה מקוננת, תנו תווית ללולאה החיצונית:

תווית היא מזהה ואחריו נקודתיים, שממוקם ישירות לפני ה-for. Go מדווחת על תווית שלא נעשה בה שימוש כשגיאת קומפילציה, בדיוק כמו על משתנה שלא נעשה בו שימוש.

break בתוך switch ו-select

בתוך for, break במקרה של switch או select יוצא מה-switch או ה-select, לא מהלולאה:

for {
	switch cmd := next(); cmd {
	case "quit":
		break // only exits the switch; the loop keeps running
	}
}

השתמשו ב-break עם תווית, או ב-return, כדי לצאת מהלולאה מתוך switch או select.

משתני לולאה החל מ-Go 1.22

Go 1.22 שינתה את ההיקף של משתני הלולאה: כל איטרציה מקבלת עכשיו עותק חדש של i ושל v. קודם, משתנה אחד שימש לכל הלולאה, וזה שבר closures ו-goroutines שלכדו אותו:

ב-Go 1.22 ומעלה הקוד מדפיס 0 1 2 ו-[aa bb cc]. בסמנטיקה הישנה, אותה תוכנית שנכתבה עם for i := 0; i < 3; i++ (range על מספר שלם לא היה קיים לפני 1.22) הדפיסה 3 3 3, וה-goroutines התחרו על i ו-name המשותפים. הפתרון העוקף הישן, i := i בראש גוף הלולאה, כבר לא נחוץ.

ההתנהגות נבחרת לכל מודול לפי שורת ה-go ב-go.mod, לא לפי גרסת הקומפיילר. מודול שעדיין מציין go 1.21 שומר על הסמנטיקה הישנה גם כשהוא נבנה עם Go 1.24.

טעויות נפוצות

שינוי העותק. for _, v := range items { v.Count++ } משנה עותק. השתמשו ב-items[i].Count++, או עברו על slice של מצביעים.

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

מחיקה מ-slice לפי אינדקס בלולאה קדימה. הסרת איבר i מזיזה את השאר מקום אחד אחורה, ולכן האיבר הבא מדולג. בנו slice חדש, עברו על הלולאה מהסוף להתחלה, או השתמשו ב-slices.DeleteFunc.

טעות של אחד עם <=. for i := 0; i <= len(s); i++ קורא את s[len(s)] בסיבוב האחרון ונכנס ל-panic עם index out of range. השתמשו ב-<, או עדיף, ב-range.

עבודה כבדה בתנאי. התנאי רץ בכל איטרציה. for i := 0; i < expensiveCount(); i++ קוראת לפונקציה בכל פעם; חשבו אותה פעם אחת לפני הלולאה.

שימוש ב-float כמונה. for x := 0.0; x != 1.0; x += 0.1 אף פעם לא מסתיימת, כי ל-0.1 אין ייצוג בינארי מדויק. ספרו עם מספר שלם וחשבו ממנו את ה-float.

שאלות נפוצות

איך כותבים לולאת for ב-Go?

לצורה הקלאסית יש שלושה חלקים שמופרדים בנקודה-פסיק, בלי סוגריים:

for i := 0; i < 5; i++ {
	fmt.Println(i)
}

החל מ-Go 1.22 אפשר לכתוב גם for i := range 5, שסופר מ-0 עד 4. כדי לעבור על slice, השתמשו ב-for i, v := range items.

האם יש ב-Go לולאת while?

אין מילת מפתח while, אבל for עם תנאי בלבד היא לולאת while: for n > 0 { n /= 2 }. for { ... } בלי תנאי היא לולאה אינסופית שיוצאים ממנה עם break או return.

איך עוברים על map בלולאה ב-Go?

for key, value := range m { ... }. סדר המעבר לא מוגדר, וסביבת הריצה הופכת אותו לאקראי, כך שהוא יכול להשתנות מלולאה ללולאה. אף פעם אל תסתמכו עליו. לסדר יציב, אספו את המפתחות, מיינו אותם עם slices.Sort, ועברו על המפתחות הממוינים.

איך יוצאים מלולאה מקוננת ב-Go?

שימו תווית לפני הלולאה החיצונית וציינו אותה ב-break: outer: for ... { for ... { if found { break outer } } }. break רגיל יוצא רק מה-for, ה-switch או ה-select הפנימי ביותר. באותו אופן, continue outer קופץ לאיטרציה הבאה של הלולאה המתויגת.

מה השתנה במשתני הלולאה ב-Go 1.22?

לפני Go 1.22, ללולאת for היה משתנה אחד שכל האיטרציות חלקו, כך ש-closures ו-goroutines שהופעלו בלולאה ראו לעיתים קרובות כולם את הערך הסופי. החל מ-Go 1.22 כל איטרציה מקבלת עותק משלה, ולכן לכידת i או v ב-closure עובדת כמצופה. ההתנהגות החדשה חלה כשה-go.mod של המודול מציין go 1.22 ומעלה.

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

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

להתחיל