Menu
Coddy logo textTech

מעטפת גנרית

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

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

הרעיון פשוט: יוצרים struct שמאגד void* עם enum שמזהה את הטיפוס המאוחסן. כך, המעטפת תמיד יודעת איך לפרש את התוכן שלה.

typedef enum {
    TYPE_INT,
    TYPE_STRING
} DataType;
typedef struct {
    void* data;
    DataType type;
} Wrapper;

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

Wrapper wrap_int(int value) {
    int* p = malloc(sizeof(int));
    *p = value;
    return (Wrapper){ .data = p, .type = TYPE_INT };
}
void print_wrapper(Wrapper* w) {
    if (w->type == TYPE_INT) {
        printf("%d\n", *(int*)w->data);
    } else if (w->type == TYPE_STRING) {
        printf("%s\n", (char*)w->data);
    }
}

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

challenge icon

אתגר

קל

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

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

  • wrapper.h: הגדירו טיפוס enum בשם DataType עם הערכים TYPE_INT ו-TYPE_STRING. לאחר מכן הגדירו מבנה Wrapper שמאגד מצביע נתונים מסוג void* עם שדה מסוג DataType. הצהירו על אבות הטיפוס של הפונקציות ליצירת עטיפות ולהדפסת התוכן שלהן.
  • wrapper.c: ממשו את הפונקציונליות של העטיפה:
    • wrap_int: מקבלת מספר שלם, מקצה עבורו זיכרון, שומרת את הערך ומחזירה Wrapper עם תגית הסוג המתאימה
    • wrap_string: מקבלת מחרוזת (char*), מקצה זיכרון ומעתיקה את המחרוזת, ומחזירה Wrapper עם תגית סוג המחרוזת
    • print_wrapper: בודקת את תגית הסוג ומדפיסה את הנתונים בהתאם (מספר שלם כפי שהוא, מחרוזת כפי שהיא)
    • free_wrapper: משחררת את הנתונים שהוקצו דינמית בתוך העטיפה
  • main.c: חברו את הכול יחד. קראו סימון סוג (i עבור מספר שלם, s עבור מחרוזת) ולאחריו את הערך. צרו את העטיפה המתאימה, הדפיסו את תוכנה ושחררו את הזיכרון.

התוכנית שלכם תקבל שתי כניסות:

  1. סימון סוג: i עבור מספר שלם או s עבור מחרוזת
  2. הערך לעטיפה

פלט לדוגמה כאשר הכניסות הן i ו-42:

42

פלט לדוגמה כאשר הכניסות הן s ו-Hello:

Hello

פלט לדוגמה כאשר הכניסות הן i ו--100:

-100

פלט לדוגמה כאשר הכניסות הן s ו-Generic Programming:

Generic Programming

זכרו להשתמש בשומרי הכללה בקובץ הכותרת שלכם. בעת העתקת מחרוזת, הקצו מספיק מקום למחרוזת ולתו המסיים null באמצעות strlen ו-strcpy.

נסו בעצמכם

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

int main() {
    char type;
    scanf("%c", &type);
    getchar(); // קלוט את תו השורה החדשה
    
    Wrapper w;
    
    if (type == 'i') {
        int value;
        scanf("%d", &value);
        // TODO: צור מעטפת למספר שלם באמצעות wrap_int
        
    } else if (type == 's') {
        char str[256];
        fgets(str, sizeof(str), stdin);
        // הסר את תו השורה החדשה שבסוף, אם הוא קיים
        str[strcspn(str, "\n")] = '\0';
        // TODO: צור מעטפת למחרוזת באמצעות wrap_string
        
    }
    
    // TODO: הדפס את תוכן המעטפת באמצעות print_wrapper
    
    // TODO: שחרר את הזיכרון של המעטפת באמצעות free_wrapper
    
    return 0;
}
quiz iconבחנו את עצמכם

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

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

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