המדריך השלם | |||||
|
|
מצביעים (pointers)
אך אם יש בידינו משנה שכבר הוקצה בעבורו מקום בזיכרון ניתן להציב את כתובתו במצביע כך:
אם הכתובת של x היא B206 אזי pi יכיל את כתובת זו. זיכרון דינמי ישנן שתי שיטות לביצוע הקצאת זיכרון: הקצאת זיכרון סטטית והקצאת זיכרון דינאמית. הקצאת זיכרון סטטית - המהדר קובע את דרישות האחסון על פי הצהרת המשתנים (כמובן בזמן ההידור). הקצאת זיכרון דינאמית - הקצאת מקום נעשה בזמן ביצוע התוכנית. (על יד פקודת malloc). מכיוון שהקצאת הזיכרון נעשתה בזמן ביצוע התוכנית יש לדאוג לשחרר את הזיכרון ,נעשה זאת על ידי פקודת free (name_pointer). חשוב מאוד לשחרר את הזיכרון בעבור משתנים שההקצאה עבורם הייתה דינאמית, אם לא נעשה זאת המקום בזיכרון יהיה תפוס ולא נוכל לשנות ולהשתמש בו שוב. מצב זה נקרא דליפת זיכרון - במצב זה מוקצה זיכרון שוב ושוב ואם אין משחררים מגיעים למצב שאין עוד מקום בזיכרון להקצאות נוספות הנדרשות על ידי התוכנית. פונקציה זו נמצאת בספרייה alloc.h. משתנה שבעבורו הייתה הקצאת זיכרון סטטית המהדר דואג לשחרור הזיכרון . נסכם את מה שהסברנו עד כה: מצביע הינו משתנה שמצביע למשתנה אחר, משמע שהמצביע תוכנו הוא כתובת של משתנה אחר. המצביע לא מכיל ערך במובן המקובל אלא מכיל כתובת של משתנה המכיל ערך סטנדרטי. מכיוון שמצביע מכיל כתובת ולא ערך הוא מחולק ל- 2: המצביע המכיל כתובת וכתובת מוצבעת המכילה ערך. נושא המצביעים הוא אחד הנושאים הכי קשים בשפה ועם זאת בעל כוח רב - לדוגמא כשאנו מעוניינים לבנות רשימה של סטודנטים, אין אנו יודעים כמה מקומות להקצות כי יש סטודנטים והרשימה גדלה ולכן פה יש שימוש במצביעים - מכיוון שאין אנו צריכים לדעת מה מספר הסטודנטים אלא אנו יכולים לצרף סטודנטים חדשים לרשימה על ידי שימוש ברשימה מקושרת שהיא מעיין מערך דינאמי. יש בכך חיסכון גדול מכיוון שאם הינו עובדים עם מערך הינו צריכים להגדיר את גודלו ואם באיזשהו שלב היו נגמרים המקומות במערך אז אנו בבעיה וכן בהתחלה מכיוון שידוע כי יתווספו עד סטודנטים לרשימה אנו מקצים שטח זיכרון רב מדי (שכרגע הוא מבוזבז) - כל זאת נמנע אם משתמשים במצביעים. הערה: בפונקצית ההדפסה יש להשתמש בתבנית ההמרה p% בכדי להדפיס כתובת. דוגמא 1:
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט
הערה: כשמצהירים על מצביע p אין הוא מצביע על ערך "חוקי", חובה להציב ערך כתובת לפני שמשתמשים בערך המוצבע ( *p). לדוגמא:
פעולות אלו ייצרו טעויות העלולות לגרום לקריסת התוכנית. הדרך הנכונה היא:
דוגמא 2:
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט
מצביע כפרמטר של פונקציה נפתח בדוגמא: דוגמא 3:
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט
הערה לדוגמא: אם הפרמטרים של פונקצית swap לא היו מצביעים לא היינו מקבלים את אותה התוצאה: הערכים (בלבד) של iו- j היו נשלחים לפונקציה וערכי i ו-j המקוריים היו נשארים כמו שהם. הערה: מומלץ לעקוב אחר כל הכתובות של המשתנים ואחר השינויים. הערה: כאשר קוראים לפונקציה עם ארגומנטים מסוימים, המהדר מקצה זיכרון לפרמטרים של הפונקציה. זיכרון זה משתחרר כאשר הפונקציה מסתיימת, ולכן אם נתבונן בכתובת של הפרמטרים נראה שהם שונים מהכתובות של הארגומנטים ששלחנו, אך אם שולחים לפי כתובת (&) הכתובות של ערכי הארגומנטים והפרמטרים שווים וזאת מכיוון שאין המהדר מקצה זיכרון לפרמטרים אלא משתמש בכתובות של הארגומנטים ששלחנו. אין זה פלא שכאשר קוראים למספר פונקציות ערכי הפרמטרים זהים וזאת מכיוון ששוחרר הזיכרון בסיומה של כל פונקציה וכעת ניתן להשתמש באותו זיכרון בעבור הפונקציות הבאות. דוגמא 4:
כל השאר מתקיים כפי שהסברנו למעלה. הרחבה של הדוגמא הקודמת: דוגמא 5:
כתובת של מערך כשדנו במערכים טענו כי המהדר מקצה זיכרון לבלוק זיכרון רציף בהתאם לגודלו של המערך (והסברנו גם איך מחשבים את הכתובת של כל איבר בזיכרון). כעת ברור כי מערך הוא בעצם מצביע, וזאת מכיוון שידוע לנו הכתובת של המערך (הכתובת של האיבר בראשון) וכל שאר האיברים נגישים לפי ההיסט מהכתובת של האיבר הראשון.בעצם המצביע (המערך) מכיל את הכתובת הראשונה (האיבר הראשון). כאשר המערך הוא של שלמים המצביע יהיה מסוג שלם ואם המערך הוא של ממשים אז גם המצביע יהיה מסוג ממשי. לדוגמא: נצהיר על מערך של שלמים בגודל 5 int arr_i[5];
כל התאים הם מסוג שלם , שם המערך arr_i יהיה משתנה מצביע מסוג שלם ומכיל את הכתובת של האיבר הראשון במערך (2000). ניתן לגשת לאיבר הראשון במערך באופן הבא:
כשאר כותבים arr_i+1 הכוונה לכתובת הבאה אחרי הכתובת של arr_i ומכיוון שהמערך שלפנינו הוא מסוג שלם הקפיצות יהיו של שני בתים (בניגוד לאינטואיציה הקובעת שהקפיצות יהיו בבית אחד). העניין נעוץ באריתמטיקה של מצביעים: לדוגמא אם p הוא מצביע מסוג ממשי אזי הפעולה p++ תקדם את p ב 4 בתים וזאת מכיוון שגודלו בזיכרון של ממשי הוא 4. (ניתן לבצע גם הגדלה מאוחרת ומוקדמת של מצביעים , חיסור וחיבור בשלם). דוגמא 6:
דוגמא 7:
העבר את העכבר מעל הדוגמא כדי לראות הסבר מפורט
פקודת typedef ניתן להגדיר מחדש כל טיפוס על ידי פקודת typedef . תבנית: typedef character new_character; יחליף את הטיפוסcharacter בטיפוס החדש new_charecter - כלומר אם נגדיר משתנה מהטיפוס החדש הוא יתייחס אליו כמו בהתייחסותו אל משתנה שהוגדר כטיפוס הישן. לדוגמא:
האופרטור sizeof האופרטור sizeof מחזיר את גודלו של הטיפוס בבתים. תבנית: sizeof (name_of_charecter) לדוגמא: sizeof (float) - מחזיר 4. אילו הינו כותבים:
הכללת הקובץ alloc.h: ספרייה זו מכילה פונקציות בעבור פעולות של הקצאה ושחרור זיכרון. כבר דיברנו על הפקודה free לשחרור זיכרון שהוקצה באופן דינאמי. הקצאה דינמית - malloc כעת נכיר את הפונקציה להקצאת זיכרון בזמן ביצוע malloc - לשם כך יש צורך להגדיר מצביע למערך ולציין את גודל המערך בבתים. כפי שהסברנו מצביע למערך יכיל את הכתובת של האיבר הראשון במערך. גודלו של המערך הוא: גודל הטיפוס*מספר איברים במערך לכן מערך של שלמים בגודל 5 יכיל 10 בתים (כי כל שלם תופס 2 בתים). התבנית לשימוש בפונקציה malloc(): variable_pointer = (pointer_type) malloc (size_of_memory); דוגמא:
הקצאת זיכרון בעזרת הפונקציה calloc בפונקציה malloc יש צורך לחשב את גודל מקום בזיכרון. בפונקציה calloc אין צורך לעשות זאת אלא רק לשלוח כארגומנטים לפונקציה מספר האיברים הדרוש וכארגומנט השני גודל כל איבר. התבנית לשימוש בפונקציה calloc(): variable_pointer = (casting) calloc (num_of_elements, size_of_element); יש צורך ב casting לפני הפקודה calloc (כמו בפונקציה (malloc לפי סוג הטיפוס שבעבורו מקצים זיכרון. דוגמא:
הערה: כאשר מקצים זיכרון הערך המוחזר על ידי פונקצית הקצאת זיכרון היא כתובת. יש מקרים שבעבורם הקצאת הזיכרון נכשלת והערך המוחזר על ידי הפונקציה היא null ומכיוון שלא קיבלנו כתובת לא ניתן להשתמש במצביע כמצביע "חוקי" ונרצה לסיים את התוכנית. לכן לאחר הקצאת זיכרון תמיד צריך לבדוק אם ההקצאה הצליחה. דוגמא 8:
שינוי גודל מערך דינאמי בזמן ביצוע מערך דינאמי הוא בעצם מערך שניתן לשנות את גודלו. מצהירים על מצביע בגודל מסוים (מספר איברים כרצוני) על ידי הפונקציות שהזכרנו malloc או calloc, כעת נרצה לשנות את גודלו של המערך בזמן ביצוע התוכנית לשל כך נשתמש בפונקציה realloc. התבנית לשימוש בפונקציה realloc(): variable_pointer = (casting) realloc (variable_pointer, size_of_memory); דוגמא 9:
|