430 likes | 437 Views
תרגול 3. מבנים טענות נכונות (assert). לי-טל משיח litalma@cs.technion.ac.il נערך ע''י ודים אייזנברג. מבנים - Structures. struct < struct name> { <type name1> <field1>; <type name2> <field2>; . . <type namen > < fieldn >; };
E N D
תרגול 3 מבנים טענות נכונות (assert) לי-טל משיח litalma@cs.technion.ac.il נערך ע''י ודים אייזנברג
מבנים - Structures struct <struct name> { <type name1> <field1>; <type name2> <field2>; . . <type namen> <fieldn>; }; • אבסטרקציה על אוסף נתונים עם שמות שונים וטיפוסים שונים • דרך ליצור טיפוסים חדשים ע"י הרכבה מטיפוסים קיימים • הבדלים ממערך: • במערך כל האיברים מאותו טיפוס • במערך אין שמות לאיברים - מתייחסים לאיברים על פי מספרם הסידורי
מבנים - Structures • הגדרת משתנה: struct <struct name> <variable name>; • דוגמא - מבנה המגדיר טפוס תאריך. חודשים שמורים בפורמט של שלושה תווים. structDate_t { int day; char month[4]; int year; }; • הגדרת משתנה מסוג date: structDate_t d; Why 4 chars ?
תכנות בלי מבנים intstart_day, start_year; intend_day, end_year; char start_month[4], end_month[4]; intcompare_dates(int day1, char* month1, int year1, int day2, char* month2, int year2); ... compare_dates(start_day, start_month, start_year, end_day, end_month, end_year);
תכנות עם מבנים structDate_t start; structDate_t end; intcompare_dates(structDate_t date1, structDate_t date2); ... compare_dates(start,end); • הקוד קצר יותר • הקוד קריא יותר • מספר הפרמטרים לפונקציות קטן יותר • פחות סיכוי להתבלבל בין הפרמטרים
מבנים - Structures • פנייה לשדה בתוך משתנה מסוג struct נעשית ע"י: <variable name>.<field name>; • כאשר נתון מצביע לstruct-, ניתן לגשת לשדה בצורה ישירה: (*<pointer name>).<field name> ; • מכיוון שדרך זו מסורבלת, קיימת דרך נוספת ופשוטה יותר לגשת לשדה: <pointer name> -> <field name> ; • דוגמא: structDate_t* p; p=&d; p->day=12; /* or (*p).day=12; */
מבנים - Structures • תוכנית דוגמא: int main() { structDate_t date , *pdate; date.day = 21; strcpy(date.month , "NOV"); pdate = &date ; pdate->year = 1971 ; printf ("The year is %d\n",date.year); }
שם נוסף לטיפוס • בדוגמת התאריך נוצר טיפוס בשם structDate_t • בכדי להימנע מהצורך ברישום struct כל פעם, ניתן לתת לטיפוס הזה שם נוסף המורכב ממלה אחת בלבד, למשל Date • דרך טבעית יותר להתייחס לטיפוס, באותה צורה כמו לטיפוסים הפרימיטיביים של השפה: inti; char c; Date date; • ב-C נותנים לטיפוס שם נוסף ע''יtypedef Date הוא שם נוסף ל-structDate_t
שם נוסף לטיפוס באמצעות typedef typedef <old type name> <new type name> ; • הפקודה נותנת שם נוסף לשם קיים של טיפוס • אחרי הפקודה שני השמות מתייחסים לאותו טיפוס • לדוגמא: typedefint ID; typedefint * Element; ... ID i; Element element; זהה ל-: inti; int * element;
שם נוסף למבנה באמצעות typedef typedefstructDate_t Date ; • ניתן גם להגדיר טיפוס של מצביע למבנה: typedefstructDate_t * pDate; או typedef Date* pDate; • ניתן לבצע את הגדרת המבנה ונתינת שם נוסף יחד: typedefstructDate_t { int day; char month[4]; int year; } Date; typedef Date* pDate;
שימוש במבנה בתוך מבנה • ניתן להגדיר בתוך מבנה שדה מטיפוס מבנה אחר typedefstructDate_t { int day; char month[4]; int year; } Date ; typedefstructPerson_t { char* name; int height; Date birth; } Person ; • גישה לשדה של המבנה הפנימי: Person p ; Person* pp=&p; p.birth.year = 1994 pp->birth.year = 1994; • מדוע לא pp->birth->year?
שימוש במצביע למבנה בתוך מבנה • במקום להגדיר את birth כשדה מטיפוסDate ניתן היה להגדירו כמצביע למבנה מטיפוס Date typedefstructPerson_t { char* name; int height; Date* birth; } Person ; • גישה לשדה של המבנה הפנימי: Person p ; Person* pp=&p; p.birth->year = 1994 pp->birth->year = 1994; • שמו לב: כאשר הקצינו את pPersonלא הקצנו כחלק ממנו מקום למבנה מטיפוס Date אלא למצביע אליו • לכן קטע הקוד הנתון אינו תקין ! • כיצד ניתן לתקנו?
מבנים המתייחסים לעצמם • בהגדרת מבנה ניתן להשתמש במצביעים מטיפוס מצביע למבנה עצמו: typedefstructDate_t { int day; char month[4]; int year; structDate_t* next; } Date ; • הדבר מאפשר לשמור את כתובתו של מבנה אחר מאותו טיפוס בתוך המבנה: int main() { Date dt1, dt2, *pdt ; dt1.day = 31 ; dt1.year = 1992 ; strcpy(dt1.month,”DEC”); dt2.day = 1 ; dt2.year = 1993 ; strcpy(dt2.month,”JAN”); pdt = &dt2; dt1.next = &dt2; printf ("Year in dt1: %d\n Year in dt2: %d\n ",dt1.year, (dt1.next)->year); }
dt1 31 DEC 1992 dt2 1 JAN 1993 ? תמונת הזיכרון לפני ביצוע פקודות ההדפסה • ניתן לשנות את ערכו של השדה year של המשתנה dt2 בכמה אופנים: dt2.year = 1994; pdtyear = 1994; (dt1.next) year = 1994; • נוצרה כאן רשימה מקושרת בת שני איברים • מה צריך להיות ערכו של השדה next ב-dt כדי שנדע שאין איבר נוסף? pdt
רשימה מקושרת של מבנים המוקצים באופן דינמי • הבעיה: נרצה לקרוא מהקלט הסטנדרטי מספר לא ידוע של תאריכים, ולהדפיסם בסדר הפוך לסדר הקריאה • פתרון: רשימה מקושרת של תאריכים
רשימה מקושרת של מבנים המוקצים באופן דינמי האם הבדיקה מספיקה ? int main() { Date *top = NULL, *tmp = NULL ; int day, year ; char month[4]; while (scanf("%d %s %d", &day ,month , &year) != EOF){ tmp = (Date*) malloc(sizeof(Date)); if (tmp == NULL) { free_date_list(top); exit(0); } tmp->day = day; tmp->year = year; strcpy(tmp->month ,month); tmp->next = top ; top = tmp; } print_date_list(top); return 0; } מה חסר בסוף הפונקציה ?
רשימה מקושרת של מבנים המוקצים באופן דינמי void free_date_list(Date *top){ while (top != NULL) { Date * tmp=top; top=top->next; free(tmp); } }
רשימה מקושרת של מבנים המוקצים באופן דינמי void print_date_list(Date *top){ Date *tmp = top; while (tmp != NULL) { printf("%d %s %d\n", tmp->day, tmp->mon, tmp->year); tmp = tmp->next ; } }
שיפורים לקוד הקודם int main() { Date *top = NULL; Date date; while (scanf("%d %s %d", &date.day ,date.month , &date.year) == 3){ Date*tmp = (Date*) malloc(sizeof(Date)); if (tmp == NULL) { free_date_list(top); exit(0); } *tmp = date; tmp->next = top ; top = tmp; } print_date_list(top); free_date_list(top); return 0; } int main() { Date *top = NULL, *tmp = NULL ; intday,year ; char month[4]; while (scanf("%d %s %d", &day ,month , &year) != EOF){ tmp = (Date*) malloc(sizeof(Date)); if (tmp == NULL) { free_date_list(top); exit(0); } tmp->day = day ; tmp->year = year ; strcpy(tmp->month ,month); tmp->next = top ; top = tmp; } print_date_list(top); return 0; }
דוגמה נוספת לשימוש במבנים ומצביעים • הבעיה: תוכנית שמדפיסה אירועים היסטוריים שקרו בתאריך מסוים. • התוכנית • תקרא קובץ שמכיל אירועים היסטוריים • תקלוט תאריכים • עבור כל תאריך תדפיס אילו אירועים היסטוריים קרו באותו התאריך דוגמא לקובץ האירועים events: 1 JAN 404 Last gladiator competition 6 MAY 1889 Eiffel tower opens 21 NOV 1794 Honolulu harbor discovered 1 JAN 1852 First US public bath opens 2 MAR 1969 First takeoff of the Concorde 6 MAY 1915 Orson Welles is born 6 MAY 1626 Manhattan purchased for 1000$ 2 MAR 1969 First landing of the Concorde
דוגמה נוספת לשימוש במבנים ומצביעים • הפעלת התוכנית והפלט המתקבל: > important_dates events enter date: 2 MAR 1969 First takeoff of the concorde First landing of the concorde enter date: 23 NOV 1999 Nothing special
next next next next description description description description Historical Date date events list next פתרון אפשרי historicalDateList • נשמור רשימה מקושרת של תאריכים • לכל תאריך נשמור רשימה של אירועים שקרו בתאריך זה event date events list next date events list next
מבנים • הגדרות #define MAX_LINE_LENGTH 100 • מבנה תאריך typedefstructDate_t { int day; char month[4]; int year; } Date;
מבנים • מבנה אירוע typedef struct Event_t { char* description ; struct Event_t *next; } Event; שימו לב לשמות המלאים של השדות. description ברור יותר מאשר d,des,desc,dsc לא צריך להתאמץ להמציא/להבין קיצורים : dscrptn,descript
מבנים • מבנה תאריך היסטורי typedef struct HistoricalDate_t { Date date ; Event *eventList; struct HistoricalDate_t *next ; } HistoricalDate;
פונקציות עזר – טיפול בקלט • קריאת תאריך מקובץ קלט נתון (הקצאת התאריך באחריות הקורא לפונקציה !) intreadDate(FILE* inputFile, Date* date) { if (inputFile == NULL || date == NULL) { return 0; } if (fscanf (inputFile, “%d %s %d ”, &(date->day),date->month, &(date->year))==3) { return 1; } return 0; }
פונקציות עזר – טיפול בקלט • קריאת שורת אירוע היסטורי מקובץ קלט נתון. (הקצאת התאריך, והמחרוזת לתיאור האירוע באחריות הקורא לפונקציה !) intreadEvent(FILE* inputFile, Date* date, char* buffer) { if (inputFile == NULL || date == NULL || buffer == NULL) { return 0 ; } if (readDate(inputFile,date) ==0) { return 0; } if(fgets(buffer, MAX_LINE_LENGTH, inputFile)==NULL) { return 0; } return 1; }
פונקציות עזר – הקצאת צומת אירוע היסטורי Event* allocateEvent(char* description) { Event *newEvent; if (description == NULL) return NULL; newEvent=(Event*)malloc(sizeof(Event)); if (newEvent == NULL) return NULL; newEvent->description=(char*)malloc(strlen(description)+1); if (newEvent->description == NULL) { free (newEvent) ; return NULL; } strcpy(newEvent->description, description); newEvent->next = NULL; returnnewEvent; }
פונקציות עזר – הקצאת צומת תאריך היסטורי HistoricalDate* allocateHistoricalDate(Date date) { HistoricalDate *newHistoricalDate = (HistoricalDate*) malloc(sizeof(HistoricalDate)); if (newHistoricalDate == NULL){ return NULL; } newHistoricalDate ->date = date; newHistoricalDate ->eventList = NULL; newHistoricalDate ->next = NULL; returnnewHistoricalDate; }
פונקציות עזר – מציאת תאריך היסטורי ברשימת תאריכים HistoricalDate* find(HistoricalDate* first, Date date){ if (first == NULL) { return NULL; } if (first->date.day == date.day && strcmp(first->date.month,date.month) == 0 && first->date.year == date.year) { return first; } return find(first->next,date); }
פונקציות עזר – הדפסת רשימת אירועים היסטוריים void printEvents(Event* event) { if (event == NULL ) { return; } printf (“%s\n”, event->description); printEvents(event->next); }
פונקציות עזר – בניית מבנה הנתונים HistoricalDate* readEvents(FILE* inputFIle) { char buffer[MAX_LINE_LENGTH]; Date date ; HistoricalDate *firstHistoricalDate =NULL , *currentHistoricalDate = NULL; Event *event = NULL; while (readEvent(inputFIle,&date,buffer)) { currentHistoricalDate = find(firstHistoricalDate ,date) ; if (currentHistoricalDate == NULL) { /* in case the date doesn't exist – add this date to be the first in the list */ currentHistoricalDate = allocateHistoricalDate(date); if (currentHistoricalDate == NULL) { return NULL ; } currentHistoricalDate->next = firstHistoricalDate ; firstHistoricalDate = currentHistoricalDate ; } event = allocateEvent(buffer); if (event == NULL) { return NULL; } event->next = currentHistoricalDate->eventList; currentHistoricalDate->eventList = event; }return first ; }
התוכנית הראשית int main(intargc, char* argv[]) { FILE* inputFile = NULL; HistoricalDate* historicalDateList= NULL , *historicalDate = NULL; Date date; if (argc != 2) return 1 ; if ((inputFIle = fopen(argv[1],”r”))==NULL) return 2 ; historicalDateList = readEvents(inputFile); /*creating the list*/ fclose(inputFile); if (historicalDateList == NULL) return 3 ; while (readDate(stdin,&date)) { historicalDate = find(historicalDateList ,date) ; if (historicalDate == NULL) printf (“Nothing special\n”); else printEvents(historicalDate->eventList); } return 0; }
השיפורים הנדרשים • שחרור הזיכרון בסוף התוכנית. • שחרור הזיכרון שכבר הוקצה במידה וקרה כשלון במהלך ריצת הפונקציה readEvents. • הודעות שגיאה ברורות למשתמש. • החלפת מספרי קסם שמציינים ערכי שגיאה בערכי define או enum
טענות נכונות - assert • כלי תכנות מועיל למציאת שגיאות בזמן פיתוח תוכנה • נועדו לביטוי טענות לגבי התוכנה שאמורות להיות נכונות בהיעדר שגיאות בתוכנה • ב-C מתבצע ע''י קריאה למאקרו assertשמקבל ביטוי משוערך ל-int • מוגדר ב-assert.h • נבדקות אוטומטית בזמן ריצת התוכנה
טענות נכונות - assert prog.c: int main(intargc, char *argv[]) { ... if(argc > 3) { ... } else if (argc < 2) { ... } else { /* if we are here argc is 2 */ ... } ... assert(argc == 2);
טענות נכונות - assert > gccprog.c -o prog > prog > prog a > prog a b c > prog a b prog: prog.c:12: main: Assertion `argc == 2' failed. Abort >
טענות נכונות - assert • כשהטענה לא מתקיימת – התוכנית נעצרת ומודפסת הודעת שגיאה שמפרטת את • הטענה שלא התקיימה • מיקום הטענה : • שם הקובץ • מספר שורה • שם הפונקציה
טענות נכונות - assert • ניתן לבטל את מנגנון assertsע''יקימפול עם דגל-DNDEBUG : > gcc –DNDEBUG prog.c -o prog > prog a b > • נהוג להפעיל את הדגל הזה בגרסת ייצור (production version) • asserts מופעלים רק בגרסת פיתוח (development version) • בבדיקה האוטומטית של תרגילי הבית התוכניות ייקומפלו עם הדגל הזה • מומלץ להשתמש ב-asserts בזמן פיתוח של תרגילי הבית
טענות נכונות – שימוש לא נכון • שימוש ב-assert כדי לזהות שגיאות אפשריות של משתמש או של מערכת הפעלה : int main(int argc, char *argv[]) { assert(argc == 2); /* the program must be called with only one parameter */ ... if(argc != 2) { fprintf(stderr, “the program must be called with” ); fprintf(stderr,” only one parameter\n”); exit(0); }
טענות נכונות – שימוש לא נכון FILE* fd = fopen(“input.txt" , "r"); assert(fd != NULL); if(fd == NULL) { error(“Failed to open input.txt!\n”); }
טענות נכונות – שימוש לא נכון • הכנסת חישובים, הנחוצים לביצוע התוכנית לתוך הassert- assert((result = calculate()) >= MIN_RESULT); /* working with result */ ... • ב-production version הassert- לא יופעל והחישוב הנחוץ לא יתבצע !
טענות נכונות – שימוש נכון result = calculate(); assert(result >= MIN_RESULT); /* working with result */ ...