עבור לתוכן

צריך עזרה בנוגע למצביעים בC++

Featured Replies

פורסם

אהלן, התחלתי לא מזמן ללמוד C++ מהאתר thenewboston ואשמח לקבל הסבר לגבי משהו עם פוינטרים ורפרנסים שלא בדיוק הבנתי..

באחד המדריכים נכתב הקוד הבא:

class Sally{ public: Sally(); void printCrap(); protected: private:};Sally::Sally(){}void Sally::printCrap(){  cout << "Steak" << endl;}int main(){  Sally so;  Sally *sallyPointer = &so;  so.printCrap();  sallyPointer -> printCrap();}

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

במדריך אחר, בו הוא לימד על פרינד, נכתב הקוד הבא::

 class Sally{  public:    Sally(){salVar=0;}  private:    int salVar;  friend void SallyFriend(Sally &sfo);};void SallyFriend(Sally &sfo){  sfo.salVar = 99;  cout << sfo.salVar << endl;}int main(){  Sally bob;  SallyFriend(bob);} 

], במדריך הזה, הוא העביר לפונקציה סאליפרינד את האוביקט בוב כפרמטר, וקלט את זה בפונקציה עם הסימן & שלמיטב הבנתי זה הכתובת בזכרון ולאר מכן השתמש באוביקט הזה מתוך הפונקציה..השאלה שלי..עכשיו לא הבנתי מה בדיוק הולך פה, בתוכנית השנייה, ואיך זה מתאפשר כל הדבר הזה?לא צריך קודם כל איזשהו מצביע כדי לעשות את זה?הבנתי את התוכנית הראשונה ואחרי זה התוכנית השנייה בלבלה אותי, מישהו יכול לעשות לי פה קצת סדר?תודה מראש לעוזרים

עריכה:

קטעי הקוד משום מה מתאגדים להם לשורה אחת למרות שזה היה מסודר, איך לסדר את זה?

פורסם

אתה יכול לנסות לערוך את ההודעה וללחוץ על כפתור "תצוגת קודים" (כפתור עם ציור toggle.gif) ואז להדביק שם את הקוד, אולי זה יסדר את השורות.

לשאלתך:

ל-& יש מספר משמעויות שונות ב-++C. אחת מהן (שהייתה קיימת עוד ב-C) היא אכן הכתובת של משתנה כלשהו. משמעות חדשה, שהוסיפו ב-++C היא סוג חדש של משתנה - רפרנס. כמו שמגדירים *int שהוא "מצביע ל-int" אז אפשר להגדיר ∫ שהוא "רפרנס ל-int".

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

int x;
int& y = x;

אז עכשיו y "מצביע" ל-x, אבל מתייחסים אליו כאל משתנה רגיל (לא כמצביע, דהיינו לא צריך *), וכל שינוי ב-y יגרור שינוי ב-x. דהיינו, הקוד הבא:

x = 5;
y++;
printf("%d", x);

ידפיס 6.

שים לב שלא ניתן לשנות את המשתנה אליו מצביע y - הוא לנצח יצביע על x.

מאחורי הקלעים הקומפיילר בעצם מתרגם את y למצביע לכל דבר (כלומר טיפוס *int) וכל פעולה על y מתורגמת לפעולה על y*. כלומר הקוד הנ"ל שקול ל:

int x = 5;
int *y = &x;
(*y)++;
printf("%d", *y);

למה זה טוב? זה מאפשר לנו להעביר משתנים לפונקציה by reference. להזכירך, בדרך כלל פרמטרים לפונקציה מועברים by value. כלומר אם הוגדרה הפונקציה (int f(int x אז קריאה לפונקציה (f(a תעתיק את a למשתנה חדש בשם x, והפונקציה תשתמש ב-x הזה, ולא ב-a. ככה אם הפונקציה תשנה את x אז היא לא משתנה את a. לעומת זאת, אם מגדירים את הפונקציה כך:

int f(int& x)

אז בעת הקריאה לפונקציה (f(a, המשתנה a לא מועתק, אלא הכתובת שלו מועתקת לתוך x, וכעת שינוי ב-x כן יגרור שינוי ב-a. זה נותן לנו לעשות שני דברים:

1. לכתוב פונקציה שמשנות את המשתנים שהועברו אליהן, לדוגמה:

void swap(int& x, int& y) {
int tmp = x;
x = y;
y = tmp;
}

בדוגמה הזו, קריאה ל-(swap(a,b מחליפה בין המשתנים a ו-b. לעומת זאת, אם הפרמטרים לפונקציה היו מוגדרים כ-int במקום ∫ אז הפונקציה לא הייתה יכולה לשנות את a ו-b, ולא הייתה עושה את הנדרש.

2. למנוע העתקה מיותרת של מחלקות. אם הפונקציה Sallyfriend הייתה מוגדרת כך:

void Sallyfriend(Sally sfo)

אז בעת קריאה לפונקציה הזו, כל האובייקט היה מועתק לתוך הפרמטר sfo. אם למחלקה היה מוגדר copy constructor אז ההעתקה הייתה משתמשת בפונקציה הזו. בנוסף, כיוון שהאובייקט הועתק, השינוי ב-sfo.salVar לא היה משתנה את האובייקט המקורי שהועבר לפונקציה, אלא רק את העותק שלו (שנזרק ביציאה מהפונקציה). לכן, אנחנו מגדירים את הפונקציה כך:

void Sallyfriend(Sally &sfo)

ככה בעת הקריאה לפונקציה האובייקט לא מועתק בשלמותו, אלא רק מועבר מצביע אליו, אבל אפשר להתייחס אליו כאל אובייקט מסוג Sally (ולא כאל מצביע). יכלנו גם לכתוב את Sallyfriend כך:

void Sallyfriend(Sally *sfo){
sfo->salVar = 99;
cout << sfo->salVar << endl;
}

ואז לקרוא לפונקציה ככה:

Sallyfriend(&bob);

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

וואו, יצא לי יותר ארוך משציפיתי.

פורסם
  • מחבר

וואי אתה לא מבין איך סידרת לי את הדברים בראש עכשיו! תודה רבה!

עוד שאלה קטנה, למעשה, הקוד השני שרשמת והקוד השלישי יבצעו את אותה פעולה, אבל בקוד השני y יצביע תמיד על x, אז איזה שיטה מקובלת יותר?יש שימושים מסויימים לכל שיטה?

פורסם

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

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

חוץ מזה, רפרנס זה דבר שקיים רק ב-++C. ב-C, שעליה מבוססת ++C, קיימים רק מצביעים, ולכן תמיד היית חייב להשתמש במצביע.

פורסם

בכל הדוגמאות של שניצל, אפשר היה להסתדר עם פוינטרים ולאו דווקא רפרנסים.

הסיבה העיקרית להכללת רפרנסים בשפה היא operator overloading

דרך טובה להסתכל על רפרנסים הוא כאל alias כלומר, שם נוסף לאותו אובייקט. בפועל הקומפיילר משתמש בכתובת של אותו אובייקט.

פורסם

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

אבל אתה צודק בזה שב-operator overloading אי אפשר להשתמש במצביעים ולכן חייבים להשתמש במנגנון הזה.

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים