1 أساسيات الدوال

في عالم البرمجة، تعتبر الدالة مجموعة من الشفرات التي تقوم بتنفيذ مهمة محددة ويمكن أن تحتوي على مُعاملات دخل وقيم مُرجعة. في لغة Go، تُستخدم الدوال على نطاق واسع لتنظيم وإعادة استخدام الشفرات. يُمكن أن يجعل استخدام الدوال بفعالية الشفرات أكثر إيجازاً وسهولة قراءة وصيانة.

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

2 تعريف الدوال

2.1 إعلان الدالة

في لغة Go، الشكل العام لإعلان الدالة هو:

func اسم_الدالة(مُعاملات) نوع_القيمة_المرجعة {
    // جسم الدالة
}

دعنا نقوم بتفكيك هذه العناصر:

  1. الكلمة المفتاحية func تُستخدم لإعلان دالة.
  2. اسم_الدالة هو اسم الدالة، وفقاً لتقاليد اللغة Go في التسمية. تكون الدالة التي يبدأ اسمها بحرف كبير قابلة للاستيراد، وهذا يعني أنها مرئية خارج الحزمة؛ الدالة التي يبدأ اسمها بحرف صغير غير قابلة للاستيراد ويمكن استخدامها داخل نفس الحزمة فقط.
  3. مُعاملات هو قائمة المُعاملات التي تستقبلها الدالة، مُفصولة بفواصل. يجب تحديد نوع كل مُعامل، وإذا كانت هناك عدة مُعاملات من نفس النوع، يمكن تحديد النوع مرة واحدة فقط.
  4. نوع_القيمة_المرجعة هو نوع قيمة إرجاع الدالة. إن كانت الدالة لا ترجع قيمة، يمكن حذف هذا الجزء. إذا كانت الدالة ترجع قيم مرجعة متعددة، يجب وضعها بين قوسين.

على سبيل المثال، يُمكننا أن نقوم بإعلان دالة بسيطة لحساب مجموع عددين:

func جمع(أ int, ب int) int {
    return أ + ب
}

في هذا المثال، اسم الدالة هو جمع، تأخذ مُعاملين من نوع int (أ وب)، وترجع مجموعهما بنوع القيمة المرجعة int.

2.2 قائمة المُعاملات

تعرّف قائمة المُعاملات ما إذا كانت الدالة تقبل المُعاملات ونوع كل مُعامل. المُعاملات هي نوع خاص من المتغيرات تُستخدم لتمرير البيانات إلى الدالة.

func تحية(الاسم string, العمر int) {
    fmt.Printf("مرحبا، %s! أنت عمرك %d سنة.\n", الاسم, العمر)
}

في دالة تحية هذه، الاسم و العمر هما مُعاملات. نوع ال الاسم هو string، ونوع العمر هو int. عند استدعاء هذه الدالة، يجب تقديم القيم الفعلية لهذه المُعاملات.

2.3 أنواع القيم المرجعة

يُمكن للدوال أن تُرجع نتائج مُحسوبة، وتعرّف نوع القيم المرجعة نوع البيانات لقيمة إرجاع الدالة. يُمكن للدوال أن تكون بدون قيمة إرجاع، أو يمكن أن تحتوي على قيمة إرجاع واحدة أو أكثر.

إذا كانت الدالة تحتوي على قيم مرجعة متعددة، يجب وضع أنواعها بين قوسين خلال الإعلان:

func قسمة(المقسوم, المقسوم_عليه float64) (float64, خطأ) {
    if المقسوم_عليه == 0 {
        return 0, errors.New("لا يمكن القسمة على الصفر")
    }
    return المقسوم / المقسوم_عليه, nil
}

تُرجع دالة قسمة هنا قيمتين: الناتج ورسالة خطأ. في حال كان المقسوم عليه الصفر، يُرجع خطأ.

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

في الـGolang، عندما لا نكون متأكدين من عدد المُعاملات التي سيمررها الداعي عند تعريف دالة، يُمكننا استخدام المُعاملات المتغيرة. تُشير المُعاملات المتغيرة إلى أن الدالة يمكن أن تقبل أي عدد من المُعاملات. هذا مفيد للغاية عند التعامل مع كمية معلومات غير مؤكدة أو تنفيذ أنواع معينة من الدوال كإعطاء التهيئة أو التلخيص أو وظائف الاستدعاء.

سيناريوهات التطبيق

يُستخدم المُعاملات المتغيرة بشكل شائع في السيناريوهات التالية:

  1. دمج السلاسل: مثل دوال fmt.Sprintf و strings.Join.
  2. معالجة المصفوفات/الشرائح: عند التعامل مع مصفوفات أو شرائح من طول متغير، مثل حساب المجموع أو دمج عدة شرائح.
  3. سجل الأخطاء ومعالجتها: عند التعامل مع أخطاء متعددة أو تسجيل معلومات سجل متعددة، مثل log.Printf أو دوال تلخيص الأخطاء المخصصة.
  4. دوال المساعدة: يُستخدم في واجهات برمجة التطبيقات أو المكتبات لتوفير طُرق اتصال أكثر مرونة للمستخدمين لاستدعاء الدوال.

استخدام متغيرات الباراميترات

إليك مثال بسيط على استخدام متغيرات الباراميتر:

// قم بتعريف دالة تأخذ عدد متغير من المعلمات الصحيحة وترجع مجموعها
func Sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

func main() {
    // يمكنك تمرير أي عدد من المعلمات
    fmt.Println(Sum(1, 2))          // الناتج: 3
    fmt.Println(Sum(1, 2, 3, 4))    // الناتج: 10
    
    // يمكنك أيضًا تمرير مصفوفة واستخدام النقاط الثلاث بعد المصفوفة
    numbers := []int{1, 2, 3, 4, 5}
    fmt.Println(Sum(numbers...))    // الناتج: 15
}

في دالة Sum ، nums هو مصفوفة من الأعداد الصحيحة التي تحتوي على جميع المعلمات الممررة إلى دالة Sum. يمكننا استخدام حلقة مدى للانتقال عبر هذه المصفوفة وحساب المجموع.

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

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

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

3 استدعاء الدوال

3.1 استدعاء الدوال الأساسي

استدعاء دالة يعني تنفيذ كود الدالة. في Go، استدعاء دالة محددة بطريقة بسيطة للغاية، ما عليك سوى استخدام اسم الدالة وتمرير المعلمات المناسبة. على سبيل المثال:

result := add(3, 4)
fmt.Println(result)  // الناتج: 7

في هذا المثال، تم استدعاء دالة add وتم تمرير عددين كمعلمات، ثم تم تعيين الناتج المرجعي إلى المتغير result.

3.2 تمرير المعلمات

عند تمرير المعلمات إلى دالة، يفترض Go استخدام مرور القيمة، مما يعني تمرير نسخة من قيمة المعلمة، ولن يتم تغيير البيانات الأصلية. ومع ذلك، إذا كنت ترغب في أن تقوم الدالة بتعديل المتغيرات الخارجية، أو لأسباب الأداء، يمكنك استخدام مرور الإشارات، مثل استخدام المؤشرات أو تمرير الهياكل الكبيرة.

فيما يلي أمثلة على مرور القيمة ومرور الإشارات:

// مثال على مرور القيمة
func double(val int) {
    val *= 2
}

// مثال على مرور الإشارة
func doublePtr(val *int) {
    *val *= 2
}

func main() {
    value := 3
    double(value)
    fmt.Println(value)  // يظهر 3، القيمة تظل كما هي

    doublePtr(&value)
    fmt.Println(value)  // يظهر 6. القيمة مضاعفة
}

في المثال أعلاه، تحاول دالة double تضاعف القيمة الممررة val، لكنها تضاعف نسختها فقط، مما يترك المتغير الأصلي value دون تغيير. ومع ذلك، تغير دالة doublePtr قيمة المتغير الأصلي عن طريق استلام مؤشر إلى متغير عدد صحيح كمعلمة.