Menu
Coddy logo textTech

מבנים גנריים

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 70 מתוך 107.

בדומה לפונקציות, גם למבנים (structs) יכולים להיות פרמטרים של טיפוס. מבנה גנרי מאפשר לך ליצור מבני נתונים שעובדים עם כל טיפוס תוך שמירה על בטיחות טיפוסים. זה מושלם לבניית מבני אחסון לשימוש חוזר, כמו מחסניות, תורים או זוגות מפתח-ערך.

כדי להגדיר מבנה גנרי, הציבו את פרמטר הטיפוס אחרי שם המבנה:

type Box[T any] struct {
    Value T
}

func main() {
    intBox := Box[int]{Value: 42}
    strBox := Box[string]{Value: "hello"}
    
    fmt.Println(intBox.Value)  // 42
    fmt.Println(strBox.Value)  // שלום
}

בניגוד לפונקציות גנריות, כשאתה יוצר מופע של מבנה גנרי, עליך לציין במפורש את הטיפוס. Go אינה יכולה להסיק את הטיפוס מההקשר בלבד.

למבנים גנריים יכולים להיות כמה פרמטרים של טיפוס, ולכן הם אידיאליים למבנים כמו זוגות או מפות:

type Pair[K, V any] struct {
    Key   K
    Value V
}

p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value)  // age: 25

אפשר גם להחיל אילוצים כדי לוודא שהטיפוסים המאוחסנים תומכים בפעולות מסוימות:

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

מבנים גנריים הם הבסיס לבניית מבני נתונים לשימוש חוזר ובטיחותיים מבחינת טיפוסים ב-Go. בשיעור הבא תלמדו כיצד להגדיר מתודות על הטיפוסים הגנריים האלה.

challenge icon

אתגר

קל

בואו נבנה מערכת מלאי גנרית שיכולה לעקוב אחר סוגים שונים של פריטים! תיצרו מבני מכלים לשימוש חוזר שעובדים עם כל סוג, תוך שמירה על בטיחות טיפוסים מלאה.

תארגנו את הקוד שלכם בשני קבצים:

  • inventory.go: הגדירו את מבני המכלים הגנריים שלכם.

    צרו מבנה גנרי Item[T any] עם שני שדות: Name (מחרוזת) ו-Data (מטיפוס T). הוא מייצג כל פריט עם נתונים משויכים מטיפוס גמיש.

    צרו מבנה גנרי Container[T any] עם שדה יחיד Items שמכיל פרוסה של Item[T]. המכל הזה יכול לאחסן כמה פריטים מאותו טיפוס נתונים.

    צרו מבנה גנרי מוגבל PricedItem[T int | float64] עם שלושה שדות: Name (מחרוזת), Quantity (מספר שלם) ו-Price (מטיפוס T). האילוץ מבטיח שהמחירים יהיו תמיד מספריים.

    ממשו פונקציה NewContainer[T any]() *Container[T] שיוצרת ומחזירה מצביע ל-Container ריק.

    ממשו מתודה Add עבור *Container[T] שמקבלת שם (מחרוזת) ונתונים (T), יוצרת Item ומוסיפה אותו לפרוסת Items של המכל.

    ממשו מתודה Count עבור Container[T] שמחזירה את מספר הפריטים במכל.

  • main.go: הדגימו את המבנים הגנריים שלכם עם טיפוסים שונים.

    קראו טיפוס פריט (string, int או priced), ואז קראו מספר פריטים ולאחריו את פרטי הפריטים.

    עבור הטיפוס string: קראו זוגות של שם ונתונים מסוג מחרוזת. צרו Container[string], הוסיפו את כל הפריטים, ואז הדפיסו כל פריט בתבנית [Name]: [Data] ואחריו את המספר הכולל.

    עבור הטיפוס int: קראו זוגות של שם ונתונים מסוג מספר שלם. צרו Container[int], הוסיפו את כל הפריטים, ואז הדפיסו כל פריט בתבנית [Name]: [Data] ואחריו את המספר הכולל.

    עבור הטיפוס priced: קראו שלשות של שם, כמות ומחיר (כמספר עשרוני). צרו ישירות מופעים של PricedItem[float64] והדפיסו כל אחד בתבנית [Name] x[Quantity] @ [Price], כשהמחיר מוצג עם ספרה אחת אחרי הנקודה העשרונית.

    הדפיסו את שורת הספירה כך: Total items: [count]

הקלטים הבאים יסופקו:

  • שורה 1: טיפוס הפריט (string, int או priced)
  • שורה 2: מספר הפריטים (מספר שלם)
  • השורות הבאות: פרטי הפריטים בהתאם לטיפוס

לדוגמה, בהינתן:

string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paper

הפלט שלכם צריך להיות:

Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3

ובהינתן:

int
2
Apples
50
Oranges
30

הפלט שלכם צריך להיות:

Apples: 50
Oranges: 30
Total items: 2

ובהינתן:

priced
2
Widget
10
19.99
Gadget
5
49.50

הפלט שלכם צריך להיות:

Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2

שימו לב שאותו מבנה Container עובד בצורה חלקה גם עם מחרוזות וגם עם מספרים שלמים, ואילו PricedItem משתמש באילוץ כדי להבטיח שרק טיפוסים מספריים יוכלו לשמש למחירים. עליכם לציין במפורש את פרמטר הטיפוס כשיוצרים מופעים של המבנים הגנריים האלה.

נסו בעצמכם

package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// קרא את סוג הפריט
	scanner.Scan()
	itemType := scanner.Text()
	
	// קרא את count
	scanner.Scan()
	count, _ := strconv.Atoi(scanner.Text())
	
	switch itemType {
	case "string":
		// TODO: צור Container[string] באמצעות NewContainer
		// TODO: קרא 'count' פריטים (לכל אחד שם בשורה אחת ונתונים בשורה הבאה)
		// TODO: הוסף כל פריט למכל
		// TODO: הדפס כל פריט בפורמט "[Name]: [Data]"
		// TODO: הדפס "Total items: [count]" באמצעות המתודה Count
		
	case "int":
		// TODO: צור Container[int] באמצעות NewContainer
		// TODO: קרא 'count' פריטים (לכל אחד שם בשורה אחת ונתון מספרי שלם בשורה הבאה)
		// TODO: הוסף כל פריט למכל
		// TODO: הדפס כל פריט בפורמט "[Name]: [Data]"
		// TODO: הדפס "Total items: [count]" באמצעות המתודה Count
		
	case "priced":
		// TODO: צור slice להחזקת מופעים של PricedItem[float64]
		// TODO: קרא 'count' פריטים (לכל אחד שם, כמות ומחיר בשורות נפרדות)
		// TODO: צור PricedItem[float64] לכל פריט והוסף אותו ל-slice
		// TODO: הדפס כל פריט בפורמט "[Name] x[Quantity] @ [Price]", כשהמחיר מוצג עם ספרה אחת אחרי הנקודה העשרונית
		// TODO: הדפס "Total items: [count]"
	}
	
	_ = scanner // השתמש ב-scanner לקריאת הקלט
	_ = count   // השתמש ב-count עבור הלולאה
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין