تدفق التحكم

تدفق التحكم

تخيّل أنك طاهٍ تطبخ: إذا كان المقلاة ساخناً، أضف الزيت (if)؛ اختر طرق طبخ مختلفة حسب المادة (switch)؛ استمر بالتحريك حتى ينضج (for)؛ لا تنسَ إطفاء النار قبل التقديم (defer). البرامج تعمل بنفس الطريقة — تدفق التحكم يحدد مسار تنفيذ الكود، ويُعلّم الكمبيوتر "اتخاذ القرارات" و"تكرار المهام".


1. المفاهيم الأساسية

جمل تدفق التحكم في Go مختصرة لكن قوية، تشمل بشكل رئيسي الفئات التالية:

1.1 شرط if / else

جملة if في Go لها ميزة فريدة: يمكنك إضافة جملة تهيئة قبل الشرط، مفصولة بفاصلة منقوطة ;. نطاق المتغير محدود بكتلة if/else.

GO
// الشكل الأساسي
if condition {
    // يُنفذ عندما يكون الشرط صحيحاً
} else if otherCondition {
    // يُنفذ عندما يكون شرط آخر صحيحاً
} else {
    // يُنفذ عندما لا يتطابق أي من أعلاه
}

// مع جملة تهيئة
if initStatement; condition {
    // المتغير المُهيّأ مرئي فقط داخل كتلة if/else
}

1.2 جملة switch

switch في Go لها اختلافان رئيسيان عن اللغات الأخرى:

GO
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

1.5 defer (تأجيل التنفيذ)

جملة defer تؤجل استدعاء دالة حتى تعود الدالة المحيطة. استدعاءات defer المتعددة تُنفذ بترتيب آخر دخول، أول خروج (LIFO).

GO
func example() {
    defer fmt.Println("سُجّل أولاً، يُنفذ أخيراً")
    defer fmt.Println("سُجّل ثانياً، يُنفذ قبل الأخير")
    fmt.Println("تنفيذ عادي")
}
// ترتيب المخرجات: تنفيذ عادي → سُجّل ثانياً → سُجّل أولاً

2. الصياغة والاستخدام الأساسي

استخدام if / else

GO
// if/else قياسي
score := 85
if score >= 90 {
    fmt.Println("ممتاز")
} else if score >= 80 {
    fmt.Println("جيد")
} else if score >= 60 {
    fmt.Println("ناجح")
} else {
    fmt.Println("راسب")
}
💡 نصيحة: في Go، شروط if لا تحتاج أقواس، لكن الأقواس المعقوفة {} مطلوبة، ويجب أن يكون else على نفس السطر كقوس الإغلاق لـ if. هذه قاعدة صياغة Go — عدم اتباعها سيُسبب خطأ ترجمة.

GO
// if مع جملة تهيئة
// نطاق err محدود بكتلة if/else؛ لا يمكن الوصول إليها من الخارج
if err := doSomething(); err != nil {
    fmt.Println("خطأ:", err)
}
💡 نصيحة: if مع جمل التهيئة شائعة جداً في Go، خاصة للتعامل مع الأخطاء. تُقلل نطاق المتغير وتتجنب تلويث النطاق الخارجي.

استخدام switch

GO
// switch أساسي
day := "الأربعاء"
switch day {
case "الإثنين":
    fmt.Println("بداية أسبوع جديد")
case "الثلاثاء", "الأربعاء", "الخميس":
    fmt.Println("يوم عمل")
case "الجمعة":
    fmt.Println("أوشك على عطلة نهاية الأسبوع")
case "السبت", "الأحد":
    fmt.Println("عطلة نهاية أسبوع سعيدة")
default:
    fmt.Println("يوم غير صالح")
}
💡 نصيحة: switch في Go لا يتطلب break بعد كل case. إذا كنت حقاً بحاجة إلى "السقوط" إلى الحالة التالية، استخدم كلمة fallthrough، لكن هذا نادر.

GO
// 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

GO
// حلقة 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

GO
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.

GO
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("ليل")
    }
}
▶ جرّب الكود

المخرجات:

TEXT
الدرجة 78، التقدير: ناجح
الشهر 8 هو الصيف
مساء

النقاط الرئيسية:


مثال 2: الاستخدام المتوسط (الصعوبة ⭐⭐)

هذا المثال يعرض أشكال مختلفة من حلقات for واستخدام break / continue.

GO
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 // الخروج من الحلقة الخارجية
            }
        }
    }
}
▶ جرّب الكود

المخرجات:

TEXT
مجموع 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

النقاط الرئيسية:


مثال 3: التطبيق الشامل (الصعوبة ⭐⭐⭐)

هذا المثال يجمع جميع جمل تدفق التحكم لتنفيذ نظام بسيط لإدارة درجات الطلاب.

GO
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)
}
▶ جرّب الكود

المخرجات:

TEXT
========================================
         تقرير درجات الطلاب
========================================

الطالب 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)

النقاط الرئيسية:


3. حالات استخدام شائعة

الحالة 1: سلسلة معالجة الأخطاء (if + جملة تهيئة)

في Go، الاستخدام الأكثر شيوعاً لـ if مع جمل التهيئة هو معالجة الأخطاء:

GO
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)

GO
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 صراحةً.

GO
// استخدام fallthrough (نادر)
x := 1
switch x {
case 1:
    fmt.Println("واحد")
    fallthrough // متابعة تنفيذ كود الحالة التالية
case 2:
    fmt.Println("اثنان") // سيتم تنفيذه
default:
    fmt.Println("أخرى")
}
// المخرجات: واحد \n اثنان

س2: هل المتغيرات في for range نسخ؟

نعم. متغير القيمة في for range هو نسخة من العنصر؛ تعديله لن يؤثر على المجموعة الأصلية. إذا كنت بحاجة لتعديل عناصر في المجموعة الأصلية، اصل إليها بالفهرس.

GO
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، وليس عند عودة الدالة.

GO
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
// 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)
}

📖 ملخص


📝 تمارين

تمرين 1 (⭐)

اكتب برنامجاً يأخذ سنة كمدخل ويحدد ما إذا كانت سنة كبيسة. القواعد:

GO
package main

import "fmt"

func main() {
    year := 2024 // غيّر هذه القيمة للاختبار

    // اكتب الكود هنا
    // تلميح: استخدم شكل if init; cond
}

تمرين 2 (⭐⭐)

اكتب برنامجاً يستخدم حلقات for لطباعة جدول الضرب. متطلبات التنسيق:

GO
package main

import "fmt"

func main() {
    // اكتب الكود هنا
    // تلميح: استخدم حلقتين for متداخلتين
    // الحلقة الخارجية i من 1 إلى 9
    // الحلقة الداخلية j من 1 إلى i
}

تمرين 3 (⭐⭐⭐)

اكتب برنامجاً ينفذ لعبة بسيطة لتخمين الأرقام:

  1. استخدم حلقة لتمكين المستخدم من التخمين بشكل متكرر حتى يُصيب
  2. بعد كل تخمين، أعطِ تلميحاً: "أعلى" أو "أقل"
  3. استخدم defer لتسجيل أوقات بداية ونهاية اللعبة
  4. استخدم switch لإعطاء تصنيفات مختلفة بناءً على عدد التخمينات (1-3: عبقري، 4-6: جيد، 7+: حاول مرة أخرى)
GO
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، والقيم المتعددة للإرجاع، والمعاملات المتغيرة، والدوال المجهولة، والإغلاقات، والمفاهيم الأساسية الأخرى.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%