למה לקבץ נתונים יחד
עד עכשיו כל משתנה עמד בפני עצמו: int כאן, string שם. אבל תוכניות אמיתיות עוסקות בדברים שבנויים מכמה חלקים: לנקודה יש x ו-y, לסטודנט יש שם, גיל וממוצע ציונים. להעביר את כל אלה כמשתנים נפרדים ומפוזרים זה מתכון לטעויות: שום דבר לא קושר ביניהם, ופונקציה שצריכה את שלושתם חייבת לקבל שלושה פרמטרים.
struct פותר את זה. הוא מגדיר טיפוס חדש שמאגד משתנים קשורים, החברים שלו, ליחידה אחת. אחרי שהוא מוגדר, מתייחסים לכל החבילה כערך יחיד שאפשר לאחסן, להעתיק ולהעביר לפונקציות.
ניגשים לכל חבר עם אופרטור הנקודה (s.name, s.age). הנקודה-פסיק אחרי ה-} שסוגר הגדרת struct היא חובה: השמטה שלה היא אחת משגיאות הקומפילציה הנפוצות ביותר של מתחילים ב-C++.
אתחול struct
הצבה ידנית של כל שדה עובדת, אבל היא ארוכה וקל לשכוח חבר. האפשרות הנקייה יותר היא aggregate initialization: רושמים את הערכים בסוגריים מסולסלים, באותו סדר שבו החברים הוצהרו.
היזהרו מברירת המחדל הריקה: Point p; (בלי סוגריים) משאיר ב-x וב-y ערכי זבל, כי חברים מטיפוסים מובנים לא מאופסים אוטומטית. Point p{}; מאתחל אותם ל-0. העדיפו את הסוגריים. אפשר גם לקבוע ערכי ברירת מחדל ישירות בהגדרה, כך שאפילו Point p; מתחיל נקי:
Structs כפרמטרים של פונקציות
struct הוא ערך יחיד, אז פונקציה יכולה לקבל פרמטר אחד במקום שלושה. רק זכרו שהעברת struct by value מעתיקה כל חבר. לכל דבר גדול יותר מכמה משתני int, העבירו אותו כהפניה const כדי לחסוך את ההעתקה: אותו כלל שראיתם בדף על הפניות.
החזרת struct שלם מפונקציה היא הדרך האידיומטית להחזיר כמה ערכים בבת אחת, והרבה יותר נקייה מאשר להתעסק בכמה הפניות פלט.
הוספת פונקציות חברות ובנאים
struct לא מוגבל לנתונים. הוא יכול להכיל פונקציות חברות שפועלות על החברים שלו, ובנאי (constructor) שמאתחל את האובייקט ברגע שהוא נוצר. כאן struct מתחיל להיראות כמו אובייקט קטן עם התנהגות.
בתוך פונקציה חברה ניגשים לחברים לפי השם (width, height): הם מתייחסים לעותק של האובייקט הזה. הסימון area() const אומר למהדר שהפונקציה רק קוראת את האובייקט, וכך אפשר לקרוא לה גם על ערכי const Rectangle. בנאים הם נושא בפני עצמו, כולל תחביר רשימת האתחול : width(w), והדף על בנאים נכנס לזה לעומק.
Struct מול class: ההבדל האמיתי
אולי שמעתם ש"structs הם לנתונים, ו-classes הם לאובייקטים". זו מוסכמה, לא כלל של השפה. ב-C++ struct ו-class הם כמעט אותו דבר: ההבדל המובנה היחיד הוא רמת הגישה שבברירת המחדל.
struct S {
int x; // public כברירת מחדל
};
class C {
int x; // private כברירת מחדל
};
החברים של struct הם public אלא אם מסמנים אחרת, והחברים של class מתחילים כ-private. זה הכול: לשניהם יכולים להיות בנאים, פונקציות חברות, ירושה וכל השאר. אפשר אפילו להוסיף ל-struct מציינֵי גישה מפורשים כדי להסתיר חברים:
המסקנה המעשית: השתמשו ב-struct לחבילות נתונים שקופות שבהן כל שדה נועד לשינוי חופשי (Point, Color, רשומת הגדרות), ופנו ל-class כשרוצים להגן על מצב פנימי מאחורי ממשק ציבורי. המהדר מתייחס לשניהם אותו דבר; מילת המפתח רק מבטאת את הכוונה שלכם.
מערכים ו-vectors של structs
מכיוון ש-struct הוא טיפוס רגיל, אפשר לשים הרבה כאלה במערך או ב-vector ולעבור עליהם בלולאה כמו על כל ערך אחר.
שימו לב לסוגריים המקוננים: כל {"Keyboard", 49.99} מאתחל Product אחד ב-aggregate initialization, והסוגריים החיצוניים בונים את ה-vector. שימוש ב-const Product& בלולאת ה-range-based חוסך העתקה של כל struct בכל איטרציה: אם תורידו את ה-& תשכפלו כל איבר בלי צורך.
הבא בתור: Enums
Structs מאפשרים לבנות טיפוס מכמה ערכים שמאוגדים יחד. הפרק הבא הולך בכיוון ההפוך: enum מגדיר טיפוס שיכול להחזיק בדיוק ערך אחד מתוך קבוצה קטנה של שמות, מושלם לדברים כמו Color::Red, Direction::North או הסטטוס של מכונת מצבים. תראו איך enum class נותן קבועים קריאים ובטוחים מבחינת טיפוסים, שמשתלבים באופן טבעי עם ה-structs וה-classes שאתם בונים עכשיו.
שאלות נפוצות
מה זה struct ב-C++?
struct הוא טיפוס שהמשתמש מגדיר, שמקבץ כמה משתנים קשורים (שנקראים חברים, members) תחת שם אחד. במקום להתעסק במשתנים נפרדים string name, int age ו-double gpa, מאגדים אותם לטיפוס אחד Student ומעבירים את האובייקט הזה ממקום למקום. struct יכול להכיל גם פונקציות חברות ובנאים: ב-C++ מודרני הוא מחלקה מלאה שהחברים שלה public כברירת מחדל.
מה ההבדל בין struct ל-class ב-C++?
מבחינה טכנית רק רמת הגישה שבברירת המחדל: החברים של struct הם public אלא אם מציינים אחרת, והחברים של class הם private כברירת מחדל. אותו דבר נכון לגבי ירושה. כל השאר (בנאים, פונקציות חברות, מתודות, ירושה) עובד בדיוק אותו דבר. לפי המוסכמה, מתכנתים משתמשים ב-struct לחבילות נתונים פשוטות וב-class לטיפוסים עם אינווריאנטים ומצב פנימי מוסתר.
איך מאתחלים struct ב-C++?
הדרך הפשוטה ביותר היא aggregate initialization עם סוגריים מסולסלים לפי סדר החברים: Point p{3, 4};. אפשר גם להציב כל שדה ידנית עם אופרטור הנקודה (p.x = 3;), לתת לחברים ערכי ברירת מחדל בהגדרה (int x = 0;), או לכתוב בנאי כדי שה-struct יאתחל את עצמו. אתחול עם סוגריים מסולסלים עדיף כי הוא לא משאיר שדות לא מאותחלים בלי שתשימו לב.