1 / 62

מצביעים (Pointers)

מצביעים (Pointers). הגדרות בסיסיות העברה לפונקציה by reference אריתמטיקה של פוינטרים פוינטרים ומערכים הקצאה דינאמית. הוכן ע"י ד"ר דני קוטלר, המכללה האקדמית תל-חי. תזכורת: משתנה. משתנה הוא מקום שמור בזיכרון, המשמש את התוכנית לשמירת נתונים.

luana
Télécharger la présentation

מצביעים (Pointers)

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. מצביעים (Pointers) הגדרות בסיסיות העברה לפונקציה by reference אריתמטיקה של פוינטרים פוינטרים ומערכים הקצאה דינאמית הוכן ע"י ד"ר דני קוטלר, המכללה האקדמית תל-חי מבוא למדעי המחשב - שיעור 8

  2. תזכורת: משתנה • משתנה הוא מקום שמור בזיכרון, המשמש את התוכנית לשמירת נתונים. • לכל משתנה יש ערך מספרי, שהוא המספר הבינארי השמור במשתנה. • למשתנה יש שם המשמש לגישה אליו: קריאת הערך שלו או שינוי הערך שלו. • לכל משתנה יש כתובת בזיכרון, בה הוא שמור int x = 17; 0065FDF0 00010001 עד עכשיו לא התייחסנו לזה מבוא למדעי המחשב - שיעור 8

  3. שם המשתנה לעומת הכתובת שלו • שם המשתנה משמש את שפת התיכנות העלית ונועד להקל על המתכנת את הגישה למשתנה • בתהליך ההידור נעזר המהדר בטבלה הקרויה symbol table שמתאימה לכל משתנה את כתובתו בזיכרון. בתרגום לשפת מכונה כל שם משתנה מוחלף בכתובת שלו. • רוב שפות התכנות העליות משתמשות רק בשמות המשתנים כדי לגשת אליהםואין להן כלים לעבודה עם כתובות. • שפת C מאפשרת עבודה עם כתובות של משתנים באמצעות מצביעים(פוינטרים). מבוא למדעי המחשב - שיעור 8

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

  5. מהו פוינטר? • פוינטר הוא משתנה שהערכים שהוא מקבל הם כתובות בזיכרון. לרוב אלה כתובות של משתנים אחרים. • לכל טיפוס יש את טיפוס הפוינטר שלו: • פוינטר ל int – מיועד לכתובות של int • פוינטר ל char– מיועד לכתובות של char • פוינטר ל double– מיועד לכתובות של double • וגם: פוינטר לפוינטר ל int – מיועד לכתובות של פוינטרים ל int • (פוינטר ל int יכול להכיל רק כתובת של משתנה מטיפוס int וכן הלאה. הסיבה לכך תובן בהמשך) מבוא למדעי המחשב - שיעור 8

  6. הגדרת פוינטר הטיפוס שעליו מצביע הפוינטר שם הפוינטר • סינטקס: • למשל: • ואפשר גם • וגם באותה פקודה עם משתנים רגילים: type*pointer_name; int*p; char *s; int*p; int* p; int* p; int*p, x; intx,*p; int רגיל int רגיל פוינטר פוינטר מבוא למדעי המחשב - שיעור 8

  7. הצבת ערך לפוינטר • כדי להציב כתובת לתוך פוינטר נשתמש באופרטור & (אופרטור הכתובת) • בכל המקרים הנ"ל ערך הפוינטר p הוא הכתובת של המשתנה x. במקרה כזה אומרים ש p מצביע על x double x; double *p; p = &x; double x, *p=&x; double x; double *p=&x; השמה איתחול מבוא למדעי המחשב - שיעור 8

  8. סימון מקובל • כאשר פוינטר מצביע על משתנה • נהוג לסמן זאת כך: double x; double *p; p = &x; p x מבוא למדעי המחשב - שיעור 8

  9. השמה של ערך מפורש • טכנית, ניתן להציב ערך מספרי מפורש לתוך פוינטר: • כיוון שהכתובת המפורשת לא בהכרח פנויה לשימוש התוכנית שלנו, וקרוב לוודאי שהיא בשימוש אחר, מומלץ לא לבצע השמות מפורשות. גישה לכתובת שאינה מוקצה לנו נקראת דריסת זיכרון int*p = (int *)100; כשר, אבל מסריח! המרה לטיפוס "כתובת של int" מבוא למדעי המחשב - שיעור 8

  10. הכתובת אפס 0 • כתובת מפורשת אחת שכן נשתמש בה היא הכתובת אפס: 0 • הכתובת 0 לא קיימת ומשתמשים בה כאשר רוצים שפוינטר לא יצביע על כתובת כלשהי (למשל באיתחול) • למשל או • NULL הוא קבוע סימבולי שלם שערכו 0 ונהוג להשתמש בו לציון הכתובת 0. אין צורך בהמרה double *p = NULL; double *p = 0; מבוא למדעי המחשב - שיעור 8

  11. גישה למשתנה באמצעות פוינטר • האופרטור * (dereference operator)מאפשר גישה לכתובת עליה מצביע פוינטר. • נניח ש p מצביע על x: • במצב זה הביטוי *p זהה לביטוי x: p intx = 5, *p=&x; x printf("*p: %d ",*p); *p: 5 *p = 7; printf(" x: %d ",x); x: 7 מבוא למדעי המחשב - שיעור 8

  12. האופרטור * • האופרטור * מבצע את הפעולה ההפוכה לזו של האופרטור &. • הביטוי *&x זהה לביטוי x • הביטוי &*p זהה לביטוי p • האופרטור * יהיה שימושי במיוחד כאשר פוינטר יצביע על כתובת שאין לה שם של משתנה. p * x & מבוא למדעי המחשב - שיעור 8

  13. ההבדל בין *לאופרטור * • יש הבדל בין ה *המופיע בהגדרת פוינטר לבין האופרטור *, המוצמד לפוינטר שכבר הוגדר קודם. intx; int *p=&x; *p = 7; intx; int *p = &x; הגדרת פוינטר השמה של כתובת אופרטור * השמה של int מבוא למדעי המחשב - שיעור 8

  14. הערה כללית על שימוש בתווים בשפת C • לתו מסויים יכולים להיות תפקידים שונים בשפה. • דוגמה: * • כפל • הגדרת פוינטר • האופרטור * • דוגמה: % • חלק ממציין פורמט בתוך מחרוזת הבקרה של printf ו scanf (למשל: “%d”) • אופרטור השארית • המהדר "מבין" למה התכוון המתכנת לפי ההקשר מבוא למדעי המחשב - שיעור 8

  15. הדפסת הערך של פוינטר • ניתן להדפיס את הערך של פוינטר (כלומר, הכתובת) באמצעות מציין הפורמט %p • הערך של הפוינטר מודפס כמספר בבסיס 16 (הספרות 0-9 בתוספת האותיות A-F) מבוא למדעי המחשב - שיעור 8

  16. דוגמה inta=1, b=2; int* p = &b; printf("a = %d and its address is %p\n", a, &a); printf("b = %d and its address is %p\n", b, p); printf("p(hexa) = %p p(decimal) = %d\n", p, p); printf("the address of p is %p\n", &p); a = 1 and its address is 0018FF2C b = 2 and its address is 0018FF20 p(hexa) = 0018FF20 p(decimal) = 1638176 the address of p is 0018FF14 מבוא למדעי המחשב - שיעור 8

  17. כתובת של פוינטר • פוינטר הוא משתנה בפני עצמו וגם לו יש כתובת • כדי לטפל בכתובת של פוינטר יש צורך בפוינטר מיוחד - פוינטר לפוינטר • למשל, כדי לטפל בכתובת של פוינטר ל int יש צורך בפוינטר לפוינטר ל int. • הגדרה: intx; int *p = &x; int**q = &p; הטיפוס עליו מצביע q q הוא פוינטר מבוא למדעי המחשב - שיעור 8

  18. הדגמה: int n=10, *p=&n, *q=(int *)200; double x=3.141592654, *r=&x, **s=&r; printf("n = %d *p = %d\n",n,*p); printf("&n = %p p = %p\n",&n,p); printf("q(hexa) = %p q(decimal) = %d\n",q,q); printf("size of p: %d\n",sizeof(p)); printf("x = %f *r = %f\n",x,*r); printf("r = %p *s = %p\n",r,*s); printf("s = %p &s = %p\n",s, &s); printf("size of r: %d, size of s: %d\n",sizeof(r),sizeof(s)); n = 10 *p = 10 &n = 0018FF2C p = 0018FF2C q(hexa) = 000000C8 q(decimal) = 200 size of p: 4 x = 3.141593 *r = 3.141593 r = 0018FF04 *s = 0018FF04 s = 0018FEF8 &s = 0018FEEC size of r: 4, size of s: 4 לכל הפוינטרים אותו גודל מבוא למדעי המחשב - שיעור 8 int ***

  19. העברת משתנה לפונקציה void swap(int a, int b) { inttemp=a; a=b; b=temp; { int main() } inta=1, b=2; swap(a,b); printf("a=%d, b=%d\n",a,b); return0; { a=1, b=2 aו b לא השתנו שאלה: במה זה שונה מהפונקציה swap שראינו בשיעור הקודם? מבוא למדעי המחשב - שיעור 8

  20. העברה by value • העברת ארגומנט לפונקציה היא העברה by value. • כלומר, הפונקציה מעתיקה את הערך שהועבר לה למשתנה פנימי שלה. • כל שינוי שיתבצע על המשתנה הפנימי של הפונקציה לא ישפיע על הארגומנט החיצוני • לדוגמה, בתוכנית הנ"ל יש שני משתנים שונים בשם a, אחד של הפונקציה main ואחד של הפונקציה swap (וכנ"ל עבור b) מבוא למדעי המחשב - שיעור 8

  21. העברה by value לעומת by reference by reference by value a a main main שונים a swap swap אותו a מבוא למדעי המחשב - שיעור 8

  22. העברה by reference • העברה by referenceהיא העברה בה הארגומנט עצמו מועבר לפונקציה (ולא העתק שלו). • דוגמה: העברה של מערך לפונקציה. • בהעברה by reference מועברת הכתובת של הארגומנט לפונקציה. • העברה של כתובת נעשית באמצעות פוינטר. מבוא למדעי המחשב - שיעור 8

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

  24. העברה by reference • העברה לפונקציה: מעבירים את הכתובת של המשתנה • פרמטר הפונקציה: פוינטר • גישה בתוך הפונקציה:האופרטור * int a=1, b=2; swap(&a, &b); p q a b void swap(int*p, int*q) { int temp = *p; *p = *q; *q = temp; { מבוא למדעי המחשב - שיעור 8

  25. העברת כתובת לפונקציה void swap(int *p, int *q) { int temp = *p; *p = *q; *q = temp; { int main() } int a=1, b=2; swap(&a, &b); printf("a=%d, b=%d\n",a,b); return 0; { a=2, b=1 הפעם, aו bהשתנו מבוא למדעי המחשב - שיעור 8

  26. דוגמה: bubble sort void swap(int *p, int *q) { int temp = *p; *p = *q; *q = temp; { voidbubble_sort(int a[], int size) { inti, j; for(i = size-1; i > 0; i--) for(j=0; j < i; ++j) if(a[j] > a[j+1]) swap(&a[j], &a[j+1]); } מבוא למדעי המחשב - שיעור 8

  27. דוגמה: scanf intx; scanf("%d", &x); כדי ש scanf תוכל לשנות את x יש להעביר לה את הכתובת intx, *p=&x; scanf("%d", p); אפשר להעביר כתובת באמצעות פוינטר intx; scanf("%d", x); שאלה: מה יקרה אם נשכח את האופרטור &? מבוא למדעי המחשב - שיעור 8

  28. אריתמטיקה של פוינטרים • שאלה: אם הערכים שפוינטרים מכילים הם כתובות, מדוע צריך פוינטר מיוחד לכל טיפוס? • תשובה א': האופרטור *. כאשר רושמים *p=5, אופן ההשמה של 5 בתוך הכתובת תלוי בטיפוס המשתנה השמור באותה כתובת • תשובה ב': אריתמטיקה של פוינטרים. פעולתם של אופרטורים אריתמטיים שפועלים על פוינטרים תלויה בטיפוס הפוינטר. מבוא למדעי המחשב - שיעור 8

  29. האופרטור ++ intx, *p=&x; ++p; x p p p אפשר גם p++ (ההבדל ידוע) sizeof(int) char c, *p=&c; ++p; c אפשר גם p++ sizeof(char) double y, *p=&y; ++p; y אפשר גם p++ sizeof(double) מבוא למדעי המחשב - שיעור 8

  30. האופרטור -- x intx, *p=&x; --p; p p p אפשר גם p-- (ההבדל ידוע) sizeof(int) char c, *p=&c; --p; c אפשר גם p-- sizeof(char) y double y, *p=&y; --p; sizeof(double) אפשר גם p-- מבוא למדעי המחשב - שיעור 8

  31. האופרטור + • סינטקס: • דוגמה: • באופן דומה מוגדר גם אופרטור - pointer = pointer + int p q intx, *p=&x; int *q = p+5; x 5*sizeof(int) pointer = pointer - int מבוא למדעי המחשב - שיעור 8

  32. האופרטור - • סינטקס: • דוגמה: int = pointer - pointer q p double x, y; double *p=&x, *q=&y; int n = p-q; x y n = (number of addresses between &y and &x) / sizeof(double) מבוא למדעי המחשב - שיעור 8

  33. פוינטרים ומערכים • מערך הוא פוינטר קבוע: a הוא פוינטר קבוע double a[10]; הדפסת כתובת תחילת המערך a פוינטר קבוע. לא ניתן לשנות את הערך שלו מה אסור? מה מותר? a = &x; printf("%p", a); a++; ++a; *a = 1; זהה ל a[0] = 1 a--; --a; double *p = a+5; זהה ל p = &a[5] int n = p - a; מספר כתובות ה int בין a ל p מבוא למדעי המחשב - שיעור 8

  34. פוינטרים ומערכים • מבחינת סינטקס אפשר להתייחס למערכים ופוינטרים באותה צורה a p inta[10], *p=a, n; מבוא למדעי המחשב - שיעור 8

  35. הערה על העברת מערך לפונקציה • כיוון שמערך הוא פוינטר, כאשר מועבר מערך לפונקציה, למעשה מועברת כתובת • מכאן שהעברה של מערך לפונקציה היא העברה by reference • זאת הסיבה שכאשר מערך מועבר לפונקציה, מועבר המערך עצמו ולא העתק שלו, לכן הפונקציה יכולה לשנות את ערכי המערך המועבר אליה. מבוא למדעי המחשב - שיעור 8

  36. דוגמה: bubble_sort כתיב מערכים כתיב פוינטרים voidbubble_sort( int a[], int n) { registerinti, j; for(i = n-1; i > 0; --i) for(j = 0; j < i; ++j) if(a[j] > a[j+1]) swap(&a[j], &a[j+1]); } void bubble_sort2( int *p , int n) { registerinti, j; for(i = n-1; i > 0; --i) for(j = 0; j < i; ++j) if(*(p+j) > *(p+j+1)) swap(p+j, p+j+1); } מבוא למדעי המחשב - שיעור 8

  37. דוגמה: חיפוש בינארי כתיב מערכים intbinsearch(intnum,int a[],intsize) { intlow = 0; inthigh = size; intmid; while(low < high){ mid = (high + low) / 2; if(num < a[mid]) high = mid; elseif(num > a[mid]) low = mid +1; else returnmid; } return-1; } מבוא למדעי המחשב - שיעור 8

  38. דוגמה: חיפוש בינארי החזרת הכתובת בה נמצא המספר כתיב פוינטרים int *binsearch(intnum, int *start, int*end) { int*low = start; int*high = end; int*mid; while(low < high) { mid = low + (high - low) / 2; if(num < *mid) high = mid; elseif (num > *mid) low = mid +1; else returnmid; } returnNULL; } חיפוש בין שתי כתובות אם המספר לא נמצא, מוחזרת כתובת שלא קיימת מבוא למדעי המחשב - שיעור 8

  39. החזרת כמה ערכים מפונקציה • העברה by reference מאפשרת להחזיר תוצאת חישוב מפונקציה גם דרך אחד הפרמטרים (בנוסף לערך המוחזר הרגיל) • בצורה כזאת ניתן להחזיר יותר מערך אחד מפונקציה • דוגמה לכזאת פונקציה היא scanf. הפונקציה קוראת ערכים מהקלט לתוך הפרמטרים שמועברים אליה (כולם כתובות) וגם מחזירה את מספר הקריאות התקינות שביצעה. מבוא למדעי המחשב - שיעור 8

  40. דוגמה: min_max • הפונקציה מקבלת מערך ומוצאת את הערך המינימלי ואת הערך המקסימלי במערך. כתובת החזרת הערך המינימלי כתובת החזרת הערך המקסימלי voidmin_max(int *a, int *pmin, int *pmax, int n) { inti; *pmin = *pmax = a[0]; for(i=1; i<n;i++) { *pmax=(a[i] > *pmax)? a[i]:*pmax; *pmin=(a[i] < *pmin)? a[i]:*pmin; } } עידכון הערך המקסימלי עידכון הערך המינימלי מבוא למדעי המחשב - שיעור 8

  41. הקצאה דינמית • כפי שראינו בפרק על מערכים, גודל מערך צריך להיות מספר קבוע. • הסיבה לכך היא שכמות הזיכרון המוקצה לתוכנית מתוך הזיכרון ה"רגיל" (ה stack) צריכה להיות ידועה בזמן הידור התוכנית. • לכן, גודל מערך לא יכול להיות ביטוי שיש בו משתנה. למשל, הקוד הבא אינו חוקי: double a[10]; גם אם הערך של n ידוע, גודל המערך לא יכול להכיל שם של משתנה. intn=5; double a[n]; מבוא למדעי המחשב - שיעור 8

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

  43. מערך מוקצה דינמית • כדי להקצות מערך באופן דינמי יש צורך בפוינטר המצביע על טיפוס המערך • ברגע שידוע גודל המערך נשתמש באחת הפונקציות מהספרייה stdlib.h כדי להקצות: • malloc • calloc • realloc להקצאת מערך מטיפוס int int*p; הקצאה ראשונית שינוי גודל ההקצאה מבוא למדעי המחשב - שיעור 8

  44. הפונקציה malloc המרה לטיפוס הפוינטר כתובת תחילת ההקצאה מספר הבתים המוקצים • סינטקס: • פרמטר: מספר שלם המציין את מספר הבתים המוקצים • ערך מוחזר: • אם ההקצאה הצליחה - כתובת תחילת הזיכרון המוקצה • אם ההקצאה לא הצליחה - NULL pointer= (cast) malloc(number of bytes); מבוא למדעי המחשב - שיעור 8

  45. הפונקציה malloc • דוגמה: int n; int*a; printf(“Enter array size: “); scanf(“%d”, &n); if (n>0) a = (int *)malloc(n*sizeof(int)); … a הוא מעכשיו שם המערך המרה לטיפוס הפוינטר a מספר הבתים במערך מבוא למדעי המחשב - שיעור 8

  46. הפונקציה malloc • הערה: הפרמטר של malloc יבוטא בדרך כלל כ • זאת כדי שהתוכנית תתאים למחשבים בהם גודל הטיפוסים הוא שונה מזה שעליו כתבנו את התוכנית. n * sizeof(int) מספר התאים במערך מספר הבתים בכל תא במערך מבוא למדעי המחשב - שיעור 8

  47. המרה לטיפוס הפוינטר • הפונקציה malloc היא פונקציה כללית המתאימה להקצאה מכל טיפוס והערך שהיא מחזירה הוא כתובת מטיפוס סתמי (void *). לכן יש להמיר את הערך המוחזר לטיפוס של הפוינטר שלנו. char *s; int n, m, *a; ... s = (char *)malloc(n*sizeof(char)); a = (int *)malloc(m*sizeof(int)); מבוא למדעי המחשב - שיעור 8

  48. הפונקציה malloc • אחרי כל הקצאה יש לבדוק אם ההקצאה הצליחה. ניסיון לגשת לזיכרון שלא הוקצה יגרום לשגיאת ריצה • a= (int*)malloc(n*sizeof(int)); • if (a==NULL) { • printf("problem allocating memory“); • exit(1); • } • for (i=0;i<n;++i) • scanf(“%d”, &a[i]); אם ההקצאה לא הצליחה תהיה כאן שגיאת ריצה מבוא למדעי המחשב - שיעור 8

  49. הפונקציה calloc כתובת תחילת ההקצאה גודל כל תא במערך • סינטקס: • פרמטרים: • מספר שלם המציין את גודל המערך • מספר שלם המציין את מספר הבתים בכל תא במערך • ערך מוחזר: • אם ההקצאה הצליחה - כתובת תחילת הזיכרון המוקצה • אם ההקצאה לא הצליחה - NULL גודל המערך pointer= (cast) calloc(size of array, size of each cell); מבוא למדעי המחשב - שיעור 8

  50. הפונקציה calloc • דוגמה: int n; int*a; printf(“Enter array size: “); scanf(“%d”, &n); if (n>0) a = (int*)calloc(n, sizeof(int)); … a הוא מעכשיו שם המערך המרה לטיפוס הפוינטר a גודל המערך גודל כל תא במערך מבוא למדעי המחשב - שיעור 8

More Related