C++/העמסת אופרטורים: הבדלים בין גרסאות בדף

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי
< C++
תוכן שנמחק תוכן שנוסף
דף חדש: {{בעבודה}} כל ביטוי מורכב מפעולות ופרמטרים איתם מתבצעות הפעולות, פעולות אלו נקראות '''אופרטורים''' ואילו ...
 
Ybungalobill (שיחה | תרומות)
אין תקציר עריכה
שורה 1: שורה 1:
{{C++}}
{{בעבודה}}
{{בעבודה}}
כל ביטוי מורכב מפעולות ופרמטרים איתם מתבצעות הפעולות, פעולות אלו נקראות '''אופרטורים''' ואילו הפרמטרים איתם עובדים האופרטורים נקראים '''אופרנדים'''. בC++ ישנם אופרטורים רבים שחלקם מקבלים כפרמטרים טיפוסים ומרחבי שמות (כמו :: ו-sizeof), פעולתם מתבצעת רק בעת הידור התוכנית, ואילו האחרים מקבלים אובייקטים, אופרטורים אלה עובדים עם האוביקטיים בזמן ריצה. כאשר אנו מגדירים מחלקות אנו מוסיפים טיפוסים חדשים לשפה, אבל עד כה ביצענו איתם פעולות רק ע"י קריאות לפונקציות המחלקה או פונקציות רגילות. כדי להגדיר טיפוס המשתלב בשפה בדומה לטיפוסים המובנים נגדיר עבורו אופרטורים. דבר זה נקרא '''העמסת אופרטורים''' כיוון שאנו מעמיסים אופרטורים נוספים לאופרטורים הקיימים. המטרה העיקרית בהעמסת אופרטורים היא נוחות וקריאות הקוד, הרי הקוד {{קוד בשורה|<nowiki>a = b + c</nowiki>}} קריא יותר למתכנת מאשר {{קוד בשורה|<nowiki>a.set(add(b, c))</nowiki>}}. שימו לב: בשני הביטויים האלה אנו לא יודעים מהי באמת התוצאה כי לא ידועים לנו לא הטיפוסים של a, b, c ולא המשמעות של =, +, add, set עבור טיפוסים אלה.
כל ביטוי מורכב מפעולות ופרמטרים איתם מתבצעות הפעולות, פעולות אלו נקראות '''אופרטורים''' ואילו הפרמטרים איתם עובדים האופרטורים נקראים '''אופרנדים'''. בC++ ישנם אופרטורים רבים שחלקם מקבלים כפרמטרים טיפוסים ומרחבי שמות (כמו :: ו-sizeof), פעולתם מתבצעת רק בעת הידור התוכנית, ואילו האחרים מקבלים אובייקטים, אופרטורים אלה עובדים עם האוביקטיים בזמן ריצה. כאשר אנו מגדירים מחלקות אנו מוסיפים טיפוסים חדשים לשפה, אבל עד כה ביצענו איתם פעולות רק ע"י קריאות לפונקציות המחלקה או פונקציות רגילות. כדי להגדיר טיפוס המשתלב בשפה בדומה לטיפוסים המובנים נגדיר עבורו אופרטורים. דבר זה נקרא '''העמסת אופרטורים''' כיוון שאנו מעמיסים אופרטורים נוספים לאופרטורים הקיימים. המטרה העיקרית בהעמסת אופרטורים היא נוחות וקריאות הקוד, הרי הקוד {{קוד בשורה|<nowiki>a = b + c</nowiki>}} קריא יותר למתכנת מאשר {{קוד בשורה|<nowiki>a.set(add(b, c))</nowiki>}}. שימו לב: בשני הביטויים האלה אנו לא יודעים מהי באמת התוצאה כי לא ידועים לנו לא הטיפוסים של a, b, c ולא המשמעות של =, +, add, set עבור טיפוסים אלה.
שורה 4: שורה 5:
== הגדרת פונקציית האופרטור ==
== הגדרת פונקציית האופרטור ==


מבחינת המהדר אופרטור הוא פונקציה שכדי לקרוא לה נשתמש בתחביר שונה מזה של קריאה לפונקציה רגילה. להלן חלק מהגדרת מבנה המייצג ווקטור מתמי דו מימדי ואופרטור המחבר שני ווקטורים:
מבחינת המהדר אופרטור הוא פונקציה שכדי לקרוא לה נשתמש בתחביר שונה מזה של קריאה לפונקציה רגילה. להלן חלק מהגדרת מבנה המייצג ווקטור מתמי דו מימדי ופונקציית האופרטור המחבר שני ווקטורים:
<div style="direction: ltr;"><source lang="cpp">
<div style="direction: ltr;"><source lang="cpp">
struct Vector2D
struct Vector2D
שורה 22: שורה 23:
נסביר את הכתוב:
נסביר את הכתוב:
* הגדרנו טיפוס זה כמבנה ולא כמחלקה כיוון שאין לנו צורך להסתיר את משתניו, הרי לא קיימים ערכים בלתי חוקיים עבור רכיבי הווקטור ודווקא נרצה לתת אפשרות לגשת למשתנים אלו: {{קוד בשורה|<nowiki>cout << vec.x << ',' << vec.y;</nowiki>}}.
* הגדרנו טיפוס זה כמבנה ולא כמחלקה כיוון שאין לנו צורך להסתיר את משתניו, הרי לא קיימים ערכים בלתי חוקיים עבור רכיבי הווקטור ודווקא נרצה לתת אפשרות לגשת למשתנים אלו: {{קוד בשורה|<nowiki>cout << vec.x << ',' << vec.y;</nowiki>}}.
* שם האופרטור מורכב מהמילה operator השמורה וסימן האופרטור עצמו, במקרה זה +.
* שם פונקציית האופרטור מורכב מהמילה operator השמורה וסימן האופרטור עצמו, במקרה זה +.
* פונקציית האופרטור מקבלת מספר פרמטרים לפי מספר האופרנדים, במקרה זה שני פרמטרים. אלה משתנים מיוחסים קבועים, הם יתיחסו אל האופרנדים של האופרטור במקום הקריאה. הגדרנו את הפרמטרים כמשתנים מיוחסים כי גודל האובייקט Vector2D גדול יחסית.
* פונקציית האופרטור מקבלת מספר פרמטרים לפי מספר האופרנדים, במקרה זה שני פרמטרים. אלה משתני יחוס קבועים, הם יתיחסו אל האופרנדים של האופרטור במקום הקריאה. הגדרנו את הפרמטרים כמשתני יחוס כי גודל האובייקט Vector2D גדול יחסית, אולם ניתן גם לתת לפרמטרים את טיפוס את אופרנד עצמו, לא בהכרח משתנה מיוחס.
* פונקציית האופרטור מחזירה אובייקט Vector2D שבו נמצאת התוצאה של חיבור שני הווקטורים. לא נוכל להחזיר משתנה מיוחס כיוון שאנו מחזירים אובייקט לוקלי.
* פונקציית האופרטור מחזירה אובייקט Vector2D שבו נמצאת התוצאה של חיבור שני הווקטורים. לא נוכל להחזיר משתנה יחוס כיוון שאנו מחזירים אובייקט לוקלי.
* פונקציית האופרטור מוגדרת כ-inline כי היא קצרה ומחזירה אובייקט גדול יחסית. נגדיר את רוב האופרטורים הפשוטים כ-inline.
* פונקציית האופרטור מוגדרת כ-inline כי היא קצרה ומחזירה אובייקט גדול יחסית. נגדיר את רוב האופרטורים הפשוטים כ-inline.

עכשיו נוכל להשתמש באופרטור שהגדרנו עבור טיפוס הווקטור, כאילו ש-Vector2D הוא טיפוס מובנה:
<div style="direction: ltr;"><source lang="cpp">
Vector2D foo = {10, 20};
Vector2D bar = {5, 0};
Vector2D klop = foo + bar;
</source></div>

לאחר ביצוע קטע זה במשתנה klop ימצא הערך {{קוד בשורה|<nowiki>{15.0, 20.0}</nowiki>}}.
שימו לב שאנחנו לא חייבים להגדיר את האופרטור = ואת הבנאי המעתיק כי משמעותם כברירת מחדל מתאימה לטיפוס זה.

כאשר הטיפוס של האופרנד הראשון הוא טיפוס אותו הגדרנו בכוחות עצמינו, נוכל להגדיר את פונקציית האופרטור כפונקציית הטיפוס שלנו (מחלקה, מבנה או אפילו איגוד). את חלק מהאופרטורים אנו חייבים להגדיר בדרך זו, למשל את {{קוד בשורה|<nowiki>operator +=</nowiki>}}:
<div style="direction: ltr;"><source lang="cpp">
struct Vector2D
{
// ...

Vector2D& operator += (const Vector2D &b)
{
x += b.x;
y += b.y;
return *this;
}
};
</source></div>

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

הערך המוחזר כאן הוא משתנה יחוס המתייחס לאובייקט אותו הגדלנו באמצעות האופרטור. טריק זה מקובל עבור רוב אופרטורים המשנים את האובייקט בכלל ואופרטורי השמה בפרט, דבר זה מאפשר לשרשר את האופרטורים, כך שהערך המוחזר מפעולה אחת יהיה לאופרנד של הפעולה הבאה, לדוגמה:
<div style="direction: ltr;"><source lang="cpp">
Vector2D foo = {1, 1};
Vector2D bar = {5, -5};
foo = (bar += foo);
</source></div>

תחילה יתבצע הביטוי {{קוד בשורה|<nowiki>(bar += foo)</nowiki>}}, ערך משתנה bar יהפוך ל-{{קוד בשורה|<nowiki>{6.0, -4.0}</nowiki>}}, ביטוי זה יחזיר משתנה יחוס ל-bar אשר ישמש לנו כאופרנד לפעולת ההשמה. כשתתבצע פעולת ההשמה, יועתק ערכו החדש של bar למשתנה foo.

== קריאה לפונקציית האופרטור ==

כאמור פונקציית האופרטור היא פונקציה רגילה בעלת שם מיוחד אשר ניתן לקרוא לה בצורה קצרה, ע"י שימוש בסימן האופרטור. נוכל לקרוא לפונקציית האופרטור באופן גלוי, בעמצעות שמה המלא, להלן שתי שורות מהדוגמות שלמעלה בהן קוראים לפונקציות האופרטור בדרך זו:
<div style="direction: ltr;"><source lang="cpp">
Vector2D klop = operator + (foo, bar);
foo.operator = (bar.operator += (foo));
</source></div>

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

''טיפ:'' כשתתקלו בביטוי מסובך ולא ברור נסו לדמיין או לכתוב אותו בצורה כזאת ולהבין אילו בדיוק אופרטורים נקראים לפי טיפוסי האופרנדים שלהם.

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

גרסה מ־15:40, 12 ביוני 2007


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


כל ביטוי מורכב מפעולות ופרמטרים איתם מתבצעות הפעולות, פעולות אלו נקראות אופרטורים ואילו הפרמטרים איתם עובדים האופרטורים נקראים אופרנדים. בC++ ישנם אופרטורים רבים שחלקם מקבלים כפרמטרים טיפוסים ומרחבי שמות (כמו :: ו-sizeof), פעולתם מתבצעת רק בעת הידור התוכנית, ואילו האחרים מקבלים אובייקטים, אופרטורים אלה עובדים עם האוביקטיים בזמן ריצה. כאשר אנו מגדירים מחלקות אנו מוסיפים טיפוסים חדשים לשפה, אבל עד כה ביצענו איתם פעולות רק ע"י קריאות לפונקציות המחלקה או פונקציות רגילות. כדי להגדיר טיפוס המשתלב בשפה בדומה לטיפוסים המובנים נגדיר עבורו אופרטורים. דבר זה נקרא העמסת אופרטורים כיוון שאנו מעמיסים אופרטורים נוספים לאופרטורים הקיימים. המטרה העיקרית בהעמסת אופרטורים היא נוחות וקריאות הקוד, הרי הקוד a = b + c קריא יותר למתכנת מאשר a.set(add(b, c)). שימו לב: בשני הביטויים האלה אנו לא יודעים מהי באמת התוצאה כי לא ידועים לנו לא הטיפוסים של a, b, c ולא המשמעות של =, +, add, set עבור טיפוסים אלה.

הגדרת פונקציית האופרטור

מבחינת המהדר אופרטור הוא פונקציה שכדי לקרוא לה נשתמש בתחביר שונה מזה של קריאה לפונקציה רגילה. להלן חלק מהגדרת מבנה המייצג ווקטור מתמי דו מימדי ופונקציית האופרטור המחבר שני ווקטורים:

struct Vector2D
{
    double x, y;
    // ...
};

inline Vector2D operator + (const Vector2D &a, const Vector2D &b)
{
    Vector2D res;
    res.x = a.x + b.x;
    res.y = a.y + b.y;
    return res;
}

נסביר את הכתוב:

  • הגדרנו טיפוס זה כמבנה ולא כמחלקה כיוון שאין לנו צורך להסתיר את משתניו, הרי לא קיימים ערכים בלתי חוקיים עבור רכיבי הווקטור ודווקא נרצה לתת אפשרות לגשת למשתנים אלו: cout << vec.x << ',' << vec.y;.
  • שם פונקציית האופרטור מורכב מהמילה operator השמורה וסימן האופרטור עצמו, במקרה זה +.
  • פונקציית האופרטור מקבלת מספר פרמטרים לפי מספר האופרנדים, במקרה זה שני פרמטרים. אלה משתני יחוס קבועים, הם יתיחסו אל האופרנדים של האופרטור במקום הקריאה. הגדרנו את הפרמטרים כמשתני יחוס כי גודל האובייקט Vector2D גדול יחסית, אולם ניתן גם לתת לפרמטרים את טיפוס את אופרנד עצמו, לא בהכרח משתנה מיוחס.
  • פונקציית האופרטור מחזירה אובייקט Vector2D שבו נמצאת התוצאה של חיבור שני הווקטורים. לא נוכל להחזיר משתנה יחוס כיוון שאנו מחזירים אובייקט לוקלי.
  • פונקציית האופרטור מוגדרת כ-inline כי היא קצרה ומחזירה אובייקט גדול יחסית. נגדיר את רוב האופרטורים הפשוטים כ-inline.

עכשיו נוכל להשתמש באופרטור שהגדרנו עבור טיפוס הווקטור, כאילו ש-Vector2D הוא טיפוס מובנה:

Vector2D foo = {10, 20};
Vector2D bar = {5, 0};
Vector2D klop = foo + bar;

לאחר ביצוע קטע זה במשתנה klop ימצא הערך {15.0, 20.0}. שימו לב שאנחנו לא חייבים להגדיר את האופרטור = ואת הבנאי המעתיק כי משמעותם כברירת מחדל מתאימה לטיפוס זה.

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

struct Vector2D
{
    // ...

    Vector2D& operator += (const Vector2D &b)
    {
        x += b.x;
        y += b.y;
        return *this;
    }
};

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

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

Vector2D foo = {1, 1};
Vector2D bar = {5, -5};
foo = (bar += foo);

תחילה יתבצע הביטוי (bar += foo), ערך משתנה bar יהפוך ל-{6.0, -4.0}, ביטוי זה יחזיר משתנה יחוס ל-bar אשר ישמש לנו כאופרנד לפעולת ההשמה. כשתתבצע פעולת ההשמה, יועתק ערכו החדש של bar למשתנה foo.

קריאה לפונקציית האופרטור

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

Vector2D klop = operator + (foo, bar);
foo.operator = (bar.operator += (foo));

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

טיפ: כשתתקלו בביטוי מסובך ולא ברור נסו לדמיין או לכתוב אותו בצורה כזאת ולהבין אילו בדיוק אופרטורים נקראים לפי טיפוסי האופרנדים שלהם.

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