تدفق التحكم
تدفق التحكم
تخيّل أنك طاهٍ تطبخ: إذا كان المقلاة ساخناً، أضف الزيت (if)؛ اختر طرق طبخ مختلفة حسب المادة (switch)؛ استمر بالتحريك حتى ينضج (for)؛ لا تنسَ إطفاء النار قبل التقديم (defer). البرامج تعمل بنفس الطريقة — تدفق التحكم يحدد مسار تنفيذ الكود، ويُعلّم الكمبيوتر "اتخاذ القرارات" و"تكرار المهام".
1. المفاهيم الأساسية
جمل تدفق التحكم في Go مختصرة لكن قوية، تشمل بشكل رئيسي الفئات التالية:
1.1 شرط if / else
جملة if في Go لها ميزة فريدة: يمكنك إضافة جملة تهيئة قبل الشرط، مفصولة بفاصلة منقوطة ;. نطاق المتغير محدود بكتلة if/else.
// الشكل الأساسي
if condition {
// يُنفذ عندما يكون الشرط صحيحاً
} else if otherCondition {
// يُنفذ عندما يكون شرط آخر صحيحاً
} else {
// يُنفذ عندما لا يتطابق أي من أعلاه
}
// مع جملة تهيئة
if initStatement; condition {
// المتغير المُهيّأ مرئي فقط داخل كتلة if/else
}
1.2 جملة switch
switch في Go لها اختلافان رئيسيان عن اللغات الأخرى:
- توقف تلقائي: كل
caseينتهي تلقائياً ولا "يسقط" إلى الحالة التالية. - دعم تبديل النوع: يمكنك التحقق من نوع المتغير.
switch variable {
case value1:
// معالجة value1
case value2, value3: // قيم متعددة مفصولة بفواصل
// معالجة value2 أو value3
default:
// المعالجة الافتراضية
}
1.3 حلقة for
for هي الحلقة الوحيدة في Go. تُحل محل while و do-while وغيرها من الحلقات الموجودة في لغات أخرى.
| الشكل | الصياغة | حالة الاستخدام |
|---|---|---|
| for تقليدي | for init; cond; post {} |
عدد تكرار معروف |
| بشكل while | for cond {} |
حلقة شرطية |
| حلقة لا نهائية | for {} |
تنفيذ مستمر |
| iterate بـ range | for i, v := range collection {} |
iterate على مجموعات |
1.4 break و continue
break: يُنهي الحلقة الحالية فوراً.continue: يتخطى الكود المتبقي في التكرار الحالي وينتقل إلى التالي.
1.5 defer (تأجيل التنفيذ)
جملة defer تؤجل استدعاء دالة حتى تعود الدالة المحيطة. استدعاءات defer المتعددة تُنفذ بترتيب آخر دخول، أول خروج (LIFO).
func example() {
defer fmt.Println("سُجّل أولاً، يُنفذ أخيراً")
defer fmt.Println("سُجّل ثانياً، يُنفذ قبل الأخير")
fmt.Println("تنفيذ عادي")
}
// ترتيب المخرجات: تنفيذ عادي → سُجّل ثانياً → سُجّل أولاً
2. الصياغة والاستخدام الأساسي
استخدام if / else
// if/else قياسي
score := 85
if score >= 90 {
fmt.Println("ممتاز")
} else if score >= 80 {
fmt.Println("جيد")
} else if score >= 60 {
fmt.Println("ناجح")
} else {
fmt.Println("راسب")
}
if لا تحتاج أقواس، لكن الأقواس المعقوفة {} مطلوبة، ويجب أن يكون else على نفس السطر كقوس الإغلاق لـ if. هذه قاعدة صياغة Go — عدم اتباعها سيُسبب خطأ ترجمة.
// if مع جملة تهيئة
// نطاق err محدود بكتلة if/else؛ لا يمكن الوصول إليها من الخارج
if err := doSomething(); err != nil {
fmt.Println("خطأ:", err)
}
if مع جمل التهيئة شائعة جداً في Go، خاصة للتعامل مع الأخطاء. تُقلل نطاق المتغير وتتجنب تلويث النطاق الخارجي.
استخدام switch
// switch أساسي
day := "الأربعاء"
switch day {
case "الإثنين":
fmt.Println("بداية أسبوع جديد")
case "الثلاثاء", "الأربعاء", "الخميس":
fmt.Println("يوم عمل")
case "الجمعة":
fmt.Println("أوشك على عطلة نهاية الأسبوع")
case "السبت", "الأحد":
fmt.Println("عطلة نهاية أسبوع سعيدة")
default:
fmt.Println("يوم غير صالح")
}
switch في Go لا يتطلب break بعد كل case. إذا كنت حقاً بحاجة إلى "السقوط" إلى الحالة التالية، استخدم كلمة fallthrough، لكن هذا نادر.
// switch بدون علامة (يُحل محل سلاسل if-else الطويلة)
score := 85
switch {
case score >= 90:
fmt.Println("ممتاز")
case score >= 80:
fmt.Println("جيد")
case score >= 60:
fmt.Println("ناجح")
default:
fmt.Println("راسب")
}
switch متغيراً، يُعادل switch true ويمكنه استبدال سلاسل if-else الطويلة بكود أنظف.
استخدام حلقة for
// حلقة for تقليدية
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// بشكل while
count := 0
for count < 5 {
fmt.Println(count)
count++
}
// حلقة لا نهائية (استخدم مع break)
for {
fmt.Println("اضغط Ctrl+C للخروج")
break // استخدام break هنا لتجنب حلقة لا نهائية حقيقية
}
استخدام defer
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("فشل فتح الملف:", err)
return
}
defer file.Close() // ضمان إغلاق الملف عند انتهاء الدالة
// ... قراءة محتوى الملف ...
}
defer تُقيّم فورياً عند ظهور جملة defer، وليس عند تنفيذ الدالة. انتبه لهذا التفاصيل لتجنب السلوك غير المتوقع.
مثال 1: الاستخدام الأساسي (الصعوبة ⭐)
هذا المثال يعرض الاستخدام الأساسي لـ if/else و switch.
package main
import "fmt"
func main() {
// ===== مثال if/else: تحديد التقدير =====
score := 78
// استخدام if مع جملة تهيئة
// نطاق المتغير rank محدود بكتلة if/else
if rank := score / 10; rank >= 9 {
fmt.Printf("الدرجة %d، التقدير: ممتاز\n", score)
} else if rank >= 8 {
fmt.Printf("الدرجة %d، التقدير: جيد\n", score)
} else if rank >= 6 {
fmt.Printf("الدرجة %d، التقدير: ناجح\n", score)
} else {
fmt.Printf("الدرجة %d، التقدير: راسب\n", score)
}
// ===== مثال switch: تحديد الفصل حسب الشهر =====
month := 8
switch month {
case 3, 4, 5:
fmt.Printf("الشهر %d هو الربيع\n", month)
case 6, 7, 8:
fmt.Printf("الشهر %d هو الصيف\n", month)
case 9, 10, 11:
fmt.Printf("الشهر %d هو الخريف\n", month)
case 12, 1, 2:
fmt.Printf("الشهر %d هو الشتاء\n", month)
default:
fmt.Printf("%d ليس شهراً صالحاً\n", month)
}
// ===== switch بدون علامة: يُحل محل سلسلة if-else =====
hour := 14
switch {
case hour < 6:
fmt.Println("فجر")
case hour < 12:
fmt.Println("صباح")
case hour < 18:
fmt.Println("مساء")
default:
fmt.Println("ليل")
}
}
المخرجات:
الدرجة 78، التقدير: ناجح
الشهر 8 هو الصيف
مساء
النقاط الرئيسية:
rank := score / 10هي جملة تهيئة؛ نطاقrankمحدود بكتلةif/elsecase 3, 4, 5فيswitchيعني تطابق أي من 3 أو 4 أو 5switchبدون علامة مناسب لاستبدال سلاسلif-elseالمعقدة
مثال 2: الاستخدام المتوسط (الصعوبة ⭐⭐)
هذا المثال يعرض أشكال مختلفة من حلقات for واستخدام break / continue.
package main
import "fmt"
func main() {
// ===== حلقة for تقليدية: مجموع 1 إلى 100 =====
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Printf("مجموع 1 إلى 100: %d\n", sum)
// ===== range على slice =====
fruits := []string{"تفاح", "موز", "برتقال", "عنب"}
for index, fruit := range fruits {
fmt.Printf("الفهرس %d: %s\n", index, fruit)
}
// ===== range على نص (بحرف rune) =====
text := "Goعربي"
for i, ch := range text {
fmt.Printf("الموضع %d: %c (Unicode: %U)\n", i, ch, ch)
}
// ===== break: إيجاد أول عدد يقبل القسمة على 7 =====
for i := 1; i <= 100; i++ {
if i%7 == 0 {
fmt.Printf("أول عدد يقبل القسمة على 7: %d\n", i)
break // يُنهي الحلقة فوراً بعد إيجاده
}
}
// ===== continue: طباعة جميع الأعداد الفردية من 1-20 =====
fmt.Print("الأعداد الفردية 1-20: ")
for i := 1; i <= 20; i++ {
if i%2 == 0 {
continue // تخطي الأعداد الزوجية، الانتقال للتكرار التالي
}
fmt.Printf("%d ", i)
}
fmt.Println()
// ===== break مع علامة: الخروج من الحلقة الخارجية =====
fmt.Println("البحث عن العدد 5 في مصفوفة ثنائية الأبعاد:")
matrix := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
found: // اسم العلامة
for row, cols := range matrix {
for col, val := range cols {
if val == 5 {
fmt.Printf("وُجد! الموقع: الصف %d، العمود %d\n", row, col)
break found // الخروج من الحلقة الخارجية
}
}
}
}
المخرجات:
مجموع 1 إلى 100: 5050
الفهرس 0: تفاح
الفهرس 1: موز
الفهرس 2: برتقال
الفهرس 3: عنب
الموضع 0: G (Unicode: U+0047)
الموضع 1: o (Unicode: U+006F)
الموضع 2: ع (Unicode: U+0639)
الموضع 4: ر (Unicode: U+0631)
الموضع 6: ب (Unicode: U+0628)
الموضع 8: ي (Unicode: U+064A)
أول عدد يقبل القسمة على 7: 7
الأعداد الفردية 1-20: 1 3 5 7 9 11 13 15 17 19
البحث عن العدد 5 في مصفوفة ثنائية الأبعاد:
وُجد! الموقع: الصف 1، العمود 1
النقاط الرئيسية:
for i := 1; i <= 100; i++هي الحلقة القياسية من ثلاثة أجزاءrangeعلى slice يُعيد الفهرس والقيمة؛ على نص يُiterate بحرف Unicode (rune)break foundيستخدم علامة للخروج من حلقة خارجية محددة، مفيد جداً في الحلقات المتداخلة
مثال 3: التطبيق الشامل (الصعوبة ⭐⭐⭐)
هذا المثال يجمع جميع جمل تدفق التحكم لتنفيذ نظام بسيط لإدارة درجات الطلاب.
package main
import "fmt"
// هيكل الطالب
type Student struct {
Name string
Scores []int
}
// GetAverage يحسب متوسط الدرجة
func (s Student) GetAverage() float64 {
if len(s.Scores) == 0 {
return 0
}
total := 0
for _, score := range s.Scores {
total += score
}
return float64(total) / float64(len(s.Scores))
}
// GetGrade يُعيد التقدير بناءً على متوسط الدرجة
func (s Student) GetGrade() string {
avg := s.GetAverage()
switch {
case avg >= 90:
return "A (ممتاز)"
case avg >= 80:
return "B (جيد)"
case avg >= 70:
return "C (متوسط)"
case avg >= 60:
return "D (ناجح)"
default:
return "F (راسب)"
}
}
// PrintReport يطبع تقرير الدرجات
func PrintReport(students []Student) {
fmt.Println("========================================")
fmt.Println(" تقرير درجات الطلاب")
fmt.Println("========================================")
// استخدام defer لضمان طباعة نهاية التقرير بعد جميع بيانات الطلاب
defer fmt.Println("========================================")
defer fmt.Println(" انتهى التقرير")
for i, student := range students {
// استخدام defer لعرض ترتيب LIFO
defer func(name string, idx int) {
fmt.Printf("[تنظيف] انتهى معالجة تقرير %s\n", name)
}(student.Name, i)
// طباعة معلومات الطالب
fmt.Printf("\nالطالب %d: %s\n", i+1, student.Name)
fmt.Printf(" الدرجات: %v\n", student.Scores)
fmt.Printf(" المتوسط: %.1f\n", student.GetAverage())
fmt.Printf(" التقدير: %s\n", student.GetGrade())
// تحليل درجة كل مادة
subjects := []string{"اللغة العربية", "الرياضيات", "اللغة الإنجليزية"}
for j, score := range student.Scores {
// التأكد من أن الفهرس ضمن النطاق
subject := "غير معروف"
if j < len(subjects) {
subject = subjects[j]
}
// التحقق من درجة كل مادة
if score < 60 {
fmt.Printf(" ⚠ %s (%d) راسب، يحتاج إعادة\n", subject, score)
}
}
}
}
// FindTopStudent يجد الطالب בעל أعلى متوسط
func FindTopStudent(students []Student) (string, float64) {
if len(students) == 0 {
return "", 0
}
topName := students[0].Name
topAvg := students[0].GetAverage()
for _, s := range students[1:] {
if avg := s.GetAverage(); avg > topAvg {
topName = s.Name
topAvg = avg
}
}
return topName, topAvg
}
func main() {
// إنشاء بيانات الطلاب
students := []Student{
{"Alice", []int{85, 92, 78}},
{"Bob", []int{90, 95, 88}},
{"Charlie", []int{72, 58, 65}},
{"Diana", []int{95, 98, 92}},
}
// طباعة تقرير الدرجات
PrintReport(students)
// إيجاد الطالب المتفوق
topName, topAvg := FindTopStudent(students)
fmt.Printf("\nالطالب المتفوق: %s (المتوسط: %.1f)\n", topName, topAvg)
}
المخرجات:
========================================
تقرير درجات الطلاب
========================================
الطالب 1: Alice
الدرجات: [85 92 78]
المتوسط: 85.0
التقدير: B (جيد)
الطالب 2: Bob
الدرجات: [90 95 88]
المتوسط: 91.0
التقدير: A (ممتاز)
الطالب 3: Charlie
الدرجات: [72 58 65]
المتوسط: 65.0
التقدير: D (ناجح)
⚠ الرياضيات (58) راسب، يحتاج إعادة
الطالب 4: Diana
الدرجات: [95 98 92]
المتوسط: 95.0
التقدير: A (ممتاز)
[تنظيف] انتهى معالجة تقرير Diana
[تنظيف] انتهى معالجة تقرير Charlie
[تنظيف] انتهى معالجة تقرير Bob
[تنظيف] انتهى معالجة تقرير Alice
========================================
انتهى التقرير
الطالب المتفوق: Diana (المتوسط: 95.0)
النقاط الرئيسية:
- تسجيلات
deferالمتعددة تُنفذ بترتيب LIFO (آخر دخول، أول خروج): Diana → Charlie → Bob → Alice defer fmt.Println("انتهى التقرير")سُجّل أولاً لكنه يُنفذ أخيراً (لأنه سُجّل أولاً، يُستخرج أخيراً)switchبدون علامة يُحل محل سلاسلif-elseالمعقدة لتحديد التقديراتfor rangeيُiterate على slice من الهياكل؛for i, s := range students[1:]يبدأ المقارنة من العنصر الثانيdeferفي الدوال المجهولة يلتزم قيم المعاملات فورياً (يمرر نسخاً منstudent.Nameوi)
3. حالات استخدام شائعة
الحالة 1: سلسلة معالجة الأخطاء (if + جملة تهيئة)
في Go، الاستخدام الأكثر شيوعاً لـ if مع جمل التهيئة هو معالجة الأخطاء:
package main
import (
"fmt"
"strconv"
)
func main() {
// تحليل الأعداد ومعالجة الأخطاء
inputs := []string{"42", "abc", "100", "xyz", "0"}
for _, input := range inputs {
// إجراء تحويل النوع في التهيئة، التحقق من الخطأ في الشرط
if num, err := strconv.Atoi(input); err != nil {
fmt.Printf("لا يمكن تحليل %q: %v\n", input, err)
} else {
fmt.Printf("تم التحليل بنجاح: %q → %d\n", input, num)
}
}
}
الحالة 2: iterate على Map مع تصفية شرطية (for + switch)
package main
import "fmt"
func main() {
// جدول درجات الطلاب
students := map[string]int{
"Alice": 85,
"Bob": 92,
"Charlie": 58,
"Diana": 76,
"Eve": 45,
}
// عد الطلاب في كل مستوى دراسي
excellent, good, pass, fail := 0, 0, 0, 0
for name, score := range students {
switch {
case score >= 90:
excellent++
fmt.Printf("%s: %d (ممتاز)\n", name, score)
case score >= 80:
good++
fmt.Printf("%s: %d (جيد)\n", name, score)
case score >= 60:
pass++
fmt.Printf("%s: %d (ناجح)\n", name, score)
default:
fail++
fmt.Printf("%s: %d (راسب)\n", name, score)
}
}
fmt.Printf("\nالملخص: ممتاز %d، جيد %d، ناجح %d، راسب %d\n",
excellent, good, pass, fail)
}
❓ أسئلة شائعة
س1: لماذا لا يحتاج switch في Go إلى break؟
مصممو Go اعتقدوا أن سلوك fallthrough في switch بلغة C كان مصدراً شائعاً للأخطاء. switch في Go يُنهي تلقائياً بعد كل حالة، مما يمنع السقوط غير المقصود من جمل break المنسية. إذا كنت بحاجة فعلية إلى fallthrough، يمكنك استخدام كلمة fallthrough صراحةً.
// استخدام fallthrough (نادر)
x := 1
switch x {
case 1:
fmt.Println("واحد")
fallthrough // متابعة تنفيذ كود الحالة التالية
case 2:
fmt.Println("اثنان") // سيتم تنفيذه
default:
fmt.Println("أخرى")
}
// المخرجات: واحد \n اثنان
س2: هل المتغيرات في for range نسخ؟
نعم. متغير القيمة في for range هو نسخة من العنصر؛ تعديله لن يؤثر على المجموعة الأصلية. إذا كنت بحاجة لتعديل عناصر في المجموعة الأصلية، اصل إليها بالفهرس.
nums := []int{1, 2, 3, 4, 5}
// خاطئ: تعديل نسخة
for _, n := range nums {
n *= 2 // لن يؤثر على nums
}
// صحيح: تعديل الأصل عبر الفهرس
for i := range nums {
nums[i] *= 2 // سيُعدّل nums
}
س3: متى تُقيّم معاملات defer؟
معاملات defer تُقيّم عند ظهور جملة defer، وليس عند عودة الدالة.
func main() {
x := 10
defer fmt.Println("x في defer:", x) // x تُقيّم هنا كـ 10
x = 20
fmt.Println("x بعد التعديل:", x)
}
// المخرجات:
// x بعد التعديل: 20
// x في defer: 10 (ليست 20!)
س4: كيف ألتقط متغيرات الحلقة بشكل صحيح عند استخدام goroutines في حلقة؟
قبل Go 1.22، متغيرات حلقة for كانت تتشارك نفس العنوان عبر جميع التكرارات، مما قد يُسبب مشاكل في goroutines. Go 1.22+ أصلحت هذا السلوك — كل تكرار يحصل على متغيره الخاص. إذا كنت تستخدم إصداراً أقدم، مرر المتغير كمعامل:
// Go 1.22+ — استخدم مباشرة
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i) // كل goroutine تحصل على i الخاص بها
}()
}
// الإصدارات الأقدم — مرر صراحةً كمعامل
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println(n) // n نسخة من i
}(i)
}
📖 ملخص
if/elseيدعم جمل التهيئة (if init; cond {})؛ نطاق المتغير محدود بالكتلةswitchلا يحتاج break افتراضياً؛ كل حالة تنتهي تلقائياً؛ يدعم switch بدون علامة كبديل لسلاسل if-elseforهو الحلقة الوحيدة في Go، بأربعة أشكال: for تقليدي، بشكل while، حلقة لا نهائية، iterate بـ rangebreakيمكن استخدامه مع علامات للخروج من مستويات حلقة محددة؛continueيتخطى التكرار الحاليdeferيُنفذ بترتيب آخر دخول، أول خروج (LIFO)؛ المعاملات تُقيّم فوراً عند جملة deferrangeيُعيد نسخاً؛ تعديل النسخ لا يؤثر على المجموعة الأصلية
📝 تمارين
تمرين 1 (⭐)
اكتب برنامجاً يأخذ سنة كمدخل ويحدد ما إذا كانت سنة كبيسة. القواعد:
- السنة كبيسة إذا كانت تقبل القسمة على 4 لكن ليس على 100، أو إذا كانت تقبل القسمة على 400.
- استخدم شكل
ifمع جملة تهيئة.
package main
import "fmt"
func main() {
year := 2024 // غيّر هذه القيمة للاختبار
// اكتب الكود هنا
// تلميح: استخدم شكل if init; cond
}
تمرين 2 (⭐⭐)
اكتب برنامجاً يستخدم حلقات for لطباعة جدول الضرب. متطلبات التنسيق:
- مضروب واحد لكل صف
- استخدم tab
\tللمحاذاة - اجمع بين حلقات
forوالإخراج المنسق
package main
import "fmt"
func main() {
// اكتب الكود هنا
// تلميح: استخدم حلقتين for متداخلتين
// الحلقة الخارجية i من 1 إلى 9
// الحلقة الداخلية j من 1 إلى i
}
تمرين 3 (⭐⭐⭐)
اكتب برنامجاً ينفذ لعبة بسيطة لتخمين الأرقام:
- استخدم حلقة لتمكين المستخدم من التخمين بشكل متكرر حتى يُصيب
- بعد كل تخمين، أعطِ تلميحاً: "أعلى" أو "أقل"
- استخدم
deferلتسجيل أوقات بداية ونهاية اللعبة - استخدم
switchلإعطاء تصنيفات مختلفة بناءً على عدد التخمينات (1-3: عبقري، 4-6: جيد، 7+: حاول مرة أخرى)
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// استخدم defer لتسجيل وقت النهاية
start := time.Now()
defer func() {
fmt.Printf("\nمدة اللعبة: %v\n", time.Since(start))
}()
// توليد عدد عشوائي 1-100
target := rand.Intn(100) + 1
attempts := 0
fmt.Println("=== لعبة تخمين الأرقام ===")
fmt.Println("أنا أفكر في عدد بين 1-100، حاول تخمينه!")
// اكتب الكود هنا
// تلميحات:
// 1. استخدم حلقة for لإبقاء اللعبة مستمرة
// 2. استخدم fmt.Scan لقراءة مدخلات المستخدم
// 3. استخدم switch للتحقق من نتيجة التخمين ومستوى التصنيف
// 4. استخدم break للخروج من الحلقة عند التخمين الصحيح
}
الدرس التالي
الدرس التالي: الدوال → تعلّم تعريفات الدوال في Go، والقيم المتعددة للإرجاع، والمعاملات المتغيرة، والدوال المجهولة، والإغلاقات، والمفاهيم الأساسية الأخرى.



