1 / 73

קורס תכנות – סמסטר ב' תשס"ח

קורס תכנות – סמסטר ב' תשס"ח. שיעור תשיעי: כתובות ומצביעים מבנים. www.cs.tau.ac.il/~armon/prog08b.htm. נושאי השיעור היום. כתובות ומצביעים: - תזכורת ודוגמאות נוספות - מצביעים ומערכים מערכים ופונקציות מבנים. תזכורת: כתובות ומצביעים.

dunn
Download Presentation

קורס תכנות – סמסטר ב' תשס"ח

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. קורס תכנות – סמסטר ב' תשס"ח שיעור תשיעי: כתובות ומצביעים מבנים www.cs.tau.ac.il/~armon/prog08b.htm

  2. נושאי השיעור היום • כתובות ומצביעים: - תזכורת ודוגמאות נוספות - מצביעים ומערכים • מערכים ופונקציות • מבנים

  3. תזכורת: כתובות ומצביעים • לכל תא בזיכרון יש מספר סידורי (כתובת). כל משתנה נשמר החל מכתובת מסויימת בזיכרון. • מצביע (פויינטר) הוא משתנה ששומר כתובת של משתנה אחר. למשל: int *the_pointer;.זה מצביע ששומר כתובת של int. • לכל טיפוס-משתנה ניתן להגדיר מצביע לשמירת כתובת של משתנה כזה: char *ptav; double *ptr1, *ptr2, *ptr3; • הפעולה & נותנת את כתובתו של משתנה, למשל &i. • הפעולה * נותנת את המשתנה שנמצא בכתובת מסויימת (כלומר ניגשת לערך של המשתנה שעליו הפויינטר מצביע). למשל: *my_pointer=10;

  4. תזכורת: מציאת הכתובת של משתנה התוכנית הזיכרון טבלת הכתובות int main() { int i=10; } משתנה כתובת גודל 10 i 75004 7500 &i זה 7500

  5. תזכורת: גישה לערך בכתובת מסויימת התוכנית הזיכרון טבלת הכתובות int main() { char c=‘A’; } משתנה כתובת גודל 65 c 92001 9200 *(9200) זה ערך המשתנה c, כלומר זה 65

  6. 100 my_pointer_to_int i תזכורת: גישה למשתנה על-ידי מצביע • קטע תוכנית להדגמת משמעות הפעולות האלה: int i=10; int *my_pointer_to_int; my_pointer_to_int =&i; *my_pointer_to_int=100; printf(“The value of i is now %d”, i); intהגדרת מצביע על iשמים במצביע את כתובת המשתנה שמים 100במשתנה שהפויינטר מצביע עליו יודפס המספר 100 345 כתובת 345 (למשל)

  7. כתובות: נקודות לתשומת לב • לא ניתן לשנות כתובת של משתנה. כלומר לא ניתן לכתוב למשל: &i=5000. • ניתן לשנות את הערך שנמצא בכתובת מסויימת. • למשל: *(&c)=‘A’;. • זה כמו לכתובc=‘A’;. • מסוכן לשנות את הערך שנמצא בכתובת כלשהי שאיננה כתובת של משתנה. למשל *(4000)=123;. • אנחנו עלולים לשנות מקום בזיכרון ששייך למערכת ההפעלה, ולגרום לתוכנית לעוף. • לכן נשנה רק ערכים של משתנים שהגדרנו.

  8. תזכורת: מצביעים ופונקציות • העברת כתובת של משתנה לפונקציה מאפשרת לה לשנות אותו אפילו שהוא לא מוגדר בה. • זו הסיבה שבהעברת מערך לפונקציה שונה ערכו המקורי (מה שמועבר הוא כתובת ההתחלה של המערך בזיכרון).

  9. תזכורת: מצביעים ופונקציות – דוגמא • תארנו פונקציה שמקבלת כתובות של שני משתנים מסוג int, ומחליפה בין הערכים שלהם. void swap(int *first, int *second) { int temp; temp=*first; *first=*second; *second=temp; } השימוש למשל על-ידי:swap(&i, &j); first ערך המשתנה שכתובתו הועברה אל יוחלף עם ערך המשתנה שכתובתו הועברה אל second

  10. עוד על פונקציות ומצביעים • אמרנו שהערך שמוחזר מפונקציה יכול להיות מצביע: int *address_of _the_higher_number(int *a, int*b); • אבל החזרת כתובת משתנה שהוגדר בתוך הפונקציה תגרום לשגיאה. כי המשתנים שמוגדרים בתוך הפונקציה נעלמים עם סיומה. למשל הפונקציה הבאה תגרום לשגיאה: int *give_pointer_to_zero() { int i=0; return &i; } 0 i

  11. . . . . . . . . . . array[0] array[9] מצביעים ומערכים • הגדרת מערך מקצה בזיכרון מקומות רצופים עבור התאים שלו. • בנוסף, מוקצה משתנה ששמו הוא שם-המערך, שמכיל את כתובת התא הראשון של המערך (לא ניתן לשנות את ערכו). • לכן אפשר לגשת לתאי מערך גם ע"י חישוב הכתובת שלהם. למשל *(array+5) שקול ל- array[5]. • המחשב יודע לאיזה כתובת לגשת כשעושים את החשבון הזה, כי הוא יודע כמה מקום תופס כל תא במערך לפי הטיפוס. כתובת 500 (למשל) 500 array

  12. מצביעים ומערכים • אי-אפשר לשנות כתובת של מערך (היא נקבעת כשהוא מוגדר). • לכן לא ניתן לכתוב array=array1, או array=0. • אפשר לשים כתובת של מערך בתוך מצביע. אפשר לכתוב: char *array_ptr; array_ptr=array; • (השורה האחרונה שקולה לשורה:array_ptr = &array[0]; ) • כלומר,משתנהמערך הוא סוג של מצביע (אבל לא להיפך). • מערך הוא למעשה מצביע שמקבל כתובת בהגדרה ולא ניתן לשנות אותה. (שמים במצביע את כתובת התא הראשון במערך)

  13. גישה לתאי-מערך לפי הכתובת • אם הגדרנו למשל: int array[10]; int *array_ptr; array_ptr=array; אז אפשר לגשת לתאי-המערך גם לפי הכתובת שלהם, כמו משתנים אחרים.3 הפעולות הבאות עושות אותו דבר (שמות 100 בתא מס' 5 במערך): array[5]=100; *(array+5)=100; *(array_ptr+5)=100; • - המחשב יודע כמה בתים להתקדם כשאנחנו מבקשים להתקדם ב-5 תאים קדימה, כי הגדרנו שטיפוס הערכים הוא int והוא יודע כמה בתים כל int תופס (כאמור, ערכי המערך שמורים בזיכרון ברצף). (מצביע על התא הראשון במערך) גישה לתא שנמצא 5 תאים אחרי התא הראשון

  14. גישה למערך לפי כתובת – דוגמא נוספת • נאמר שמוגדרים: int i,array[10]={0}; int *ptr; • אז 3 הקטעים הבאים עושים בדיוק אותו דבר (מדפיסים את המערך): • for (i=0; i<10; i++) printf(“%d ”, array[i]); • for (i=0; i<10; i++) printf(“%d ”, *(array+i)); • for(ptr=array; ptr<= &array[9]; ptr++) printf(“%d ”, *ptr); 1) הדפסה על-ידי גישה רגילה לתאי המערך 2) הדפסה על-ידי גישה לכל תא לפי הכתובת שלו יחסית לתא הראשון 3) הדפסה ע"י קידום מצביע מכתובת התא הראשון עד כתובת התא האחרון

  15. מערכים ופונקציות - הסבר • אמרנו בעבר שכשמעבירים מערך לפונקציה שינויים שנעשה בפונקציה ישפיעו על המערך המקורי. • זה נובע מכך שלפונקציה מועברת הכתובת בזיכרון של המערך המקורי, והיא פועלת ישירות על ערכיו (ולא על העתק שלהם). • למשל, כפי שאמרנו, הפונקציה הבאה תאפס מערך שמועבר אליה: void zero_array(int a[ ], int size) { int i; for(i=0 ; i<size; i++) a[i]=0; }

  16. מערכים ופונקציות - הסבר • אמרנו בעבר שכשמעבירים מערך לפונקציה שינויים שנעשה בפונקציה ישפיעו על המערך המקורי. • זה נובע מכך שלפונקציה מועברת הכתובת בזיכרון של המערך המקורי, והיא פועלת ישירות על ערכיו (ולא על העתק שלהם). • למשל, כפי שאמרנו, הפונקציה הבאה תאפס מערך שמועבר אליה: void zero_array(int *a, int size) { int i; for(i=0 ; i<size; i++) a[i]=0; } נציין שאם פונקציה מצפה לקבל מצביע, אפשר להעביר אליה מערך מהסוג הזה, כי בשני המקרים מה שמועבר הוא כתובת בזיכרון.

  17. מערכים ופונקציות • למשל הפונקציות שהזכרנו על מחרוזות (כמו strcmp, strlen) מוגדרות למעשה עבור מצביעים מסוג char *. • באחריותנו להפעיל אותן באמת על מחרוזת (כלומר רצף תווים שמסתיים ב- '0\') ולא סתם על מצביע לתו או מערך של תווים (נקבל תוצאות שגויות אם אין את תו הסיום). • כמו-כן, כזכור, אם פונקציה מוגדרת על מערך בגודל מסויים, ניתן להעביר אליה גם מערך בגודל אחר של אותו טיפוס (כי כאמור מה שמועבר זה רק כתובת ההתחלה). • שוב, זה באחריותנו שתתבצע בפונקציה פעולה הגיונית ושהיא תדע מה גודל המערך ולא תחרוג ממנו.

  18. "מצביע על כלום" • לפעמים נירצה לסמן שמצביע לא מצביע לשום משתנה (למשל לפני שהתחלנו להשתמש בו). • מקובל לעשות זאת ע"י זה שנכניס אליו את הכתובת 0 (שאיננה כתובת אפשרית של משתנה). • למעשה, במקום לרשום 0 מקובל לרשום NULL. זהו קבוע שערכו 0, שמוגדר ע"י #define בספריה stdlib.h. למשל: #include<stdlib.h> int *ptr=NULL; • לא לבלבל בין הכתובת NULL לבין תו סיום מחרוזת ‘\0’

  19. דוגמא: חיפוש תו במחרוזת מקבלת מצביע לתחילת מחרוזת ותו #include<stdlib.h> char *my_strchr(char *str, char tav) { while((*str != ‘\0’) && (*str != tav)) str++; if (*str == tav) return str; return NULL } מתקדמים עד שמצאנו את התו או הגענו לסוף המחרוזת אם מצאנו מוחזר מצביע להופעה הראשונה אם לא מצאנו מחזירים NULL

  20. דוגמא: חיפוש תו במחרוזת מקבלת מצביע לתחילת מחרוזת ותו #include<stdlib.h> char *my_strchr(char *str, char tav) { while((*str != ‘\0’) && (*str != tav)) str++; if (*str == tav) return str; return NULL; } int main() { char word[]=“HELLO”, tav=‘L’, *place; place=my_strchr(word, tav); return 0; } מתקדמים עד שמצאנו את התו או הגענו לסוף המחרוזת אם מצאנו מוחזר מצביע להופעה הראשונה אם לא מצאנו מחזירים NULL הדגמת אופן הקריאה לפונקציה הזו

  21. מערכים ופונקציות – נקודה לתשומת לב • אם ננסה להחזיר מפונקציה מערך שהגדרנו בתוכה או מצביע על מערך כזה, אז נקבל שגיאה. int * zero_array() { int array[100]={0}; return array; } • זה מכיוון שמוחזרת כתובת של המערך, אבל כל מה שהוגדר בתוך הפונקציה נמחק כשהיא מסתיימת.

  22. מצביעים ומערכים - סיכום • משתנה מערך הוא סוג של מצביע: הוא מכיל כתובת (של התא הראשון במערך), אבל אי-אפשר לשנות אותה. • זו הסיבה שבהעברת מערך לפונקציה המערך המקורי מושפע. • אפשר לגשת לתא במערך גם בעזרת חישוב כתובתו יחסית לכתובת התחלת המערך. למשל: *(array+5)=100; שאלות?

  23. נקודה לתשומת-לב: איתחול מה שמוצבע • ככלל, לפני שכותבים ערך לכתובת שנמצאת במצביע, צריך שהמצביע יכיל כתובת של משתנה כלשהו שהגדרנו (אחרת תיקרה תעופה). int *ptr; *ptr=10; • בהתאם, בהגדרת מצביע לא ניתן לכתוב ערכים למקום שהוא מצביע עליו. למשל לא ניתן לרשום: int *my_array={1,2,3}; יכתוב לכתובת שנמצאת במצביע לפני שאיתחלנו אותו

  24. דיברנו על: • כתובות בזיכרון • מצביעים והשימוש בהם • מערכים ומצביעים • מערכים ופונקציות סיכום מצביעים

  25. שאלות נוספות? מצביעים

  26. מבנים

  27. טיפוסי משתנים בשפת C • הכרנו במהלך הקורס את טיפוסי המשתנים הבסיסיים שניתן לייצג בשפת C: מספר שלם/ממשי, תו, מחרוזת, ומערך. • כשכתבנו תוכניות לפתרון בעיות כלשהן, יצגנו את נתוני התוכנית ע"י משתנים מהטיפוסים האלה. • כידוע, בבעיות מציאותית יכולים להיות אובייקטים מורכבים, שלא בהכרח מתאימים ליצוג ע"י מספר, תו, או אפילו מערך. • למשל בבעיות גיאומטריות מדובר על נקודות, ישרים, מצולעים, וכדומה. • מינהלת-האוניברסיטה מטפלת בפקולטות, קורסים, סטודנטים, וכו'.

  28. טיפוסי משתנים בשפת C • כשכותבים תוכנות לפתרון בעיות מציאותיות, נוח שיהיו לנו טיפוסי משתנים שמייצגים את האובייקטים המציאותיים האלה. • למשל, לטיפול במספרים מרוכבים היינו רוצים טיפוס שייצג מספר מרוכב. • לפתרון בעיות גיאומטריות נירצה למשל טיפוס שמתאר נקודה במישור וטיפוסים שמתארים מצולעים שונים. • במערכת המידע של האוניברסיטה נירצה טיפוסי-משתנים עבור סטודנט, קורס, וכו' (שכל אחד מהם כולל כמה שדות-מידע רלוונטיים).

  29. יצירת טיפוסי משתנים חדשים • שפת C מאפשרת לנו להגדיר טיפוסי משתנים חדשים שיתאימו לבעיה שאנחנו רוצים לפתור. • טיפוס חדש כזה נקרא "מבנה" (structure). הפקודה שתשמש אותנו לכך היא struct. • בדרך-כלל מבנה כולל כמה משתנים, שנקראים "שדות" (דוגמא בשקף הבא).

  30. הגדרת מבנה חדש – דוגמא • נקודה במישור מיוצגת על-ידי שתי קואורדינטות, X ו- Y. הגדרת מבנה שייצג נקודה תיראה למשל כך: struct point { double x; double y; }; • ההגדרה הזו לא תופיע בתוך פונקציה, אלא בתחילת הקובץ (בד"כ מייד אחרי שורות ה- #include וה- #define). • כעת ניתן להגדיר בתוכנית משתנים מהסוג הזה, כפי שניראה בשקף הבא.

  31. הגדרת מבנה חדש – המשך הדוגמא • דוגמא להגדרת משתנים מהטיפוס החדש: int main() { struct point P1,P2; return 0; } • בכך הגדרנו שתי נקודות (שני משתנים מסוג מבנה point), שלכל אחת מהן יש שני שדות. P2 P1 x y x y

  32. הגדרת מבנה חדש – המשך הדוגמא ניתן לגשת לכל אחד מהשדות של מבנה ולכתוב/לקרוא אליו/ממנו. למשל: int main() { struct point P1,P2; P1.x = 6; P1.y = 7; P2.x = 4; P2.y = 2; printf(“%g\n”, P1.y); return 0; } P2 P1 x y 4 x y 6 2 7 (יודפס 7)

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

  34. מבנים - תיאור פורמלי • הגדרת מבנה (טיפוס משתנה חדש): structשם הטיפוס החדש{ טיפוס שם-שדה; ….. טיפוס שם-שדה; } ; • הגדרת משתנה מהסוג החדש שהגדרנו: ;שם משתנה שם הטיפוס החדשstruct • פנייה לשדה של משתנה שהוא מבנה נעשית ע"י שם המשתנה.שם השדה

  35. הגדרת מבנה חדש - הדוגמא הקודמת #include<stdio.h> struct point { double x; double y; }; int main() { struct point P1,P2; P1.x = 6; P1.y = 7; P2.x = 4; P2.y = 2; printf(“%g\n”, P1.y); return 0; } P2 P1 x y 4 x y 6 2 7

  36. איתחול מבנים • גם משתנים מטיפוס חדש אפשר לאתחל בשורת ההגדרה. למשל בהתייחס לדוגמא הקודמת: struct point P = {6,7}; • השדה x של המשתנהP יאותחל ל-6 והשדהy ל-7. • באופן כללי, האיתחול הוא לפי סדר השדות בהגדרת המבנה, כלומר האיבר הראשון ברשימת האיתחול יכנס לשדה הראשון, השני לשני, וכן הלאה (אם האיתחול הוא חלקי, השאר יאותחל באפסים).

  37. מבנים – הגדרה • מהם מבנים • הגדרת מבנים • איתחול מבנים • שאלות?

  38. פעולות על מבנים • השמה בין מבנים מאותו סוגמתבצעת בצורה הרגילה: P1 = P2; • התוכן של P2 פשוט מועתק לתוך P1. (העתקה שדה-שדה) • אם אחד השדות הוא מערך אז גם הוא מועתק (שינוי שלו ב- P1 לא ישפיע על P2). • פעולות השוואה (== והאחרות) ופעולות חשבוןאינן פועלותעבור מבנים(זה לא יתקמפל). • כלומר נצטרך לכתוב פונקציות עבור הפעולות שנירצה ליישם (למשל נשווה את כל אחד מהשדות כדי לבצע השוואה).

  39. pointדוגמא: פונקציה להשוואת מבני int equal(struct point p, struct pointq) { return ( (p.x == q.x) && (p.y == q.y)) } אם הנקודות זהות יוחזר 1, ואחרת 0

  40. דוגמא לשימוש במבנים • נכתוב פונקציה שמחשבת מרחק בין שתי נקודות: double dist( struct point p, struct point q) { double d; d = (p.x – q.x)*(p.x – q.x) + (p.y–q.y)*(p.y-q.y); return sqrt(d); } (יופיע בתחילת הקובץ #include<math.h> כדי להשתמש בפונקציה sqrt)

  41. דוגמת שימוש במבנים - המשך …. int main() { struct point p,q; printf(“Enter x and y coord. of the first point\n”); scanf(“%lf%lf”, &p.x,&p.y); printf(“Enter x and y coord. of the second point\n”); scanf(“%lf%lf”, &q.x,&q.y); printf(“Their distance is %g\n”, dist(p,q)); return 0; }

  42. פונקציות ומבנים • איך מבני הנקודות מועברים לפונקציה?

  43. פונקציות ומבנים • איך מבני הנקודות מועברים לפונקציה? • הם מועברים על ידי העתקת הערכים של השדות שלהם לפרמטרים של הפונקציה. • כמו במשתנים רגילים call by value)), כמו עבור int. • שינוי הנקודה בפונקציה לא ישנה את הנקודה ב- main. • פונקציה יכולה גם להחזיר מבנה, כמו כל משתנה אחר (גם במקרה הזה הערכים יועתקו).

  44. פונקציות ומבנים – נקודה לתשומת-לב • אם אחד השדות של המבנה הוא מערך, ומעבירים את המבנה הזה לפונקציה, אז המערך מועתק (לא רק כתובת המערך מועברת). • כלומר אם נשנה בפונקציה מערך שהוא שדה של מבנה, אזלא תהיה לזה השפעה על המערך המקורי.

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

  46. מבנים – סיכום ביניים • שימוש במבנים מאפשר הגדרת טיפוסי משתנים חדשים שקרובים לעולם-הבעיה שאנחנו פותרים. • לא ניתן להשתמש בפעולות החשבון וההשוואה הרגילות על מבנים - יש להגדיר פונקציות עבורן בהתאם לצורך (רק פעולת ההשמה מוגדרת, ובה מתבצעת גם העתקת מערכים). • אפשר להשתמש במבנים כמו בטיפוסים רגילים מבחינת העברה לפונקציה (למעט העובדה שמערך בתוך מבנה מועתק ולא מועבר המערך המקורי). • כעת נדבר על מבנים ומצביעים, מערכים של מבנים, ומבנים בתוך מבנים.

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

  48. מבנים ומצביעים בדיוק כמו טיפוסי-משתנים שהכרנו בעבר, אפשר להגדיר מצביע לטיפוס שהוא מבנה (שיכיל את כתובת ההתחלה שלו). למשל: struct point P={5,6}; struct point*ptr; ptr = &P ; P.x P P.y ptr

  49. מצביעים ומבנים – גישה לשדות המבנה • בדוגמא הזאת, כדי להגיע לשדות של P דרך ptr שמצביע על P, אפשר להשתמש ב- * כרגיל: • (*ptr).x = 3; שקול ל- P.x = 3; • (*ptr).y = 7; שקול ל- P.y = 7; (צריך סוגריים כי אחרת לנקודה יש קדימות)

  50. מצביעים ומבנים – גישה לשדות המבנה • בדוגמא הזאת, כדי להגיע לשדות של P דרך ptr שמצביע על P, אפשר להשתמש ב- * כרגיל: • (*ptr).x = 3; שקול ל- P.x = 3; • (*ptr).y = 7; שקול ל- P.y = 7; • אבל בדרך-כלל נשתמש לצורך זה בסימון מקוצר: חץ <- • ptr->x = 3; שקול ל-P.x = 3; • ptr->y = 7; שקול ל-P.y = 7; • כלומר משמעות החץ היא גישה לשדה במבנה שמצביעים עליו. (צריך סוגריים כי אחרת לנקודה יש קדימות)

More Related