الدوال

الدوال

الدوال مثل "آلات البيع" في الحياة اليومية — تُدخل المعاملات، تُعالج وفق قواعد محددة مسبقاً، وتُعيد النتائج. في Go، الدوال مواطنون من الدرجة الأولى: يمكن إسنادها إلى متغيرات،تمريرها كوسيطات، وإرجاعها كقيم، وحتى يمكن أن تكون بدون اسم. إتقان الدوال هو خطوة أساسية لكتابة كود Go أنيق.


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

المفهوم الوصف
تعريف func استخدم كلمة func لإعلان الدوال، مع دعم أنواع المعاملات وقيم الإرجاع
قيم إرجاع متعددة دوال Go يمكنها إرجاع قيم متعددة، تُستخدم عادة للنتيجة + خطأ
قيم إرجاع مُسماة يمكن تسمية قيم الإرجاع، واستخدامها مباشرة في جسم الدالة، وإرجاعها تلقائياً بـ return
معاملات متغيرة استخدم ...Type لقبول أي عدد من الوسيطات
دوال مجهولة دوال بدون أسماء، تُستخدم غالباً للتنفيذ الفوري أو الإسناد إلى متغيرات
إغلاقات دوال مجهولة تلتقط متغيرات خارجية، مُشكّلة إغلاقات
دالة init كل حزمة يمكن أن تحتوي عدة دوال init تُنفذ تلقائياً عند بدء البرنامج
defer تأخير التنفيذ، يُنفذ بترتيب المكدس قبل عودة الدالة

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

تعريف الدالة

GO
// تعريف دالة أساسي
func functionName(parameterList) returnType {
    // جسم الدالة
    return value
}

// بدون معاملات، بدون قيمة إرجاع
func sayHello() {
    fmt.Println("Hello!")
}

// مع معاملات وقيمة إرجاع
func add(a int, b int) int {
    return a + b
}

// اختصار عندما تكون أنواع المعاملات متماثلة
func add(a, b int) int {
    return a + b
}

قيم الإرجاع المتعددة

GO
// إرجاع قيمتين: نتيجة وخطأ
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("المقسوم عليه لا يمكن أن يكون صفراً")
    }
    return a / b, nil
}

// استلام كلا القيمتين عند الاستدعاء
result, err := divide(10, 3)

القيم المُسماة للإرجاع

GO
// قيم إرجاع مُسماة: استخدم أسماء المتغيرات مباشرة في جسم الدالة
func rectangleInfo(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = 2 * (length + width)
    return // يُعيد تلقائياً القيم المُسماة للإرجاع
}

المعاملات المتغيرة

GO
// معاملات متغيرة: استخدم ...Type لقبول أي عدد من الوسيطات
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// استدعاء
sum(1, 2, 3)       // 6
sum(1, 2, 3, 4, 5) // 15

الدوال المجهولة والإغلاقات

GO
// إسناد دالة مجهولة إلى متغير
greet := func(name string) string {
    return "Hello, " + name
}

// دالة تُستدعى فوراً
func() {
    fmt.Println("تُنفذ فوراً")
}()

// إغلاقة: تلتقط متغيراً خارجياً
func counter() func() int {
    count := 0
    return func() int {
        count++ // تلتقط المتغير الخارجي count
        return count
    }
}

دالة init

GO
// دالة init: تُنفذ تلقائياً عند بدء البرنامج، لا تحتاج استدعاء يدوياً
// كل حزمة يمكن أن تحتوي عدة دوال init
func init() {
    fmt.Println("دالة init تُنفذت")
}

defer

GO
// defer: تأخير التنفيذ، يُنفذ بترتيب LIFO (آخر دخول، أول خروج) قبل عودة الدالة
func readFile(filename string) {
    f, _ := os.Open(filename)
    defer f.Close() // إغلاق الملف قبل انتهاء الدالة
    // ... قراءة الملف
}
💡 نصيحة: في Go، جميع وسيطات الدوال تُمرر بالقيمة. slices و maps و channels و المؤشرات وغيرها تمرر نسخة من المرجع، لكن البيانات المشار إليها مشتركة.

💡 نصيحة: معاملات جملة defer تُقيّم عند الإعلان، وليس عند التنفيذ. هذه مصيدة شائعة.

💡 نصيحة: دوال init لا يمكن استدعاءها من دوال أخرى؛ تُنفذ تلقائياً قبل main. إذا كان هناك عدة دوال init، تُنفذ بترتيب أسماء الملفات المصدرية.


3. الصياغة والاستخدام الأساسي (أمثلة)

مثال 1: الاستخدام الأساسي (الصعوبة ⭐)

الهدف: تعلّم تعريف الدوال الأساسية والاستدعاء وقيم الإرجاع المتعددة.

GO
package main

import (
    "errors"
    "fmt"
)

// greet تقبل اسماً وتُعيد تحية
func greet(name string) string {
    return "Hello, " + name + "!"
}

// add تُعيد مجموع عددين صحيحين
func add(a, b int) int {
    return a + b
}

// swap تُبدّل نصين (تُظهر قيم الإرجاع المتعددة)
func swap(a, b string) (string, string) {
    return b, a
}

// divide تُظهر معالجة الأخطاء
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("المقسوم عليه لا يمكن أن يكون صفراً")
    }
    return a / b, nil
}

func main() {
    // استدعاء دوال بقيمة إرجاع واحدة
    fmt.Println(greet("Alice")) // المخرجات: Hello, Alice!
    fmt.Println(add(3, 5))      // المخرجات: 8

    // استدعاء دالة بقيم إرجاع متعددة
    x, y := swap("hello", "world")
    fmt.Println(x, y) // المخرجات: world hello

    // استدعاء دالة مع معالجة الأخطاء
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("خطأ:", err)
    } else {
        fmt.Printf("10 / 3 = %.2f\n", result) // المخرجات: 10 / 3 = 3.33
    }

    // اختبار خطأ القسمة على صفر
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("خطأ:", err) // المخرجات: خطأ: المقسوم عليه لا يمكن أن يكون صفراً
    }
}
▶ جرّب الكود

المخرجات:

TEXT
Hello, Alice!
8
world hello
10 / 3 = 3.33
خطأ: المقسوم عليه لا يمكن أن يكون صفراً

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


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

الهدف: تعلّم القيم المُسماة للإرجاع، والمعاملات المتغيرة، والدوال المجهولة، والإغلاقات.

GO
package main

import "fmt"

// ============ القيم المُسماة للإرجاع ============

// rectangle تحسب المساحة والمحيط لمُستطيل (تستخدم قيماً مُسماة للإرجاع)
func rectangle(length, width float64) (area, perimeter float64) {
    area = length * width           // استخدام المتغيرات المُسماة مباشرة
    perimeter = 2 * (length + width)
    return // يُعيد تلقائياً القيم المُسماة، لا حاجة لكتابة return area, perimeter
}

// ============ المعاملات المتغيرة ============

// sum تحسب مجموع أي عدد من الأعداد الصحيحة
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// average تحسب المتوسط، تُظهر خلط المعاملات المتغيرة مع أخرى
func average(first float64, rest ...float64) float64 {
    total := first
    count := 1.0
    for _, v := range rest {
        total += v
        count++
    }
    return total / count
}

// ============ الدوال المجهولة ============

// apply تطبق دالة على عدد صحيح وتُعيد النتيجة
func apply(n int, f func(int) int) int {
    return f(n)
}

func main() {
    // القيم المُسماة للإرجاع
    area, perimeter := rectangle(5, 3)
    fmt.Printf("المساحة: %.1f، المحيط: %.1f\n", area, perimeter)
    // المخرجات: المساحة: 15.0، المحيط: 16.0

    // المعاملات المتغيرة
    fmt.Println("sum(1,2,3):", sum(1, 2, 3))           // 6
    fmt.Println("sum(1,2,3,4,5):", sum(1, 2, 3, 4, 5)) // 15

    // المعاملات المتغيرة: تمرير slice
    nums := []int{10, 20, 30}
    fmt.Println("sum(nums...):", sum(nums...)) // 60 (استخدم ... لتوسيع slice)

    // خلط المعاملات المتغيرة مع أخرى
    fmt.Printf("المتوسط: %.2f\n", average(10, 20, 30)) // 20.00

    // دوال مجهولة كوسيطات
    double := func(n int) int { return n * 2 }
    square := func(n int) int { return n * n }

    fmt.Println("apply(5, double):", apply(5, double)) // 10
    fmt.Println("apply(5, square):", apply(5, square)) // 25

    // استدعاء فوري لدالة مجهولة
    msg := "Hello"
    func(m string) {
        fmt.Println(m)
    }(msg)

    // ============ الإغلاقات ============

    // counter تُعيد إغلاقة عداد
    counter := func() func() int {
        count := 0
        return func() int {
            count++ // تلتقط المتغير الخارجي count
            return count
        }
    }()

    fmt.Println("counter:", counter()) // 1
    fmt.Println("counter:", counter()) // 2
    fmt.Println("counter:", counter()) // 3

    // إغلاقة تُنفذ مُجمِّعاً
    adder := func() func(int) int {
        sum := 0
        return func(n int) int {
            sum += n
            return sum
        }
    }()

    fmt.Println("adder(10):", adder(10)) // 10
    fmt.Println("adder(20):", adder(20)) // 30
    fmt.Println("adder(30):", adder(30)) // 60
}
▶ جرّب الكود

المخرجات:

TEXT
المساحة: 15.0، المحيط: 16.0
sum(1,2,3): 6
sum(1,2,3,4,5): 15
sum(nums...): 60
المتوسط: 20.00
apply(5, double): 10
apply(5, square): 25
Hello
counter: 1
counter: 2
counter: 3
adder(10): 10
adder(20): 30
adder(30): 60

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


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

الهدف: أنماط تصميم الدوال الواقعية: نمط خيارات الوظائف، سلسلة الوسيطات، defer مع معالجة الأخطاء.

GO
package main

import (
    "fmt"
    "strings"
    "time"
)

// ============ نمط خيارات الوظائف ============

// تكوين الخادم
type Server struct {
    host    string
    port    int
    timeout time.Duration
    maxConn int
}

// Option هو نوع دالة لخيارات التكوين
type Option func(*Server)

// WithHost يُعيّن عنوان المضيف
func WithHost(host string) Option {
    return func(s *Server) {
        s.host = host
    }
}

// WithPort يُعيّن المنفذ
func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

// WithTimeout يُعيّن مدة المهلة
func WithTimeout(timeout time.Duration) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

// WithMaxConn يُعيّن الحد الأقصى للاتصالات
func WithMaxConn(max int) Option {
    return func(s *Server) {
        s.maxConn = max
    }
}

// NewServer يُنشئ خادماً باستخدام نمط الخيارات
func NewServer(opts ...Option) *Server {
    // التكوين الافتراضي
    s := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
        maxConn: 100,
    }
    // تطبيق جميع الخيارات
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// String يُنفذ واجهة Stringer
func (s *Server) String() string {
    return fmt.Sprintf("Server{host:%s, port:%d, timeout:%v, maxConn:%d}",
        s.host, s.port, s.timeout, s.maxConn)
}

// ============ نمط سلسلة الوسيطات ============

// Handler هو نوع دالة معالجة
type Handler func(string) string

// Middleware هو نوع وسيط
type Middleware func(Handler) Handler

// loggingMiddleware يُسجّل المدخلات والمخرجات
func loggingMiddleware(next Handler) Handler {
    return func(input string) string {
        fmt.Printf("[سجل] المدخلات: %s\n", input)
        result := next(input)
        fmt.Printf("[سجل] المخرجات: %s\n", result)
        return result
    }
}

// upperMiddleware يُحوّل المدخلات إلى أحرف كبيرة
func upperMiddleware(next Handler) Handler {
    return func(input string) string {
        return next(strings.ToUpper(input))
    }
}

// wrapMiddleware يُغلّف المخرجات بأقواس
func wrapMiddleware(next Handler) Handler {
    return func(input string) string {
        return "【" + next(input) + "】"
    }
}

// chain يجمع عدة وسيطات في سلسلة
func chain(handler Handler, middlewares ...Middleware) Handler {
    // التغليف من الخلف إلى الأمام، ضمان تنفيذ أول وسيط أولاً
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// ============ defer في الممارسة: تنظيف الموارد واستعادة الأخطاء ============

// simulateDB تحاكي عمليات قاعدة البيانات
func simulateDB() (err error) {
    fmt.Println("1. الحصول على اتصال قاعدة البيانات")

    // defer يُنفذ بترتيب LIFO
    defer fmt.Println("5. تحرير اتصال قاعدة البيانات (يُنفذ أخيراً)")
    defer fmt.Println("4. كتابة سجل العمليات")
    defer fmt.Println("3. إغلاق المؤشر")

    fmt.Println("2. تنفيذ الاستعلام")

    // محاكاة استعادة panic
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("  [استعادة] تم التقاط panic: %v\n", r)
            err = fmt.Errorf("فشلت العملية: %v", r)
        }
    }()

    // محاكاة panic
    panic("انتهت مهلة الاتصال")

    // الكود أدناه لن يُنفذ
    // fmt.Println("اكتمل الاستعلام")
    // return nil
}

func main() {
    // ===== نمط خيارات الوظائف =====
    fmt.Println("=== نمط خيارات الوظائف ===")

    // استخدام التكوين الافتراضي
    s1 := NewServer()
    fmt.Println(s1)

    // تكوين مخصص
    s2 := NewServer(
        WithHost("0.0.0.0"),
        WithPort(9090),
        WithTimeout(60*time.Second),
        WithMaxConn(500),
    )
    fmt.Println(s2)

    // ===== سلسلة الوسيطات =====
    fmt.Println("\n=== سلسلة الوسيطات ===")

    // المعالج الأساسي
    handler := func(s string) string {
        return "تُمعالجة: " + s
    }

    // دمج الوسيطات
    final := chain(handler, loggingMiddleware, upperMiddleware, wrapMiddleware)
    result := final("hello world")
    fmt.Println("النتيجة النهائية:", result)

    // ===== defer واستعادة الأخطاء =====
    fmt.Println("\n=== ترتيب تنفيذ defer ===")
    err := simulateDB()
    if err != nil {
        fmt.Println("الدالة الرئيسية التقطت خطأ:", err)
    }
}
▶ جرّب الكود

المخرجات:

TEXT
=== نمط خيارات الوظائف ===
Server{host:localhost, port:8080, timeout:30s, maxConn:100}
Server{host:0.0.0.0, port:9090, timeout:1m0s, maxConn:500}

=== سلسلة الوسيطات ===
[سجل] المدخلات: HELLO WORLD
[سجل] المخرجات: 【تُمعالجة: HELLO WORLD】
النتيجة النهائية: 【تُمعالجة: HELLO WORLD】

=== ترتيب تنفيذ defer ===
1. الحصول على اتصال قاعدة البيانات
2. تنفيذ الاستعلام
  [استعادة] تم التقاط panic: انتهت مهلة الاتصال
3. إغلاق المؤشر
4. كتابة سجل العمليات
5. تحرير اتصال قاعدة البيانات (يُنفذ أخيراً)
الدالة الرئيسية التقطت خطأ: فشلت العملية: انتهت مهلة الاتصال

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


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

الحالة 1: تغليف معالجة الأخطاء

في المشاريع الحقيقية، غالباً ما تحتاج إلى تغليف منطق معالجة أخطاء موحد:

GO
package main

import (
    "fmt"
    "strconv"
)

// Result هو غلاف نتيجة موحد
type Result struct {
    Data    interface{}
    Err     error
}

// parseJSON يحاكي تحليل JSON، يُعيد نتيجة وخطأ
func parseJSON(jsonStr string) (map[string]interface{}, error) {
    // عرض مبسط: استخدم encoding/json في المشاريع الحقيقية
    if jsonStr == "" {
        return nil, fmt.Errorf("نص JSON لا يمكن أن يكون فارغاً")
    }
    return map[string]interface{}{"key": "value"}, nil
}

// parseInt يُحوّل نصاً إلى عدد صحيح بشكل آمن
func parseInt(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("فشل تحليل العدد الصحيح '%s': %w", s, err)
    }
    return n, nil
}

// withRetry يُعيد محاولة دالة
func withRetry(attempts int, f func() error) error {
    var err error
    for i := 0; i < attempts; i++ {
        err = f()
        if err == nil {
            return nil
        }
        fmt.Printf("  المحاولة %d فشلت: %v\n", i+1, err)
    }
    return fmt.Errorf("فشل بعد %d محاولات: %w", attempts, err)
}

func main() {
    // معالجة الأخطاء
    result, err := parseJSON(`{"name": "test"}`)
    if err != nil {
        fmt.Println("خطأ التحليل:", err)
        return
    }
    fmt.Println("نتيجة التحليل:", result)

    // تحويل آمن للأنواع
    num, err := parseInt("123")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("نتيجة التحويل:", num)

    // آلية إعادة المحاولة
    attempt := 0
    err = withRetry(3, func() error {
        attempt++
        if attempt < 3 {
            return fmt.Errorf("فشل مُحاكى")
        }
        return nil
    })
    if err != nil {
        fmt.Println("الفشل النهائي:", err)
    } else {
        fmt.Println("النجاح النهائي!")
    }
}

الحالة 2: الدوال كخطوط معالجة البيانات

GO
package main

import (
    "fmt"
    "strings"
)

// Transform هو نوع دالة تحويل البيانات
type Transform func(string) string

// Pipeline هو خط معالجة البيانات
type Pipeline struct {
    steps []Transform
}

// NewPipeline يُنشئ خطاً جديداً
func NewPipeline() *Pipeline {
    return &Pipeline{}
}

// Add يُضيف خطوات معالجة
func (p *Pipeline) Add(steps ...Transform) *Pipeline {
    p.steps = append(p.steps, steps...)
    return p
}

// Execute يُشغّل الخط
func (p *Pipeline) Execute(input string) string {
    result := input
    for _, step := range p.steps {
        result = step(result)
    }
    return result
}

func main() {
    // تعريف دوال التحويل
    trim := func(s string) string { return strings.TrimSpace(s) }
    lower := func(s string) string { return strings.ToLower(s) }
    replace := func(s string) string { return strings.ReplaceAll(s, " ", "-") }
    prefix := func(s string) string { return "processed-" + s }

    // بناء خط المعالجة
    pipeline := NewPipeline().
        Add(trim, lower, replace, prefix)

    // تنفيذ الخط
    result := pipeline.Execute("  Hello World Go  ")
    fmt.Println(result) // المخرجات: processed-hello-world-go

    // إعادة استخدام الخط لمدخلات متعددة
    inputs := []string{"  Foo Bar  ", "  HELLO  ", "  Go Lang  "}
    for _, input := range inputs {
        fmt.Printf("%-20s => %s\n", input, pipeline.Execute(input))
    }
}

❓ أسئلة شائعة

س1: هل معاملات الدوال تُمرر بالقيمة أم بالمرجع؟

ج: في Go، جميع معاملات الدوال تُمرر بالقيمة. لكن slices و maps و channels و المؤشرات وأنواع مشابهة تمرر "نسخة من المرجع" — تعديل البيانات المشار إليها يؤثر على البيانات الأصلية، لكن إعادة إسنادها لا يؤثر على المتغير الأصلي.

GO
package main

import "fmt"

// modifySlice تُعدّل محتوى slice (يؤثر على الأصل)
func modifySlice(s []int) {
    s[0] = 999 // تعديل العنصر يؤثر على slice الأصلية
    fmt.Println("داخل الدالة:", s)
}

// reassignSlice تُعيد إسناد slice (لا يؤثر على الأصل)
func reassignSlice(s []int) {
    s = append(s, 4, 5, 6) // append قد يُنشئ مصفوفة أساسية جديدة
    fmt.Println("داخل الدالة:", s)
}

func main() {
    nums := []int{1, 2, 3}

    modifySlice(nums)
    fmt.Println("خارج الدالة:", nums) // [999 2 3]، slice الأصلية تُعدّلت

    reassignSlice(nums)
    fmt.Println("خارج الدالة:", nums) // [999 2 3]، slice الأصلية لم تتغير
}

س2: متى تُقيّم معاملات defer؟

ج: معاملات جملة defer تُقيّم عند الإعلان، وليس عند التنفيذ.

GO
package main

import "fmt"

func main() {
    x := 10

    // معاملات defer تُقيّم وقت الإعلان، x = 10
    defer fmt.Println("x في defer =", x)

    x = 20
    fmt.Println("x بعد التعديل =", x)

    // إذا كنت بحاجة للتقييم وقت تنفيذ defer، استخدم إغلاقة
    defer func() {
        fmt.Println("x في إغلاقة defer =", x) // x = 20
    }()

    x = 30
}

المخرجات:

TEXT
x بعد التعديل = 20
x في إغلاقة defer = 30
x في defer = 10

س3: هل يمكن أن تحتوي حزمة على عدة دوال init؟ ما ترتيب التنفيذ؟

ج: نعم. حزمة (أو حتى ملف واحد) يمكن أن تحتوي عدة دوال init. ترتيب التنفيذ:

  1. أولاً، تنفيذ دوال init للحزم المُستوردة
  2. داخل نفس الحزمة، يُنفذ بترتيب أسماء الملفات المصدرية
  3. داخل نفس الملف، يُنفذ بترتيب ظهور دوال init
GO
package main

import "fmt"

func init() {
    fmt.Println("init الأولى")
}

func init() {
    fmt.Println("init الثانية")
}

func main() {
    fmt.Println("دالة main")
}

المخرجات:

TEXT
init الأولى
init الثانية
دالة main

س4: كيف أُنفذ معاملات اختيارية؟

ج: Go لا تدعم المعاملات الاختيارية مباشرة. الطرق الثلاث التالية تُستخدم عادةً:

GO
package main

import "fmt"

// الطريقة 1: استخدام هيكل
type Config struct {
    Host string
    Port int
    Debug bool
}

func connect(cfg Config) {
    fmt.Printf("الاتصال بـ %s:%d (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

// الطريقة 2: استخدام نمط خيارات الوظائف (مُوصى به)
type Option func(*Config)

func WithHost(host string) Option {
    return func(c *Config) { c.Host = host }
}

func WithPort(port int) Option {
    return func(c *Config) { c.Port = port }
}

func WithDebug(debug bool) Option {
    return func(c *Config) { c.Debug = debug }
}

func newConnect(opts ...Option) {
    cfg := &Config{Host: "localhost", Port: 8080, Debug: false}
    for _, opt := range opts {
        opt(cfg)
    }
    fmt.Printf("الاتصال بـ %s:%d (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}

func main() {
    // طريقة الهيكل
    connect(Config{Host: "127.0.0.1", Port: 3306})

    // نمط الخيارات
    newConnect()
    newConnect(WithHost("127.0.0.1"), WithPort(3306), WithDebug(true))
}

📖 ملخص


📝 تمارين

تمرين 1 (⭐): تمارين الدوال الأساسية

المتطلبات: اكتب واختبر الدوال التالية:

  1. max(a, b int) int — تُعيد الأكبر من عددين صحيحين
  2. isEven(n int) bool — تتحقق ما إذا كان العدد زوجياً
  3. swap(a, b *int) — تُبدّل عددين صحيحين باستخدام المؤشرات
GO
// إطار مرجعي
package main

import "fmt"

func max(a, b int) int {
    // نفّذ هنا
    return 0
}

func isEven(n int) bool {
    // نفّذ هنا
    return false
}

func swap(a, b *int) {
    // نفّذ هنا (تلميح: استخدم المؤشرات)
}

func main() {
    fmt.Println(max(3, 5))     // يجب أن يطبع: 5
    fmt.Println(isEven(4))     // يجب أن يطبع: true
    fmt.Println(isEven(7))     // يجب أن يطبع: false

    x, y := 10, 20
    swap(&x, &y)
    fmt.Println(x, y)          // يجب أن يطبع: 20 10
}

تمرين 2 (⭐⭐): المعاملات المتغيرة والإغلاقات

المتطلبات:

  1. اكتب filter(nums []int, predicate func(int) bool) []int — تُصفّي عناصر slice تحقق شرطاً
  2. اكتب makeMultiplier(factor int) func(int) int — تُعيد إغلاقة ضرب
  3. استخدم filter وإغلاقة لاختيار جميع الأعداد الزوجية من slice
GO
// إطار مرجعي
package main

import "fmt"

func filter(nums []int, predicate func(int) bool) []int {
    // نفّذ: iterate على nums، أضف العناصر التي تحقق الشرط إلى slice النتيجة
    return nil
}

func makeMultiplier(factor int) func(int) int {
    // نفّذ: أعد إغلاقة تضرب المدخل في factor
    return nil
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // تصفية الأعداد الزوجية
    evens := filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println("الأعداد الزوجية:", evens) // [2 4 6 8 10]

    // تصفية الأعداد أكبر من 5
    big := filter(nums, func(n int) bool {
        return n > 5
    })
    fmt.Println("أكبر من 5:", big) // [6 7 8 9 10]

    // استخدام الإغلاقات لإنشاء مُضاعفات
    double := makeMultiplier(2)
    triple := makeMultiplier(3)

    fmt.Println("double(5):", double(5)) // 10
    fmt.Println("triple(5):", triple(5)) // 15
}

تمرين 3 (⭐⭐⭐): مشروع عملي — آلة حاسبة بنمط الأوامر

المتطلبات: تنفيذ نظام آلة حاسبة يدعم تسجيل الأوامر:

  1. عرّف هيكل Calculator بخريطة أوامر map[string]func([]int) int
  2. نفّذ Register(name string, op func([]int) int) لتسجيل الأوامر
  3. نفّذ Execute(name string, args []int) (int, error) لتنفيذ الأوامر
  4. سجّل الأوامر التالية: add (مجموع)، mul (حاصل ضرب)، max (أكبر)
  5. استخدم دالة init لتسجيل جميع الأوامر تلقائياً
GO
// إطار مرجعي
package main

import (
    "errors"
    "fmt"
)

type Calculator struct {
    // عرّف الحقول هنا
}

func NewCalculator() *Calculator {
    // نفّذ هنا
    return nil
}

func (c *Calculator) Register(name string, op func([]int) int) {
    // نفّذ هنا
}

func (c *Calculator) Execute(name string, args []int) (int, error) {
    // نفّذ: ابحث عن الأمر ونفّذه؛ أعد خطأ إذا لم يُوجد
    return 0, nil
}

func (c *Calculator) ListCommands() []string {
    // نفّذ: أعد جميع أسماء الأوامر المُسجّلة
    return nil
}

func main() {
    calc := NewCalculator()

    // تسجيل الأوامر
    calc.Register("add", func(args []int) int {
        sum := 0
        for _, v := range args {
            sum += v
        }
        return sum
    })

    calc.Register("mul", func(args []int) int {
        product := 1
        for _, v := range args {
            product *= v
        }
        return product
    })

    calc.Register("max", func(args []int) int {
        if len(args) == 0 {
            return 0
        }
        m := args[0]
        for _, v := range args[1:] {
            if v > m {
                m = v
            }
        }
        return m
    })

    // اختبار
    tests := []struct {
        cmd  string
        args []int
    }{
        {"add", []int{1, 2, 3, 4, 5}},
        {"mul", []int{2, 3, 4}},
        {"max", []int{10, 3, 7, 9, 1}},
    }

    for _, t := range tests {
        result, err := calc.Execute(t.cmd, t.args)
        if err != nil {
            fmt.Printf("خطأ: %v\n", err)
        } else {
            fmt.Printf("%s(%v) = %d\n", t.cmd, t.args, result)
        }
    }

    // قائمة جميع الأوامر
    fmt.Println("الأوامر المتاحة:", calc.ListCommands())

    // اختبار أمر غير موجود
    _, err := calc.Execute("sqrt", []int{16})
    if err != nil {
        fmt.Println("خطأ:", err)
    }
}

المخرجات المتوقعة:

TEXT
add([1 2 3 4 5]) = 15
mul([2 3 4]) = 24
max([10 3 7 9 1]) = 10
الأوامر المتاحة: [add mul max]
خطأ: أمر غير معروف: sqrt

الدرس التالي

الدرس التالي: المصفوفات و Slices →

Web-Tutorial.com

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

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

100%