Download presentation
1
לי-טל משיח litalma@cs.technion.ac.il
תרגול מס' 4 פתוח מודולרי ב-C makefile ADT-Abstract Data Type דוגמא פשוטה לADT- (תאריך( מצביעים לפונקציות לי-טל משיח
2
Cפיתוח מודולרי ב- הבעיה: הפתרון – תכנות מודולרי
כתיבת תוכניות גדולות הינה קשה חלוקה לפונקיות איננה מספיקה ישנה קבוצה של פונקציות הקשורות זו לזו נירצה להפריד קבוצה זו מיתר התוכנית הדבר יאפשר שימוש חוזר בהן הפתרון – תכנות מודולרי אפשרות לכתוב ולבדוק כל מודול לחוד אפשרות לעשות שימוש חוזר במודולים
3
עבודה עם מודולי תוכנה כל מודול של התוכנה יורכב משני חלקים:
הממשק interface, מכונה גם ה-header של המודול יכיל את ההצהרות על הפונקציות אותן מממש המודול יכיל הגדרות של קבועים והגדרות של טיפוסים אשר מודולים אחרים הנעזרים במודול זה צריכים להכיר (למשל ערכי שגיאות אפשריים של הפונקציות) המימוש implementation, מכונה גם ה-source של המודול יכיל את גוף הפונקציות אשר הוצהרו ב-header יכול להכיל פונקציות פנימיות למודול אשר לא נועדו לשימוש מחוץ למודול משתנים גלובלים שרק לפונקציות של המודול יש גישה אליהם וכד' הפונקציות המוגדרות ב header הינן השרותים אותם מספק המודול לשאר התוכנית
4
דוגמא: מודול תאריך בתוכנית שנכתוב מתבצעות פעולות הקשורות לתאריכים
בכדי שנוכל לנצל את הפונקציות המטפלות בתאריכים במספר תוכניות נממשן במודול נפרד בשם date
5
date.h: headerקובץ ה- /* date.h - date module header file
This module defines a date type and implements some date manipulation functions. */ #include <stdio.h> typedef struct Date_t { int day; char month[4]; int year; } Date; typedef enum {DateSuccess,DateFail,DateFailRead, DateBadArg} DateResult; /* reads a date from an open stream fd. Expects the date dt points to allocated Date. */ DateResult dateRead (FILE* ifd, Date* dt); /* writes the date to the output stream ofd. */ DateResult datePrint (FILE* ofd, Date dt); /* returns the date’s month number. –1 if fails */ DateResult dateMonth(Date dt, int* month) /* returns the number of days between dates. -1 if fails. */ DateResult dateDiff(Date a, Date b, int* diff)
6
date.c :sourceקובץ ה- /* date.c - date module implementation */
#include <string.h> #include "date.h" static const char* month[]= {"JAN", "FEB", "MAR", “APR”, "MAY", "JUN", "JUL", "AUG", “SEP", "OCT", "NOV", "DEC"}; Result dateRead(FILE* fd, Date dt) { int month =0; if (fd == NULL || dt == NULL) return DateBadArg; if (fscanf (fd, "%d %s %d", &(dt->day), dt->month, &(dt->year)) != 3) return DateFailRead; if (dt->day < 1 || dt->day > 31 || dateMonth(dt, &month) != DateSuccess || dt->year < 0) return DateFail; return DateSuccess; }
7
date.c :sourceקובץ ה- DateResult datePrint(FILE* fd, Date dt) {
if (fd == NULL || dt == NULL) return DateBadArg; if (fprintf (fd, "%d %s %d ", dt->day, dt->month, dt->year) < 0) return DateFail; return DateSuccess; } DateResult dateMonth(Date dt, int* month) int j = 0; if (dt == NULL) for (j = 0; j < 12; j++) if (strcmp(dt->month, months[j]) == 0){ *month = j+1;
8
date.c :sourceקובץ ה- DateResult dateDiff(Date a, Date b, int* diff) {
int da, db, res; if ((res = dateMonth(a,&da)) != DateSuccess) return res; if ((res = dateMonth(b,&db)) != DateSuccess) da = a->day + 30 * da * a->year; db = b->day + 30 * db * b->year; *diff = ((da - db < 0) ? -(da - db) : da - db); return DateSuccess; }
9
proc.cקובץ האפליקציה: #include <stdio.h> #include "date.h"
int main() { Date d1,d2; int month, diff; if (dateRead(stdin,&d1) == DateFail || dateRead(stdin,&d2) == DateFail) return 1; printf ("The month of "); datePrint(stdout,d1); dateMonth(d1, &month); printf("is %d.\n", month); printf ("The difference between "); printf(" and "); datePrint(stdout,d2); dateDiff(d1, d2,&diff); printf("is %d days.\n", diff); return 0; }
10
תמיכת השפה בכתיבת מודולים
כתיבת תוכניות במספר מודולים הנה טכניקה מומלצת, הקומפייר של שפת C תומך באפשרות זו תהליך יצירת תוכנית מורכב משני שלבים: הידור (compilation) הקומפיילר מהדר כל קובץ source (.c) בנפרד הקומפיילר אינו צריך לראות את המימוש של כל הפונקציות אשר אותו מודול נעזר בהן, אלא רק את הצהרותיהן לשם כך קיימת פקודת ה include. פקודה זו מוסיפה לכל קובץ source את ההגדרות הכתובות בקובץ ה-header אשר בוצע לו include תוצאת שלב זה הנה object file. זהו קובץ אשר מכיל את קובץ ה source מקומפל חלקית. כל הקוד אשר היה כתוב בקובץ מקומפל, אך קריאות לפונקציות אשר מימושן לא נמצא באותו קובץ עדיין לא מבוצעות ונשמרות לשלב הבא קישור (linking) של כל קבצי ה- object ה linker מחבר את כל קבצי ה object לתוכנית אחת. אם בקובץ object אחד היתה קריאה לפונקציה הנמצאת בקובץ object אחר, ה linker יצור קריאה זו.
11
תמיכת השפה בכתיבת מודולים
כאשר מבצעים ניתן להפריד את השלבים: gcc prog.c date.c מבוצעים שני השלבים ביחד gcc –c prog.c date.c יוצר את קבצי ה object gcc prog.o date.o מקשר את קבצי ה object
12
Compiling and Linking
13
Header שימוש בקבצי קבצי כותרת משמשים להצהרות של קבועים, טיפוסי משתנים המשותפים בכל הקבצים, משתנים ופונק' כל קובץ שצריך את ההגדרות הנ"ל יכלול את השורה: #include "defs.h" מכיוון שהקובץ עשוי להיכלל במספר קבצים, יש שימוש ב-ifndef#, אשר בודק האם קבוע מסוים הוגדר רק בפעם הראשונה בה יתבצע #include "defs.h" יכללו ההגדרות וההצהרות בפועל /* file : defs.h */ #ifndef DEFS_H #define DEFS_H #define TRUE 1 #define FALSE 0 typedef struct { char name[20]; int salary; int id; } employee; . #endif
14
פיתוח יעיל של תוכנה ממספר קבצים
הבעיה בשמוש במספר קבצים היא שעדכון מודול מסויים עשוי לגרור צורך לעדכן כל תוכנית שעושה שימוש במודול הזה. לכן, יש לחלק הגדרה של מודול לשני חלקים: interface - הפעולות (פונקציות) המסופקות ע"י המודול implementation - אופן מימוש הפעולות (פונקציות) אם השימוש במודול נעשה רק ע"י שימוש בפעולות המותרות, והשינוי נעשה רק באופן מימוש הפונקציות, אין צורך לשנות את הקוד של תוכנית המשתמשת במודול, אלא רק לקמפל את המודול מחדש, ולקשר אותו שוב עם התוכנית המשתמשת שיטת העבודה הידור (compilation) של כל קובץ לחוד קישור (linking) של כל קבצי ה- object
15
פיתוח יעיל של תוכנה ממספר קבצים
דוגמא: תוכנית המורכבת מהקבצים הבאים: calc.c control.c main_prog.c התהליך יהיה כזה: בכל פעם שמשנים קובץ מסוים, מספיק להדר שוב רק אותו ולבצע קישור מחדש. לדוגמא, אם שונה control.c, מספיק לבצע: gcc -c calc.c gcc -c control.c gcc -c main_prog.c gcc calc.o control.o main_prog.o -o prog gcc -c control.c gcc calc.o control.o main_prog.o -o prog
16
פיתוח יעיל של תוכנה ממספר קבצים
ניתן להפוך את התהליך לאוטומטי ע"י שימוש בתוכנה make, המקבלת קובץ פקודות (Makefile) שאומר לה איך לקמפל כל קובץ מה התלויות הקיימות בין הקבצים קובץ ה Makefile המתאים לדוגמא הנ"ל הוא: כאשר משנים קובץ, מריצים make ומתבצעים רק הצעדים הדרושים prog: main_prog.o calc.o control.o gcc calc.o control.o main_prog.o -o prog calc.o: calc.c gcc -c calc.c control.o: control.c gcc -c control.c main_prog.o: main_prog.c gcc -c main_prog.c
17
make מאפשר הידור וקישור אוטומטיים של קבצי תוכנה
לא מהדר קבצים שאין צורך בהידורם מאפשר הידור תוכניות שכתובות בשפות שונות נוח לשינוי (העברת אופציות למהדר) מאפשר עבודה נוחה עם פרויקטים גדולים מתעד קשר בין קבצים קיים בסביבות עבודה מודרניות
18
make לדוגמא, פרוייקט הניראה כך:
19
make קובץ ה makefile המתאים לפרויקט זה: הפעלת make על הפרויקט
prog:a.o b.o c.o gcc a.o b.o c.o -o prog a.o:a.c a.h b.h gcc -c a.c b.o:b.c b.h gcc -c b.c c.o:c.c c.h b.h gcc -c c.c > make > make prog
20
makeשימוש ב- make [ -f filename ] [ target ]
1. אם לא משתמשים באופציה -f הוא מחפש קובץ בשם makefile במדריך הנוכחי. 2. אם לא נמצא הקובץ הנ"ל הוא מחפש קובץ בשם Makefile במדריך הנוכחי. 3. אם משתמשים באופציה -f הוא מחפש את הקובץ filename במדריך הנוכחי. אם target מופיע בפקודה make, מבצע את רשימת הפקודות המופיעות ב target בקובץ התלויות. אחרת מבצע את ה target הראשון אופן פעולת make make בודק את התלויות ב target אותו רוצים לבצע. אם יש קובץ ברשימת התלויות שהוא חדש יותר מקובץ ה- target יש לבצע את הפקודה של ה-target על מנת לעדכנו בדיקת התלויות נעשית באופן רקורסיבי, כך שיבוצעו כל תת-הפקודות בסדר הנכון, כדי ליצור target מעודכן
21
makefileמבנה קובץ קובץ makefile מורכב ממספר entry's אחד אחרי השני.
המבנה של entry בקובץ makefile: <targets>: <files> <TAB> <compilation command for targets> כל מקום בשורה המתחילה ב # נחשבת כהערה עד סופה ו- make מתעלם ממנה. הערות: אם לא נשים TAB בתחילת שורת הפקודה, היא לא תתבצע. ניתן לשבור שורה ארוכה לכמה שורות, אך יש להוסיף / בסוף כל שורה פרט לאחרונה (ועדיין יש לשים TAB בתחילת כל שורה). אין לשים רווחים לא בתחילת שורה ולא בסופה סיום שורות ב-Enter
22
הגדרת מקרו אם יש מחרוזת שחוזרת פעמים רבות או שהיא עשויה להשתנות
בעתיד אז ניתן להגדירה כמקרו ולגשת אליה באמצעות $ - הוא ה target הנוכחי $* - הוא ה target הנוכחי ללא סיומת CC = gcc OBJS = a.o b.o c.o EXEC = prog DEBUG_FLAG = # now empty, assign -g for debug COMP_FLAG = -c CLEAN = clean $(EXEC) : $(OBJS) $(CC) $(DEBUG_FLAG) $(OBJS) -o a.o : a.c a.h b.h $(CC) $(DEBUG_FLAG) $(COMP_FLAG) $*.c b.o : b.c b.h c.o : c.c c.h b.h clean: rm -f $(OBJS) $(EXEC) > make clean rm -f a.o b.o c.o prog > make gcc -c a.c gcc -c b.c gcc -c c.c gcc a.o b.o c.o -o prog
23
באופן אוטומטיmakefileיצירת
אפשר למצוא את כל הקשרים בין קבצים ע"י שימוש בפקודה הבאה: gcc -MM c-files למשל: ניתן לשמור פלט זה בקובץ ולהשתמש בו כשלד ל makefile הדרוש > gcc -MM *.c a.o : a.c a.h b.h b.o : b.c b.h c.o : c.c c.h b.h
24
ADT Abstract Data Type
25
ADT מבנה נתונים מופשט ADT הינו מודול מסוג מיוחד
מגדירים טפוס של מצביע למבנה שגודלו ושדותיו אינם ידועים נותנים למשתמש קבוצת פונקציות אשר רק בעזרתן ניתן לטפל בטפוס כיוון ש ADT הנו מודול, הכללים החלים על כתיבה נכונה של מודול נכונים גם כאן ניתן לחשוב על ADT כעל קופסא שחורה המספקת שירות מסוים. מקבל השרות אינו יודע (ואינו מתעניין) כיצד השירות ניתן
26
תאריךADTדוגמא: במודול התאריך שניתן, ישנה בעיה. כיוון שהמבנה חשוף המשתמש יכול לשנות את השדות שלא דרך פונקציות הממשק. באופן זה, יתכן ויגרם שהמבנה יהיה במצב לא תקין – לדוגמא ששדה היום בתאריך יהיה 45 נראה לכן מימוש של תאריך כ ADT
27
Date.hקובץ הממשק - /* date.h - date ADT module header file
This module defines a pointer to date type and implements some date manipulation functions.*/ #ifndef DATE_H #define DATE_H #include <stdio.h> /* for FILE definition */ typedef struct Date_t *Date ; typedef enum {DateSuccess, DateFail, DateFailRead, DateBadArg} DateResult; /* allocates a new date. returns NULL if fails.*/ Date dateCreate(int day, int month, int year); /* reads a date from an open stream ifd */ DateResult dateRead (FILE* ifd, Date dt); /* writes the date to the output stream ofd */ DateResult datePrint (FILE* ofd, Date dt); /* returns the date’s month number */ DateResult dateMonth(Date dt, int* month); /* returns the number of days between dates*/ DateResult dateDiff(Date a, Date b, int* diff); /* deallocates a new date. returns NULL if fails.*/ void dateDestroy (Date dt); #endif
28
Date.cקובץ המימוש - /* date.c - date ADT module implementation */
#include <string.h> #include "date.h" struct Date_t { int day; char[4] month; int year; }; static const char* monthes[]= {“JAN”, “FEB”, ”MAR”, “APR”, ”MAY”, “JUN”, “JUL”, “AUG”, “SEP”, “OCT”, “NOV”, “DEC”};
29
Date.cקובץ המימוש - Date dateCreate(int day, int month, int year) {
Date dt = NULL; if (day < 1 || day > 31 || month < 1 || month > 12) return NULL ; dt = (Date) malloc(sizeof(struct Date_t)); if (dt == NULL) return NULL; dt->day = day ; strcpy (dt->month,monthes[month-1]); dt->year = year ; return dt ; }
30
Date.cקובץ המימוש - DateResult dateRead(FILE* fd, Date dt) {
int month =0; if (fd == NULL || dt == NULL) return DateBadArg; if (fscanf (fd, "%d %s %d", &(dt->day), dt->month, &(dt->year)) != 3) return DateFailRead; if (dt->day < 1 || dt->day > 31 || dateMonth(dt, &month) != DateSuccess || dt->year < 0) return DateFail; return DateSuccess; } DateResult datePrint(FILE* fd, Date dt) if (fprintf (fd, "%d %s %d ", dt->day, dt->month, dt->year) < 0)
31
Date.cקובץ המימוש - DateResult dateMonth(Date dt, int* month) {
int j = 0; if (dt == NULL) return DateBadArg; for (j = 0; j < 12; j++) if (strcmp(dt->month, months[j]) == 0){ *month = j+1; return DateSuccess; } return DateFail;
32
Date.cקובץ המימוש - DateResult dateDiff(Date a, Date b, int* diff) {
int da, db, res; if ((res = dateMonth(a,&da)) != DateSuccess) return res; if ((res = dateMonth(b,&db)) != DateSuccess) da = a->day + 30 * da * a->year; db = b->day + 30 * db * b->year; *diff = ((da - db < 0) ? -(da - db) : da - db); return DateSuccess; } void dateDestroy(Date dt) if (dt == NULL) return; free(dt);
33
תוכנית הנעזרת במודול התאריך
#include <stdio.h> #include "date.h" int main() { Date d1,d2; int month, diff; if ((d1 = dateCreate(1,1,1)) == NULL)||(d2 = dateCreate(1,1,1)) == NULL)) return 1; if (dateRead(stdin,d1) == Fail || dateRead(stdin,d2) == Fail) printf ("The month of "); datePrint(stdout, d1); dateMonth(d1, &month); printf("is %d.\n", month); printf("The difference between "); printf("and "); datePrint(stdout, d2); dateDiff(d1, d2,&diff); printf("is %d days.\n", diff); dateDestroy(d1); dateDestroy(d2); return 0; }
34
מצביעים לפונקציות כאשר אנו קוראים לפונקציה התוכנית מבצעת את הפעולות הבאות: הארגומנטים המועברים לפונקציה מושמים במקום מסויים (מחסנית הקריאות) ע”פ הסדר בו מופיעים בהגדרת הפונקציה קופצים אל תחילת הפונקציה ומתחילים לבצע בסיום הפונקציה הערך המוחזר מהפונקציה מושם במקום מיוחד (מחסנית הקריאות) הערך המוחזר מועבר למשתנה שאמור להכיל את תוצאת הפונקציה מסקנה אם קיימות שתי פונקציות המקבלות את אותם פרמטרים בדיוק, ומחזירות את אותו טיפוס , הדבר היחיד המשתנה בין הקריאות לפונקציות הנו הכתובת של תחילת הפונקציה. לתוכנית לא אכפת לאיזו מהפונקציות היא קוראת כיצד ניתן לנצל זאת ? ניתן להגדיר מצביעים לפונקציות אשר יכולים להכיל את כתובת התחלה של פונקציות הזהות מבחינת הצהרתן נוכל לקרוא לפונקציות שונות ע”י מצביע זה
35
דוגמא פשוטה: פונקציות השוואה בין מספרים, מה ההבדל בינהן ?
typedef enum { Left, Eq, Right } Relation ; Relation bigger(int a, int b) { if (a > b) return Left ; if (a==b) return Eq ; return Right ; } Relation bigger2(int a, int b) { if (a<0) a = -a; if (b<0) b = -b; if (a > b) return Left ; if (a==b) return Eq ; return Right ; }
36
דוגמא פשוטה: int main() {
/* pfunc is a pointer to a function with signature Relation f(int, int) */ Relation (*pfunc)(int, int) ; int a = -5 , b = 3 ; Relation c ; c = getchar(); if ( c == '1') pfunc = bigger; else pfunc = bigger2; c = pfunc(a,b); switch (c) { case Left: printf ("%d\n",a); break; case Eq: printf ("%d\n",a); case Right: printf ("%d\n",b); default: printf ("Error\n"); return 0; } מה ההבדל? Relation (*pfunc) (int, int) ; Relation *pfunc (int, int) ;
37
דוגמא מתקדמת – מיון מערכים
#include <stdio.h> typedef enum { Left, Right, Eq } Relation; typedef Relation (*cmp_func)(int, int); int sort(int *arr, int n, cmp_func cmp){ int i, j, t; if(arr==NULL || cmp==NULL) return 0; for(i=0; i<n; i++) { for(j=i+1; j<n; j++) { if(cmp(arr[i], arr[j])==Left) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return 1; Relation bigger(int a, int b){ if(a>b) return Left; if(a==b) return Eq; return Right; } Relation abs_bigger(int a, int b){ if(a<0) a=-a; if(b<0) b=-b;
38
דוגמא מתקדמת – מיון מערכים
int main(){ int arr[] = { 1, -3, 9, -10, -5 }; sort(arr, 5, bigger); /* */ sort(arr,5,abs_bigger);/* */ return 0; }
39
פונקצית מיון כללית נרחיב את פונקצית המיון למיין כל טיפוס שהוא
פונקצית מיון כללית יכולה להכתב ע"י מצביעים מטפוס void* typedef Relation (*cmp_func)(void* , void* ); int sort(void **arr, int n, cmp_func cmp) { int i, j; void *t; if(arr==NULL || cmp==NULL) return 0; for(i=0; i<n; i++) { for(j=i+1; j<n; j++) { if(cmp(arr[i], arr[j])==Left) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return 1; typedef Relation (*cmp_func)(int, int); int sort(int *arr, int n, cmp_func cmp) { int i, j, t; if(arr==NULL || cmp==NULL) return 0; for(i=0; i<n; i++) { for(j=i+1; j<n; j++) { if(cmp(arr[i], arr[j])==Left) { t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return 1;
40
פונקצית מיון כללית למיון int-ים למיון תאריכים
Relation cmp(void* a, void* b) { return bigger (*((int*)a ), (*(int*)b)); } Relation dateComp(void* date1, void* date2){ Date d1 = (Date)date1; Date d2 = (Date)date2; if(d1->year > d2->year) return Left; if(d1->year < d2->year) return Right; if(dateMonth(d1) > dateMonth(d2)) return Left; if(dateMonth(d1) < dateMonth(d2)) return Right; if(d1->day > d2->day) return Left; if(d1->day < d2->day) return Right; return Eq; }
41
מיון תאריכים int main() { int i=0; Date d[3];
d[0] = dateCreate(20, 5, 2010); d[1] = dateCreate(1, 1, 2000); d[2] = dateCreate(2, 2, 2001); sort(d,3,dateComp); for (i=0; i<3; i++) datePrint(d[i]); return 0; } 1 JAN 2000 2 FEB 2001 20 MAY 2010
Similar presentations
© 2024 SlidePlayer.com Inc.
All rights reserved.