מבוא ל־Generics
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 67 מתוך 107.
לפני Go 1.18, כתיבת קוד לשימוש חוזר עבור טיפוסים שונים חייבה שכפול של פונקציות או שימוש בממשק הריק any עם המרות טיפוס. גנריות פותרות את הבעיה הזאת בכך שהן מאפשרות לכתוב פונקציות וטיפוסים שעובדים עם כמה טיפוסים, תוך שמירה על בטיחות טיפוסים מלאה.
נניח שצריך למצוא את הערך המינימלי בפרוסה. ללא גנריות, תצטרכו פונקציות נפרדות לכל טיפוס:
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
}עם טיפוסים גנריים, כותבים את הלוגיקה פעם אחת באמצעות פרמטר טיפוס בסוגריים מרובעים:
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}). המהדר בודק טיפוסים בזמן הקומפילציה, כך שמקבלים בטיחות ללא תקורה בזמן ריצה.
בשיעורים הבאים נחקור לעומק פרמטרים של טיפוסים, אילוצים ומבנים גנריים.
אתגר
קלבואו נבנה מנתח ציונים שעובד גם עם ציונים של מספרים שלמים וגם עם ציונים של מספרים בנקודה צפה באמצעות גנריות! תיצרו פונקציה גנרית אחת שתוכל למצוא את הערך המרבי בפרוסה, וכך לא יהיה צורך בקוד כפול עבור סוגים מספריים שונים.
תארגנו את הקוד שלכם בשני קבצים:
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עם הטיפוס המתאים, והדפיסו את התוצאות.עבור קלט של מספרים שלמים, הדפיסו:
Max (int): [value] Sum (int): [value]עבור קלט של מספרים בנקודה צפה, הדפיסו ספרה אחת אחרי הנקודה העשרונית:
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] מאפשר למהדר ליצור קוד בטוח מבחינת טיפוסים עבור כל שימוש, בזמן שאתם כותבים את הלוגיקה פעם אחת בלבד.
נסו בעצמכם
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" {
// קרא ערכים של מספרים שלמים
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: קרא ל-Max ול-Sum עם פרוסה של מספרים שלמים
// TODO: הדפס את התוצאות בפורמט "Max (int): [value]" ו-"Sum (int): [value]"
} else if typeStr == "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 עם פרוסה של float64
// TODO: הדפס את התוצאות עם ספרה אחת אחרי הנקודה העשרונית בפורמט "Max (float): [value]" ו-"Sum (float): [value]"
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין