Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

مقدمة في الأنواع العامة

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 67 من 107.

قبل إصدار Go 1.18، كانت كتابة كود قابل لإعادة الاستخدام لأنواع مختلفة تعني إما تكرار الدوال أو استخدام الواجهة الفارغة any مع تأكيدات النوع (type assertions). تحل الـ Generics هذه المشكلة من خلال السماح لك بكتابة دوال وأنواع تعمل مع أنواع متعددة مع الحفاظ على أمان النوع (type safety) الكامل.

فكر في العثور على القيمة الصغرى في شريحة (slice). بدون استخدام الميزات العامة (generics)، ستحتاج إلى دوال منفصلة لكل نوع:

func MinInt(values []int) int {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

func MinFloat64(values []float64) float64 {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

باستخدام الأنواع العامة (generics)، تكتب المنطق مرة واحدة باستخدام معامل نوع (type parameter) داخل أقواس مربعة:

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

يعلن [T int | float64] عن معلمة نوع T يمكن أن تكون إما int أو float64. يمكنك الآن استدعاء Min([]int{3, 1, 2}) أو Min([]float64{3.5, 1.2}) باستخدام نفس الدالة. يتحقق المترجم من الأنواع في وقت التجميع، لذا تحصل على الأمان دون أعباء إضافية في وقت التشغيل.

في الدروس القادمة، سنستكشف بارامترات النوع (type parameters)، والقيود (constraints)، والهياكل العامة (generic structs) بالتفصيل.

challenge icon

التحدي

سهل

لنقم ببناء محلل درجات يعمل مع كل من الدرجات الصحيحة والكسرية باستخدام القوالب العامة (generics)! ستقوم بإنشاء دالة عامة واحدة يمكنها العثور على القيمة القصوى في شريحة (slice)، مما يلغي الحاجة إلى تكرار الكود لأنواع عددية مختلفة.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • analyzer.go: حدد دالة التحليل العامة الخاصة بك.

    قم بإنشاء دالة عامة Max[T int | float64](values []T) T تجد وتعيد القيمة القصوى في شريحة. يجب أن تعمل الدالة مع كل من النوعين int و float64 من خلال معلمة النوع T.

    قم أيضاً بإنشاء دالة عامة Sum[T int | float64](values []T) T تحسب وتعيد مجموع كل القيم في الشريحة.

  • main.go: اقرأ المدخلات واستعرض دوالك العامة مع أنواع مختلفة.

    اقرأ مؤشر النوع (int أو float)، ثم اقرأ عدداً متبوعاً بهذا القدر من القيم. قم بتحليل القيم وفقاً للنوع، واستدعِ كلاً من Max و Sum بالنوع المناسب، واطبع النتائج.

    بالنسبة لمدخلات الأعداد الصحيحة (integer)، اطبع:

    Max (int): [value]
    Sum (int): [value]

    بالنسبة لمدخلات الأعداد الكسرية (float)، اطبع مع منزلة عشرية واحدة:

    Max (float): [value]
    Sum (float): [value]

سيتم توفير المدخلات التالية:

  • السطر 1: مؤشر النوع (int أو float)
  • السطر 2: عدد القيم (عدد صحيح)
  • الأسطر التالية: قيمة واحدة في كل سطر

على سبيل المثال، إذا أعطينا:

int
4
15
8
23
11

يجب أن يكون مخرجك:

Max (int): 23
Sum (int): 57

وإذا أعطينا:

float
3
4.5
9.2
6.8

يجب أن يكون مخرجك:

Max (float): 9.2
Sum (float): 20.5

لاحظ كيف تعالج نفس الدوال العامة كلاً من البيانات الصحيحة والكسرية—تسمح معلمة النوع [T int | float64] للمترجم بإنشاء كود آمن النوع (type-safe) لكل استخدام بينما تكتب أنت المنطق مرة واحدة فقط.

ورقة مرجعية

تسمح لك الأنواع العامة (Generics) بكتابة كود قابل لإعادة الاستخدام يعمل مع أنواع متعددة مع الحفاظ على سلامة النوع (type safety). قبل إصدار Go 1.18، كنت بحاجة إلى وظائف منفصلة لكل نوع أو استخدام الواجهة الفارغة (empty interface) مع تأكيدات النوع (type assertions).

يتم التصريح عن معلمات النوع (Type parameters) داخل أقواس مربعة وتحدد الأنواع التي يمكن للدالة العامة قبولها:

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

يصرح بناء الجملة [T int | float64] عن معلمة نوع T يمكن أن تكون إما int أو float64. يقوم عامل الأنبوب | بسرد الأنواع المسموح بها.

يمكنك استدعاء الدوال العامة بأنواع مختلفة:

Min([]int{3, 1, 2})           // يعمل مع int
Min([]float64{3.5, 1.2})      // يعمل مع float64

يقوم المترجم (compiler) بإجراء فحص النوع في وقت الترجمة، مما يوفر الأمان دون أعباء إضافية في وقت التشغيل.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// قراءة مؤشر النوع
	typeStr, _ := reader.ReadString('\n')
	typeStr = strings.TrimSpace(typeStr)

	// قراءة العدد
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	if typeStr == "int" {
		// قراءة قيم الأعداد الصحيحة (integer)
		values := make([]int, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.Atoi(strings.TrimSpace(line))
		}

		// TODO: استدعاء Max و Sum مع شريحة (slice) من الأعداد الصحيحة
		// TODO: طباعة النتائج بالتنسيق "Max (int): [value]" و "Sum (int): [value]"

	} else if typeStr == "float" {
		// قراءة قيم الأعداد العشرية (float)
		values := make([]float64, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
		}

		// TODO: استدعاء Max و Sum مع شريحة (slice) من نوع float64
		// TODO: طباعة النتائج مع منزلة عشرية واحدة بالتنسيق "Max (float): [value]" و "Sum (float): [value]"
	}
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming