View
92
Download
0
Category
Preview:
DESCRIPTION
תרגול מס' 4. הידור של מספר קבצים חלוקה למודולים Makefile טיפוסי נתונים מופשטים – Abstract data types. הידור של מספר קבצים. שלבי ההידור עיבוד מקדים הידור קישור פונקציות סטטיות. הידור של מספר קבצים. לא נוח לשמור את כל הקוד בקובץ יחיד עבור ת ו כנה גדולה - PowerPoint PPT Presentation
Citation preview
3מבוא לתכנות מערכות - 234122
הידור של מספר קבצים
לא נוח לשמור את כל הקוד בקובץ יחיד עבור תוכנה גדולה
ניתן לחלק את הקוד למספר קבצים ולקמפל כל קובץ בנפרד ולקשראת כל הקבצים לקובץ הרצה יחיד בסוף התהליך
-כדי להדר מספר קבצים בgcc ניתן פשוט לרשום מספר קבצים :gccכפרמטרים לשורת הפקודה של
> gcc a.c b.c mytest*.c
4מבוא לתכנות מערכות - 234122
Object Files
שלבי הקומפילציה
:את הידור הקוד ניתן לחלק לשלושה שלביםPreprocessing- עיבוד מקדים 1.
Compilationהידור - 2.
Linkingקישור - 3.
Linker
Preprocessor
SourceFiles
Compiler
a.h
a.c
b.c
c.c
a.o
c.o
b.o prog
5מבוא לתכנות מערכות - 234122
Preprocessingעיבוד מקדים – -שלב העיבוד המקדים עובר על קובץ הקוד ומבצע את ההוראות עבור הpreprocessor
הן כל השורות המתחילות ב-#preprocessorהוראות עבור ה-–
פעולות העיבוד המקדים הן כולן פעולות גזירה והדבקה פשוטות–
#define <macro> <value > הגדרת מאקרו חדש. לאחר ההגדרה בכל מקום שיופיע -<macro<-בקוד תבוצע החלפה ל >value>ניתן להגדיר יותר מסתם קבועים בעזרת מאקרו אך חשוב לשים לב להשלכות–
#include <filename >הוסף את תוכן הקובץ לתוך הקוד במיקום הנוכחי -הקוד שמתווסף עובר גם הוא עיבוד מקדים–
ההתייחסות היא לקובץ מן הספריה הסטנדרטית> < אם שם הקובץ מופיע בתוך –
ההתייחסות היא לקובץ שנמצא עם שאר הקוד שכתבנו" " אם שם הקובץ מופיע במרכאות –
#ifdefined <macro >< אם מוגדר מאקרו בשם -macro המשך כרגיל, אחרת מחק את >endifכל הקוד עד להופעת #
6מבוא לתכנות מערכות - 234122
Compilationהידור - שלב ההידור מקבל קבצי קוד מקור שעברו את שלב העיבוד המקדים
c, קבצים אלו "הודבקו" לתוך קבצי ה-hשלב זה אינו מקבל קבצי –הרלוונטיים
לכל קובץ קוד נוצר קובץ בינארי אשר מכיל את הקוד המהודר מקובץהקודgcc עבור oוסיומתו היא . object fileקובץ זה קרוי –
ייתכן וחלק מהפונקציות שהוכרזו לא מומשו ביחידת הקומפילציה יכיל "חורים"objectהנוכחית, במקרה זה קובץ ה-
קובץ זה תלוי מהדר ומערכת הפעלה - לכן קובץ אובייקט הנוצר עבורWindows שונה מקובץ הנוצר עבור מחשב ביתי עם studשרת ה-
7מבוא לתכנות מערכות - 234122
Linkingקישור - שלב הקישור מקבל כקלט קבצי אובייקט ומקשר את כולם לקובץ הרצה
יחיד
בשלב זה לכל "חור" שהושאר בקובץ אובייקט מקושרת הפונקציההמתאימה
אם אכן קיים לה מימוש באחד מקבצי האובייקט–
:ייתכנו שגיאות בשלב זה הנקראות שגיאות קישורלא נמצא מימוש עבור פונקציה מסוימת–
a.c:11: undefined reference to `my_function'
נמצא יותר ממימוש אחד עבור פונקציה מסוימת–
a.c:10: multiple definition of `main'
b.c:10: first defined here
8מבוא לתכנות מערכות - 234122
פונקציות סטטיות
:ניתן להכריז על פונקציה כסטטית
static <function declaration>
במקרה זה הפונקציה אינה נראית מחוץ ליחידת הקומפילציה שלה לא ינסה לקשר אותה לקריאות לפונקציה מיחידות קומפילציה linkerוה-
אחרות
:דוגמהstatic int square(int n) {
return n*n;
}
מיחידת קומפילציה אחרת squareבמקרה זה אם תהיה הכרזה וקריאה ל-–הפונקציה הסטטית לא תקושר לקריאה זו.
9מבוא לתכנות מערכות - 234122
הידור של מספר קבצים - סיכום
ניתן לקמפל מספר קבצים ביחד לקובץ הרצה יחיד:עיבוד-מקדים, הידור וקישורההידור ניתן לחלוקה לשלושה שלבים בשלב העיבוד המקדים מוחלפים מאקרוים ומבוצעות פעולותinclude בשלב ההידור נוצר מכל קובץcקובץ אובייקט בשלב הקישור מחוברים קבצי האובייקט לקובץ הרצה יחידכדי להסתיר פונקציות מיחידות קומפילציה אחרות ניתן להגדירן כסטטיות
11מבוא לתכנות מערכות - 234122
חלוקה למודולים
אורך החיים של קוד יכול להיות עשרות שנים, לאורך תקופה זו יש לתחזקאת הקוד
תיקון באגים–
הכנסת תכונות חדשות–
התאמה להתקנים חדשים–
הכנסת שינויים לתוכנה קיימת עלולה ליצור באגים חדשיםככל שהתוכנה מסובכת יותר, הסיכוי לטעויות גדל–
:תכנות מודולאריהפתרוןחלוקת התוכנה למודולים לפי תפקידם–
מאפשר שימוש חוזר במודולים עבור תוכנות אחרות–
מסתיר פרטי מימוש ומקטין את התלויות בקוד–
12מבוא לתכנות מערכות - 234122
מודול
מודול תוכנה מחולק לשני חלקים: המנשק והמימוש
מנשק(interface):מגדיר את הפעולות שניתן לעשות בעזרת המודול–
חלק זה חשוף למשתמש במודול–
( מימושimplementation):מספק את המימוש לפעולות שניתן לבצע דרך מנשק המודול–
חלק זה אינו חשוף למשתמש–
למשל, עבור מכונית הגה הוא חלק מהמנשק ואילו המנוע הוא חלקמהמימוש
13מבוא לתכנות מערכות - 234122
Cמודולים ב--כדי לכתוב מודולים בC ניצור לכל מודל קובץ c וקובץ h
-קובץ הh:יכיל את מנשק המודול הכרזות על הפונקציות–
הגדרות טיפוסים–
הכרזות על קבועים שיש בהם עניין למשתמש )למשל קודי שגיאה(–
-קובץ הc :יכיל את מימוש המודולמימושי פונקציות המנשק ופונקציות פנימיות נוספות–
הגדרות טיפוסים לשימוש פנימי–
הכרזות על קבועים שאין בהם עניין למשתמש–
2נראה כעת כיצד יוצרים מודול מטיפוס הנתונים עבור תאריך מתרגול
14מבוא לתכנות מערכות - 234122
date.hמנשק המודול - #ifndef DATE_H_#define DATE_H_ #include <stdbool.h>#include <stdio.h> #define MIN_DAY 1#define MAX_DAY 31#define MIN_MONTH 1#define MAX_MONTH 12#define DAYS_IN_YEAR 365 /** * A module for a date datatype */typedef struct Date_t {
int day;char month[4];int year;
} Date;
includeהגנה נגד כפול,
מה קורה בלעדיה?
15מבוא לתכנות מערכות - 234122
date.hמנשק המודול - /** Possible error codes */typedef enum {
DATE_SUCCESS, DATE_NULL_ARG, DATE_FAIL, DATE_INVALID} DateResult;
/** writes the date to the stream fd */DateResult datePrint(Date date, FILE* fd);
/** Reads a date from the stream fd */DateResult dateRead(Date* date, FILE* fd);
/** Returns true if both dates are identical */bool dateEquals(Date date1, Date date2);
/** Returns the number of days between the dates */int dateDifference(Date date1, Date date2);
/** Checks if the date has valid values */bool dateIsValid(Date date); #endif /* DATE_H_ */
כדי לאפשר למשתמש שימוש נוח במודול עלינו לספק לו קודי
שגיאה מפורטים, לכן נגדיר טיפוס מיוחד למטרה זו ונשתמש
בו
יש לספק תיעוד מפורט יותר של ערכי החזרה
והשגיאה. תיעוד זה אינו מסופק כאן מפאת חוסר
המקום בשקף
16מבוא לתכנות מערכות - 234122
date.cמימוש המודול - #include "date.h"#include <string.h> #define INVALID_MONTH 0static const char* const months[] = { "JAN", "FEB", "MAR", " APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; static int monthToInt(char* month) {
for (int i = MIN_MONTH; i <= MAX_MONTH; i++) {if (strcmp(month, months[i - 1]) == 0) {return i;}}return INVALID_MONTH;
} static int dateToDays(Date date) {
int month = monthToInt(date.month);return date.day + month*(MAX_DAY - MIN_DAY + 1) + DAYS_IN_YEAR * date.year;
}
פונקציות פנימיות אינן מעניינות את המשתמש ולכן יש להכריז עליהן
כסטטיות
אין צורך להכריז מחדש על הפונקציות, פשוט כוללים את
המנשק בקובץ
17מבוא לתכנות מערכות - 234122
date.cמימוש המודול - DateResult datePrint(Date date, FILE* fd) {
if (!fd) {return DATE_NULL_ARG;
}fprintf(fd, "%d %s %d\n", date.day, date.month, date.year);return DATE_SUCCESS;
} DateResult dateRead(Date* date, FILE* fd) {
if (!date || !fd) {return DATE_NULL_ARG;
}if (fscanf(fd, "%d %s %d", &(date->day), date->month, &(date-
>year)) != 3) {return DATE_FAIL;
}return dateIsValid(*date) ? DATE_SUCCESS : DATE_INVALID;
}
18מבוא לתכנות מערכות - 234122
date.cמימוש המודול -
bool dateEquals(Date date1, Date date2) {return date1.day == date2.day &&
strcmp(date1.month,date2.month) == 0 &&date1.year == date2.year;
} int dateDifference(Date date1, Date date2) {
int days1 = dateToDays(date1);int days2 = dateToDays(date2);return days1 - days2;
}
19מבוא לתכנות מערכות - 234122
שימוש במודול התאריך#include "date.h"
int main() {Date date1 = { 21, "NOV", 1970 };Date date2;DateResult result = dateRead(&date2, stdin);if (result == DATE_FAIL) {
fprintf(stderr,"Bad date format\n");return 0;
}if (result == DATE_INVALID) {
fprintf(stderr,"Invalid date\n");return 0;
}datePrint(date1, stdout);datePrint(date2, stdout);if (!dateEquals(date1,date2)) {
int diff = dateDifference(date1,date2);int absoluteDiff = diff < 0 ? -diff : diff; printf("The dates are %d days apart\n", absoluteDiff );
}return 0;
}
כעת ניתן להשתמש במודול התאריך בתכניות שונות וגם לעדכן את מודול התאריך
בקלות
20מבוא לתכנות מערכות - 234122
חלוקה למודולים - סיכום
נהוג לחלק את הקוד למודולים כדי לאפשר שימוש חוזר ותחזוקה נוחהמודול מוגדר משני חלקים - מנשק ומימושהמנשק מכיל רק את מה שהמשתמש במודול זקוק לו-בC מגדירים את מנשק המודול בקובץ h ואת המימוש בקובץ c יש להגן על קבציh מפני includeכפול -פונקציות פנימיות של המודול יש להגדיר כstatic
22מבוא לתכנות מערכות - 234122
בנייה של תוכנה גדולה
כאשר התוכנה גדלה זמן הקומפילציה גדללפעמים יותר משעה עבור קומפילציה של כל הקוד בבת אחת–
נרצה להימנע מקומפילציה מחדש של כל הקוד לאחר שינוי קטן
נניח שבתוכנה שלנו קיימים הקבציםa.c, b.c-ו c.cכיצד נבנה את התוכנה כך שנוכל לקמפל מחדש רק חלקים שישתנו?–
?a.cכיצד נקמפל את התוכנה מחדש לאחר שינוי ב-–
23מבוא לתכנות מערכות - 234122
בנייה הדרגתית של תוכנה
:לדוגמה, התכנית שלנו מורכבת מהקבצים הבאים–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
24מבוא לתכנות מערכות - 234122
makeMake היא כלי פשוט ויעיל המאפשר בנייה הדרגתית של תוכנה בלי חזרה
על חלקים שלא עודכנו–make מאפשרת יצירת קובץ Makefile אשר יכיל הוראות לבניית התוכנה
תשתמש בהוראות כדי makeבכל פעם שנרצה לבנות מחדש את התוכנה –לבנות מחדש רק את החלקים שהשתנו
prog: calc.o control.o main_prog.o gcc calc.o control.o main_prog.o -
o prog
calc.o: calc.c calc.hgcc -c calc.c
control.o: control.c calc.h control.hgcc -c control.c
main_prog.o: calc.h control.hgcc -c main_prog.c
> make
25מבוא לתכנות מערכות - 234122
makeשימוש ב--ה( לאחר כתיבת קובץ ההוראותmakefile-השימוש ב ,)make מתבצע
בעזרת הפקודה הבאה:
> make [-f filename] [target]
Makefile או makefile תחפש קובץ בשם makeאם לא מוגדר שם קובץ, –בתיקיה הנוכחית
מאפשר לבצע רק חלק מתהליך הבנייה או לטפל במקרים targetהארגומנט –makefileמיוחדים. אם הוא אינו מופיע יבוצע הכלל הראשון ב-
סביבות עבודה בד"כ מסוגלות ליצור קובץ בנייה אוטומטי, אך בפרויקטיםגדולים דרושה התאמה אישית של קבצים אלו
26מבוא לתכנות מערכות - 234122
makefile-הmakefileמכיל את רשימת הקבצים הדרושים לתוכנה והתלויות ביניהם -הmakefile( מורכב מכללים rules)
כל כלל מתחיל בשורה מהצורה:–
a.o: a.c a.h b.h הוא שם הקובץ שנוצר מכלל זהtargetכאשר שם הכלל, הקרוי גם •
targetהתלויות הם הקבצים ששינוי בהם משפיע על שינוי ה-•השורות הבאות בכלל מכילות את רשימת הפקודות שיש לבצע עבור הכלל–
TABכל שורת פקודה חייבת להתחיל ב-•:a.oלדוגמה, כלל עבור יצירת קובץ האובייקט –
a.o: a.c a.h b.h
gcc -c a.c
מסמן הערה עד סוף השורה#התו בדומה למאקרו ב-\ניתן להשתמש בתו( כדי לשבור שורה ארוכה C)
27מבוא לתכנות מערכות - 234122
דוגמה:נסתכל על התכנית הבאה-קובץ הmake המתאים ייראה
כך:
הפעלתmake:תיראה כך
a.c:#include "a.h"
a.h:#include "b.h"
b.c:#include "b.h"
b.h:
c.c:#include "b.h"#include "c.h"
c.h:
a.o b.o c.o
prog
prog: a.o b.o c.ogcc a.o b.o c.o -o
prog
a.o: a.c a.h b.hgcc -c a.c
b.o: b.c b.hgcc -c b.c
c.o: c.c c.h b.hgcc -c c.c
> make
> make prog
28מבוא לתכנות מערכות - 234122
makeשיטת העבודה של כאשרmake< מקבלת את הכלל name>: <dependencies לביצוע היא >
משתמשת באלגוריתם הבא:< יש לבצע בדיקה:dependenciesלכל שם ב->–
אם קיים כלל מתאים לתלות הזו נבצע אותו תחילה בצורה רקורסיבית•
אם לא קיים כלל נבדוק אם הקובץ עודכן מאז הפעם האחרונה שביצענו •את הכלל )לפי חתימת הזמן על הקובץ(
<nameאם נמצאו תלויות שהשתנו יש לבצע את הפקודות עבור הכלל >–
אם לא היה שינוי באף אחד מהתלויות של הכלל אין צורך לבצע כלום–
29מבוא לתכנות מערכות - 234122
מציאת תלויות
-ניתן למצוא את התלויות בתוכנה ולקבל שלד עבור הmakefile בעזרת הפקודה הבאה
> gcc -MM *.c
:לדוגמה, הפלט עבור הדוגמה הקודמת
> 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
30מבוא לתכנות מערכות - 234122
makefileאפשרויות נוספות ב-
עבור מחרוזותmakefile בתוך מאקרוניתן להגדיר •למקרה של מחרוזות החוזרות מספר פעמים•
עבור מחרוזות שצפויות להשתנות בעתיד•
מאקרו מוסיפים בקובץ שורה מהצורה הבאה:להגדירכדי •
<MACRO NAME> = <string>
למאקרו משתמשים ב-$ ושם המאקרו בסוגריים:להתייחסכדי •
$(<MACRO NAME>) :קיימים מספר מאקרוים מוגדרים מראש
הנוכחיtarget הוא ה-$@–
הנוכחי ללא סיומתtargetהוא ה-$* –
31מבוא לתכנות מערכות - 234122
מתקדם יותרmakefileדוגמה ל-
CC = gccOBJS = a.o b.o c.oEXEC = progDEBUG_FLAG = # now empty, assign -g for debugCOMP_FLAG = -std=c99 -Wall -Werror
$(EXEC) : $(OBJS)$(CC) $(DEBUG_FLAG) $(OBJS) -o $@
a.o : a.c a.h b.h$(CC) -c $(DEBUG_FLAG) $
(COMP_FLAG) $*.cb.o : b.c b.h
$(CC) -c $(DEBUG_FLAG) $(COMP_FLAG) $*.cc.o : c.c c.h b.h
$(CC) -c $(DEBUG_FLAG) $(COMP_FLAG) $*.cclean:
rm -f $(OBJS) $(EXEC)
> make clean
rm -f a.o b.o c.o prog
> make
gcc -c -Wall a.c
gcc -c -Wall b.c
gcc -c -Wall c.c
gcc a.o b.o c.o -o prog
32מבוא לתכנות מערכות - 234122
להרבה מהקבצים שיש ליצור בבניית התוכנה יש כללים קבועים בבנייתם בעל אותו שם c נוצרים על בעזרת הידור של קובץ oלמשל כל קבצי ה-–
באותה צורה
-כדי למנוע שכפולי קוד בקובץ הMakeקיימים כללי מובנים ומשתנים מוגדרים
מראש לסוגי קבצים מסוימים יודעתo, makeלמשל עבור קבצי –
להשתמש בפקודה הבאה ליצירתם כברירת מחדל:cמקובץ
כללים מובנים
CC=gccOBJS=a.o b.o c.oEXEC=progDEBUG=# now empty, assign -g for debugCFLAGS=-std=c99 -Wall -Werror $(DEBUG)
$(EXEC) : $(OBJS)
a.o : a.c a.h b.hb.o : b.c b.hc.o : c.c c.h b.h
clean:rm -f $(OBJS) $(EXEC)
$(CC) $(CFLAGS) -c
makeקובץ ה-החדש המנצל את החוקים המובנים
33מבוא לתכנות מערכות - 234122
Makefileסיכום -
ניתן להגדיר קבציmakefileכדי להקל ולזרז את בניית התוכנה הפקודהmake-משתמשת ב makefile כדי לבנות את התוכנה בצורה
אוטומטית ניתן להגדיר מאקרו בתוךmakefileכדי להקל על שינויו בעתיד -ניתן להשתמש בgcc כדי ליצור שלד של makefile
35מבוא לתכנות מערכות - 234122
בעיה
:משתמש במודול התאריך שלנו כתב את הקוד הבאint main() {
Date date1 = { 21, "NOV", 1970 };
Date date2;
printf("Enter a day number:");
scanf("%d", &date2.day);
// ... more code ...
return 0;
}
?מה הבעיה? מה גורם לה
36מבוא לתכנות מערכות - 234122
Encapsulationהסתרה - הבעיה בטיפוסי הנתונים שלנו - המימוש חשוף
קוד במקום להשתמש בקוד קייםלשכפלהמשתמש עלול –
שינויים עתידיים במודול "ישברו" את הקוד של המשתמש בו–
הפתרון: נסתיר את המימוש של טיפוס הנתונים מהמשתמש כך שקודלא יתקמפלאשר אינו משתמש במנשק
חשוב לציין - המשתמש יכול להיות כותב המודול בעצמו
37מבוא לתכנות מערכות - 234122
טיפוס נתונים מופשט
-כדי להסתיר את המימוש מהמשתמש בC:נשתמש בשיטה הבאה טיפוס מצביע למבנה נכריז על hבקובץ ה-–
typedef struct datatype_t* Datetype;
את המבנה נממש cבקובץ ה-–
struct datatype_t {
// fields ...
};
משתמשים במודול לא יוכלו לבצעdereferenceלמצביע להשתמש בו רק דרך המנשק המוגדרמשתמשים במודול חייבים
38מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.h#ifndef DATE_H_#define DATE_H_ #include <stdbool.h>#include <stdio.h> #define MIN_DAY 1#define MAX_DAY 31#define MIN_MONTH 1#define MAX_MONTH 12#define DAYS_IN_YEAR 365 /** * A module for a date */typedef struct Date_t* Date; typedef enum {
DATE_SUCCESS, DATE_NULL_ARG, DATE_FAIL, DATE_INVALID} DateResult;
רק טיפוס המצביע חשוף, עם הגדרה זו לא ניתן לבצע
dereference-ל Date
39מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.h/** Allocates a new date */Date dateCreate(int day, int month, int year); /** Allocates a new date which is a copy of the argument */Date dateCopy(Date date); /** Frees an existing date object */void dateDestroy(Date date); DateResult datePrint(Date date, FILE* fd);DateResult dateRead(Date date, FILE* fd);bool dateEquals(Date date1, Date date2);int dateDifference(Date date1, Date date2);bool dateIsValid(Date date); #endif /* DATE_H_ */
מאחר והמשתמש לא יכול ליצור עצמים מטיפוס התאריך עלינו
לספק לו מנשק לביצוע פעולות אלו
40מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.c#include "date.h"#include <stdlib.h>#include <string.h>#define INVALID_MONTH 0
struct Date_t {int day;char month[4];int year;
};
static const char* const months[] = {"JAN", "FEB", "MAR", " APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
static int monthToInt(char* month) {for (int i = MIN_MONTH; i <= MAX_MONTH; i++) {
if (strcmp(month, months[i - 1]) == 0) {
return i;}
}return INVALID_MONTH;
}
Cהמבנה מוגדר בקובץ ה-ולכן גישה לשדותיו אפשרית רק מיחידת הקומפילציה הזו
41מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.cstatic bool isDayValid(int day) {
return day >= MIN_DAY && day <= MAX_DAY;} static bool isMonthNumberValid(int month) {
return month >= MIN_MONTH && month <= MAX_MONTH;} static int dateToDays(Date date) {
int month = monthToInt(date->month);return date->day + month*(MAX_DAY - MIN_DAY + 1) +
DAYS_IN_YEAR * date->year;}
42מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.cDate dateCreate(int day, int month, int year) {
if (!isDayValid(day) || !isMonthNumberValid(month)) {
return NULL;}Date date = malloc(sizeof(*date));if (!date) {
return NULL;}date->day = day;strcpy(date->month, months[month-1]);date->year = year;return date;
}
43מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.cDate dateCopy(Date date) {
if (!date) {return NULL;
}return dateCreate(date->day, monthToInt(date->month),
date->year);} void dateDestroy(Date date) {
free(date);} bool dateIsValid(Date date) {
return isDayValid(date->day) &&monthToInt(date->month) != INVALID_MONTH;
}
למה בכלל לטרוח?
44מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.cDateResult datePrint(Date date, FILE* fd) {
if(!fd) {return DATE_NULL_ARG;
}fprintf(fd, "%d %s %d\n", date->day, date->month, date->year);return DATE_SUCCESS;
} DateResult dateRead(Date date, FILE* fd) {
if(!date || !fd) {return DATE_NULL_ARG;
}if(fscanf(fd,"%d %s %d",&(date->day),date->month,&(date->year)) !=
3) {return DATE_FAIL;
}return dateIsValid(date) ? DATE_SUCCESS : DATE_INVALID;
}
45מבוא לתכנות מערכות - 234122
ADT - עבור תאריך date.cbool dateEquals(Date date1, Date date2) {
return date1->day == date2->day &&strcmp(date1->month,date2->month) == 0 &&date1->year == date2->year;
} int dateDifference(Date date1, Date date2) {
int days1 = dateToDays(date1);int days2 = dateToDays(date2);return days1 - days2;
}
46מבוא לתכנות מערכות - 234122
עבור תאריךADTשימוש ב-#include "date.h" int main() {
Date date1 = dateCreate(21, 11, 1970);Date date2 = dateCreate(1,1,0);DateResult result = dateRead(date2, stdin);if (result != DATE_SUCCESS) {fprintf(stderr,"Bad input\n");return 0;}if (!dateEquals(date1,date2)) {int diff = dateDifference(date1,date2);int absoluteDiff = diff < 0 ? -diff : diff; printf("The dates are %d days apart\n", absoluteDiff ); }dateDestroy(date1);dateDestroy(date2);return 0;
}
לא לשכוח לשחרר!ההקצאות הן
דינאמיות!
מה הבעיה?
47מבוא לתכנות מערכות - 234122
טיפוסי נתונים מופשטים - סיכום
טיפוסי נתונים מופשטים נבדלים מטיפוסי נתונים רגילים בניתוק המימושמהמנשק
כדי להסתיר את המימוש שלstruct-ב Cנשתמש במצביעים למבנה את הגדרת המבנה נשים בקובץC
49מבוא לתכנות מערכות - 234122
מצביעים לפונקציות
?מה משותף לשתי הפונקציות הבאות
bool bigger(int a, int b);
bool abs_bigger(int a, int b);
ביצוע קריאה לפונקציות בעלות אותה חתימה נעשה באותה צורה בקודמכונה
השמת המשתנים במקום מוגדר )בד"כ על המחסנית(1.
קפיצה לכתובת תחילת הפונקציה בזיכרון2.
כתיבת ערך החזרה למקום מוסכם3.
קפיצה לכתובת ממנה נקראה הפונקציה4.
זהים לכל שתי פונקציות בעלות אותה חתימה4 ו-3, 1שלבים לכן ניתן לקרוא לפונקציות שונות בעזרת קוד דומה–
50מבוא לתכנות מערכות - 234122
מצביעים לפונקציות:ניתן להכריז על מצביע לפונקציה בעלת חתימה מסוימת
<return type> (*<name>)(<parameters>) = <initial value>;
ומחזירה intלמשל הכרזה על מצביע עבור פונקציה המקבלת משתנה יחיד מטיפוס –int-המאותחל ל NULL:תיראה כך
int (*ptr)(int) = NULL;
ניתן לאחסן במצביע לפונקציה את כתובתה של פונקציה בעלת חתימה זהה לזושהוגדרה במצביע
int square(int n);
...
ptr = square; // &square also works
:ניתן לקרוא לפונקציה דרך המצביעprintf("%d", ptr(5)); // (*ptr)(5) also works
לא נהוג להשתמש ב-& ו-* עבור מצביעים
לפונקציות
מה קורה בלי הסוגריים?
51מבוא לתכנות מערכות - 234122
דוגמה בסיסית:ברשותנו שתי הפונקציות הבאות
נכתוב את הפונקציהmax המקבלת מצביע לפונקציה ומחזירה את האיבר הגדולמביניהם בהתאם לקריטריון שמועבר לה
int max(int a, int b, bool (*compare)(int,int)) {
return compare(a,b) ? a : b;
}
מה יהיו תוצאות כל אחת מההרצות הבאות שלmax?max(-7, 5, isBigger);
max(-7, 5, isBiggerAbs);
bool isBiggerAbs(int a, int b) {
int abs_a = a > 0 ? a : -a;
int abs_b = b > 0 ? b : -b;
return abs_a > abs_b;}
bool isBigger(int a, int b) {
return a > b;}
52מבוא לתכנות מערכות - 234122
עוד דוגמה
בעזרת מצביעים לפונקציות ניתןליצור קוד שבכל הרצה שלו יתנהג
אחרת:
int main() {bool (*function)(int, int);
if (getchar() == '1') {
function = isBigger;} else {
function = isBiggerAbs;}
int a = -5, b = 3;bool b = function(a, b);
if (b) {
printf("%d",a);} else {
printf("%d",b);}return 0;
}
53מבוא לתכנות מערכות - 234122
דוגמה - מיוןכתבו פונקציה למיון מערך של מספרים שלמים המאפשרת מיון לפי קריטריון משתנה
typedef bool (*CmpFunction)(int, int);
void sort(int* array, int n, CmpFunction compare) {
assert(array != NULL && compare != NULL);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (compare(arr[i], arr[j])) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
כדי להימנע מהתחביר הלא נוח של מצביעים לפונקציות
typedefניתן להשתמש ב-
54מבוא לתכנות מערכות - 234122
דוגמה - מיון
:דוגמה לשימושint main(){ int arr[] = { 1, -3, 9, -10, -5 }; sort(arr, 5, isBigger); // -10 -5 -3 1 9 sort(arr, 5, isBiggerAbs); // 1 -3 -5 9 -10 return 0;}
55מבוא לתכנות מערכות - 234122
קוד גנרי
קוד גנרי הוא קוד המסוגל לעבוד על עצמים מטיפוסים שונים
-בC:ניתן לכתוב קוד גנרי בעזרת כדי לייצג עצמים כללייםvoidמצביעים ל-–
מצביעים לפוקציות כדי לייצג את הפעולות על העצמים–
בשיטה זו נוכל לממש אלגוריתמים פעם אחת בלבד ולמנוע שכפול קוד
56מבוא לתכנות מערכות - 234122
מיון גנריכתבו פונקציה למיון מערך של עצמים כלשהם המאפשרת מיון לפי קריטריון משתנה
typedef bool (*CmpFunction)(void*, void*);
void sort(void** array, int n, CmpFunction compare) {
assert(array != NULL && compare != NULL);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (compare(arr[i], arr[j])) {
void* tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
57מבוא לתכנות מערכות - 234122
שימוש בפונקצית המיון הגנרי
כדי למיין באמצעות הפונקציה החדשה עלינו ליצור פונקציות השוואהמתאימות:
bool intIsBigger(void* a, void* b) {
return isBigger(*(int*) a, *(int*) b);
}
bool dateIsBigger (void* date1, void* date2) {
assert(date1 && date2);
int difference = dateDifference(date1,date2);
return intIsBigger(difference, 0);
}
למה לא חייבים המרה
מפורשת?
58מבוא לתכנות מערכות - 234122
שימוש בפונקצית המיון הגנריint main() {
void* dates[3];dates[0] = dateCreate(20, 5, 2010);dates[1] = dateCreate(1, 1, 2000);dates[2] = dateCreate(2, 2, 2001);
int* numbers[3];numbers[0] = malloc(sizeof(int)); *numbers[0] = 17;numbers[1] = malloc(sizeof(int)); *numbers[1] = 1;numbers[2] = malloc(sizeof(int)); *numbers[2] = 7;
sort(dates, 3, dateIsBigger);sort((void**)numbers, 3, intIsBigger);
for (int i = 0; i < 3; i++) {
datePrint(dates[i], stdout); printf("\n");}
for (int i=0; i<3; i++) {printf("%d\n",*numbers[i]);
}return 0;
}
1 JAN 20002 FEB 200120 MAY 20101717
למה דרושה המרה
מפורשת?
מה חסר?
Recommended