Menu

Struct ב-C: הגדרה, אתחול ושימוש במבנים

איך מקבצים ערכים קשורים לטיפוס אחד עם struct ב-C: הצהרה, גישה לשדות עם אופרטור הנקודה, designated initializers, מערכים של structs, קינון, וכלל ההעתקה בהעברה שמפתיע את כולם.

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

מערך מחזיק הרבה ערכים מטיפוס אחד. struct עושה את ההפך: הוא מחזיק כמה ערכים מטיפוסים שונים ומתייחס אליהם כדבר אחד. נקודה היא x ו-y. עובד הוא שם, מספר מזהה ומשכורת. בלי structs הייתם סוחבים אותם כשלושה משתנים נפרדים ומקווים שאף פעם לא תתבלבלו איזו משכורת שייכת למי.

הצהרה על טיפוס struct

הצהרת struct נותנת שם לתגית ומפרטת את השדות בתוך סוגריים מסולסלים:

struct Point {
    int x;
    int y;
};

הנקודה־פסיק אחרי הסוגר המסולסל הסוגר היא חובה. שכחה שלה מובילה לאחת מהודעות השגיאה המבלבלות ביותר שמתחיל ב-C יפגוש, כי הקומפיילר מאשים את השורה הבאה.

זה מצהיר על טיפוס, לא על משתנה. ב-C השם המלא של הטיפוס הוא struct Point, כולל מילת המפתח:

קוראים וכותבים שדות עם אופרטור הנקודה, p.x. משתנה struct הוא משתנה רגיל: הוא חי על ה-stack, אפשר להשים אותו ל-struct אחר מאותו טיפוס עם =, והוא נעלם בסוף ה-scope שלו.

כתיבה של struct Point בכל מקום מתישה, ולכן כמעט כל קוד C אמיתי עוטף את ההצהרה ב-typedef, כך שהטיפוס הוא פשוט Point.

אתחול struct

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

struct Point p = {3, 4};   // x = 3, y = 4

designated initializer נוקב בשם של כל שדה במקום זאת, וזה ברור ובטוח יותר:

שלושה דברים ששווה לשים לב אליהם:

  • הצורה עם השמות יכולה לפרט שדות בכל סדר, כך שהוספה או שינוי סדר של שדה בהמשך לא מזיזים בשקט ערכים למקומות הלא נכונים.
  • כל שדה שמשמיטים מאותחל לאפס: {0} היא הדרך האידיומטית לאפס struct שלם.
  • שדה char name[32] הוא מערך אמיתי בתוך ה-struct, כך שהמחרוזת נשמרת ב-struct עצמו ולא במקום אחר. זה גם אומר ש-a.name = "Ada"; בדיעבד אינו חוקי, וצריך strcpy (ראו פונקציות מחרוזות).

struct בלי מאתחל אינו מאותחל, וקריאה של השדות שלו לפני שכותבים אותם היא undefined behavior. = {0} לא עולה כלום ומעלים את כל סוג הבאגים הזה.

השמה והשוואה

השמה של struct מעתיקה כל שדה:

a לא נפגע: b קיבל עותק משלו. אבל שימו לב למה ש-C לא נותנת לכם: אין == ל-structs. if (a == b) לא יתקמפל. השוואה פירושה השוואת שדות:

if (a.x == b.x && a.y == b.y) { /* שווים */ }

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

מערכים של structs

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

staff[i].salary נקרא "איבר i, ואז השדה salary שלו": האינדוקס נקשר חזק יותר מהנקודה, ולכן לא צריך סוגריים.

structs מועתקים לתוך פונקציות

זה הכלל שתופס אנשים. כשמעבירים struct לפונקציה, C מעתיקה אותו. הפונקציה עובדת על עותק פרטי משלה:

p עדיין (3, 4). קריאה של struct by value, כמו ש-distance_from_origin עושה, תקינה לגמרי ולעיתים קרובות היא הבחירה הברורה ביותר ל-structs קטנים. אבל אם הפונקציה חייבת לשנות את ה-struct של הקורא, או אם ה-struct גדול מספיק כדי שהעתקתו תהיה בזבזנית, העבירו מצביע במקום. זה הנושא של העמוד structs ומצביעים.

החזרה של struct by value עובדת באותה דרך והיא חוקית לגמרי:

struct Point make_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

structs מקוננים

שדה של struct יכול להיות בעצמו struct. שרשרו נקודות כדי להגיע פנימה:

b.published.year נכנס רמה אחת בכל פעם. קינון הוא הדרך לבנות מודלים אמיתיים של נתונים ב-C, וה-struct הפנימי נשמר בתוך החיצוני ולא דרך מצביע, כך שכל ה-Book הוא בלוק זיכרון רציף אחד.

גודל, ריפוד וסדר השדות

sizeof אומר לכם כמה גדול struct, והתשובה לעיתים קרובות גדולה מסכום השדות שלו:

במכונה טיפוסית Wasteful הוא 12 בתים ו-Tidy הוא 8, למרות ששניהם מחזיקים את אותם שלושה שדות. הקומפיילר מכניס ריפוד כך שכל שדה יישב בכתובת שהטיפוס שלו אוהב: int רוצה בדרך כלל כפולה של 4. קיבוץ שדות גדולים לפני קטנים נוטה לארוז טוב יותר.

זה משנה לעיתים רחוקות (מערך של מיליון איברים, פריסה של חבילת רשת) ואף פעם לא לנכונות של הקוד שלכם. מה שזה כן מסביר הוא למה sizeof מפתיע אתכם, ולמה memcmp על structs הוא רעיון רע.

שאלות נפוצות

איך מצהירים על struct ב-C?

כותבים struct, אחריו שם תגית ורשימת שדות בתוך סוגריים מסולסלים: struct Point { int x; int y; };. זה מצהיר על טיפוס, לא על משתנה. כדי ליצור משתנה כותבים אחר כך struct Point p;: המילה struct היא חלק משם הטיפוס ב-C, ולכן רוב הקוד מצמיד לה typedef.

מה ההבדל בין . לבין -> ב-structs?

השתמשו ב-. כשיש לכם את ה-struct עצמו (p.x) וב--> כשיש לכם מצביע אליו (ptr->x). החץ הוא קיצור של (*ptr).x. ראו structs ומצביעים לפרטים.

האם structs מועברים by value או by reference ב-C?

by value, תמיד. העברת struct לפונקציה מעתיקה כל שדה, כך ששינויים בתוך הפונקציה לא משפיעים על המשתנה של הקורא. כדי לשנות את המקורי, או כדי לא להעתיק struct גדול, העבירו מצביע במקום.

איך מאתחלים struct ב-C?

או לפי מיקום, struct Point p = {3, 4};, או עם designated initializers, struct Point p = {.y = 4, .x = 3};. הצורה עם השמות נוקבת בכל שדה, ולכן היא שורדת גם אם מישהו ישנה את סדר השדות ב-struct בהמשך, ומשאירה שדות שלא צוינו מאופסים.

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

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

להתחיל