Menu

const ב-Golang: קבועים עם טיפוס ובלי טיפוס

איך const עובד ב-Go: הצהרה על קבועים, ההבדל בין קבועים עם טיפוס לקבועים בלי טיפוס, ביטויים קבועים בדיוק שרירותי, ולמה אין ב-Go קבועים מסוג slice, map או struct.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

קבוע הוא ערך שנקבע בזמן קומפילציה. מצהירים עליו עם const, ו-Go דוחה כל ניסיון לשנות אותו.

אפשר להצהיר על קבועים ברמת החבילה או בתוך פונקציה, אחד בכל שורה או מקובצים בבלוק const ( ... ). MB ו-GB מחושבים מקבועים אחרים, והקומפיילר מבצע את החישוב הזה פעם אחת, בזמן הבנייה.

השמה לקבוע, כמו MaxRetries = 5, היא שגיאת קומפילציה:

./main.go:8:2: cannot assign to MaxRetries (neither addressable nor a map index expression)

מה יכול להיות קבוע

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

כל מה שדורש הרצת קוד או הקצאת זיכרון לא נכנס:

const Colors = []string{"red", "green"} // slice
const Started = time.Now()              // function call
./main.go:9:16: []string{…} (value of type []string) is not constant
./main.go:10:13: time.Now() (value of struct type time.Time) is not constant

למה אין ב-Go קבועים מסוג slice או map

slices, maps ו-structs שמכילים אותם הם הפניות לזיכרון שקיים בזמן ריצה, ולכן הקומפיילר לא יכול לצרוב אותם בתוכנית כערכים קבועים, ול-Go אין מאפיין readonly או final למשתנים. הפתרונות המקובלים:

ההעתקה עולה הקצאה אחת בכל קריאה, וזה בסדר לנתונים בגודל של קונפיגורציה. לחיפוש לפי מפתח, switch בתוך פונקציה הוא עוד אפשרות שמתנהגת כמו קבוע: func statusText(code int) string { switch code { ... } }.

קבועים בלי טיפוס

קבוע שהוצהר בלי טיפוס הוא untyped. יש לו סוג (מספר שלם, מספר עשרוני, rune, מחרוזת, bool) אבל אין לו טיפוס Go מסוים עד שמשתמשים בו, והוא מתאים את עצמו להקשר:

Ratio עובד כ-int, כ-float64, כ-uint8 וכ-float32 בלי המרה. בגלל זה time.Sleep(2 * time.Second) מתקמפל: 2 הוא בלי טיפוס והופך ל-time.Duration.

כשההקשר לא דורש טיפוס, כמו ב-x := Ratio, הקבוע מקבל את טיפוס ברירת המחדל שלו:

סוג הקבוע בלי טיפוסטיפוס ברירת מחדל
מספר שלם (42)int
מספר עשרוני (4.2)float64
rune ('a')rune (int32)
מספר מרוכב (2i)complex128
מחרוזתstring
בוליאניbool

חשבון מדויק

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

את Huge עצמו אי אפשר להדפיס כמספר שלם, כי העברתו ל-fmt.Println ממירה אותו ל-int, והקומפיילר תופס את זה (float64(Huge) עובד, כי float יכול להחזיק ערך בסדר הגודל הזה):

cannot use Huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)

אותה בדיקה עוצרת טעויות קטנות יותר בזמן קומפילציה, כמו var b byte = 300:

cannot use 300 (untyped int constant) as byte value in variable declaration (overflows)

קבועים עם טיפוס

תנו לקבוע טיפוס והוא יתנהג כמו ערך מאותו טיפוס בכל מקום, כולל הסירוב של Go לערבב טיפוסים:

const Limit int = 10

var f float64 = 2
fmt.Println(Limit * f)
invalid operation: Limit * f (mismatched types int and float64)

עם const Limit = 10 (בלי טיפוס), Limit * f מתקמפל ונותן 20. לכן השאירו קבועים בלי טיפוס, אלא אם לטיפוס יש משמעות. יש לו משמעות כשמצהירים על קבועים מטיפוס בעל שם משלכם, וכך Go בונה enums:

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
)

כאן הטיפוס הוא העיקר: פונקציה שמקבלת Weekday מתעדת מה היא רוצה. iota ודפוסי ה-enum שנבנים עליו מוסברים בדף על enums ו-iota.

const מול var

constvar
יכול להשתנותלאכן
הערך ידועבזמן קומפילציהבזמן ריצה
טיפוסים מותריםbool, מספרי, stringכל טיפוס
תופס זיכרון בזמן ריצהלאכן
אפשר לקחת את הכתובת שלו (&x)לאכן
אי שימוש בו הוא שגיאהלאכן, למשתנים מקומיים

השתמשו ב-const לכל מה שבאמת קבוע: מגבלות, גדלים, שמות, קודי פרוטוקול, מחרוזות פורמט. מקבלים בחינם בדיקות overflow בזמן קומפילציה וחשבון מדויק.

מלכודות

קבועים עם טיפוס לא מתאימים את עצמם. אי אפשר להעביר את const Timeout int = 5 למקום שבו מצופה time.Duration בלי המרה; את const Timeout = 5 בלי טיפוס אפשר להכפיל: Timeout * time.Second.

חילוק שלמים מתבצע על קבועים שלמים. const Half = 1 / 2 הוא 0, כי שני האופרנדים הם מספרים שלמים בלי טיפוס. כתבו 1.0 / 2 כדי לקבל 0.5.

אין כתובת. &MaxRetries לא מתקמפל. אם API מצפה ל-*int, העתיקו קודם את הקבוע למשתנה.

שאלות נפוצות

איך מצהירים על קבוע ב-Go?

עם const: const MaxRetries = 3 או, עם טיפוס מפורש, const Timeout time.Duration = 5 * time.Second. אפשר לקבץ כמה קבועים בבלוק: const ( A = 1; B = 2 ). הערך חייב להיות ניתן לחישוב בזמן קומפילציה.

האם אפשר ליצור slice, map או מערך קבועים ב-Go?

לא. קבועים יכולים להיות רק ערכים בוליאניים, מספרים (כולל runes) ומחרוזות. const Colors = []string{"red"} נכשל עם is not constant. השתמשו ב-var ברמת החבילה, ואם אסור לקוראים לשנות אותו, חשפו פונקציה שמחזירה עותק חדש.

מהו קבוע בלי טיפוס (untyped constant) ב-Go?

קבוע שהוצהר בלי טיפוס, כמו const Pi = 3.14159. אין לו טיפוס קבוע עד שמשתמשים בו, כך שאותו קבוע עובד כ-float32, כ-float64 או, אם הערך שלו מאפשר זאת, כ-int. קבועים בלי טיפוס הם גם מדויקים: הקומפיילר שומר לפחות 256 סיביות של דיוק, ולכן const Big = 1 << 100 חוקי כל עוד משתמשים בו רק בביטויים שהתוצאות שלהם נכנסות לטיפוס.

מה ההבדל בין const ל-var ב-Go?

const נקבע בזמן קומפילציה ולעולם לא יכול להשתנות; הוא לא תופס זיכרון בזמן ריצה ויכול להחזיק רק bool, מספר או מחרוזת. var הוא מקום בזיכרון שאפשר להשים אליו מחדש, והוא יכול להחזיק כל טיפוס, כולל ערכים שמחושבים בזמן ריצה כמו time.Now().

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל