|
||||||||||||||||||||
קורס:"שפת C"
מתוך הספר: C - מדריך מקצועי
הגדרת מבנה והתייחסות לשדותיוהכרנו עד עתה משתנים מטיפוס נתונים יחיד: שלם, ממשי, תו או מערך מסוג מסוים. לדוגמא: int x; float y; char str[20]; מבנה (structure) ב - C מאפשר הגדרת אוסף משתנים מטיפוסים שונים כיחידה אחת. מבנה נקרא לפעמים גם רשומה (record). לדוגמא, נניח שאנו רוצים לכתוב תוכנה לניהול אוסף תקליטורי מוזיקה. לכל תקליט נרצה לשמור את המשתנים הבאים:
מבנה ב- C מוגדר ע"י המילה השמורה struct. קיימות שתי דרכים להגדיר טיפוס מבנה:
דרך א': הגדרת מבנה כטיפוסבשיטה זו, הכרזה על מבנה נעשית תוך שימוש בהוראה 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 סיבות:
אנו נשתמש בדרך א' מעתה ואילך, למעט מקרה מסוים של התייחסות עצמית, שנעסוק בו ברשימות מקושרות בפרק 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);
int exist(CD arr[], int array_size, int num);
int find_cd_by_name(CD arr[], int array_size, char name[]);
void swap(CD *pcd1, CD *pcd2); void sort_by_cost(CD arr[], int array_size);
עיין/י בקוד הפונקציות וקוד התכנית המשתמשת שבעמ' 264-266. unionunion הוא סוג משתנה היכול להכיל בזמנים שונים נתון מגודל ומטיפוס שונה. לדוגמא, אם נרצה לייצג בתכנית מספר טלפון פעם כמחרוזת ופעם כשלם ארוך (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"); כלומר ההתייחסות לסוג הייצוג היא ברישום של גישה לשדות במבנה. כללים
סיכום
תרגילי סיכוםבצע/י את תרגילי הסיכום שבסוף פרק זה.
|