Menu
Coddy logo textTech

כלל האיבר הראשון

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C של Coddy. שיעור 29 מתוך 61.

כשמשבצים מבנה בתור האיבר הראשון של מבנה אחר, קורה משהו עוצמתי: כתובת הזיכרון של המבנה החיצוני זהה לכתובת הזיכרון של האיבר הראשון שלו. תקן C מבטיח זאת.

נתבונן בדוגמה הקודמת שלנו:

typedef struct {
    int id;
} Parent;

typedef struct {
    Parent parent;  // האיבר הראשון
    int grade;
} Child;

כשיוצרים משתנה מסוג Child, האיבר Parent נמצא בתחילת הזיכרון של Child. המשמעות היא שמצביע אל Child מצביע בדיוק לאותה כתובת כמו מצביע אל האיבר Parent שלו.

Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr;  // המרת טיפוס בטוחה!

שני המצביעים מכילים את אותה כתובת. כך אפשר להעביר Child* לכל פונקציה שמצפה לקבל Parent*:

void print_id(Parent* p) {
    printf("ID: %d\n", p->id);
}

// בתוך main:
Child c;
c.parent.id = 42;
print_id((Parent*)&c);  // עובד בצורה מושלמת

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

challenge icon

אתגר

קל

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

התובנה המרכזית שתיישמו: כאשר Animal הוא האיבר הראשון של Dog, לשני המבנים יש אותה כתובת זיכרון התחלתית. פירוש הדבר הוא שאפשר להעביר Dog* לכל פונקציה שמצפה לקבל Animal*, באמצעות המרת המצביע.

תארגנו את הקוד בשלושה קבצים:

  • animal.h: הגדירו את שני המבנים עם הגנות הכללה. במבנה Animal צריך להיות שדה יחיד: legs (מספר שלם שמייצג את מספר הרגליים). במבנה Dog יש לשלב את Animal בתור האיבר הראשון ולהוסיף שדה name (מערך תווים באורך 50 תווים). כמו כן, הכריזו על פונקציה בשם print_animal שמקבלת פרמטר מסוג Animal*.
  • animal.c: ממשו את הפונקציה print_animal. הפונקציה הזאת פועלת עם טיפוס הבסיס. היא מכירה רק את Animal ומדפיסה כמה רגליים יש לבעל החיים.
  • main.c: צרו משתנה מסוג Dog ואתחלו את השדות שלו (גם את מספר הרגליים של החיה המשולבת וגם את שם הכלב). לאחר מכן, הדגימו את כלל האיבר הראשון באמצעות המרת Dog* שלכם ל-Animal* והעברתו אל print_animal. הדפיסו גם את שם הכלב בנפרד כדי להציג את התמונה המלאה.

תקבלו שתי קלטים: שם הכלב (מחרוזת) ומספר הרגליים (מספר שלם).

הפלט שלכם צריך להיראות כך:

Dog: Buddy
Legs: 4

כאשר Buddy הוא שם הכלב ו-4 הוא מספר הרגליים. הדפיסו תחילה את שם הכלב מתוך main.c, ואז קראו ל-print_animal עם המצביע המומר כדי להציג את מספר הרגליים.

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

נסו בעצמכם

#include <stdio.h>
#include <string.h>
#include "animal.h"

int main() {
    // קראו את הקלט
    char name[50];
    int legs;
    scanf("%s", name);
    scanf("%d", &legs);
    
    // TODO: צרו משתנה מסוג Dog
    
    // TODO: הגדירו את שם הכלב באמצעות strcpy
    
    // TODO: הגדירו את מספר הרגליים (גשו אליו דרך ה-Animal המוטמע)
    
    // TODO: הדפיסו את שם הכלב בפורמט: "Dog: <name>"
    
    // TODO: המירו את Dog* ל-Animal* וקראו ל-print_animal
    // זה מדגים את כלל האיבר הראשון!
    
    return 0;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C אונליין