1 / 57

תרגול מס ' 3

תרגול מס ' 3. מצביעים הקצאת זיכרון דינאמית מבנים - Structures טיפוסי נתונים - Data types העברת פרמטרים ל- main טענות נכונות. מצביעים. שימוש בסיסי אריתמטיקת מצביעים void* מצביע למצביע. מבנה הזיכרון - תזכורת. כתובת. ערך הבית. הזיכרון מורכב מתאים הקרויים בתים:

claire-lamb
Télécharger la présentation

תרגול מס ' 3

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. תרגול מס' 3 מצביעים הקצאת זיכרון דינאמית מבנים - Structures טיפוסי נתונים - Data types העברת פרמטרים ל-main טענות נכונות

  2. מצביעים שימוש בסיסי אריתמטיקת מצביעים void* מצביע למצביע מבוא לתכנות מערכות - 234122

  3. מבנה הזיכרון - תזכורת כתובת ערך הבית • הזיכרון מורכב מתאים הקרויים בתים: • כל בית מכיל ערך מספרי • פירוש הערכים כערך לא מספרי הוא ע"י התכנית • לכל בית יש שם – כתובת • הכתובת היא מיקומו בזיכרון • בד"כ כתובות בזיכרון נרשמות בבסיס הקסדצימלי (16) • משתנים מאוחסנים בבתים: • טיפוסים שונים דורשים מספר שונה של בתים, למשל: • int צורך 4 או 8 בתים • char צורך בית יחיד. int התופס 4 בתים 7 0x0200 0 0x0201 0 0x0202 0 0x0203 0 0x0204 0 0x0205 0 0x0206 0 0x0207 104 0x0208 מחרוזת “hello” המורכבת ממספר תווים 101 0x0209 108 0x020A 108 0x020B 111 0x020C 0 0x020D מבוא לתכנות מערכות - 234122

  4. מצביעים - תזכורת • עבור טיפוס T נקרא ל-T*מצביע ל-T • למשל int* הוא מצביע ל-int • מצביע מסוג T* הוא משתנה אשר שומר כתובת של משתנה מטיפוס T. • ניתן לקבל את כתובתו של משתנהע"י שימוש באופרטור & • לא ניתן להשתמש ב-& על ביטויים או קבועים (מדוע?) • ניתן לקרוא את ערכו של המצביע ע"י אופרטור * • פעולה זו קרויה dereferencing intn = 5; int* ptr = &n; // ptr now points to n printf("%d",*ptr); // dereferencing ptr 0x208 0x0204 34 0x0205 37 0x0206 0 0x0207 8 0x0208 20 0x020A 11 0x020B 9 0x020C מבוא לתכנות מערכות - 234122

  5. הכתובת 0x0 - NULL • הכתובת 0 הינה כתובת לא חוקית: • אף עצם אינו יכול להיות בעל כתובת זו • ניתן לעשות שימוש בכתובת זו כדי לציין שמצביע מסוים אינו מצביע לאף עצם כרגע • השתמשו ב-NULLכאשר אתם מתייחסים לכתובת ולא בקבוע 0 • שימוש בקבועים כאלו משפר את קריאות הקוד • נסיון לקרוא מצביע המכיל את הכתובת NULL יגרום לקריסת התוכנה. • ב-UNIX תתקבל ההודעה:“segmentation fault” • גישה למצביע המכיל "זבל" תגרוםלתכניתלהתנהג בצורה לא צפויה • אסור להשאיר מצביעים לא מאותחלים בקוד! • ניתן להכריז על המשתנה מאוחר יותר (C99) • במקרה ולא ניתן יש לאתחל אותו ל-NULL מבוא לתכנות מערכות - 234122

  6. אריתמטיקת מצביעים • ניתן בנוסף לבצע פעולות חשבוניות על מצביעים • חיבור מספרים שלמים: int n = ...; int* ptr2 = ptr + n; • התוצאה היא כתובתו של המשתנה מטיפוס מתאים nתאים קדימה/אחורה • חיסור שני מצביעים: int diff = ptr2 - ptr; • התוצאה היא מספר שלם (int) • פעולות אלו מאפשרות להסתכל על המשתנה הבא/הקודם בזיכרון • הכרחי לשימוש במערכים ומחרוזות • מסוכן– טעויות חשבוניות עלולות לגרום לקריאת "זבל" מהזיכרון • שאלה: מדוע לא ניתן לחבר שני מצביעים? מבוא לתכנות מערכות - 234122

  7. מצביעים – גישה למערך • ניתן להשתמש במצביע כדי לגשת למשתנים הנמצאים בהמשך בזיכרון, למשל כך: int* ptr = ...; intn = *(ptr + 5); • האופרטור [ ]משמש כקיצור לפעולה זו: int n = ptr[5]; • כלומר הפעולות הבאות שקולות: *(ptr + n) ≡ptr[n] מבוא לתכנות מערכות - 234122

  8. מצביעים ומערכים • מערכים ומצביעים מתנהגים בצורה דומה • ניתן להשתמש בשם המערך כמצביע לאיבר הראשון בו • כאשר שולחים מערך לפונקציה ניתן לשלוח אותו כמצביע: void sort(int* array, int size); • מצביע יכול לשמש כאיטרטור עבור מערך intarray[N]; //... for(int* ptr = array; ptr < array+N; ptr++) { printf("%d ",*ptr); } • הבדלים: • הכרזה על מערך מקצה זיכרון כגודל המערך, הכרזה על מצביע אינה מקצה זיכרון לאחסון המשתנים! • ניתן לשנות את ערכו של מצביע, אך לא ניתן לשנות את "ערכו" של תחילת המערך ב-C99 ניתן להכריז על משתנה בתוך לולאת for מבוא לתכנות מערכות - 234122

  9. void* • ניתן להגדיר מצביעים מטיפוס void*. מצביעים אלו יכולים לקבל את כתובתו של כל משתנה • לא ניתן לקרוא מצביע מטיפוס void*, יש להמירו קודם לכן int n = 5; double d = 3.14; void* ptr = &n; ptr = &d; double d2 = *ptr; // Error: cannot dereference void* double d3 = *(double*)ptr; // O.K. – option 1 double* dptr = ptr; // Implicit cast from void* to double* double d4 = *dptr; // O.K. – option 2 מבוא לתכנות מערכות - 234122

  10. מצביע למצביע • ניתן ליצור מצביע לכל טיפוס, בפרט עבור טיפוס T* ניתן ליצור מצביע מטיפוס T** • מתקבל מצביע למצביע של T • אפשר להמשיך לכל מספר של * • דוגמאות: • שליחת מערך של מצביעים לפונקציה: voidsort_pointers(int** array, int size); • כתיבת פונקצית swap עבור מחרוזות: voidswap_strings(char** str1, char** str2) { char* temp = *str1; *str1 = *str2; *str2 = temp; } • מדוע יש כאן צורך במצביע למצביע? מבוא לתכנות מערכות - 234122

  11. מצביעים - סיכום • מצביעים משמשים להתייחסות לתאי זיכרון • ניתן לקבל את כתובתו של משתנה ע"י אופרטור & • ניתן לקרוא ממצביע ולקבל את הערך המוצבע ע"י * • הערך NULLמציין שאין עצם מוצבע ואסור לקרוא אותו • ניתן לבצע פעולות חשבוניות על מצביעים • מאפשר התייחסות למצביעים בדומה למערכים • חשוב לאתחל מצביעים • הרצת קוד הניגש למצביעים המכילים ערך לא תקין תגרום להתנהגות לא מוגדרת • הכרזה על מצביע אינה מאתחלת זיכרון עבור המשתנה המוצבע! • מצביע מטיפוס void*יכול להצביע לעצם מכל סוג ומשמש לכתיבת קוד גנרי מבוא לתכנות מערכות - 234122

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

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

  14. הרוע של משתנים גלובליים • משתנים גלובליים, משתנים סטטיים של קובץ ומשתנים סטטיים של פונקציה נחשבים לתכנות רע • הסיבה העיקרית לכך - שימוש במשתנים אלו מקשה על הבנת ודיבוג הקוד: • כדי להבין פונקציה המשתמשת במשתנה גלובלי יש להסתכל בקוד נוסף • קשה לצפות את תוצאת הפונקציה כי היא אינה תלויה רק בפרמטרים שלה • קשה לצפות השלכות של שינויים על ערך המשתנה • בשימוש במשתנה סטטי של פונקציה - בשביל לצפות את תוצאת הפונקציה צריך לדעת מה קרה בהרצות קודמות • אין להשתמש במשתנים גלובליים במת"מ • בקורסים מתקדמים בהמשך התואר תראו מקרים בהם חובה או מומלץ להשתמש במשתנים כאלו מבוא לתכנות מערכות - 234122

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

  16. הקצאת זיכרון באמצעות malloc • כדי להקצות זיכרון נשתמש בפונקציה malloc: void* malloc(size_t bytes); • mallocמקבלת גודל בבתים של זיכרון אותו עליה להקצות • ערך החזרה מכיל מצביע לתחילת גוש הזיכרון שהוקצה • התוצאה היא תמיד גוש זיכרון רציף • במקרה של כשלון מוחזר NULL • לאחר מכן ניתן להתייחס לשטח המוצבע כאל משתנה או מערך: int* my_array = malloc(sizeof(int) * n); for (int i=0; i<n; i++) { my_array[i] = i; } מבוא לתכנות מערכות - 234122

  17. קביעת הגודל אותו מקצים • כיצר נדע כמה בתים עלינו להקצות עבור משתנה מסוג int? • נסיון ראשון: int* ptr = malloc(4); • 4 הוא מספר קסם - מספר לא ברור המופיע בקוד שאינו 0 או 1 • מספרי קסם הם הרגל תכנותי רע: • פוגעים בקריאות הקוד • מקשים על שינויים עתידיים בקוד • למשל מעבר לסביבה בה גודלו של int הוא 8 • ככל שצריך יותר שינויים הסיכוי לפספס אחד מהם גדל • יש להימנע ממספרי קסם: • ע"י הגדרת קבועיםבעזרת #define שיקלו על שינויים ועל קריאת הקוד • ע"י שמירת ערכם במשתנה קבוע בעל שם ברור מבוא לתכנות מערכות - 234122

  18. קביעת הגודל - נסיון שני • נסיון שני - נגדיר את הגודל של int כקבוע: #define SIZE_OF_INT 4 int* ptr = malloc(SIZE_OF_INT); • עכשיו הקוד קריא וקל לשנות את הערך • אבל אם נעביר את הקוד לסביבה אחרת עדיין נצטרך לעדכן את הערך • קוד שדורש שינויים במעבר בין סביבות שונות נקרא non-portable מבוא לתכנות מערכות - 234122

  19. אופרטור sizeof • נשתמש באופרטור sizeofאשר מחזיר את הגודל המתאים: int* ptr = malloc(sizeof(int)); • ניתן להפעיל את sizeof על שמות טיפוסים או על משתנים • עבור שם טיפוס יוחזר הגודל בבתים של הטיפוס: int* ptr = malloc(sizeof(int)); • עבור הפעלה על משתנה יוחזר הגודל של הטיפוס של המשתנה בבתים: int* ptr = malloc(sizeof(*ptr)); // = sizeof(int) • שימו לב להבדל בין גודל של מצביע לגודל העצם המוצבע • מה נעשה אם ברצוננו להקצות זיכרון לעותק של מחרוזת? char* str = "This is a string"; char* copy = malloc(sizeof(char)*(strlen(str)+1)); למה השיטה הזו עדיפה? אפשר להוריד את sizeof(char), מובטח שהוא תמיד 1 למה צריך 1+? מבוא לתכנות מערכות - 234122

  20. בדיקת ערכי חזרה • malloc עלולה להיכשל בהקצאת הזיכרון - במקרה זה מוחזר NULL • מה קורה במקרה זה אם malloc נכשלת? int* my_array = malloc (sizeof(int) * n); for (int i=0; i<n; i++) { my_array[i] = i; } • הפתרון: בדיקת ערך ההחזרה של פונקציות העלולות להיכשל וטיפול בו • הטיפול צריך להופיע מיד לאחר ההקצאה ולפני השימוש הראשון int* my_array = malloc(sizeof(int) * n); if(my_array == NULL) { // or !my_array handle_memory_error(); } • בהמשך נראה מקרים נוספים של שגיאות יותר שכיחות ופשוטות להתמודדות מבוא לתכנות מערכות - 234122

  21. שחרור זיכרון באמצעות free • הפונקציה free משמשת לשחרור גוש זיכרון שהוקצה ע"י malloc voidfree(void* ptr); • המצביע שנשלח ל-free חייב להצביע לתחילת גוש הזיכרון (אותו ערך שהתקבל מ-malloc) • לאחר שחרור הזיכרון אסור לגשת יותר לערכים בזיכרון ששוחרר • אם שולחים NULL ל-free לא מתבצע כלום • כלומר אין צורך לבדוק את הפרמטר הנשלח ולוודא שאינו NULL • למה זה טוב? • אסור לשחרר את אותו זיכרון פעמיים או לשלוח ל-free מצביע שאינו מצביע לתחילת גוש זיכרון שהוקצה דינאמית (או NULL) int* my_array = malloc(sizeof(int) * n); // ... using my_array ... free(my_array); מבוא לתכנות מערכות - 234122

  22. מקרי קצה • במקרה ונשלח NULL ל-free לא מתבצע כלום if(ptr != NULL) { free(ptr); } • ניתן להחליף את הקוד הקודם בזה: free(ptr); • NULL הוא מקרה קצה עבור free • מה היה קורה אם free לא היתה מתמודדת עם מקרה הקצה הזה? • עדיף לטפל במקרי קצה בתוך הפונקציה • מונע מהמשתמש בה ליצור באגים ושכפולי קוד מבוא לתכנות מערכות - 234122

  23. גישה לזיכרון אחרי ששוחרר • גישה לכתובת זיכרון שאינה מוקצה (או הוקצתה ושוחררה) אינה מוגדרת • שחרור כפול של כתובת זיכרון אינו מוגדר • קוד שתוצאתו אינה מוגדרת הוא קוד שמתקמפל ורץ אך אינו מחשב את הערכים הצפויים. • נותן תוצאות שאינן צפויות • בחלק מהמקרים התוצאה שתוחזר אכן מתאימה לציפיות • קוד שאינו מוגדר הוא באג קשה לטיפול • קשה לצפות את התנהגותו והשלכותיו • יכול להשפיע על משתנים באזור אחד בקוד • חשוב להקפיד על שימוש נכון בשפה כדי להימנע ממקרים אלו מבוא לתכנות מערכות - 234122

  24. התנהגות לא מוגדרת - דוגמה • האם שתי התכניות הבאות מתנהגות בצורה זהה? #include<stdio.h> #define N 7 intmain() { int a[N] = {0}; int i; for (i=0; i < N; i++) { printf("%d\n", i); a[N-1-(i+1)] = a[i]; } return 0; } #include<stdio.h> #define N 7 intmain() { int i; int a[N] = {0}; for (i=0; i < N; i++) { printf("%d\n", i); a[N-1-(i+1)] = a[i]; } return 0; } מבוא לתכנות מערכות - 234122

  25. דליפות זיכרון • דליפת זיכרון מתרחשת כאשר שוכחים לשחרר זיכרון שהוקצה: voidsort(int* array, int n) { int* copy = malloc(sizeof(int) * n); // ... some code without free(copy) return; } • דליפת זיכרון אינה גורמת ישירות לשגיאות בהתנהגות התוכנה • דליפת זיכרון יגרמו לצריכת זיכרון גדלה של התוכנה ככל שזמן ריצתה גדל ולהאטת התוכנה ומערכת ההפעלה כולה • תחת UNIX ניתן להשתמש בכלי valgrindלאיתור דליפות זיכרון • valgrind מריץ את התכנית שלכם ומחפש גושי זיכרון שהוקצו אך לא שוחררו • ניתן למצוא מידע נוסף על השימוש ב-valgrind בתרגול עזר 3 מבוא לתכנות מערכות - 234122

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

  27. הקצאת זיכרון דינאמית - סיכום • מומלץ לא להשתמש במשתנים גלובליים וסטטיים • ניתן להשתמש ב-malloc ו-free כדי להקצות ולשחרר זיכרון בצורה מפורשת • עבור יצירת מערכים בגודל לא ידוע • עבור שמירת ערכים לאורך התכנית • ניהול הזיכרון מתבצע ע"י מצביעים לתחילת גוש הזיכרון שהוקצה • יש לבדוק הצלחת הקצאת זיכרון • יש לזכור לשחרר את הזיכרון המוקצה כאשר אין בו צורך יותר • ניתן להשתמש ב-valgrind כדי למצוא בקלות גישות לא מוגדרות לזיכרון מבוא לתכנות מערכות - 234122

  28. מבנים הגדרת מבנה פעולות על מבנים typedef מבוא לתכנות מערכות - 234122

  29. הטיפוסים הקיימים אינם מספיקים • נניח שברצוננו לכתוב תוכנה לניהול אנשי קשר, לכל איש קשר נשמור: שם פרטי, שם משפחה, מספר טלפון, כתובת e-mail וכתובת מגורים. • לשם כך נצטרך לשמור 5 מערכים שונים! • כל פונקציה שתצטרך לקבל את פרטיו של איש קשר כלשהו תצטרך לקבל 5 פרמטרים שונים לפחות! voidsomeFunction(char* firstname, char* lastname, char* address, char* email, int number, ... more?); • כדי להימנע מריבוי משתנים ניתן להגדיר טיפוסים חדשים המהווים הרכבה של מספר טיפוסים קיימים voidsomeFunction(Contactcontact, ...); מבוא לתכנות מערכות - 234122

  30. מבנים - Structures • ניתן להגדיר טיפוסים חדשים המהווים הרכבה של מספר טיפוסים קיימים בעזרת המילה השמורה struct: struct<name>{ <typename 1><field name 1>; <typename2><field name 2>; ... <typenamen><field name n>; } <declarations>; • הטיפוס החדש מורכב משדות: • לכל שדה יש שם • טיפוס השדה נקבע לפי הגדרת המבנה • המבנים נשמרים בזיכרון ברצף • ניתן להשתמש במערכים בעלי גודל קבוע כשדות - כל המערך נשמר במבנה • ניתן להשתמש במצביעים כשדות - במקרה זה הערך המוצבע אינו חלק מהמבנה מבוא לתכנות מערכות - 234122

  31. מבנים - דוגמאות structpoint { doublex; doubley; }; structdate { intday; charmonth[4]; intyear; }; structperson { char* name; structdatebirth; }; כל המערך נשמר בתוך המבנה למה 4? point x=3.0 date y=2.5 person day=31 day=31 name=0x0ffef6 month="NOV" month="MAR" birth "Ehud Banai" המחרוזת נשמרת מחוץ למבנה year=1953 year=1971 מבוא לתכנות מערכות - 234122

  32. שימוש במבנים • הטיפוס החדש מוגדר בשם struct <name> • כדי לגשת לשדות של משתנה מטיפוס המבנה נשתמש באופרטור . (נקודה) structpoint p; p.x= 3.0; p.y= 2.5; doubledistance = sqrt(p.x* p.x+ p.y* p.y); • עבור מצביע למבנה ניתן להשתמש באופרטור החץ <- structpoint* p = malloc(sizeof(*p)); (*p).x = 3.0; // Must use parentheses, annoying p->y = 2.5; // Same thing, only clearer doubledistance = sqrt(p->x * p->x + p->y * p->y); מה חסר? מבוא לתכנות מערכות - 234122

  33. פעולות על מבנים • ניתן לאתחל מבנים בעזרת התחביר הבא: structdate d = { 31, "NOV", 1970 }; • ניתן לבצע השמה בין מבנים מאותו הטיפוס: structdate d1,d2; // ... d1 = d2; • במקרה זה מתבצעת השמה בין כל שני שדות תואמים • מבנים מועברים ומוחזרים מפונקציות by value – כלומר מועתקים • גם במקרה זה מתבצעת ההעתקה שדה-שדה • הפעולות האלו אינן מתאימות למבנים מסובכים יותר (בד"כ בגלל מצביעים) מבוא לתכנות מערכות - 234122

  34. מבנים עם מצביעים • מבנים המכילים מצביעים אינם מתאימים בדרך כלל לביצוע השמות והעתקות • מה יקרה אם נבצע השמה בין שני המבנים בדוגמה זו? • מסיבה זו וכדי למנוע העתקות כבדות ומיותרות של מבנים בדרך כלל נשתמש במבנים ע"י מצביעים • נשלח לפונקציות (ונקבל כערכי חזרה) מצביעים למבנה • יוצא הדופן הוא מבנים קטנים ופשוטים כגון point person1 person2 day=9 day=31 name=0x0ffef6 name=0x0ffed0 "Ehud Banai" "Yuval Banai" month="MAR" month="JUN" birth birth year=1953 year=1962 מבוא לתכנות מערכות - 234122

  35. הגדרת טיפוסים בעזרת typedef • המילה השמורה typedef משמשת להגדרת טיפוסים חדשים ע"י נתינת שם חדש לטיפוס קיים typedefintlength; • פקודת typedef עובדת על שורת הכרזה של משתנה – אך מגדירה טיפוס חדש במקום משתנה. • נשתמש בפקודת typedef כדי לתת שמות נוחים לטיפוסים: typedefstructpoint Point; • במקרה זה נוכל להתייחס למבנה מעכשיו כ-Point (ללא המילה השמורה struct) • נוח לתת שם גם לטיפוס המצביע למבנה: typedefstruct date Date, *pDate; • עבור מבנים מסובכים נשתמש תמיד במצביעים ולכן במקרים האלו נשמור את השם ה"נוח" לטיפוס המצביע: typedefstruct person *Person; מבוא לתכנות מערכות - 234122

  36. הגדרת טיפוסים בעזרת typedef • ניתן להוסיף typedef ישירות על הגדרת המבנה: typedefstructpoint { doublex; doubley; } Point; • ניתן להשמיט את שם הטיפוס בהגדרה ולהשאיר רק את השם החדש: typedefenum { RED, GREEN, BLUE} Color; typedefstruct { doublex; doubley; } Point; מבוא לתכנות מערכות - 234122

  37. מבנים - סיכום • מבנים מאפשרים הרכבה של מספר טיפוסים קיימים כדי להקל על קריאות הקוד • מבנה מורכב משדות בעלי שם • ניתן לגשת לשדות ע"י האופרטורים . ו- ->. • העתקה והשמה של מבנים בטוחה כל עוד אין בהם מצביעים • מומלץ להשתמש ב-typedef כדי לתת שם נוח לטיפוס החדש מבוא לתכנות מערכות - 234122

  38. טיפוסי נתונים מבוא לתכנות מערכות - 234122

  39. טיפוסי נתונים – Data types • typedefstructdate_t{ • intday; • charmonth[4]; • intyear; • } Date; • intmain() { • Date d1 = {21, "NOV", 1970}; • Date d2; • scanf("%d %3s %d", &d2.day, d2.month, &d2.year); • printf("%d %s %d\n", d1.day, d1.month, d1.year); • printf("%d %s %d\n", d2.day, d2.month, d2.year); // deja-vu • if (d1.day == d2.day&&strcmp(d1.month,d2.month) == 0 && • d1.year == d2.year) { • printf("The dates are equal\n"); • } • return 0; • } אלו בעיות יש בקוד הזה? מבוא לתכנות מערכות - 234122

  40. טיפוסי נתונים - Data types • תאריך הוא יותרמהרכבה של שני מספרים שלמים וארבעה תווים • לא כל צירוף של ערכים עבור המבנה Date הוא אכן תאריך חוקי • 5 BLA 2010 - אין חודש מתאים ל-“BLA” • 31 SEP 1978 - ב-ספטמבר יש רק 30 ימים • 29 FEB 2010 - בפברואר 2010 יש רק 28 ימים • מי שמשתמש במבנה התאריך צפוי להשתמש בו בצורות מסוימות • הדפסת תאריך • מציאת התאריך המוקדם יותר מבין שני תאריכים • מציאת מספר הימים בין שני תאריכים מבוא לתכנות מערכות - 234122

  41. טיפוסי נתונים - Data types • כדי לוודא את נכונות השימוש בתאריכים ולמנוע את שכפולי הקוד בשימוש בתאריכים עלינו לכתוב פונקציות מתאימות לטיפול בתאריכים • לצירוף של טיפוס והפעולות האפשריות עליו קוראים טיפוס נתונים -Data type • טיפוסי הנתונים המובנים בשפה נקראים טיפוסי נתונים פרימטיביים • למשל int, float ומצביעים (לכל אחד מהם פעולות שונות אפשריות) • יצירת טיפוסי נתונים מהווה את הבסיס לכתיבת תוכנה גדולה בצורה מסודרת ופשוטה מבוא לתכנות מערכות - 234122

  42. טיפוס נתונים לתאריך • #include<stdio.h> • #include<string.h> • #include<stdbool.h> • typedefstructDate_t { • intday; • charmonth[4]; • intyear; • } Date; • constintMIN_DAY = 1; • constintMAX_DAY = 31; • constintINVALID_MONTH = 0; • constintMIN_MONTH = 1; • constintMAX_MONTH = 12; • constintDAYS_IN_YEAR = 365; • const char* constmonths[] = { "JAN", "FEB", "MAR", " APR", "MAY", "JUN", • "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; מבצעים include רק לקבצים שהכרחיים לקמפול הקוד: stdio.h - עבור printf ו-scanf string.h - עבור strcmp stdbool.h - עבור הגדרת הטיפוס bool הגדרת קבועים מבוא לתכנות מערכות - 234122

  43. טיפוס נתונים לתאריך • /** writes the date to the standard output */ • voiddatePrint(Date date); • /** Reads a date from the standard input. • * Returns true if valid, false otherwise */ • booldateRead(Date* date); • /** Returns true if both dates are identical */ • booldateEquals(Date date1, Date date2);/** Returns the number of days between the dates */ • intdateDifference(Date date1, Date date2); • /** Translates a month string to an integer */ • intmonthToInt(char* month); • /** Calculates the number of days since 01/01/0000 */ • intdateToDays(Date date); • /** Checks if the date has valid values */ • booldateIsValid(Date date); מומלץ לתעד לפחות בקצרה את משמעות הפונקציות מעל הכרזתן תיעוד צריך להופיע מעל הפונקציה ולא בתוכה הערות באמצע הקוד בד"כ מיותרות או מסבירות קוד שהיה צריך להיכתב ברור יותר מבוא לתכנות מערכות - 234122

  44. טיפוס נתונים לתאריך • intmonthToInt(char* month) { • for (int i = MIN_MONTH; i <= MAX_MONTH; i++) { • if (strcmp(month, months[i - 1]) == 0) { • return i; • } • } • returnINVALID_MONTH; • } • intdateToDays(Date date) { • int month = monthToInt(date.month); • returndate.day+ month*(MAX_DAY - MIN_DAY + 1) + • DAYS_IN_YEAR * date.year; • } • booldateIsValid(Date date) { • returndate.day >= MIN_DAY && date.day <= MAX_DAY && • monthToInt(date.month) != INVALID_MONTH; • } מבוא לתכנות מערכות - 234122

  45. טיפוס נתונים לתאריך • voiddatePrint(Date date) { • printf("%d %s %d\n", date.day, date.month, date.year); • } • booldateRead(Date* date) { • if (date == NULL) { • return false; • } • if (scanf("%d %s %d", &(date->day), date->month, &(date->year)) != 3) { • return false; • } • returndateIsValid(*date); • } יש לבדוק את תקינות הקלט בכניסה לפונקציה במיוחד מצביעים! המנעו משכפול קוד, אם קוד כלשהו כבר נכתב הקפידו לקרוא לפונקציה המבצעת אותו ולא לכתוב אותו מחדש! אם אין פונקציה מתאימה וקוד חוזר על עצמו - יש לכתוב פונקצית עזר ולקרוא לה! מבוא לתכנות מערכות - 234122

  46. טיפוס נתונים לתאריך • booldateEquals(Date date1, Date date2) { • return date1.day == date2.day&& • strcmp(date1.month,date2.month) == 0 && • date1.year== date2.year; • } • intdateDifference(Date date1, Date date2) { • int days1 = dateToDays(date1); • int days2 = dateToDays(date2); • returndays1 - days2; • } מבוא לתכנות מערכות - 234122

  47. פונקצית ה-main המעודכנת • intmain() { • Date date1 = { 21, "NOV", 1970 }; • Date date2; • if(!dateRead(&date2)) { • printf("Invalid date\n"); • return 0; • } • datePrint(date1); • datePrint(date2); • if (dateEquals(date1,date2)) { • printf("The dates are equal\n"); • } else { • int diff = dateDifference(date1,date2); • printf("The dates are %d days apart\n", abs(diff)); • } • return 0; • } מבוא לתכנות מערכות - 234122

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

  49. העברת פרמטרים ל-main הפרמטרים argc ו-argv תכנית לדוגמה מבוא לתכנות מערכות - 234122

  50. העברת פרמטרים ל-main • את הפונקציה main המתחילה את ריצת התכנית ניתן להגדיר גם כך: intmain(intargc, char** argv) • במקרה זה יילקחו הארגומנטים משורת ההרצה של התכנית ויושמו לתוך המשתנים argc ו-argvע"י מערכת ההפעלה • argcיאותחל למספר הארגומנטים בשורת הפקודה (כולל שם הפקודה) • argvהוא מערך של מחרוזות כאשר התא ה- בו יכיל את הארגומנט ה- בשורת הפקודה • בנוסף, קיים איבר אחרון נוסף במערך המאותחל ל-NULL מבוא לתכנות מערכות - 234122

More Related