Download as pdf or txt
Download as pdf or txt
You are on page 1of 7

‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫נושא ‪ -2‬מערכים‪ ,‬מצביעים ומחרוזות‬


‫מערכים חד ממדיים‪-‬‬

‫מערך‪ -‬רעיונית כמו שאנחנו מכירים מג'אווה‪ .‬גם האתחול של מערכים יכול להתבצע באופן זהה לזה שבג'אווה‪.‬‬
‫חשוב לזכור שבמערך‪ ,‬כל הערכים שמורים ברצף בזיכרון‪ ,‬ולכן קל לגשת אליהם‪.‬‬
‫נשים לב שישנן מספר דרכים אפשריות לאתחל מערך‪:‬‬

‫בשורה הראשונה זה הדבר הטריוויאלי‪ ,‬מקצים גודל למערך ואת הערכים שנמצאים בו‪ .‬בשורה השנייה רואים שמגדירים‬
‫את הערך של המערך ומייחסים רק ערך אחד בסוגריים המסולסלים ‪ -‬ו ב כ ך מ ק ב ע י ם א ת כ ל א י ב ר י ה מ ע ר ך ל ה י ו ת מ ס פ ר ז ה ‪.‬‬
‫בדוגמא השלישית אנחנו רואים שאם אנחנו שמים את הערכים במערך מתי שיוצרים אותו‪ ,‬לא חייב להקצות לו גודל כי‬
‫הקומפיילר כבר רואה כמה ערכים יש בו‪.‬‬
‫בשורה הרביעית רואים דוגמא שמגדירים מערך של אותיות על ידי מעבר מחרוזת‪ .‬מעך זה יכיל את כל תווי המחרוזת פלוס‬
‫התו המיוחד שדיברנו עליו בשיעור הקודם שמייצג ס י ו ם מ ח ר ו ז ת ‪.‬‬

‫ה ע ר ה‪ :‬ב נ י ג ו ד ל מ ה ש ר א י נ ו ב פ י י ת ו ן א ו ג ' א ו ו ה ‪ ,‬ש ב מ י ד ה ו ה י י נ ו ר ו צ י ם ל ג ש ת ל א י נ ד ק ס ל א ח ו ק י ב מ ע ר ך א ז ה י י ת ה מ ו ד פ ס ת‬


‫שגיאה ולא קרה כלום‪ ,‬ב‪ C-‬הקומפיילר מעביר את האחריות אלינו ובמידה וננסה לגשת לאינדקס לא חוקי יקרו כל מיני‬
‫דברים מוזרים בזיכרון‪ ,‬דברים שאחרי זה יהיה קשה לזהות או לתקן‪ .‬לכן חשוב מאד לדאוג שבתוכנית תמיד אנחנו ניגשים‬
‫לאינדקסים תקינים וחוקיים שבתחום גודל המערך‪.‬‬

‫מצביעים ‪-Pointers‬‬

‫אופרטור & ‪ -‬אופרטור אונרי שעובד רק על ארגומנט אחד‪ .‬התפקיד שלו לקחת כלשהו משתנה ולהחזיר את הכתובת שלו‬
‫בזיכרון‪.‬‬
‫נשים לב שהזהות של הזיכרון שתוחזר לא כל כך מעניינת אותנו‪ ,‬אבל ניתן באמצעות כתובת זו לעשות כל מיני שימושים‬
‫ש י ע ז ר ו ל נ ו ל ב צ ע פ ע ו ל ו ת ב ת ו כ נ י ת ‪ .‬ב ע צ ם ה מ ש ת נ ה ‪ p‬אמנם מכיל כתובת‪ ,‬אבל באופן רעיוני ניתן להתייחס אליו כפשוט מצביע‬
‫קונקרטי לאותו משתנה ‪ -a‬דרכו ניתן לעשות שימוש בערך הקונקרטי של משתנה ‪.a‬‬
‫משתנה מצביע הוא משתנה שמכיל כתובת של משתנה אחר‪ .‬נצהיר על משתנה מצביע על ידי כתיבת * בתחילת השם של‬
‫המשתנה‪.‬‬
‫למשל אם יש לנו משתנה אינטג'ר של מספר בשם ‪ ,a‬נרצה להגדיר למשל ‪ int *p‬שזה יהיה משתנה שיכיל את הכתובת של‬
‫אותו משתנה ‪ .p‬נשים לב שכאשר אנחנו מגדירים את המשתנה ‪ *p‬הטיפוס שנרשום לפני יהיה הטיפוס של המשתנה שאובייקט‬
‫זה יצביע אליו‪ .‬כלומר‪ ,‬למרות שב‪ *p -‬בפועל תמיד יהיה כלשהו מספר של כתובת בינארית‪ ,‬הטיפוס שלו נקבע לפי האובייקט‬
‫שעליו משתנה זה יצביע‪.‬‬
‫מקרה פרטי הוא אם נגדיר פוינטר כ‪ 0 -‬או ‪ NULL‬ואז זה כמו להגיד פוינטר שמצביע לכלום‪.‬‬

‫‪1‬‬ ‫‪Tel-Aviv University‬‬


‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫בעצם המשתנה המצביע נותן גישה יותר ישירה לזיכרון של משתנים שונים‪ ,‬בניגוד לבשפות תכנות אחרות בהם אנחנו‬
‫מתייחסים למשתנה ככלשהו אובייקט סגור‪.‬‬

‫אופרטור * )כאשר מופיע כאופרטור אונרי(‪ -‬אם נפעיל את האופרטור הזה על אובייקט הצבעה ‪ ,p‬אנחנו נשלוף את הערך של‬
‫המשתנה עצמו שאליו מצביע ‪ ,a‬וכך למשל נוכל לשייך ערך זה למשתנה נוסף‪.‬‬

‫בדוגמא הנ"ל למשל אם יהיה לנו כלשהו משתנה מספרי ‪ a‬בעל ערך ‪ ,1‬ונרצה לבצע השמה למשתנה חדש ‪ b‬כך שיכיל את אותו‬
‫הערך של משתנה ‪ ,a‬נעשה את זה בצורה הזאת‪ -‬נגדיר את ‪ p‬כמשתנה ‪ Pointer‬ונשים בו את הכתובת של המשתנה ‪ ,a‬ובכך‬
‫הוא יהיה משתנה מצביע שמכיל את כתובתו‪ .‬כעת בעזרת אופרטור *‪ ,‬נוכל לבצע השמה למשתנה ‪ b‬שיכיל את הערך של‬
‫ה מ ש ת נ ה ע ל י ו מ צ ב י ע ‪.p‬‬
‫נשים לב להבדל חשוב – כאשר אנחנו רושמים ‪ *p‬בחלק ההצהרה של משתנה ‪ p‬זה אומר שאנחנו מגדירים את משתנה ‪p‬‬
‫להיות כלשהו פוינטר‪ .‬לעומת זאת‪ ,‬כאשר אנחנו משתמשים ב‪ *-‬בחלק ההשמה של הביטוי )צד ימין( כמו בשורה האחרונה‬
‫של התמונה למעלה‪ ,‬אנחנו משתמשים בו לא כמשהו שמהווה אינדיקציה אלא כאופרטור ממש שמבצע את הפעולה של לשים‬
‫במשתנה ‪ b‬את הערך של המשתנה המקורי ש‪ p-‬מצביע עליו )במקרה הזה ‪.(a‬‬

‫בצורה נוחה ניתן להסתכל על זה באופן הבא‪:‬‬


‫כאשר הצהרנו על המשתנה על ידי ‪ ,*p‬אמרנו בעצם שהוא יהיה פוינטר‪.‬‬
‫כשאר נרצה לבצע לקבוע לאיזה משתנה אותו ‪ p‬יצביע‪ ,‬נרשום = 𝑝 ואז את המשתנה שנרצה לאכסן את הכתובת שלו בפויינטר‬
‫‪ .p‬כעת‪ ,‬כל פעם שנרשום ‪ ,*p‬עם הכוכבית‪ ,‬המשמעות של זה תהיה ממש הערך שמאוכסן במשתנה שפוינטר ‪ p‬מצביע עליו‪.‬‬
‫לעומת זאת‪ ,‬אם מסיבה לא ברורה נרצה לראות שוב את הכתובת של אותו משתנה שאליו ‪ p‬מצביע‪ ,‬נשתמש ב‪ p -‬ללא‬
‫הכוכבית לפני‪.‬‬

‫מתי זה שימושי?‬
‫ראינו שבשפת ‪ C‬העברת ארגומנטים לפונקציה נעשית ‪ .By Value‬כלומר כאשר אנחנו מכניסים משתנים כארגומנטים של‬
‫פונקציה נוצר "עותק" זמני של משתנים אלו כמשתנים לוקאלים של הפונקציה‪ ,‬כך שמתי שנעשה פעולות על משתנים‬
‫לוקאלים אלו בתוך בלוק הפונקציה זה לא ישפיע על הערך של המשתנים המקוריים שהכנסנו לפונקציה אלא רק על הנוכחות‬
‫הזמנית שלהם בתוך בלוק זה‪ ,‬ואם נרצה לעשות שימוש בערכים של משתנים זמניים אלו נצטרך להעיף אותם החוצה על ידי‬
‫פקודת ‪.Return‬‬

‫‪2‬‬ ‫‪Tel-Aviv University‬‬


‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫נראה כעת שבעזרת אותם פויינטרים‪ ,‬נוכל להגדיר פונקציות כך שהעברת הארגומנטים תתבצע ‪ ,By Reference‬כלומר כאשר‬
‫נכניס משתנים לפונקציה כל הפעולות שיתבצעו עליהם בבלוק הפונקציה יתבצעו ממש על ערכי המשתנים המקוריים‬
‫שהעברנו‪ ,‬ולא על עותקים זמניים לוקאלים‪.‬‬

‫נסתכל על הדוגמא הבאה‪:‬‬

‫בפריים ה‪ Main -‬הגדרנו משתנים מספריים ‪ i‬ו‪ .j -‬בנפרד הגדרנו את הפונקציה ‪ ,Swap‬ואז מה שהכנסנו לפונקציה זו‬
‫כ א ר ג ו מ נ ט י ם ז ה א ת ה כ ת ו ב ו ת ש ל ה א ו ב י י ק ט י ם ה מ ס פ ר י י ם ‪ i‬ו ‪ ,j -‬ע ל י ד י ה א ו פ ר ט ו ר ש ל & ‪.‬‬
‫נשים לב כעת איך מוגדרת הפונקציה ‪ -Swap‬הארגומנטים איתה הפונקציה תעבוד הם ‪ ,*p, *q‬כלומר שניהם הוגדרו‬
‫כפוינטרים‪ .‬כאשר הכנסנו לארגומנטים של הפונקציה את הכתובות של משתנים ‪ i‬ו‪ ,j -‬זה כאילו רשמנו‬
‫‪𝑝 = & 𝑖,‬‬ ‫𝑗 & = 𝑞‬
‫כלומר במילים אחרות המשתנה ‪ p‬מכיל את הכתובת של המשתנה ‪ i‬מבחוץ ואילו המשתנה ‪ q‬מכיל את הכתובת של משתנה‬
‫‪ j‬מבחוץ‪ .‬העברה זו של פרמטרים היא ‪ ,By Value‬כמו תמיד בפונקציות‪ ,‬כלומר נוצרו משתנים לוקאליים זמניים ‪ p‬ו‪q -‬‬
‫המכילים את הכתובות הנ"ל‪.‬‬
‫לעומת זאת‪ ,‬נשים לב שבשורת הגדרת הפונקציה הגדרנו את ‪ p‬ו‪ q -‬להיות פויינטרים‪ .‬כלומר‪ ,‬מה שבאמת נעבוד איתו‬
‫בפונקציה זה אותם ‪ *p, *q‬שהם האובייקטים מבחוץ שפוינטרים אלו מצביעים עליהם‪.‬‬
‫לכן לאחר שיצרנו משתנה זמני ‪ ,tmp‬בשורה הראשונה שמנו במשתנה זה את הערך שהפוינטר ‪ *p‬מצביע עליו‪ ,‬שזה בעצם ערך‬
‫המשתנה ‪ i‬מבחוץ‪ .‬לאחר מכן בשורה השנייה מה שעשינו זה לשים בערך המשתנה שפוינטר ‪ *p‬מצביע עליו )כלומר ישירות‬
‫ערך ‪ i‬המקורי מבחוץ( את הערך שפוינטר ‪ *q‬מצביע עליו‪ ,‬שזה ערך משתנה ‪ j‬ישירות מבחוץ‪ .‬לאחר מכן באופן זהה שמנו‬
‫בתוך המשתנה שפוינטר ‪ q‬מצביע עליו )כלומר משתנה ‪ j‬המקורי( את הערך המספרי שיש במשתנה ‪.tmp‬‬
‫בעצם ניתן לראות שעל ידי מניפולציות עם פוינטרים‪ ,‬הצלחנו בגוף הפונקציה לשנות ישירות את ערכי המשתנים ‪ i‬ו‪ j-‬מהפריים‬
‫החיצוני‪ ,‬מבלי צורך להחזיר משהו חדש ע"י פקודת ‪ Return‬בפונקציה‪.‬‬

‫מבנה המערך‪-‬‬
‫כמו שהזכרנו מקודם‪ ,‬מה שמאפיין מערך של עצמים זה שכל איברי המערך נמצאים צמודים זה לזה בזיכרון‪ .‬כך למשל אם‬
‫מערך זה הוא מערך של ‪) Integers‬כלומר שכל עצם בו מיוצג בד"כ על ידי ‪ 4‬בתים(‪ ,‬זה אומר שהמערך ממוקדם בכלשהו‬
‫כתובת בזיכרון‪ ,‬כל שהאיבר הראשון הוא בכתובת מסוימת‪ ,‬האיבר השני ‪ 4‬בתים אחריו‪ ,‬השלישי ‪ 4‬בתים אחריו וכך הלאה‪.‬‬
‫נשים לב שכאשר אנחנו מאתחלים מערך )למשל רושמים ]‪ 𝑎[100‬לצורך העניין(‪ ,‬האובייקט ‪ a‬עצמו יהיה‪ ,‬בדומה למה שראינו‬
‫בפוינטרים‪ ,‬משתנה שמכיל את הכתובת לתחילת המערך‪ -‬כלומר את הכתובת בזיכרון איפה שמתחיל האיבר הראשון במערך‪.‬‬
‫באופן זה‪ ,‬מערך הוא סוג של פוינטר‪ ,‬ועל ידי א ר י ת מ ט י ק ה ש ל פ ו י נ ט ר י ם נ י ת ן ל ג ש ת ל א י ב ר י ם ש ו נ י ם ב מ ע ר ך ‪.‬‬

‫אמרנו למשל שהמשתנה ‪ a‬לאחר אתחול מערך מכיל את הכתובת של תחילת המערך אותו משתנה זה מייצג‪ .‬אם יהיה לנו‬
‫משתנה פוינטר ‪ ,*p‬ול‪ p -‬עצמו )זה שאחראי על אכסון הכתובת של אובייקט ההצבעה( נשייך את הערך של משתנה ‪ ,a‬בעצם‬

‫‪3‬‬ ‫‪Tel-Aviv University‬‬


‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫גם משתנה ‪ p‬יכיל את כתובת תחילת המערך שמייצג משתנה ‪ .a‬באופן זה‪ ,‬כאשר נשתמש בפוינטר באמת כמצביע )ע"י כתיבת‬
‫‪ -(*p‬האובייקט שיוחזר לנו ושהפוינטר יצביע עליו בפועל הוא למעשה האובייקט הראשון במערך‪ ,‬כלומר כמו שהכרנו בג'אווה‬
‫כ‪.𝑎[0] -‬‬
‫כעת‪ ,‬אם נעשה למשל )‪ ,∗ (𝑝 + 1‬נשים לב שמה שבתוך הסוגריים‪ ,‬הביטוי ‪ ,𝑝 + 1‬בעצם מכיל את הכתובת לאיבר הבא‬
‫בתור במערך‪ ,‬האיבר באינדקס אחד ]‪ .𝑎[1‬לכן‪ ,‬כאשר הפעלנו את אופרטור ה‪ *-‬על ביטוי זה‪ ,‬האובייקט שהפוינטר הנ"ל‬
‫יצביע עליו‪ ,‬ומה שיוחזר לנו זה למעשה האיבר הראשון במערך ‪ .a‬כלומר‪ ,‬באמצעות פעולות אריתמטיות על הכתובת‬
‫ש מ א ו כ ס נ ת ב מ ש ת נ ה ‪ ,p‬נ ו כ ל ל ה ש ת מ ש ב ת כ ו נ ה ש ל ו כ א ו ב י י ק ט מ צ ב י ע ע ל מ נ ת ל ג ש ת ל א י ב ר י ם ש ו נ י ם ב מ ע ר ך ‪.‬‬
‫כלומר‪ ,‬אם נרשום את הדברים הבאים הכל יהיה שקול‪:‬‬

‫ל א ח ר ש ש מ נ ו ב מ ש ת נ ה ‪ p‬א ת ה כ ת ו ב ת ש ל מ ע ר ך ‪ ,a‬י כ ו ל נ ו ל ע ב ו ר ע ל א י ב ר י ה מ ע ר ך ב כ מ ה ד ר כ י ם ש ו נ ו ת ‪.‬‬


‫בדוגמא הראשונה אנחנו כל פעם משנים את ערך משתנה ‪ p‬כך שערכו גודל ב‪ ,1-‬עד הגבול‪ ,‬שזה הכתובת של האיבר האחרון‬
‫במערך ‪ .a‬כעת כל פעם שנשלוף על ידי אופרטור ההצבעה * את מה שיש בכתובת שמאוכסנת ב‪ ,p-‬זה ישלוף את האיבר הבא‬
‫בתור במערך‪.‬‬
‫נשים לב שאין במערך איבר באינדקס ה‪ ,𝑎[100] -‬שכן האיבר האחרון נמצא ב‪ .𝑎[99] -‬כיוון שעשינו סימן קטן חזק מ ‪-‬‬
‫]‪ &𝑎[100‬זה תחביר שניתן להשתמש בו‪ ,‬כי מספר זה מייצג באופן רעיוני את הקצה הימני הקיצוני של המערך‪ ,‬ולכן כשעשינו‬
‫]‪ 𝑝 < &𝑎[100‬בעצם אמרנו שנעבור על כל הקפיצות של ‪ p‬עד גבול קיצוני זה‪ -‬הכתובת הראשונה שמחוץ למערך‪.‬‬
‫בדוגמא השנייה אנחנו השתמשנו בזה שכמו שאמרנו בהתחלה המשתנה ‪ a‬בעצמו מכיל את הכתובת של תחילת המערך‪ ,‬לכן‬
‫יכלנו‪ ,‬מבלי "לעבור דרך" משתנה הצבעה חדש ‪ p‬להגדיל כל פעם את ערך כתובת זו ולגשת לאיברים במערך על ידי הפעלת‬
‫אופרטור ההצבעה * עליה‪.‬‬
‫בדוגמאות השלישית והרביעית‪ ,‬שוב השתמשנו בזה ש‪ p -‬ו‪ a -‬בעצם משחקים את אותו התפקיד‪ ,‬של אכסון כתובת תחילת‬
‫המערך‪ .‬לכן יכלנו‪ ,‬ת ו ך ש י מ ו ש ב א י ז ה מ מ ש ת נ י ם א ל ו ש נ ר צ ה ‪ ,‬ל ג ש ת ל א י נ ד ק ס י ם ש ל א י ב ר י ם ב מ ע ר ך ב ד י ו ק כ מ ו ש א נ ח נ ו ר ג י ל י ם‬
‫מג'אווה או פייתון‪.‬‬

‫נשים לב לעיקרון חשוב פה‪ -‬למרות שאם למשל מדובר במערך של ‪ Integers‬כל איבר תופס ‪ 4‬בתים בזיכרון‪ ,‬וכך למשל האיבר‬
‫באינדקס ה‪ 1-‬נמצא בכתובת של ‪ 4‬בתים לאחר תחילת המערך )ולא ‪ ,(1‬עדיין את אריתמטיקת הפויינטרים ביצענו על ידי‬

‫‪4‬‬ ‫‪Tel-Aviv University‬‬


‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫הוספת כמה אינדקסים נרצה ללכת‪ ,‬כלומר אם עשינו למשל ‪ 𝑝 + 5‬אנחנו יודעים שניגש לאיבר החמישי במערך )ולא היינו‬
‫צריכים לעשות ממש ‪ 𝑝 + 20‬שלוקח בחשבון את הכתובות המדויקות(‪.‬‬
‫הסיבה לכך היא שהקומפיילר יודע כמה בתים אמורים לתפוס המשתנים של המערך‪ ,‬ולכן עושה את ההמרה הזאת בעצמו‪.‬‬
‫זו הסיבה שבעצם‪ ,‬גם בעת אתחול המערך ובמיוחד בעת אתחול הפוינטר ‪ ,p‬חשוב להצהיר מה סוג האובייקט שנרצה ש‪*p -‬‬
‫יצביע עליו בסופו של דבר‪ .‬למשל אם אמרנו שהמצביע יהיה ל‪ Int-‬אז כשנעשה ‪ +1‬הקומפיילר ידע ללכת ‪ 4‬בתים קדימה‬
‫בזיכרון‪ ,‬ולעומת זאת אם אמרנו שהמצביע יהיה ל‪ Double-‬הקומפיילר יניח שמדובר במערך של מספרים מסוג ‪Double‬‬
‫שמיוצגים ע"י ‪ 8‬בתים כל אחד‪ ,‬ולכן יזוז ‪ 8‬כתובות קדימה בזיכרון כשנעשה ‪.+1‬‬

‫הקצאת זיכרון דינמית למערך‪ -‬נזכור כי בעת אתחול מערך אנחנו חייבים להגדיר מראש מספר קבוע שיהיה מספר האיברים‬
‫ה מ ק ס י מ ל י ש י כ י ל ה מ ע ר ך ‪ .‬מ ס פ ר ז ה ח י י ב ל ה י ו ת מ ס פ ר מ פ ו ר ש ‪ ,‬ו ל א י כ ו ל ל ה י ו ת ל מ ש ל כ ל ש ה ו מ ש ת נ ה ‪ ,n‬ג ם א ם מ א ו כ ס ן ב ו‬
‫מספר טבעי‪.‬‬
‫בעזרת מצביעים‪ ,‬ניתן לבצע סוג של מניפולציה בעזרתה מתאפשר לנו לעשות הקצאה דינמית של זיכרון למערך לאחר האתחול‬
‫שלו‪.‬‬
‫בעזרת פונקציית )𝑚 ‪ 𝑐𝑎𝑙𝑙𝑜𝑐(𝑛,‬אנחנו יכולים לאתחל גם כן מערך‪ ,‬בדומה לאתחול הטריוויאלי של ]𝑛[𝑎‪ .‬אם נכתוב = 𝑝‬
‫)𝑚 ‪ 𝑐𝑎𝑙𝑙𝑜𝑐(𝑛,‬זה אומר שעבור משתנה מצביע ‪ *p‬יוקצו ‪ n‬תאים בזיכרון באורך ‪ m‬בתים כל אחד‪ .‬כלומר להקצות עבור ‪p‬‬
‫מספר מסוים של תאים רצופים בזיכרון עם גודל מוגדר מראש שזה בדיוק שקול ללהגדיר מערך‪ -‬אז בעצם מה שעשינו בפקודה‬
‫זו זה להגדיר מערך חדש‪ ,‬שמשתנה ‪ p‬יצביע אל הכתובת שלו בזיכרון‪ ,‬והוא צריך להכיל ‪ n‬עצמים לכל היותר‪ ,‬כך שלכל עצם‬
‫י ו ק צ ו ‪ m‬תאים בזיכרון‪.‬‬

‫נזכור שבסוף פקודה זו ‪ p‬יכיל את כתובת תחילת המערך שאותחל בזמן ש‪ *p -‬יצביע לנו בפועל על המערך עצמו‪.‬‬
‫בעצם מה שדינמי בטכניקה זו‪ ,‬זה שבזמן כתיבת התוכנית ניתן לרשום ב‪ calloc -‬שאנחנו רוצים להקצות למשל ‪ ,n‬כלשהו‬
‫משתנה‪ ,‬תאים של זיכרון עבור המערך המבוקש‪ ,‬ואז בזמן הריצה זה יבדוק מה הערך הנוכחי של ‪ n‬ולפיו יקבע כמה מקומות‬
‫א נ ח נ ו ר ו צ י ם ל ה ק צ ו ת ב מ ע ר ך ה ח ד ש ‪ .‬ראינו שאם אנחנו מקצים מערך בצורה הרגילה‪ ,‬אנחנו חייבים להכניס מספר מפורש‬
‫עוד בזמן כתיבת התוכנית שזה מספר התאים שיוקצו למערך‪ ,‬מבלי לקחת בחשבון דברים שיכולים לקרות במהלך ריצת‬
‫התוכנית‪.‬‬
‫חשוב לזכור שלאחר שסיימנו את השימוש במערך שאתחלנו בצורה כזו‪ ,‬יש לשחרר את הזיכרון שהשתמשנו בו ע"י פקודת‬
‫)𝑝(𝑒𝑒𝑟𝑓‪ .‬נרשום את פקודה זו בעצם אחרי שסיימנו לגמרי להשתמש במערך זה‪.‬‬

‫הצבעה עקיפה )‪-(Multiple Dereferencing‬‬


‫באותו אופן שראינו כי ניתן לקחת משתנה ולייצר לו פוינטר ‪ *p‬שיצביע אליו )על ידי השמת הכתובת של המשתנה כערך של‬
‫‪ ,(p‬באותו אופן ניתן גם ליצור פוינטר לפוינטר‪.‬‬
‫למשל אם ניקח את משתנה ‪ a‬ונייצר לו פוינטר על ידי ‪ ,*p=&a‬ניתן גם לייצר פוינטר לאותו הפוינטר על ידי ‪.**s=&p‬‬

‫‪5‬‬ ‫‪Tel-Aviv University‬‬


‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫נסתכל למשל על הדוגמא הבאה בה אנחנו רוצים להחליף בין ההצבעות של שני‬
‫פוינטרים‪ -‬הפוינטר !𝑝 מבציע על אובייקט ‪ a‬והפוינטר "𝑝 מצביע על אובייקט‬
‫‪ ,b‬ונרצה ש‪ 𝑝! -‬יצביע על ‪ a‬בזמן ש‪ 𝑝" -‬על ‪.b‬‬
‫נכתוב את פונקציית ההחלפה באופן הבא‪ ,‬כך שנשים לב שמה שהעברנו‬
‫כארגומנטים לפונקציה זה כאמור הכתובות של המצביעים "𝑝 ‪.𝑝! ,‬‬
‫כעת כאשר מגדירים את הפונקציה‪ ,‬חשוב להגדיר בהצהרה‬
‫שלה שהארגומנטים שלה יהיו מצביעים למצביעים‪ ,‬על ידי‬
‫**‪.‬‬
‫נניח כי הכתובת של ‪ a‬היא "‪ "50‬ושל ‪ b‬היא "‪."70‬‬
‫הפוינטרים המקוריים "𝑝 ‪ 𝑝! ,‬מצביעים על אובייקטים אלו‪.‬‬
‫נניח כי הכתובת של !𝑝 היא "‪ "100‬ושל "𝑝 היא "‪ "200‬שאלו‬
‫הארגומנטים שהכנסנו לפונקציית ה‪ .swap -‬נשים לב כי‬
‫כיוון‪ 𝑝! -‬מצביע ל‪ a -‬אז הערך שמאוכסן בו זה הכתובת של ‪ ("50") a‬בזמן שבאופן דומה מה שמאוכסן פיזית ב‪ 𝑝" -‬זה "‪."70‬‬
‫בשורה הראשונה אנחנו יוצרים משתנה מצביע חדש בשם ‪ *temp‬ועושים לו השמה למה שמצביע עליו !𝑝 של הפונקציה‪ .‬נזכור‬
‫כי בתוך הפונקציה‪ ,‬אותו !𝑝 הוא מצביע למצביע כלומר הכנסנו את הכתובת של המצביע !𝑝 מבחוץ ולכן אובייקט זה בתוך‬
‫הפונקציה מצביע למצביע !𝑝 מבחוץ שמכיל את הערך "‪ ."50‬לכן הערך שמשתנה ‪ temp‬מכיל הוא "‪."50‬‬
‫לאחר מכן בשורה השנייה‪ ,‬אנחנו שמים במה שמצביע אליו !𝑝 בפריים הפונקציה )שזה !𝑝 החיצוני( את מה שמצביע אליו "𝑝‬
‫של הפונקציה )שזה "𝑝 החיצוני‪ ,‬שמכיל "‪ ("70‬ולכן כעת‪ ,‬במשתנה !𝑝 המקורי מבחוץ‪ ,‬יהיה "‪."70‬‬
‫לאחר מכן אנחנו באופן דומה שמים במה שמצביע אליו "𝑝 בפריים הפונקציה )שזה בעצם "𝑝 המקורי מבחוץ( את הערך‬
‫שמאוכסן ב‪ ,temp -‬וכעת "𝑝 המקורי מכיל את המספר "‪."50‬‬
‫סה"כ קיבלנו שהמשתנה המצביע המקורי !𝑝 מכיל את הכתובת "‪ ,"70‬כלומר כמצביע ‪ *p1‬מצביע לאובייקט ‪ b‬שכתובתו‬
‫"‪ ."70‬באופן דומה כעת המצביע ‪ *p2‬המקורי מצביע לאובייקט ‪ a‬שכתובתו "‪ ,"50‬ובכך החלפנו באמת בין הערכים שכל פוינטר‬
‫מצביע אליהם‪.‬‬

‫מחרוזות‬
‫רעיונית ניתן להסתכל על מחרוזת כמערך של ‪ -Char‬אוסף של מספר מסוים של אובייקטים מסוג זה הנמצאים רצוף זה לצד‬
‫זה בזיכרון‪.‬‬
‫תזכורת‪ :‬גם אם לא רואים זאת באמת במחרוזת‪ ,‬בזיכרון המחשב כל מחרוזת‬
‫נגמרת בתו נוסף "‪ -"\0‬שהוא זה שעוזר לקומפיילר להבין שנגמרה המחרוזת‪,‬‬
‫ובנקודה זו הוא צריך להפסיק לקרוא אותה‪ .‬לכן למשל אם נסתכל על המחרוזת‬
‫”‪ ,“abc‬זו למעשה מחרוזת שמכילה בתוכה ‪ 4‬תווים‪.‬‬
‫מסיבה זו‪ ,‬ניתן ליצור משתנה של מחרוזת על ידי אתחול של מערך של ‪ ,chars‬כלומר ליצור פוינטר ‪ *p‬ולייחס לו את המחרוזת‬
‫המבוקשת‪ .‬נשים לב שבדומה למה שראינו במערכים‪ ,‬ערך של משתנה ‪ p‬ב ה ק ש ר ז ה י ה י ה ה כ ת ו ב ת ש ל ת ח י ל ת ה מ ח ר ו ז ת‬
‫בזיכרון‪ .‬לכן למשל אם נעשה ‪ ,𝑝 + 1‬מספר זה יכיל את הכתובת של האות הבאה במחרוזת‪ .‬כיוון שהקומפיילר יודע לסיים‬
‫את קריאת המחרוזת ב"‪ -"\0‬אם למשל נדפיס את המחרוזת החל מתו זה היא תודפס מנקודה זו ותפסיק עדיין בסיומה‬
‫המקורי‪ ,‬שכן שם מצוי "‪ "\0‬מבחינת המחשב‪.‬‬

‫‪6‬‬ ‫‪Tel-Aviv University‬‬


‫‪Dvir Ben Asuli‬‬ ‫‪21/03/2020‬‬

‫נזכור שאם יש לנו כלשהו מצביע 𝑝 ∗ למחרוזת‪ ,‬המתשנה 𝑝 עצמו יכיל את הכתובת לאיבר הראשון של המחרוזת ואילו )𝑝 ∗(‬
‫כמכלול יהיה אובייקט שמצביע על כתובת זו ובעצם מ י י צ ג ת א ת ה מ ח ר ו ז ת ה ב א ‪ .‬א ם נ ר צ ה ל ט י י ל ל א ו ר ך ה מ ח ר ו ז ת ע ל‬
‫האותיות השונות שיש שם‪ ,‬נוכל להשתמש ב‪ Syntax-‬הבא‪?∗ 𝑝 + + :‬‬
‫מה זה אומר?‬
‫בעצם האופרטור של ה ‪ ++‬עובד על הערך המספרי של 𝑝 עצמו‪ ,‬שזו הכתובת‪ .‬בכך שאנחנו מגדילים את הכתובת ״ב‪1-‬״ )זה‬
‫ל א ב א מ ת ‪ ,1‬כ י ה ר א י נ ו ש א ם ה ג ד ר נ ו ל מ ש ל א ת ה ט י פ ו ס ש ל ה מ צ ב י ע 𝑝 ∗ ל ה י ו ת מ צ ב י ע ל מ ח ר ו ז ת א ז ה ג ד ל ה ש כ ז ו ת ג ד י ל א ת‬
‫הכתובת 𝑝 בזיכרון בדיוק כמה שצריך כדי לעבור לתו הבא(‪ ,‬המשתנה 𝑝 יכיל כעת את הכתובת לאיבר הבא בתור במחרוזת‪,‬‬
‫ואז כאשר יפעל עליו אופרטור ∗ ה ו א י צ ב י ע ב א ו פ ן א ק ט י ב י ל מ ה ש נ מ צ א ב ז י כ ר ו ן ה ז ה ‪ ,‬ש ז ה ה ת ו ה ב א ב מ ח ר ו ז ת ‪.‬‬

‫ישנם מספר פונקציות מובנות חשובות לשימוש במחרוזות הנמצאות בספריה "‪ ,"string.h‬כמו‪:‬‬

‫כך שהראשונה משרשרת בין מחרוזות )המחרוזת ‪ s1‬תכיל את המחרוזת המשורשרת החדשה בזמן שהמחרוזת ‪ s2‬לא תשתנה‬
‫כלל(‪ ,‬השנייה משווה בין שתי מחרוזות – תחזיר ‪ −1‬אם המחרוזת ש‪ s1-‬מצביע אליה קטנה לקסיקוגרפית מהמחרוזת ש‪s2 -‬‬
‫מצביע אליה‪ ,‬תחזיר ‪ 0‬במידה והן שווה ו‪ 1 -‬במידה והמחרוזת ש‪ s1-‬מצביע אליה גדולה מהמחרוזת ש‪ s2 -‬מצביע אליה‪,‬‬
‫השלישית מעתיקה את תוכן מחרוזת ‪ s2‬למחרוזת ש‪ s1 -‬מצביעה עליה והרביעית מחזירה את אורך המחרוזת שהכנסנו‪.‬‬

‫‪7‬‬ ‫‪Tel-Aviv University‬‬

You might also like