העמסת אופרטורים ב CPP-עזרה

yosi44441

New member
העמסת אופרטורים ב CPP-עזרה

הבנתי את רוב הנושא אבל יש כמה דברים שלא יושבים לי היטב ואשמח לעזרה
הדוגמא שעבדנו עליה זה מחלקה של המספר הרציונאלים(מכילה שדה "מונה" ושדה "מכנה")
אני יודע לעשות העמסה של +/גדול /כפול/שונה/++/--
יש לי בעיה עם COUT ועם =
הרי ידוע שאם a,b הם מופעים של המחלקה ניתן לעשות a=b ואז כל השדות יועתקו
ראינו את המימוש הבא

const Rational& Rational::operator=(const Rational& num) {
this->mone = num.mone;
this->mechane = num.mechane;
return *this; }

הבנתי שזה נועד שאוכל לעשות​
a=b=c=d וכו' וכו'
לא הבנתי את המימוש הנ"ל
את החלק
Rational::eek:perator=(const Rational& num)

הבנתי כמובן, זה מימוש של = שמשייך למחלקה Rational ושמקבל num מסוג Rational (יש עליו & כך שכל שינוי שנעשה עליו ישתנה גם בתוכנית) וישמור עליו עם קבוע כדי שלא נשנה אותו בטעות בפונקציה
אגב, למה יש את ה & אם יש את ה CONST , הרי גם ככה לא ניתן לשנות אותו?
האם כדי לחסוך את הבנאי?
והחלק שאני באמת לא מבין... מה זה const Rational&
מה זה אומר שהיא מחזירה התייחסות ל Rational ושזה קבוע... לא הבנתי

כמו כן מימוש של COUT
בהצרה כתבנו
friend ostream& operator<<(ostream&, Rational);
ובמימוש

ostream& operator<<(ostream& out, Rational num){
out << num.mone << '/' << num.mechane << endl;
return out;}

אני יודע מה זה מחלקה חברה(מחלקה שיש לה גישה למשתנים פרטיים של מחלקה אחרת בCPP יש את זה כמובן)
לא הבנתי כ"כ את ההצהרה והמימוש, אשמח להסבר
מה זה אומר שהיא מחזירה ostream&
והיא מקבלת ostream& out
מה זה בידיוק ה OUT הזה ואיך הוא משמש בפונקציה ככה
out << num.mone << '/' << num.mechane << endl;

אשמח להסבר חברים
תודה רבה!!
 

BravoMan

Active member
קצת הסברים:

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

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

אז הרעיון בשורה const Rational& num הוא שהפונקציה למעשה תקבל את האובייקט המקורי שמועבר כפרמטר, ולא העתק ממנו, למרות שהיא לא הולכת לשנות אותו.
ללא ה-& כל פעם שתפעיל את האופרטור = ייוצרו וימחקו העתקים זמניים.

לגבי ostream:
הפונקציה שדורסת את האופרטור >> אינה חלק מהמחלקה Rational.
לכן, אתה לא אמור להיות מסוגל לגשת לאיברים הפרטיים של המחלקה mone, mecahne מהפונקציה הזו.
כדי לעקוף מגבלה זו, מגדירים את הפונקציה כחברה של המחלקה - freind.

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

הפרמטר הראשון שלה out הוא אובייקט ostream אליו הולכים לכתוב ערכים.
בקוד הבא, הפרמטר יהיה cout - אובייקט שמקושר לפלט הסטנדרטי:
Rational x;
cout << x;
זה כמו:

operator<<(cout, x);


הסיבה שהפונקציה גם מחזירה את האובייקט הזה, היא כדי שתוכל לשרשר הדפסות, כמו בדוגמה שלך.
למשל בקוד הבא, קודם המחשב מבצע את האופרטור עם הפרמטרים a ו-cout ואז לוקח את הערך החוזר ושם אותו כפרמטר ראשון בפעולה עם b. כך, בפעם הבאה הפונקציה נקראת עם b ו-cout.
Rational a, b;
cout << a << b;

ללא הערך החוזר, לא יכולת לבצע את הקריאה השנייה, ולכן לא היית יכול לשרשר את ההדפסות בשורה אחת, אלא היית חייב לכתוב כך:
Rational a, b;
cout << a;
cout << b;

מה שקורה בדוגמה הראשונה עם שורה אחת, נראה למחשב כך:
Rational a, b;ostream os;
os = operator<<(cout, a);
operator<<(os, b);

מקווה שזה מבהיר את הדברים.
 

yosi44441

New member
עדיין לא התיישב לי היטב אבל...

אקרא עוד כמה פעמים עד שישב לי טוב אבל בינתיים

1.
לפי מה שהבנתי תמיד עדיף להעביר פרמטרט עם & על מנת לא להפעיל את הבנאי?
לדוגמא באופרטור- שמחסר בין 2 מספרים רציונאלים, מימשתי כך

razyonal razyonal::operator-(const razyonal num) const{
razyonal tmp;
tmp.mone = mone * num.mehane - mehane * num.mone; tmp.mehane = mehane * num.mehane; tmp.reduction(); return tmp;}
היה עדיף להעביר את הפרמטר(שאותו מחסרים) עם &? כדי לחסוך בנאי? תמיד אעביר עם & ואגן עליו מפני שינוי ע"י CONST כמו שעשיתי?

עוד נקודה קטנה שתעזור לי להבין את הסברך
2.
ה out הוא אובייקט במחלקה OSTREAM? לא הייתי יכול לרשום במקומות מילה אחרת?

3.
כמו כן עדיין לא התיישב לי את הנקודה שאני מחזיר
ostream&- התייחסות לOSTREAM ... או
Rational&- התייחסות ל RATIONAL - קצת קשה לי להבין מה זה
אני מבין שאני מקבל את זה כפרמטר שזה התייחסות לאובייקט שנמצא לי בתוכנית הראשית וכל שינוי בפונקציה ישנה בראשית, אבל מה זה כאשר זה ערך מוחזר?

רב תודות!!
 

BravoMan

Active member
אתה צריך להתחיל לחשוב במונחים של אובייקטים

כל פיסת מידע שיש בתוכנית שלך, "חיה" איפשהו.

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

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

ה-out הוא שם של פרמטר מטיפוס ostream. אין הבדל בינו לבין כל פרמטר אחר שאי פעם כתבתה.
אם נוח לך, תוכל לקרוא לפרמטר yosi במקום out, אבל אז לאנשים אחרים יהיה קשה להבין למה הפרמטר משמש.
הסיבה שבחרו לקרוא לו out זה כי הוא משמש לפלט - output.
אבל כאמור, השם עצמו לא משנה למחשב, רק למתכנת שקורא את הקוד.

ערך חוזר הוא ערך חוזר.
הוא נשאר בדיוק מה שהוא היה בהתחלה.

תסתכל על הפונקציה הבאה:
int funct(int x) { cout << x;
return x;}

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

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

הפרבולה

New member
לדעתי לא תמיד כדאי להעביר פרמטרים כרפרנס

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

BravoMan

Active member
בקריאה שניה, נראה שלא ממש ברור מההודעה שלי

שהתכוונתי למקרה ספציפי של השואל בו ממוש אופרטור ומועבר אובייקט.
&nbsp
אתה בהחלט צודק - לא תמיד כדאי להעביר reference.
&nbsp
 

yosi44441

New member
בהמשך....

הבנתי את הCOUT ,אשמח לעזרה נוספת בנושא
1- האם תמיד עדיף שהפונקציה תקבל ותחזיר אובייקטים עם & כיוון שאז היא לא תפעיל את בנאי העתקה לא בכניסה ולא ביציאה מהפונקציה, מה החיסרון בלשלוח עם & (אם הבעיה היא שינוי הערכים מוסיפים const

2- אני מנסה להבין את העניין של & עדיין
לדוגמא המימוש של אופרטור [] (לדוגמא הקוד , שאמור לגרום גם להשמה וגם להחזרה של האיבר במקום האינדקס
class Vector{
int * m_data;
public: int & operator [] (int i) { static int null_int=0; if((i < 0) || (i >= size())) { cerr << "Error: index " << i << " is out of range!" << endl; return null_int; } return m_data; }};
אני לא מצליח להבין למה מחזירים int&
אני מבין שהקוד
v1 [ 2 ] = -4;יתורגם ל
v1.operator[](2) = 4;
אז כאן הוא מחזיר & כיוון שאם היה מחזיר INT רגיל זה היה להכניס 4 למספר, ולכן צריך להחזיר כתובת כדי ש 4 יכנס לכתובת?
אם כן אני עדיין לא מבין למה באופרטור ההשמה וההדפסה(שהצגתי למעלה) גם מחזירים יחוס לאובייקט
 

BravoMan

Active member
ככה:

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

יש לבחון כל מקרה לגופו, ולראות מה היא האפשרות המיטבית.

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

השורה const int *a אומרת שאי אפשר לגרום ל-a להצביע למקום אחר בזיכרון, אבל היא לא מפריע לך לשנות את הערך ששמור בזיכרון עליו מצביע a.

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

2. ניסיתי כמיטב יכולתי להסביר לך למה אופרטור הפלט מחזיר את הייחוס לאובייקט שקיבל כפרמטר, אבל נראה שאני לא מצליח. משהו מתפספס בתקשורת בנינו.
אנסה בפעם אחרונה: הוא עושה זאת, כדי שתוכל לשרשר כמה דברים עם >>, למשל כך:
string back = "forward";cout << "a" << 5 << "hello" << back;

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

מקווה שזה עוזר...
בהצלחה!
 
למעלה