דוא"ל:
תפריט משתמש




שתף |

קורס:"שפת C"

שיעור 10: מבנים 

[ <<< הקודם ] [ תוכן עניינים ] [ הבא >>> ]

מתוך הספר: C - מדריך מקצועי       C - מדריך מקצועי
תוכן עניינים



הגדרת מבנה והתייחסות לשדותיו

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

 
int    x;
float           y;
char           str[20];
 

מבנה (structure) ב - C מאפשר הגדרת אוסף משתנים מטיפוסים שונים כיחידה אחת. מבנה נקרא לפעמים גם רשומה (record).

לדוגמא, נניח שאנו רוצים לכתוב תוכנה לניהול אוסף תקליטורי מוזיקה. לכל תקליט נרצה לשמור את המשתנים הבאים:

שם השדה

הטיפוס המייצג

שם התקליט  

            char name[20]

שם הלהקה   

            char band[40]

סוג המוסיקה            

            char category[12]

מחיר              

            float cost

מספר סידורי 

            int number

מבנה ב- C מוגדר ע"י המילה השמורה struct. קיימות שתי דרכים להגדיר טיפוס מבנה:

  • הגדרת טיפוס מבנה ע"י typedef
  • הגדרת מבנה כשם תג

דרך א': הגדרת מבנה כטיפוס

בשיטה זו, הכרזה על מבנה נעשית תוך שימוש בהוראה typedef :

 
typedef struct
{
          char name[20];
          char           band [40];
          char           category[12];
          float           cost;
          int    number;
} CD;
 

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



המבנה התחבירי בהגדרת מבנה בשיטה זו:

typedef struct

{

<טיפוס>  <שם משתנה>;

<טיפוס>  <שם משתנה>;

          :

} <שם-טיפוס>;



כעת נגדיר משתנים מסוג CD :

 
CD  disc1, disc2;
 

disc1 ו- disc2 הם מבנים מהטיפוס CD שהגדרנו לעיל. בדומה למערך, ניתן לאתחל מבנה בזמן הגדרתו, לדוגמא:

 
CD cd1 = {"Best Hits", "Tiny Tim", "pop music",12.5,12};
 

דרך ב': הגדרת מבנה כשם תג

בשיטה זו מגדירים מבנה ללא שימוש ב- typedef. הכרזה על מבנה :

 
struct CD
{
char           name[20];
char           band[40];
char           category[12];
float           cost;
int    number;
};
 

המבנה התחבירי בהגדרת מבנה בשיטה זו:

struct <שם תג>

{

<טיפוס>  <שם משתנה>;

<טיפוס>  <שם משתנה>;

          ...

} ;

בשונה מהשיטה הקודמת, בהגדרת משתנים מסוג CD חובה לציין את שם התג, יחד עם המילה השמורה struct:

 
struct CD  disc1, disc2;
 

ניתן גם להגדיר משתנים בו זמנית עם הגדרת טיפוס המבנה, לדוגמא:

 
struct CD
{
char           name[20];
char           band[40];
char           category[12];
float           cost;
int    number;
} disc1, disc2;
 

וכמו כן ניתן לאתחל מבנה בזמן הגדרתו:

 
struct CD cd1 = {"Best Hits", "Tiny Tim", "pop music",12.5,12};
 

איזו דרך עדיפה?

הדרך הראשונה - הגדרת טיפוס על ידי typedef - עדיפה מ- 2 סיבות:

    1. בהעברת המבנה כפרמטר לפונקציה, למהדר קל יותר לבצע בדיקת התאמת טיפוסים כאשר הטיפוס מוגדר ע"י typedef מאשר בלעדיו (מהדרים מסוימים לא יודיעו על שגיאה בשיטה ב').
    2. נוח יותר להשתמש בטיפוס המוגדר ע"י typedef מאשר לרשום בכל פעם את המילה struct.

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

גישה לשדות המבנה

נניח שאנו רוצים לשנות את השדה cost ב- disc1. כדי לגשת לשדה מסוים משתמשים באופרטור  "." בין שם המבנה לשם השדה:

 
disc1.cost = 16.95;
 

דוגמא נוספת - אתחול שם התקליטור מהקלט:

 
gets(disc1.name);
 

בהגדרת המחרוזות במבנה ניתן להשתמש בטיפוס מחרוזת המוגדר כמערך של 256 תווים:

 
typedef char String[256];
 
typedef struct
{
          String name;
          String band;
          String category;
          float cost;
          int number;
} CD;
 

תכנית דוגמא

קריאת נתוני תקליטור מהקלט למשתנה מסוג תקליטור והדפסתם לפלט:

 
/* file: cd1.c */
#include <stdio.h>
typedef char String[256];
 
typedef struct
{
          String name;
          String band;
          String category;
          float cost;
          int number;
} CD;
 
void main()
{
          CD cd1;
 
          puts("Enter CD name, band and category, separated by newlines:");
          gets(cd1.name);
          gets(cd1.band);
          gets(cd1.category);
          printf("Enter cost:");
          scanf("%f", &cd1.cost);
          cd1.number = 1;
 
          /* print */
          puts("cd1 = ");
          printf("\tname=%s\n",cd1.name);
          printf("\tband=%s\n",cd1.band);
          printf("\tcategory=%s\n",cd1.category);
          printf("\tcost=%.2f\n",cd1.cost);
          printf("\tnumber=%d\n",cd1.number);
}
 

עבור הקלט

 
Enter CD name, band and category, separated by newlines:
Changes
David Bowie
pop
Enter cost:23.5
 


יודפס הפלט:

 
cd1 = 
    name=Changes
    band=David Bowie
    category=pop
    cost=23.50
    number=1
 

הסבר התכנית

התכנית מגדירה משתנה מסוג רשומת CD

 
          CD cd1;
 

ולאחר מכן מבקשת מהמשתמש להקליד את נתוני התקליטור:

 
          puts("Enter CD name, band and category, separated by newlines:");
 

השדות שמטיפוס מחרוזות נקראים מהקלט ע"י gets():

 
          gets(cd1.name);
          gets(cd1.band);
          gets(cd1.category);
 

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

הנתון המספרי, cost, נקרא ע"י scanf() תוך העברת כתובת השדה במבנה, בדומה לקריאת משתנה מספרי רגיל:

 
          printf("Enter cost:");
          scanf("%f", &cd1.cost);
 

המספר הסידורי מאותחל ל- 1:

 
          cd1.number = 1;
 

בשלב הבא מודפס המבנה ע"י :

 
          /* print */
          puts("cd1 = ");
          printf("\tname=%s\n",cd1.name);
          printf("\tband=%s\n",cd1.band);
          printf("\tcategory=%s\n",cd1.category);
          printf("\tcost=%.2f\n",cd1.cost);
          printf("\tnumber=%d\n",cd1.number);
 

הצבת מבנה למבנה אחר

ניתן להציב מבנה אחד למבנה אחר, בתנאי שהם מאותו הטיפוס, לדוגמא:

 
          cd2 = cd1;
 

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

הערה : יש לשים לב שכאשר המבנה מכיל מצביעים המצביע הוא המועתק, ולא תוכנו.

הרחבת הדוגמא הקודמת - עיין/י בתכנית שבעמ'  254-255.

תרגילים

קרא/י סעיף זה בספר ובצע/י את תר' 1-4 שבעמ' 255.

 

מבנה המכיל משתנה ממבנה אחר

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

לדוגמא, בתכנית לניהול מוצרים בסופרמרקט, נגדיר מבנה המתאר ספק:

 
typedef struct
{
          char           name[30];
          char           address[50];
          char           phone_num[15];
          char           fax_num[15];
} Supplier ;
 

נגדיר מבנה נוסף המתאר את המלאי עבור מוצר נתון - מקסימום, מינימום וכמות נוכחית:

 
typedef struct
{
          int max;
          int min;
          int curr;
} Inventory;
 

כעת נגדיר פריט (Item) - הפריט יתאר סוג של מוצר הקיים בסופרמרקט:

 
typedef struct
{
          char           name[30];
          Supplier   supplier;
          char           department[15];
          Inventory inventory;
          float           cost;
          int              code;
} Item ;
 

תרשים הזיכרון של רשומת Item :

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

 
          Supplier   supplier;
          ...
          Inventory inventory;
 
הערה : יש לשים לב שהטיפוס מוגדר עם אות גדולה בתחילתו, ואילו שם השדה עם אות קטנה. מקובל להבדיל כך בין טיפוסים לבין משתנים.

התכנית המשתמשת מובאת בעמ' 257.

תרגיל

בצע/י את התרגיל שבעמ' 258.

מערכי מבנים

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

 
CD cd_array[10];
 

תמונת הזיכרון של המערך:



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

 
1.      cd_array[3].cost = 17.2;
 
2.      gets(cd_array[6].name);
 

בעמ' 259 מובאת תכנית המרחיבה את תכנית התקליטורים לטיפול במערך תקליטורים (CD). עיין/י בקוד התכנית ובהסבר.

תרגיל

בצע/י את התרגיל שבעמ' 261.

מצביעים למבנים

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

 
CD  cd1;
CD *pcd = &cd1;
 

ההתייחסות לשדה במבנה בגישה דרך מצביע היא ע"י

 
(*pcd).cost = 4.5;
 

צורה זו היא מסורבלת -  הסימן ® (צירוף הסימנים ">" ו "-") משמש כחלופה נוחה לשני האופרטורים "*" ו- "." . לדוגמא, במקום הביטוי הנ"ל ניתן לרשום

 
pcd->cost = 4.5;
 

דוגמא לקריאת כל נתוני התקליטור מהקלט:

 
gets(pcd->name);
gets(pcd->band);
gets(pcd->category);
printf("Enter cost:");
scanf("%f", &pcd->cost);
 

כמו בטיפוסים רגילים, קידום מצביע מבוצע עפ"י גודל המבנה. לדוגמא

 
CD  cd;
CD *pcd = &cd;
pcd++;
 

יבוצע קידום של המצביע בגודל של מבנה שלם, כלומר הביטוי pcd++ שקול לביטוי

 
pcd = pcd + sizeof(CD);
 

העברת מבנים כפרמטרים לפונקציות

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

ניתן להגדיר פונקציה המקבלת כפרמטר מבנה ו/או מחזירה אותו כפרמטר, לדוגמא:

 
void output_one_cd(CD cd);   /* output a given CD structure */
CD  input_one_cd();       /* initialize CD from input and return it */
 

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

פתרון עדיף הוא להעביר מצביעים למבנים. ממשק הפונקציות:

 
void output_one_cd(CD *pcd);        
void input_one_cd(CD *pcd);
 

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

 
void output_one_cd(CD *pcd)
{
          printf("\ncd %d:\n", pcd->number);
          printf("\tname=%s\n",pcd->name);
          printf("\tband=%s\n",pcd->band);
          printf("\tcategory=%s\n",pcd->category);
          printf("\tcost=%.2f\n",pcd->cost);
}
 
void input_one_cd(CD *pcd)
{
          char temp[80];
          puts("Enter CD name, band and category, separated by newlines:");
          gets(pcd->name);
          gets(pcd->band);
          gets(pcd->category);
                   
          printf("Enter cost:");
          scanf("%f", &pcd->cost);
          gets(temp); /* to read up to the end of line */
}
 

הפונקציות לקריאה ולכתיבה של מערך תקליטורים שלם קוראות לשתי הפונקציות הנ"ל:

 
void input_cd_array(CD arr[], int array_size)
{
          int i;
 
          for (i=0; i<array_size; i++)
          {
                   input_one_cd(&arr[i]);
                   arr[i].number = i+1;
          }
}
 
void output_cd_array(CD arr[], int array_size)
{
          int i;
 
          printf("\nArray elements:\n");
          for (i=0; i<array_size; i++)
                   output_one_cd(&arr[i]);
}
 

פעולות על מבנים במערך

סעיף זה מובאות מספר דוגמאות לפעולות על מבנים במערך:

 
int find_dearest_cd(CD arr[], int array_size);
 
הפונקציה מחפשת ומחזירה את אינדקס התקליטור היקר ביותר:
  - משתנה האינדקס מאותחל לאיבר הראשון, 0.
  - מתבצעת לולאה על איברי המערך, החל מאיבר 1.
  - בכל שלב בו נמצא תקליטור יקר מהקודם, מעודכנים הן הערך המקסימלי והן משתנה האינדקס.
  - הפונקציה מחזירה את האינדקס.
 
int exist(CD arr[], int array_size, int num);
 
  • פונקציה לחיפוש תקליטור עפ"י שמו:
 
int find_cd_by_name(CD arr[], int array_size, char name[]);
 
  • פונקציה למיון מערך המבנים: כדי למיין את איברי המערך נגדיר פונקציות בשם swap ו- sort שתבצענה מיון בשיטת מיון בועות (Bubble sort) שהכרנו בפרק 7, "מערכים":
 
void swap(CD *pcd1, CD *pcd2);
void sort_by_cost(CD arr[], int array_size);
 
המיון מתבצע בפונקציה sort עפ"י מחירי התקליטורים: הפונקציה משווה בין כל זוג תקליטורים סמוכים במערך.
אם יש צורך בהחלפה, הפונקציה מעבירה את כתובות המבנים במערך (מדוע?) לפונקציה swap, וזו מחליפה בין ערכיהם.

עיין/י בקוד הפונקציות וקוד התכנית המשתמשת שבעמ' 264-266.

union

union הוא סוג משתנה היכול להכיל בזמנים שונים נתון מגודל ומטיפוס שונה.

לדוגמא, אם נרצה לייצג בתכנית מספר טלפון פעם כמחרוזת ופעם כשלם ארוך (long) נוכל להגדירו כ- union :

 
union phone_num
{
          long lnum;
          char snum[30];
};
 

המהדר יקצה למשתנה phone_num זכרון לפי הנתון הגדול יותר:

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

הגדרת משתנה מסוג ה- union :

 
union phone_num p1;
 

וכעת אם רוצים להתייחס למשתנה כאל long :

 
p1.lnum = 48934562;
 

לעומת זאת אם נרצה לשמר את הקידומת - 04 - בצורה נוחה נייצג אותו כמחרוזת:

 
strcpy(p1.snum, "04-8934562");
 

כלומר ההתייחסות לסוג הייצוג היא ברישום של גישה לשדות במבנה.

כללים

  • בדומה ל- struct ניתן להגדיר את ה- union כטיפוס ע"י typedef.
  • המהדר דואג להקצות למשתנה מטיפוס union את הגודל המקסימלי האפשרי, כלומר את גודל השדה המקסימלי המוגדר ב- union.
בדוגמא הנ"ל, המהדר יקצה למשתנה מסוג phone_num 30 בתים, כגודל המחרוזת.
  • בדומה למבנים ניתן לגשת לייצוגים השונים של ה- union ע"י מצביע והאופרטור  "->".

סיכום

  • מבנה (structure) ב - C מאפשר הגדרת אוסף משתנים מטיפוסים שונים כיחידה אחת. הוא נקרא גם רשומה (record).
  • המבנה מוגדר ע"י המילה השמורה struct באחת משתי הצורות - הגדרת טיפוס או שימוש בשם תג. בהתאם לכך מבוצעת הגדרת המשתנים מהמבנה.
  • המשתנים במבנה נקראים שדות. שדה יכול להיות מטיפוס כלשהו, כולל משתנה מטיפוס מבנה אחר. הגישה לשדה דרך שם המבנה היא ע"י אופרטור הנקודה ".".
  • ניתן להציב מבנה אחד למבנה אחר ע"י אופרטור ההצבה אך יש לשים לב שאם המבנה מכיל מצביע כשדה, המצביע הוא המועתק ולא התוכן המוצבע.
  • ניתן להגדיר מצביע לטיפוס מבנה בדומה להגדרת מצביע לטיפוסים אחרים - תוך שימוש באופרטור "*". כדי להתייחס לשדות המבנה דרך מצביע משתמשים באופרטור "<- " .
  • ניתן גם להגדיר מערך מבנים. ההתייחסות לשדה באחד מאיברי המערך היא ע"י האופרטור [] בצירוף האופרטור ".".
  • union הוא סוג משתנה היכול להכיל בזמנים שונים נתון מגודל ומטיפוס שונה.

תרגילי סיכום

בצע/י את תרגילי הסיכום שבסוף פרק זה.



[ <<< הקודם ] [ תוכן עניינים ] [ הבא >>> ]