הגדרה ויצירה של struct
טיפוס struct הוא רשימה של שדות עם שמות. מצהירים עליו עם type Name struct { ... } ויוצרים ערכים עם ליטרל מורכב.
פלט:
Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}
%+v מדפיס את שמות השדות ו-%#v מדפיס תחביר Go. שניהם הדרך המהירה ביותר לבדוק struct בזמן דיבוג.
שדות מאותו טיפוס יכולים לחלוק שורה: X, Y float64. שמות שדות שמתחילים באות גדולה מיוצאים (גלויים לחבילות אחרות ול-encoding/json); שמות באותיות קטנות לא.
צורות של ליטרלים
User{Name: "Ana", Age: 31} // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"} // pointer to a new struct
new(User) // pointer to a zeroed struct
העדיפו שדות עם שמות. ליטרלים לפי מיקום נשברים כשמישהו מוסיף שדה או משנה את הסדר, ו-go vet מזהיר עליהם בטיפוסי struct מחבילות אחרות (למשל net/url.Error struct literal uses unkeyed fields). צורה לפי מיקום מתאימה לטיפוסים מקומיים זעירים כמו Point{1, 2}.
אין ערכי ברירת מחדל: ערכי אפס ובנאים
ל-Go אין ברירות מחדל לשדות ואין בנאים בשפה. כל שדה מתחיל בערך האפס של הטיפוס שלו: 0, "", false, nil. שני ניבים ממלאים את החסר.
הפכו את ערך האפס לשימושי. sync.Mutex, bytes.Buffer ו-strings.Builder כולם עובדים בלי אתחול. כשאפשר לתכנן טיפוס בצורה כזאת, עשו זאת.
כתבו פונקציית NewX כששדה צריך ברירת מחדל שאינה אפס או כשצריך להקצות משהו, כמו map:
NewServer מחזירה מצביע כי לשרת יש מתודות שמשנות אותו והוא יהיה משותף. גם החזרת ה-struct כערך נפוצה בטיפוסים קטנים ו-immutable (time.Date מחזירה time.Time).
מצביעים ל-structs
Go מבצעת dereference אוטומטי למצביעים ל-structs בגישה לשדות. עם p := &User{}, כותבים p.Name, לא (*p).Name.
פלט:
{11 12}
{11 12} {0 12}
{11 0}
structs הם ערכים. הצבה וארגומנטים של פונקציה מעתיקים אותם; מצביע משתף עותק אחד. העתקות שדה אחרי שדה הן רדודות: שדה של slice או map בעותק עדיין מצביע על אותם נתונים. ראו מצביעים כדי לדעת מתי להשתמש בכל אחד.
structs אנונימיים
אפשר לכתוב טיפוס struct בתוך השורה בלי שם. זה שימושי לקיבוצים חד פעמיים, לבדיקות מבוססות טבלה ולפענוח JSON שקוראים רק פעם אחת.
הטבלה []struct{ in string; want int } היא הצורה הסטנדרטית של בדיקה ב-Go.
השוואת structs
structs ניתנים להשוואה עם == כשכל שדה הוא comparable. שוויון פירושו שכל השדות שווים. structs שניתנים להשוואה יכולים לשמש גם כמפתחות של map.
ל-struct עם שדות של slice או map, כתבו מתודת Equal או השוו בבדיקות עם reflect.DeepEqual.
Struct tags
tag הוא ליטרל מחרוזת אחרי הטיפוס של שדה. השפה מתעלמת ממנו; ספריות קוראות אותו עם reflection. הקורא הנפוץ ביותר הוא encoding/json:
זה מדפיס {"id":7,"name":"Mug"}: ה-tag משנה את שמות השדות, omitempty משמיט את ה-Price שערכו אפס, והשדה שלא מיוצא בלתי נראה למקודד. tags עוקבים אחרי המוסכמה key:"value", עם כמה מפתחות שמופרדים ברווחים: `json:"name" db:"user_name"`. tag פגום מתקמפל בלי בעיה ופשוט מתעלמים ממנו בשקט, ו-go vet תופס את זה. הדף על json מכסה את האפשרויות.
structs ריקים וסדר השדות
ל-struct{} אין שדות והוא תופס אפס בתים. משתמשים בו כערך של set (map[string]struct{}) וכאות ב-channels (chan struct{}), שבהם רק האירוע חשוב.
סדר השדות משפיע על פריסת הזיכרון: המהדר מרפד שדות לפי היישור שלהם. struct של bool, int64, bool תופס 24 בתים במערכות 64 ביט, בעוד int64, bool, bool תופס 16. זה משנה רק בטיפוסים שנשמרים בכמויות גדולות.
טעויות נפוצות
- שינוי של עותק.
for _, u := range users { u.Age++ }משנה עותק בכל פעם. השתמשו באינדקס:users[i].Age++. - שכחה לאתחל שדה של map. ערך האפס של ה-struct מחזיק map שהוא nil; כתיבה אליו גורמת ל-panic. הקצו בבנאי.
- העתקת struct שמחזיק
sync.Mutex. לעותק יש מצב נעילה משלו. העבירו structs כאלה לפי מצביע;go vetמדווח על ההעתקה. - ליטרלים לפי מיקום לטיפוסים של חבילות אחרות. הם נשברים כשנוסף שדה.
שאלות נפוצות
איך מאתחלים struct ב-Go?
עם ליטרל מורכב. שדות עם שמות הם הצורה המקובלת: u := User{Name: "Ana", Age: 31}, וכל שדה שמשמיטים מקבל את ערך האפס שלו. User{} נותן את כל ערכי האפס, &User{...} נותן מצביע, ו-new(User) נותן מצביע ל-struct מאופס.
האם Go תומכת בערכי ברירת מחדל לשדות של struct?
לא. כל שדה מתחיל בערך האפס של הטיפוס שלו. הניב המקובל הוא פונקציית בנאי, func NewServer() *Server { return &Server{Port: 8080} }, או תכנון הטיפוס כך שערך האפס שלו כבר שימושי, כמו sync.Mutex ו-bytes.Buffer.
איך משווים שני structs ב-Go?
עם ==, אם כל שדה הוא comparable. שני ערכי struct שווים כשכל השדות המתאימים שווים. struct שמכיל שדה של slice, map או פונקציה לא ניתן להשוואה עם == (שגיאת הידור); השוו את השדות בעצמכם או השתמשו ב-reflect.DeepEqual בבדיקות.
מה הם struct tags ב-Go?
ליטרלים של מחרוזות אחרי הטיפוס של שדה, כמו `json:"name,omitempty"`. המהדר מתעלם מהם; חבילות כמו encoding/json, דרייברים של מסדי נתונים ו-validators קוראות אותם עם reflection כדי לשלוט באופן שבו שדות מקודדים, נקראים או נבדקים.
איך מדפיסים struct עם שמות השדות ב-Go?
השתמשו ב-fmt.Printf("%+v\n", s), שמדפיס {Name:Ana Age:31}. %v מדפיס רק את הערכים, ו-%#v מדפיס תחביר Go כולל שם הטיפוס: main.User{Name:"Ana", Age:31}.