Go: التحكم في تدفق التنفيذ: العبارات الأربع الرئيسية للتحكم…

تدفق التحكم هو ما يجعل البرامج «تفكر» — تغطي لغة Go جميع سيناريوهات تدفق التحكم باستخدام أربع كلمات رئيسية بسيطة فقط، مما يلغي التكرار في حلقات while وdo-while.

فلسفة تصميم تدفق التحكم في لغة Go: الأقل هو الأكثر. الكلمات الرئيسية الأربع if وswitch وfor وdefer، إلى جانب break وcontinue وlabel، كافية للتعامل مع 99% من منطق الأعمال. في هذا الدرس، ستتقن جميع المفاهيم الأساسية للتحكم في التدفق في لغة Go.

1. ستتعلم



2. قصة حقيقية عن منصة تعليمية عبر الإنترنت

(1) المشكلة: 500 سطر من كود لغة C++ لتصحيح الامتحانات، ومع ذلك تستمر الأخطاء في الظهور

تشارلي هو مهندس الخلفية في منصة تعليمية عبر الإنترنت. وقد تولى للتو مسؤولية كود نظام التصحيح التلقائي:

"قال رئيس الوزراء إن معالجة 1,000 ورقة امتحان تستغرق الآن 8 ساعات، وفي كل مرة يظهر فيها خطأ برمجي، يكون إما خطأً من نوع «خارج النطاق» أو عدم وجود {} في جملة بلغة C if، مما يؤدي إلى إفساد درجات الصف بأكمله..."

فتح ملف كود C++ الذي تركه سلفه وألقى نظرة عليه:

CPP
// C++ Style: Verbose + Prone to Errors
if (score >= 60) {
    grade = "Passing Grade";
}
else  // Note: C++ else on a different line is valid, but team style is inconsistent
    grade = "Fail";

// Omission of a loop condition = an infinite loop
while (true) {  // Wanted 3 iterations, but forgot the counter
    // ... Didn't realize until 500 lines later
}

بعد توليه المسؤولية، قرر تشارلي إعادة كتابة البرنامج بلغة Go.

(2) الحل بلغة Go

يغطي لغة «Go» العملية بأكملها من خلال أربع كلمات رئيسية:

GO
// score_evaluator.go
package main

import "fmt"

func evaluateScore(score int) string {
    // (1) if contains an initialization statement
    if grade := calculateGrade(score); grade != "" {
        return fmt.Sprintf("Score: %d, Level: %s", score, grade)
    }
    return "Invalid Score"
}

func calculateGrade(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    // (2) for 4 forms
    scores := []int{95, 82, 67, 45, 88}

    for i, s := range scores {  // range form
        fmt.Printf("Question %d: %d points -> %s\n", i+1, s, evaluateScore(s))
    }
}

الناتج:

TEXT 📖 للعرض فقط
Question 1: 95 points -> Score: 95, Level A
Question 2: 82 points -> Score: 82, Level B
Question 3: 67 points -> Score: 67, Level C
Question 4: 45 points -> Score: 45, Level D
Question 5: 88 points -> Score: 88, Level B

(3) المزايا: مقارنة بين سهولة التحكم في العمليات

اللغة الكلمة الرئيسية «if» الكلمة الرئيسية «loop» التوقف التلقائي في «switch» حجم الكود لنظام التقييم
C/C++ if for/while/do-while يتطلب break يدويًّا ~500 سطر
جافا if for/while/do-while يتطلب إجراءً يدويًّا break حوالي 400 سطر
بايثون if for/while غير متوفر (لا يوجد switch) حوالي 350 سطرًا
Go if for (4 أشكال) فاصل تلقائي ~200 سطر
💡 نصيحة: تعمل Go على توحيد جميع الحلقات في for وإزالة break من السلوك الافتراضي لـ switch — كلمة رئيسية أقل، وعشرة أنواع أقل من الأخطاء.



3. عبارة if

(1) جملة if / else الأساسية

GO
package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Excellent")
    } else if score >= 60 {
        fmt.Println("Passing Grade")
    } else {
        fmt.Println("Fail")
    }
}

الناتج:

TEXT 📖 للعرض فقط
Passing Grade
🔥 خطأ شائع: في لغة Go، يجب أن تكون الكلمة الرئيسية else في نفس السطر مع } (يلزم استخدام الأقواس المتعرجة). تؤدي صيغة }else{ إلى حدوث خطأ في الترجمة.

▶ مثال: تحديد الدرجة

GO
package main

import "fmt"

func judgeGrade(score int) string {
    if score >= 90 {
        return "A"
    } else if score >= 80 {
        return "B"
    } else if score >= 70 {
        return "C"
    } else if score >= 60 {
        return "D"
    } else {
        return "F"
    }
}

func main() {
    fmt.Println(judgeGrade(95))  // A
    fmt.Println(judgeGrade(72))  // C
    fmt.Println(judgeGrade(50))  // F
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
A
C
F

(3) في حالة وجود عبارات التهيئة (ميزة في لغة Go)

يمكن لـ if تنفيذ عبارة تهيئة أولاً، تليها عملية فحص شرطي — مما يحد من نطاق المتغيرات:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Syntax: if initialization statement; condition { ... }
    // The variable `err` is scoped only within the `if-else` block.
    if n, err := strconv.Atoi("123"); err == nil {
        fmt.Printf("Conversion successful: %d\n", n)
    } else {
        fmt.Printf("Conversion failed: %v\n", err)
    }
}

الناتج:

TEXT 📖 للعرض فقط
Conversion successful: 123

▶ مثال: قراءة ملف + إغلاقه باستخدام defer

GO
package main

import (
    "fmt"
    "os"
)

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // automatically close file before function returns

    data := make([]byte, 1024)
    n, _ := file.Read(data)
    fmt.Printf("%d bytes read\n", n)

    return nil
}

func main() {
    readFile("config.txt")
}
▶ جرّب الكود

(5) الممارسات الشائعة لمعالجة الأخطاء في عبارات if (إحدى السمات المميزة للغة Go)

تتم معالجة الأخطاء في لغة Go باستخدام عبارات if. الاستخدام التقليدي:

GO
if err := doSomething(); err != nil {
    return err  // early return
}
// Normal Logic
💡 نصيحة: سيقدم الدرس الثامن شرحًا متعمقًا لنوع error وأفضل الممارسات المتعلقة بهذا النمط.



4. تغيير الفرع (توقف تلقائي)

(1) أساسيات المفاتيح الكهربائية: القاطع التلقائي

بشكل افتراضي، لا تنتشر عبارة switch في لغة Go — فهي تنتهي تلقائيًا بعد مطابقة إحدى الحالات:

GO
package main

import "fmt"

func main() {
    day := "Monday"

    switch day {
    case "Monday":
        fmt.Println("Monday: Meeting Day")
    case "Tuesday":
        fmt.Println("Tuesday: Development Day")
    case "Friday":
        fmt.Println("Friday: Demo Day")
    default:
        fmt.Println("Weekend: Day off")
    }
}

الناتج:

TEXT 📖 للعرض فقط
Monday: Meeting Day

(2) مقارنةً بـ C/Java: لا حاجة إلى break

اللغة قواعد النحو الفاصل التلقائي
C/C++ case 1: ...; case 2: ... يتطلب دليلًا break;
جافا case 1: ...; case 2: ... يتطلب دليلًا break;
انطلق case "Monday": ... فاصل تلقائي

إذا كنت ترغب في استخدام ميزة «fall-through»، فاستخدم الكلمة الرئيسية fallthrough:

GO
switch n {
case 1:
    fmt.Println("One")
    fallthrough  // force execution of the next case
case 2:
    fmt.Println("Two")
}

الناتج:

TEXT 📖 للعرض فقط
One
Two

(3) تغيير شكل التعبير (بدون علامة)

لا تضع متغيرًا بعد switch؛ اكتب الشرط الخاص بكل case على حدة:

GO
package main

import "fmt"

func gradeLevel(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    fmt.Println(gradeLevel(95))
    fmt.Println(gradeLevel(72))
}

الناتج:

TEXT 📖 للعرض فقط
A
C

▶ مثال: محول مع مطابقة متعددة القيم + توجيه بناءً على رموز حالة HTTP

GO
package main

import "fmt"

func routeStatus(code int) string {
    switch code {
    case 200, 201, 204:
        return "Success"
    case 301, 302:
        return "Redirect"
    case 400, 401, 403, 404:
        return "Client Error"
    case 500, 502, 503:
        return "Server Error"
    default:
        return "Unknown"
    }
}

func main() {
    fmt.Println(routeStatus(200))  // Success
    fmt.Println(routeStatus(404))  // Client Error
    fmt.Println(routeStatus(500))  // Server Error
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Success
Client Error
Server Error


5. الأشكال الأربعة لحلقة for

تُوحِّد لغة Go جميع الحلقات تحت بنية for، مستخدمةً صيغًا مختلفة لتنفيذ while وdo-while والحلقات اللانهائية.

(1) حلقة "for" الكلاسيكية المكونة من ثلاثة أجزاء

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 3; i++ {
        fmt.Printf("The %dth time\n", i)
    }
}

الناتج:

TEXT 📖 للعرض فقط
The 1th time
The 2th time
The 3th time

▶ مثال: نمط "while" (حلقة شرطية)

GO
package main

import "fmt"

func main() {
    n := 1
    for n < 5 {  // equivalent to while (n < 5)
        fmt.Printf("n=%d\n", n)
        n++
    }
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
n=1
n=2
n=3
n=4

(3) حلقة لا نهائية

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Classic Infinite Loop (In real-world scenarios, it would use `break` or `return`)
    for {
        fmt.Println("Print once per second...")
        time.Sleep(1 * time.Second)
        break  // prevent actual infinite loop, for demo only
    }
}

الناتج:

TEXT 📖 للعرض فقط
Print once per second...

▶ مثال: التكرار على شريحة أو خريطة أو سلسلة باستخدام range

GO
package main

import "fmt"

func main() {
    scores := []int{95, 82, 67}

    // Iterate through the slice: index, value
    for i, score := range scores {
        fmt.Printf("Problem %d: %d points\n", i+1, score)
    }

    // If only value is present: use _ to ignore index
    for _, score := range scores {
        fmt.Printf("Score: %d\n", score)
    }

    // Iterate through a map (key-value)
    ages := map[string]int{"Alice": 28, "Bob": 32}
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // Iterate over a string (by rune, not by byte)
    for i, r := range "Go" {
        fmt.Printf("Position %d: %c (code point %d)\n", i, r, r)
    }
}
▶ جرّب الكود

الناتج (ترتيب الخريطة عشوائي):

TEXT 📖 للعرض فقط
Problem 1: 95 points
Problem 2: 82 points
Problem 3: 67 points
Score: 95
Score: 82
Score: 67
Alice is 28 years old
Bob is 32 years old
Position 0: G (code point 71)
Position 1: o (code point 111)
🔥 خطأ شائع: ترتيب range map غير مضمون — فقد يختلف من مرة إلى أخرى. إذا كنت بحاجة إلى ترتيب ثابت، فقم بفرز المفاتيح أولاً قبل البدء في التكرار.

(5) مرجع سريع لأشكال كلمة "for" الأربعة

النموذج الصيغة البديل
المعيار الخاص بـ for i := 0; i < n; i++ {} C/Java for
نمط "while" for condition {} C/Java while
إنفينيت لوب for {} while(true)
تكرار النطاق for i, v := range slice {} بايثون for...in
100%
flowchart LR
    A[for init; condition; post] --> B{condition true?}
    B -->|yes| C[execute loop body]
    C --> D[execute post statement]
    D --> B
    B -->|no| E[exit loop]
    style A stroke-width:2px
    style E stroke:#f44,stroke-width:2px


6. defer: التنفيذ المؤجل (ترتيب LIFO)

تؤجل defer تنفيذ عبارة ما حتى اللحظة التي تسبق عودة الدالة الحالية مباشرةً. وتُستخدم عادةً لتنظيف الموارد (إغلاق الملفات، وإلغاء قفل الموارد، وإغلاق الاتصالات).

(1) تأجيل «الأساسيات»

GO
package main

import "fmt"

func main() {
    fmt.Println("1: Start of function")

    defer fmt.Println("3: defer execution (before the function returns)")

    fmt.Println("2: Function Body")
}

الناتج:

TEXT 📖 للعرض فقط
1: Start of function
2: Function Body
3: defer execution (before the function returns)

(2) تأجيل ترتيب LIFO (آخر ما يدخل، أول ما يخرج)

GO
package main

import "fmt"

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")

    fmt.Println("Main logic complete")
}

الناتج:

TEXT 📖 للعرض فقط
Main logic complete
3
2
1

لماذا طريقة LIFO؟ لأن defer تحاكي «إدارة الموارد القائمة على المكدس» — أي أن الموارد التي تم فتحها أخيرًا هي التي تُغلق أولاً.

▶ مثال: الاستخدام العملي لـ defer — إغلاق اتصال قاعدة البيانات

GO
package main

import (
    "database/sql"
    "fmt"
    "log"
)

func queryUser(db *sql.DB, userID int) {
    // Get the connection
    rows, err := db.Query("SELECT name FROM users WHERE id = ?", userID)
    if err != nil {
        log.Fatal(err)
    }

    // Key Point: `defer` releases resources (LIFO)
    defer rows.Close()  // Close result set when function returns

    // Business logic...
    for rows.Next() {
        var name string
        rows.Scan(&name)
        fmt.Printf("User: %s\n", name)
    }
}

func main() {
    fmt.Println("Example of Resource Management with `defer`")
    // Real-world scenario: db, _ := sql.Open("mysql", "...")
    // queryUser(db, 1)
}
▶ جرّب الكود
💡 نصيحة: سيتم تنفيذ defer file.Close() حتى لو تم استدعاء return في منتصف العملية — وهذه حالة استخدام مميزة لإدارة الموارد في لغة Go. ملاحظة: قم فقط defer بتنظيف الموارد التي فتحتها في هذه الدالة — لا تقم defer db.Close() هنا لأن db تم تمريرها إلى الدالة ويتم إدارة دورة حياتها بواسطة المُستدعي.



7. break / continue / label: القفزات الدقيقة

(1) break: الخروج من الحلقة

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 10; i++ {
        if i == 5 {
            break  // exit the entire loop
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println("End of loop")
}

الناتج:

TEXT 📖 للعرض فقط
1 2 3 4 End of loop

▶ مثال: الاستمرار في تخطي الأعداد الفردية وزيادتها

GO
package main

import "fmt"

func sumOdd(n int) int {
    sum := 0
    for i := 1; i <= n; i++ {
        if i%2 == 0 {
            continue  // skip even numbers
        }
        sum += i
    }
    return sum
}

func main() {
    fmt.Printf("1-10: Sum of odd numbers: %d\n", sumOdd(10))
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
1-10: Sum of odd numbers: 25

(3) استخدم label للخروج من حلقة متداخلة

label يحدد اسم الحلقة؛ break label يخرج من الحلقة المحددة:

GO
package main

import "fmt"

func main() {
outer:
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            if i*j == 6 {
                fmt.Printf("Hit: i=%d, j=%d\n", i, j)
                break outer  // directly exit the outer loop
            }
            fmt.Printf("(%d, %d) ", i, j)
        }
    }
    fmt.Println("Done")
}

الناتج:

TEXT 📖 للعرض فقط
(1, 1) (1, 2) (1, 3) (2, 1) (2, 2) (2, 3) (3, 1) (3, 2) Hit: i=3, j=2
Done

(4) مقارنة بين الأنواع الثلاثة من عمليات إعادة التوجيه

الكلمة المفتاحية الوظيفة خارج النطاق
break يخرج من العبارة الحالية من نوع loop أو switch مستوى واحد
continue تخطي ما تبقى من هذه الدورة مستوى واحد
break label الخروج من الحلقة عند العلامة المحددة متداخلة


8. مثال كامل: نظام تصحيح الامتحانات عبر الإنترنت

اربط جميع مسارات التحكم في هذا الدرس معًا لتنفيذ برنامج تصحيح امتحانات متعدد الأبعاد:

GO
// exam_evaluator.go
package main

import "fmt"

// Structure of the Problem
type Question struct {
    Type    string  // single/multi/judge
    Correct string  // correct answer
    Student string  // student answer
    Score   float64 // score value
}

// Scoring Function: Initialized with an `if` statement + `switch` expression + `for range`
func evaluateExam(name string, questions []Question) (total float64, details []string) {
    defer func() {
        // defer: Collect results (ensures that results are collected even if a return statement is encountered midway)
        details = append(details, fmt.Sprintf("[%s] Total score: %.1f", name, total))
    }()

    for i, q := range questions {
        var earned float64

        // switch expression format: multi-condition branching
        switch q.Type {
        case "single":
            if q.Student == q.Correct {
                earned = q.Score
            }
        case "judge":
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        case "multi":
            // Multiple-choice question: Points are awarded only for exact matches
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        default:
            continue  // skip unknown question type
        }

        total += earned
        details = append(details,
            fmt.Sprintf("Problem %d (%s): Scored %.1f points", i+1, q.Type, earned))
    }

    return total, details
}

func main() {
    questions := []Question{
        {Type: "single", Correct: "B", Student: "B", Score: 2},
        {Type: "judge", Correct: "true", Student: "true", Score: 1},
        {Type: "multi", Correct: "A,B,C", Student: "A,B", Score: 3},
        {Type: "single", Correct: "D", Student: "D", Score: 2},
    }

    // Use a `for range` loop to iterate through the student list
    students := []string{"Alice", "Bob", "Charlie"}
    for _, name := range students {
        total, details := evaluateExam(name, questions)

        fmt.Println("========================================")
        for _, d := range details {
            fmt.Println(d)
        }
    }
}

النتائج المتوقعة (الدرجات التفصيلية لكل طالب + ملخص التأجيل):

TEXT 📖 للعرض فقط
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Alice] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Bob] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Charlie] Total score: 5.0
🔥 خطأ شائع: يجب تعديل قيمة الإرجاع المسماة في defer حتى تصبح مرئية في النطاق الخارجي — في المثال أعلاه، يستخدم details قيمة الإرجاع المسماة (total float64, details []string)، ولهذا السبب يسري مفعول append داخل defer.



❓ أسئلة شائعة

س لماذا لا تحتوي لغة Go على الكلمة الرئيسية while؟
ج تستخدم لغة Go for لجميع الحلقات: for condition {} تعادل while، وfor {} تعادل while(true). وبفضل وجود كلمة رئيسية أقل، لا تضطر الفرق إلى الجدال حول ما إذا كان يجب استخدام while أم do-while.
س أليس من المزعج أن switch لا يتم تجاوزها افتراضيًّا؟
ج على العكس تمامًا، يعتقد فريق Go أن 90% من حالات switch يجب أن تكون قائمة بذاتها — فمشكلة «نسيان كتابة break، مما يؤدي إلى تجاوزها» في لغتي C وJava تُعدّ من الأخطاء الكلاسيكية. تستخدم لغة Go fallthrough للإشارة صراحةً إلى نية التخطي، وهو ما يعتبر أكثر أمانًا.
س لماذا يكون ترتيب الخريطة عشوائيًا عند إجراء عملية التكرار باستخدام for range؟
ج تم تصميم Go عمدًا بحيث تكون غير مرتبة — وذلك لمنع المبرمجين من الاعتماد على ترتيب الخريطة. إذا كنت بحاجة إلى ترتيب ثابت، فقم بفرز المفاتيح قبل إجراء عملية التكرار: keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys).
س متى يتم تنفيذ defer؟ ما هو ترتيب تنفيذ أوامر defer المتعددة؟
ج يتم تنفيذ defer قبل أن تعود الدالة الحالية (أي بعد تنفيذ الأمر return ولكن قبل خروج الدالة فعليًّا). يتم تنفيذ تعليمات defer المتعددة بترتيب LIFO (آخر دخول، أول خروج)، مما يحاكي إدارة الموارد القائمة على المكدس: حيث يتم تحرير الموارد التي تم دفعها إلى المكدس أخيرًا أولاً.
س ما هو نطاق عبارة التهيئة if؟
ج المتغيرات المُعلنة في عبارة التهيئة if لا تكون مرئية إلا داخل الكتلة if-else؛ وتصبح غير صالحة بمجرد الخروج من الكتلة if-else. وهذا يمنع حدوث الخطأ الشائع المتمثل في «تسرب المتغيرات إلى النطاق الخارجي».
س هل تُستخدم label كثيرًا في المشاريع العملية؟
ج نادرًا. يُستخدم label بشكل أساسي للخروج من الحلقات المتداخلة؛ وفي 99% من الحالات، يكفي استخدام break مع return. الإفراط في استخدام label يقلل من قابلية قراءة الكود — وهو درس مستفاد من عصر goto.
س هل يدعم تعبير switch في لغة Go التحقق من أنواع البيانات؟
ج نعم. يُستخدم switch x := y.(type) للتحقق من أنواع الواجهات؛ وسنتناول هذا الموضوع بمزيد من التفصيل في الدرس 7. ومع ذلك، فإن هذه الصيغة لا تنطبق إلا على المتغيرات من النوع interface{}.
س هل يمكن استخدام continue وbreak في عبارة switch؟
ج نعم، لكن سلوكهما يختلف. break يخرج من عبارة switch؛ أما في حلقة for، فيخرج من حلقة for. لا يمكن استخدام continue إلا في حلقة for؛ ولا يمكن استخدامه في عبارة switch (نظرًا لأن switch لا يُعتبر حلقة).

📖 ملخص


📝 تمارين

  1. المسألة الأساسية (مستوى الصعوبة ⭐): اكتب برنامج «FizzBuzz» باستخدام for وswitch: اطبع الأعداد من 1 إلى 100. عند مواجهة مضاعف العدد 3، اطبع «Fizz»؛ وعند مواجهة مضاعف العدد 5، اطبع «Buzz»؛ وعند مواجهة مضاعف العدد 15، اطبع «FizzBuzz».

  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بتنفيذ الدالة countPrimes(n int) int، التي تُرجع عدد الأعداد الأولية بين 1 و n. المتطلبات: (1) استخدم حلقة for؛ (2) استخدم عبارة if للتحقق من الأعداد الأولية؛ (3) استخدم defer لعرض وقت التنفيذ (باستخدام time.Now()) قبل أن تُرجع الدالة النتيجة.

  3. مشكلة التحدي (الصعوبة ⭐⭐⭐): قم بتنفيذ محلل لدرجات الطلاب: عند إعطاء مجموعة من الدرجات ([]float64)، قم بإخراج (1) متوسط الدرجات؛ (2) معدل النجاح (≥60 نقطة)؛ (3) أعلى وأدنى الدرجات؛ (4) توزيع الدرجات حسب الفئة (A/B/C/D/F). المتطلبات: (1) استخدم for range؛ (2) استخدم عبارة switch لتحديد الدرجات؛ (3) استخدم defer لعرض المعلومات الموجزة قبل أن تعود الدالة.

Web-Tutorial.com

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

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

100%