لدى Go كلمة حلقة واحدة بالضبط: for. لها أربعة أشكال، وتغطّي معًا كل حلقة كنت ستكتبها بـ for أو while أو do while أو foreach في لغات أخرى.
لا أقواس حول ترويسة الحلقة، والأقواس المعقوصة مطلوبة دائمًا.
الحلقة بثلاثة أجزاء
for init; condition; post {
// body
}
- يُنفَّذ
initمرة واحدة قبل الحلقة. عادة يكون تعريف متغيّر قصيرًا مثلi := 0، وهذا المتغيّر يوجد داخل الحلقة فقط. - يُفحص
conditionقبل كل تكرار. عندما يكون خاطئًا تنتهي الحلقة. - يُنفَّذ الجسم.
- يُنفَّذ
postبعد كل تكرار، وعادة يكونi++.
i++ عبارة في Go لا تعبير، لذا لا يمكن أن يحتوي 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)
}
هذا هو الشكل المعتاد للخوادم والعمّال وحلقات الإدخال. الحلقة for {} بجسم فارغ تستهلك نواة معالج كاملة؛ في الشيفرة الحقيقية يتوقّف الجسم على شيء ما (قراءة، أو استقبال من قناة، أو select).
حلقات range
تمرّ range على مجموعة وتعطي قيمة أو قيمتين في كل تكرار. ما هما يعتمد على ما تمرّ عليه:
| المرور على | القيمة الأولى | القيمة الثانية |
|---|---|---|
| شريحة أو مصفوفة | الفهرس | العنصر (نسخة) |
| نص | فهرس البايت | rune (حرف Unicode) |
| خريطة | المفتاح | القيمة |
| قناة | العنصر | (لا شيء) |
عدد صحيح n (Go 1.22) | من 0 إلى n ناقص 1 | (لا شيء) |
| دالة مكرِّر (Go 1.23) | ما تعطيه الدالة | ما تعطيه الدالة |
ثلاث تفاصيل تظهرها المخرجات:
- متغيّر القيمة نسخة.
v *= 2داخل الحلقة لن يغيّر الشريحة؛ لتعديل العناصر افهرس الشريحة بـnums[i]، كما تفعل الحلقة الثالثة. - فهرس النص إزاحة بايت. يشغل
éبايتين، فيقفز الفهرس من 1 إلى 3. - ترتيب الخريطة عشوائي عمدًا. تجعله Go عشوائيًا كي لا تعتمد الشيفرة عليه. تعطي
slices.Sorted(maps.Keys(m))(Go 1.23) المفاتيح مرتّبة.
تُشرح range كاملة في صفحة 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. سابقًا كان متغيّر واحد يُعاد استخدامه للحلقة كلها، وهذا كان يكسر الإغلاقات والـ 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++، أو مرّ على شريحة من المؤشرات.
الإلحاق أثناء المرور. تقيّم range الشريحة مرة واحدة في البداية. العناصر الملحقة داخل الحلقة لا تُزار، فتنتهي الحلقة، لكنها لن ترى العناصر الجديدة.
الحذف من شريحة بالفهرس في حلقة تسير إلى الأمام. حذف العنصر i يقدّم كل ما بعده موضعًا واحدًا، فيُتخطّى العنصر التالي. ابنِ شريحة جديدة، أو امشِ في الحلقة بالعكس، أو استخدم slices.DeleteFunc.
الانزياح بمقدار واحد مع <=. تقرأ for i := 0; i <= len(s); i++ العنصر s[len(s)] في المرور الأخير وتسبّب panic برسالة index out of range. استخدم <، أو الأفضل range.
عمل ثقيل في الشرط. يُنفَّذ الشرط في كل تكرار. for i := 0; i < expensiveCount(); i++ تستدعي الدالة في كل مرة؛ احسبها مرة واحدة قبل الحلقة.
استخدام عدد عشري كعدّاد. for x := 0.0; x != 1.0; x += 0.1 لا تنتهي أبدًا، لأن 0.1 ليس لها تمثيل ثنائي دقيق. عُدّ بعدد صحيح واحسب العدد العشري منه.
الأسئلة الشائعة
كيف تكتب حلقة for في Go؟
الشكل الكلاسيكي له ثلاثة أجزاء مفصولة بفواصل منقوطة ودون أقواس:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
منذ Go 1.22 يمكنك أيضًا كتابة for i := range 5، التي تعدّ من 0 إلى 4. وللمرور على شريحة استخدم for i, v := range items.
هل تملك Go حلقة while؟
لا توجد كلمة while، لكن for مع شرط فقط هي حلقة while: for n > 0 { n /= 2 }. وfor { ... } دون شرط حلقة لا نهائية تخرج منها بـ break أو return.
كيف أمرّ على خريطة في 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 متغيّر واحد تتشاركه كل التكرارات، فكانت الإغلاقات والـ goroutines التي تبدأ في الحلقة ترى غالبًا القيمة الأخيرة كلها. منذ Go 1.22 يحصل كل تكرار على نسخته الخاصة، فيعمل التقاط i أو v في إغلاق كما هو متوقّع. ينطبق السلوك الجديد عندما يذكر ملف go.mod للوحدة go 1.22 أو أحدث.