Presentation is loading. Please wait.

Presentation is loading. Please wait.

מצביעים והקצאה דינאמית

Similar presentations


Presentation on theme: "מצביעים והקצאה דינאמית"— Presentation transcript:

1 מצביעים והקצאה דינאמית
Engineering Programming A תרגול 9 מצביעים והקצאה דינאמית (וחזרה על רקורסיות) Introduction to C - Fall Amir Menczel

2 מטרת התרגול מצביעים (חזרה) הקצאת זיכרון דינאמית רקורסיות (אם ישאר זמן)
רקע אופרטורים על מצביעים מצביעים כפרמטרים לפונקציה הקצאת זיכרון דינאמית רקורסיות (אם ישאר זמן)

3 אופרטורים מצביעים מצביעים (pointers): מצביע הינו משתנה שהערך שלו הוא כתובת של משתנה כלשהו. במילים אחרות, מצביע הינו משתנה שמצביע למשתנה אחר. אם נרצה להגדיר את p כמצביע למשתנה כלשהו מטיפוסint , שורת ההצהרה תיראה כך: int *p; באופן כללי, תבנית הצהרה על מצביעים הינה: <variable-type> *<variable name>; האופרטור & אם x הוא משתנה אזי &x היא כתובת הזיכרון של x, כלומר האופרטור & מציין "כתובתו של…". האופרטור  האופרטור  הינו אופרטור שפועל על מצביעים. למשל עבור הדוגמה הקודמת אם היינו כותבים בהמשך p=&x(כאשר x הוא משתנה מסוג int ) *p היה שקול לx- כלומר *p הוא התוכן של תא הזיכרון ש-p מצביע עליו. אופרטורי הקידום: +,- פעולות אריתמטיות של קידום ב-k מקדמת את הפוינטר ב k*sizeof(type) בתים.

4 תאור סכמתי של הזיכרון 200 466675 x p
int x = 200 ; int p; /*Declaration of a pointer “p” that is of type “int *”.*/ p=&x; /*Assign the address of “x” to be the value of “p”.*/ בשלב זה המשתנה p מכיל את הכתובת של המשתנה x. הערה: לשם פשטות מתואר המצב בציור בו לטיפוס המצביע ולמשתנה int מוקצה לכאורה בית אחד בלבד אולם במציאות דרושים מספר בתים לייצג שני טיפוסים אלה.

5 דוגמאות int x = 200 ; int p; /*Declaration of a pointer “p” that is of type “int *”.*/ p=&x; /*Assign the address of “x” to be the value of “p”.*/ printf(“%p”, p) ;//print the address of x printf(“%d”, *p) ; //print 200 p=500; // < שקול= => x=500;. זהירות בעת שימוש במצביעים: כשמצהירים על מצביע p אין הוא בהכרח מצביע על ערך חוקי! int *pi; *pi = 100; /*wrong!!!! to correct : do before this operation : ” pi = <concrete address>; ” */ טעות זו ידועה בשם segmentation fault ,זוהי אינה שגיאת קומפילציה, אלא, שגיאה בזמן ריצה. מצביעים למצביעים - כאמור מצביע הוא משתנה המכיל כתובת של משתנה. אותו משתנה מוצבע יכול עקרונית גם הוא להיות מצביע. לדוגמה נוכל להוסיף לקוד: int **p2=&p printf(“%d”,**p2); //print 200

6 העברת מצביעים כפרמטרים לפונקציה מכיוון שמצביעים הם משתנים לכל דבר, ניתן להעביר את ערכיהם בתור פרמטרים לפונקציות - מנגנון זה מאפשר לפונקציה נקראת לשנות את ערכיהם של משתנים בסביבה הקוראת (מאותה סיבה מצביעים יכולים להיות גם ערך החזרה של פונקציה). לדוגמא ,נכתוב פונקציה אשר מחליפה בין ערכיהם של זוג משתנים מטיפוס int: void swap(int a, int b){ int tmp = a; a = *b; *b = tmp; } כתובת של מערך שמו של מערך דומה למצביע המייצג את הכתובת של האיבר הראשון בתוך המערך. על אף הדמיון הרב, קיים גם שוני בכך שמערך מייצג כתובת קבועה, שאינה ניתנת לשינוי במהלך התוכנית, בניגוד למצביע.

7 ניהול הזיכרון בתוכנית עד כה כל המשתנים שראינו היו לוקאליים. משך הקיום של משתנים מקומיים הוא הזמן אשר הפונקציה בה הם נמצאים פועלת. משתנים אלה מאוחסנים בכל כניסה לפונקציה במקום בזיכרון שנקרא מחסנית שהוא למעשה "שולחן העבודה" של הפונקציה אשר רצה ברגע הנתון. ברגע שהפונקציה סיימה את עבודתה, "שולחן העבודה" שלה על משתניו כבר אינו רלוונטי ולכן אותו קטע בזיכרון יכול לשמש את הפונקציות הבאות בתור לרוץ. לעיתים כאשר ברצוננו להקצות מקום בזיכרון למשתנה כך שגודלו אינו ידוע לנו מראש (למשל בהצהרה על מערך אנו מבזבזים הרבה זיכרון רק משום שאנו לא יודעים מראש את הגודל המתאים לקלט) אנו זקוקים להקצאה זיכרון דינאמית בזמן ריצת התוכנית. סיבה נוספת להקצאה דינאמית היא רצון למשל ליצור מבנה נתונים שישמש לאורך כל התוכנית ולא רק בפונקציה בה נוצר. בהקצאת זיכרון דינאמית – בשונה מהקצאה סטטית המקום המוקצה בזיכרון נמצא בחלק הנקרא ערימה וקיומו שם משך כל זמן ריצת התוכנית ולא רק בפונקציה שם הוא נוצר. בנוסף בניגוד להקצאה סטטית בהקצאה דינאמית על המשתמש חובת שחרור זיכרון מפורשת עם סיום התוכנית (או אפילו קטע הקוד המשתמש באותו משתנה שהוגדר דינאמית).

8 זיכרון דינמי ישנן שתי שיטות לביצוע הקצאת זיכרון: הקצאת זיכרון סטטית והקצאת זיכרון דינאמית. הקצאת זיכרון סטטית - המהדר קובע את דרישות האחסון על פי הצהרת המשתנים, בזמן הקומפילציה (כך הקצאנו זיכרון עד כה!). בעיה שלעיתים צצה היא שאין אנו יכולים לנחש מראש את כמות הזיכרון שהתוכנית שלנו עלולה לצרוך. הקצאת זיכרון דינאמית - הקצאת מקום נעשית בזמן ריצה על ידי קריאה לפונקציה malloc() (קיצור ל-memory allocation). פונקציה זו מקבלת כפרמטר את מס' הבתים שברצוננו להקצות ומחזירה את הכתובת של הבית הראשון ברצף הבתים שהקצאתה. אם הפונקציה נכשלת מוחזר הערך NULL. שחרור זיכרון דינאמית - מכיוון שהקצאת הזיכרון נעשתה בזמן ריצת התוכנית, יש לדאוג לשחרר את הזיכרון לאחר שנסיים להשתמש בו. שחרור של זיכרון דינאמי נעשה ע"י קריאה לפונקציה free(). הפונקציה מקבלת מצביע לכתובת תחילת קטע הזיכרון שרוצים לשחרר. תוכנית שאינה משחררת זיכרון נחשבת לא תקינה ויכולה לגרום לבעיות ברמת הגורם המפעיל אותה.

9 הקצאת זיכרון דינאמית שימוש בפונקציות: malloc ,free #include <stdlib.h> //must add this library pointer_variable = (pointer_type) malloc (size_of_memory); דוגמה: int size, *p_list; printf("Enter the number of elements:"); scanf("%d", &size); p_list = (int*)malloc (size * sizeof(int)); …. free(p_list); שחרור הזיכרון בסיום השימוש בו

10 דוגמא להקצאת ושחרור זיכרון
כאשר מקצים זיכרון הערך המוחזר על ידי פונקצית הקצאת זיכרון היא כתובת. יש מקרים שבעבורם הקצאת הזיכרון נכשלת והערך המוחזר על ידי הפונקציה הוא NULL. מכיוון שלא קיבלנו כתובת, לא ניתן להשתמש במצביע כמצביע "חוקי" ונרצה לסיים את התוכנית. לכן, לאחר הקצאת זיכרון תמיד צריך לבדוק האם ההקצאה הצליחה. void main(){ long *l_list; l_list =(long*) malloc (5*sizeof(long)); if (l_list == NULL){ printf ("Failed to allocate memory"); return; } … free(l_list); }

11 דוגמא נתבונן בתוכנית הבאה: printf("%d",x[0]);
#include <stdio.h> #include <stdlib.h> int* foo (); int* foo (){ int arr[3]={1,2,3}; return arr; } void main(){ int* x = foo (); printf("%d",x[0]); התנהגות תוכנית זו אינה מוגדרת מכיוון שהפונקציה מחזירה מצביע למערך לוקאלי. כאשר הפונקציה מסתיימת הזיכרון בו נמצא המערך אינו חוקי עוד לשימוש. נתקן את הפונקציה בצורה הבאה: int *arr = (int*) malloc(sizeof(int)*3); arr[0]=1; arr[1]=2; arr[2]=3; לאחר סיום ריצת הפונקציה ולאחר שסיימנו להשתמש במערך, נדאג לשחרר את הזיכרון ע"י הוספת הפקודה הבאה ל- main או לכל פונקציה אחרת בה זיהינו כי אין עוד צורך בזיכרון שהוקצה. free(arr);

12 תרגיל 1 תוכנית להדגמה של מערך דו-מימדי דינאמי: כשרוצים להגדיר מערך דו מימדי בעל גודל משתנה, עלינו ליצור מערך של מצביעים למערכים. גודל המערך לא ידוע בתחילת התכנית ולכן נגדיר את המערך באמצעות פונקצית malloc(). בתחילה נאתחל מערך של מצביעים, ולאחר מכן נאתחל כל מצביע להיות מערך של ה- type הרצוי. למשל, יצירת מערך של int-ים בגודל המוגדר ע"י המשתמש שבכל איבר בו יש את הערך של מכפלת האינדקסים שלו:

13 תרגיל 1 #include <stdio.h> #include <stdlib.h> void main(){ int i, j, k, rows, cols; int **array; printf("enter num of rows: "); scanf("%d",&rows); printf("enter num of columns: "); scanf("%d",&cols); if ( !(array=(int **)malloc(rows*sizeof(int *)) )){ printf("Memory allocation failed, quiting… "); return; } . . .

14 המשך תרגיל 1 for (i=0; i<rows; i++) } /*Fill in the different rows:*/ if ( !(array[i]=(int *)malloc(cols*sizeof(int)) ))} for (k=0; k<i; k++) /*Free all priory allocated memory:*/ free( array[k] ); free(array); printf("Memory allocation failed, quiting… "); return; /*Terminate the program!*/ } for (j=0; j<cols; j++) /*Fill in the different columns:*/ array[i][j] = i*j; for (i=0; i<rows; i++){ /*Print the different rows:*/ for (j=0; j<cols; j++) printf("%d ",array[i][j]); printf("\n");

15 המשך תרגיל 1 /*Free allocated memory:*/ for(i=0; i<rows; i++) free(array[i]); free(array); }

16 2תרגיל צריך לממש פונקציה void transpose(int** arr, int n)
arr – מערך דינאמי דו-מימדי בגודל n X n. (מייצג מטריצה ריבועית) n – גודל המימד הריבועי של המטריצה. הפונקציה תשנה את המערך הדו-מימדי ע"י ביצוע פעולת transpose עליו. אם לפני הקריאה לפונקציה arr[i][j] = x ו – arr[j][i] = y, אזי אחרי סיום ריצת הפונקציה arr[i][j] = y ו – arr[j][i] = x. שימו לב – השינוי הוא in-place (ללא מערך עזר).

17 תרגיל 2 - פתרון void tranpose (int** arr, int n) { int i, j, tmp;
for (i=0; i < n; i++) for (j=i+1; j < n; j++) { tmp = arr[i][j]; arr[i][j] = arr[j][i]; arr[j][i] = tmp; }

18 תרגיל 2 ב' צריך לממש פונקציה int** transpose2(int** arr, int n)
arr – מערך דינאמי דו-מימדי בגודל n X n. (מייצג מטריצה ריבועית) n – גודל המימד הריבועי של המטריצה. הפונקציה תייצר מערך דו-מימדי חדש, שערכי אבריו יהיו אברי המערך הדו-מימדי המקורי לאחר ביצוע פעולת transpose.

19 תרגיל 2 ב' - פתרון int** tranpose2 (int** arr, int n) { int i, j, **tmp_arr; if (! tmp_arr = (int**)malloc(sizeof(int*) * n)) return NULL; for (i = 0; i < n; i++) { if (! tmp_arr[i] = (int*)malloc(sizeof(int) * n)) { for (j = 0; j < i; j++) free(tmp_arr[j]); free(tmp_arr); } for (j = 0; j < n; j++) tmp_arr[i][j] = arr[i][j]; transpose(tmp_arr, n); return tmp_arr; היינו יכולים בשלב הזה לבצע את ה- Transpose בעצמנו, אבל העקרון המודולרי היה עובד גם עבור פעולות מסובכות יותר, כאלה שלא בהכרח היינו יודעים/רוצים לממש בעצמנו.

20 תרגיל 3 התכנית לא מדפיסה כלום. יש שגיאה בזמן ריצה .(run time error)
עיין בקטע הבא וסמן את כל התשובות הנכונות (הנח כי כל הקצאות הזיכרון מצליחות): #include <stdio.h> #include <stdlib.h> #define MAX 10 void main(){ int *ptr, *arr[MAX]; int i, j; for (i=MAX-1; i>=0; i--) if (arr[i] = (int *) malloc(i * sizeof(int))) for (j=0; j<i; j++) *(*(arr+i)+j) = j*i; //same as arr[i][j]=j*i ptr = *(arr+MAX-1); while (*ptr) printf ("%d ", *ptr--); }    התכנית לא מדפיסה כלום. יש שגיאה בזמן ריצה .(run time error) התכנית תדפיס: התכנית תדפיס אינסוף אפסים. התכנית תדפיס 0. התכנית תדפיס ערכים לא ידועים. אף לא אחת מהתשובות לעיל.

21 פתרון תרגיל 3

22 תרגיל 7: נתונה הפונקציה הבאה:
char *search(char *str1, char *str2){ int i, j, k, length; char *temp, *aux=NULL; for(i=0, length=0; *(str1+i); i++) for(j=0; *(str2+j); j++){ for(k=0; *(str1+i+k) && *(str1+i+k) == *(str2+j+k); k++); if( k>length ){ length=k; aux=str1+i; } //if } //for j if(aux) if( temp = (char *)malloc(length+1) ){ for(i=0; i<length; i++) *(temp+i )= *(aux+i); *(temp+i)='\0'; return temp; return NULL; } תרגיל 7: נתונה הפונקציה הבאה:

23 תרגיל 7 הסבר בקצרה מה יעודה של הפונקציה הנ"ל. מה הפלט של קטע קוד הבא:
מה הפלט של קטע קוד הבא: char sentence1[]=”It is clever of him to solve the problem”; char sentence2[]=”I am glad to solve your problem”, *sentence3; sentence3 = search(sentence1, sentence2); puts(sentence3);

24 תרגיל 7 - תשובות הפונקציה מוצאת את המחרוזת המשותפת הארוכה ביותר לשתי המחרוזות הניתנות. הפונקציה מעתיקה את המחרוזת שנמצאה למחרוזת חדשה ומחזירה מצביעה לתחילתה. במקרה שלא קיימת מחרוזת משותפת או שהקצאת הזיכרון נכשלה, הפונקציה מחזירה NULL.   2. “ to solve “

25 חזרה נוספת על רקורסיות

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

27 תרגיל 1 - פתרון int divideWeights(int values[], int index, int iBag1, int iBag2) { if ( (index == SIZE) && (iBag1 == iBag2) ) return 1; else if ( index == SIZE ) return 0; return ( divideWeights(values, index+1, iBag1+values[index], iBag2) || divideWeights(values, index+1, iBag1, iBag2+values[index]) ); }

28 תרגיל מס' 2 נתון מערך דו-מימדי בגודל m*n המכיל מספרים טבעיים קטנים מ-100. מסלול חוקי במערך מתחיל בתא (0,0) ומסתיים בתא (m-1,n-1), כאשר ההתקדמות תלויה בספרת האחדות והעשרות של המס' שבתא הנוכחי. אם בתא (2,3) רשום המס' 13, אז ישנם 2 דרכים להתקדם מתא זה: 1) 1+ בשורות ו 3+ בעמודות. כלומר לתא (3,6). 2) 3+ בשורות ו 1+ בעמודות. כלומר לתא (5,4). אם בתא רשום מס' חד ספרתי, למשל 3, נתייחס אליו כאל 03.

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

30 תרגיל מס' 2 (פתרון)

31 תרגיל מס' 3 כתבו פונקציה רקורסיבית void X(int lines) שמדפיסה את האות X
באמצעות כוכביות ב-lines שורות (נניח ש-lines תמיד אי-זוגי). ניתן להשתמש בלולאות. לדוגמא עבור X(9) יודפס:  * * * * * * * * * * *  הכוכבית הראשונה חייבת להופיע בתחילת השורה הראשונה. אין להגדיר פונקציה נוספת.

32 פתרן תרגיל מס' 3 פתרון: הרעיון הוא להדפיס את השורה הראשונה של X, אחר כך לפתור את תת-הבעיה עבור X ללא השורה הראשונה והאחרונה ואז להדפיס את השורה האחרונה (שהיא זהה לראשונה). ניתן להדגים את סדר הפעולות הכרונולוגי בכל קריאה רקורסיבית באמצעות התרשים הבא:

33 פתרון תרגיל מס' 3 void X(int lines){ int static blank; int I; for (i=0; i<blank; i++) putchar(' '); putchar('*'); if(lines==1){ puts(""); return; } for (i=0; i<lines-2; i++) puts("*"); blank++; X(lines-2); blank--; for (i=0; i<blank; i++) putchar(' '); }


Download ppt "מצביעים והקצאה דינאמית"

Similar presentations


Ads by Google