תכנות מונחה עצמים א` – תש"ע

Download Report

Transcript תכנות מונחה עצמים א` – תש"ע

‫תכנות מונחה עצמים‬
‫והנדסת תוכנה – תשע"ד‬
‫העמסה של אופרטורים‬
‫(‪)Operator overloading‬‬
‫‪1‬‬
‫מבוא‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪2‬‬
‫מטרה‪ :‬להכליל אופרטורים של טיפוסים בסיסיים לפעול על עצמים‪.‬‬
‫למשל‪ ,‬הכללת האופרטור "‪ "+‬עבור מספר מרוכב‪ ,‬וקטור ורשימה‬
‫משורשרת‪.‬‬
‫מתקבל קוד ברור‪.‬‬
‫האופרטורים מתפקדים בקוד כרגישים להקשרם‪.‬‬
‫ניתן להכליל את מרבית האופרטורים הקיימים אך לא ניתן להוסיף‬
‫אופרטורים חדשים (‪.)Deitel and Deitel chapter 8‬‬
‫רשימה חלקית של אופרטורים הניתנים להכללה‪-,+ :‬‬
‫‪.-<,delete,new,==,<<,>>,-=,+=,>,<,=,~,|,&,^,%,/,‬‬
‫אופרטורים שאינם ניתנים להכללה‪.? :,::,. :‬‬
)‫מבוא (המשך‬
:‫קוד ללא שימוש באופרטורים‬

Complex z1(1,2), z2(2,3), z3;
z3 = z1.add(z2);
z3.print();
z1.mul(z2).print();
Complex Complex::add(Const Complex& other) const {
return Complex(_real+other._real, _imag+other._imag);
}
3
)‫מבוא (המשך‬
:‫שימוש באופרטורים‬

Complex z1(1,2), z2(2,3), z3;
z3 = z1+z2;
cout << z3;
cout << z1*z2;
Complex Complex::operator+(Const Complex& other) const {
return Complex(_real+other._real, _imag+other._imag);
}
z3 = z1+z2
cout << z3


z3 = z1.operator+(z2)
operator<<( cout,z3 )
4
‫אופרטורים כפונקציות מחלקה‬
.‫ניתן להגדיר אופרטורים כפונקציות מחלקה או כפונקציות גלובליות‬
:‫אופרטור כפונקצית מחלקה‬


class Complex{
public:
Complex operator+(const Complex& other) const;
:
};
Complex Complex::operator+(Const Complex& other) const {
return Complex(_real+other._real, _imag+other._imag);
}
5
‫אופרטורים גלובליים‬
:‫אופרטור כפונקציה גלובלית חברה‬

class Complex {
friend Complex operator+(const Complex& z1,
const Complex& z2);
:
};
Complex operator+(const Complex& z1,const Complex& z2) ;
Complex operator+(const Complex& z1,const Complex& z2) {
return Complex(z1._real+z2._real,z1. _imag+z2._imag);
}
z3 = z1+z2  z3 = operator+(z1,z2)
z1+z2+z3
 operator+( operator+(z1,z2) , z3 )
6
)‫אופרטורים גלובליים (המשך‬
‫אופרטור כפונקציה גלובלית לא חברה (תוך שימוש בפונקציות‬
:)‫גישה‬

Complex operator+(const Complex& z1,const Complex& z2) ;
Complex operator+(const Complex& z1,const Complex& z2) {
return Complex(z1.getReal()+z2.getReal(),
z1. getImag()+z2.getImag());
}
z3 = z1+z2
z1+z2+z3


z3 = operator+(z1,z2)
operator+( operator+(z1,z2) , z3 )
7
‫אופרטורים גלובליים (סיכום ביניים)‬
‫‪‬‬
‫לפי עקרון האנקפסולציה נעדיף אופרטורים גלובליים שאינם חברים‬
‫(ונעזרים בפונקציות גישה בסיסיות) על פני אופרטורים לוקליים‪.‬‬
‫באופן זה‪ ,‬פחות פונקציות יקבלו גישה לשדות פרטיים‪.‬‬
‫‪‬‬
‫זאת בתנאי שניתן לממש בעזרת פונקציות גישה בסיסיות (ללא‬
‫פונקציות גישה מיותרות ו"חושפניות") ובתנאי שהיעילות לא נפגמת‬
‫במידה רבה (ללא העתקות רבות)‪.‬‬
‫‪‬‬
‫בנוסף‪ ,‬נעדיף להגדיר אופרטורים גלובליים לטיפול באפשרויות‬
‫רבות על פני הגדרות של אופרטורים מקומיים רבים‪.‬‬
‫‪8‬‬
‫אופרטורים גלובליים (סיכום ביניים)‬
‫‪‬‬
‫קיימים מקרים בהם מוכרחים להגדיר אופרטורים גלובליים‪:‬‬
‫‪‬‬
‫למשל‪double + Complex ,operator<< :‬‬
‫‪‬‬
‫גם ההיפך נכון‪ ,‬קיימים אופרטורים שלא ניתן להגדיר אותם‬
‫כגלובליים (המרה ‪.) =, [], (), ->,‬‬
‫‪‬‬
‫הגדרת שני אופרטורים לאותה הפעולה (אחד במחלקה ואחד‬
‫גלובלי) תגרום לשגיאת קומפילציה (‪.)ambiguous error‬‬
‫‪9‬‬
‫אופרטורים של קלט ופלט‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪ std::ostream‬היא מחלקה המייצגת זרם פלט (‪.)output stream‬‬
‫‪ std‬הוא מרחב השם (‪ )name space‬של הספרייה הסטנדרטית‪.‬‬
‫‪ std::cout‬הוא עצם גלובלי מהמחלקה ‪.std::ostream‬‬
‫‪ std::cout‬מייצג זרם פלט לערוץ הפלט הסטנדרטי (מחרוזת‬
‫שתופיע על המסך)‪.‬‬
‫שימוש באופרטור >> הקיים בספריה הסטנדרטית‪:‬‬
‫)‪std::cout << 5  std::cout.operator<<(5‬‬
‫)‪std::ostream& std::ostream::operator<<(int n‬‬
‫‪10‬‬
‫אופרטורים של קלט ופלט (המשך)‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫העמסת האופרטור << עבור מחלקת ‪.Complex‬‬
‫הפעלה ע"י ‪ std::cout<<z‬מחייבת הגדרה גלובלית של האופרטור‪.‬‬
‫הגדרת האופרטור‪:‬‬
‫לצורך שרשור‬
‫‪std::ostream& operator<<( std::ostream& os,‬‬
‫{ )‪const Complex& z‬‬
‫;”‪os >> z.getReal)( >> “+” >> z.getImag)( >> “*i‬‬
‫;‪return os‬‬
‫}‬
‫שימוש באופרטור << ביחס לטיפוסים‪ ,double :‬מחרוזות‬
‫‪std::cout << z1 << z2 ‬‬
‫) ‪operator<<( operator<<(std::cout,z1) , z2‬‬
‫‪11‬‬
‫אופרטורים של קלט ופלט (המשך)‬
‫‪‬‬
‫‪‬‬
‫‪12‬‬
‫אופרטור הקלט >> יוגדר באותו האופן‪.‬‬
‫דוגמא ‪.8.3‬‬
‫אופרטורים אונריים‬
.‫אופרטור הפועל על עצם יחיד‬
.‫אופרטור המוגדר במחלקה הוא ללא ארגומנטים‬
.‫אופרטור המוגדר גלובלית מקבל ארגומנט יחיד‬
:‫דוגמא‬
Complex Complex::operator-() const {
return Complex(-getReal(),-getImag());
}
Complex operator-( const Complex& z ) {
return Complex(-z.getReal(),-z.getImag());
}
z1 = -z1

z1 = z1.operator-();
z1 = operator-(z1);




13
‫אופרטורים של השוואה‬
.‫אינו מוגדר באופן ברירת מחדל‬
:‫דוגמא‬


bool operator==( const Complex& l, const Complex& r )
const {
return (l.getReal()==r.getReal() &&
l.getImag()==r.getImag());
}
bool operator!=(const Complex& l, const Complex& r )
const {
return !(l==r);
}
14
‫דוגמא‪ :‬מחלקת מערך‬
‫‪‬‬
‫‪‬‬
‫מערכים ב‪ C++-‬ממומשים כמצביעים‪.‬‬
‫חסרונות‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪15‬‬
‫חסרה בדיקת תחום‪.‬‬
‫אין אפשרות להשוות בין שני מערכים בעזרת אופרטור ==‪.‬‬
‫אין אפשרות להציב מערך בתוך מערך (מצביע קבוע)‪.‬‬
‫אין אפשרות לכתוב או לקרוא מערך שלם בעזרת האופרטורים >>‪.<<,‬‬
‫דוגמא‪ :‬מחלקת מערך (המשך)‬
‫‪‬‬
‫מימוש מחלקת מערך כולל‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪16‬‬
‫בדיקת תחום‪.‬‬
‫אופרטורים של השוואה‪.‬‬
‫אופרטור השמה ובנאי העתקה‪.‬‬
‫אופרטור כתיבה וקריאה‪.‬‬
‫עצם ממחלקת מערך יכיר את גודלו‪.‬‬
‫אופרטור המתייחס לתא במערך (אופרטור [] כפי שקיים עבור מערכים)‪.‬‬
‫דוגמא ‪( 8.4_6‬חלוקת האופרטורים בדוגמא לגלובליים‪/‬פונקציות‬
‫מחלקה‪/‬חברות אינה תמיד תואמת את הכללים שלנו)‪.‬‬
‫דוגמא‪ :‬מחלקת מערך (המשך)‬
‫הערות‪:‬‬
‫‪ ‬עבור מחלקות המבצעות הקצאות דינאמיות רצוי להגדיר‪ :‬בנאי‬
‫ברירת מחדל‪ ,‬פונקציה הורסת‪ ,‬בנאי העתקה ואופרטור השמה‪.‬‬
‫‪ ‬הגדרת אופרטור השמה‪:‬‬
‫)‪Array& Array::operator=(const Array& right‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪17‬‬
‫החזרת הפניה היא יעילה יותר (ביחס להחזרת ערך)‪.‬‬
‫בדוגמא של ‪ Deitel‬אופרטור ההשמה החזיר &‪ const‬זאת בכדי למנוע את‬
‫הפעולה המלאכותית הבאה‪ .(x=y)=z :‬בספרייה הסטנדרטית זה לא נהוג‪.‬‬
‫החזרת הפניה מאפשרת שרשור עבור אופרטורים הפועלים משמאל לימין‬
‫(למשל האופרטור >> אבל לא אופרטור =)‪.‬‬
‫דוגמא‪ :‬מחלקת מערך (המשך)‬
‫‪‬‬
‫הגדרת אופרטור []‪:‬‬
‫)‪int& Array::operator[](int subscript‬‬
‫‪const int& Array::operator[](int subscript) const‬‬
‫‪‬‬
‫‪‬‬
‫‪18‬‬
‫מאפשר שימוש דומה למערך‪.‬‬
‫הגדרה שנייה‪ ,‬עבור עצמים קבועים שגם את תוכנם לא נרצה לשנות‪.‬‬
‫דוגמא‪ :‬מחלקת מחרוזת‬
‫‪‬‬
‫ניתן לייצג מחרוזת ע"י‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫הגדרת אופרטור ()‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪19‬‬
‫*‪( char‬שפת ‪.)C‬‬
‫הגדרת מחלקה ‪( String‬שפת ‪.)C++‬‬
‫שימוש במחלקה קיימת ‪( std::string‬הספרייה הסטנדרטית של ‪.)C++‬‬
‫‪String String::operator() (int,int) const‬‬
‫התייחסות לעצם כאל פונקציה (‪.)function object‬‬
‫דוגמא ‪( 8.7_9‬חלוקת האופרטורים בדוגמא לגלובליים‪/‬פונקציות‬
‫מחלקה‪/‬חברות אינה תמיד תואמת את הכללים שלנו)‪.‬‬
‫אופרטורים של המרות‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫המרות בין מחלקה ‪ A‬לטיפוס בסיסי‪.‬‬
‫המרות בין מחלקה ‪ A‬למחלקה ‪.B‬‬
‫המרות בין טיפוס בסיסי אחד לטיפוס בסיסי שני – לא ניתן‪.‬‬
‫מימוש בעזרת בנאי ואופרטור המרה‪.‬‬
‫דוגמא‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪20‬‬
‫‪A::A(int n) // cast from int to A‬‬
‫‪A::operator int() const // cast from A to int‬‬
‫אין צורך בהגדרת ערך החזרה‪.‬‬
‫ניתן להגדיר אך ורק במחלקה (ולא באופן גלובלי)‪.‬‬
‫אופרטורים של המרות (המשך)‬
‫‪‬‬
‫שימוש באופרטור המרה‪:‬‬
‫;‪A a‬‬
‫‪cout << (int)a; // C syntax‬‬
‫‪cout << int(a); // C++ syntax‬‬
‫‪cout << a.operator int(); // direct syntax‬‬
‫‪‬‬
‫שימוש עקיף באופרטור המרה‪:‬‬
‫;‪cout << a‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪21‬‬
‫הקומפיילר ידאג לקרוא לאופרטור המרה‪.‬‬
‫אילו האופרטור << היה ממומש לעצם ממחלקה ‪ A‬אז הייתה קריאה אליו‪.‬‬
‫אילו היו ממומשים כמה אופרטורי המרה (ל‪ int-‬וגם ל‪ )double-‬אז הייתה‬
‫מתקבלת הודעת שגיאה עבור‪cout << a; :‬‬
‫הקומפיילר לא יבצע הרכבה של שתי המרות עקיפות (למשל מ‪ B-‬ל‪ A-‬ואחר‬
‫כך מ‪ A-‬ל‪.)int-‬‬
)‫אופרטורים של המרות (המשך‬
:‫המרות בין שתי מחלקות שאינן בסיסיות‬
A::A(const B& b)
A::operator B() const

// cast from B to A
// cast from A to B
22
‫אופרטור ‪--,++‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫אופרטור ‪ – )++x( prefix‬מחזיר את הערך שהתקבל אחרי‬
‫ההוספה‪.‬‬
‫אופרטור ‪ – )x++( postfix‬מחזיר את הערך שהיה לפני ההוספה‪.‬‬
‫מוגדרים באופן שונה כדי שהקומפיילר יבדיל ביניהם‪.‬‬
‫הגדרת אופרטור ‪:prefix‬‬
‫‪‬‬
‫‪‬‬
‫‪Date& Date::operator++() // local‬‬
‫‪Date& operator++(Date&) // global‬‬
‫מחזיר הפניה (לעצם המעודכן)‪.‬‬
‫שימוש באופרטור ‪:prefix‬‬
‫;‪Date d‬‬
‫;‪++d‬‬
‫‪d.operator++(); // using local operator‬‬
‫‪operator++(d); // using global operator‬‬
‫‪23‬‬
)‫ (המשך‬--,++ ‫אופרטור‬
:postfix ‫הגדרת אופרטור‬
Date Date::operator++(int) // local
Date operator++(Date&,int) // global
.‫“ כדי להבדיל‬dummy” ‫הגדרה בעזרת פרמטר‬
.)‫מחזיר ערך ולא הפניה (ערך ההחזרה הוא עצם זמני‬



:postfix ‫שימוש באופרטור‬

Date d;
d++;
d.operator++(0); // using local operator
operator++(d,0); // using global operator
8.10_12 ‫דוגמא‬

24
‫מחרוזת ומערך של הספרייה‬
‫הסטנדרטית‬
‫‪‬‬
‫‪‬‬
‫‪25‬‬
‫מחלקת מחרוזת (‪ )string‬של הספרייה הסטנדרטית‪:‬‬
‫דוגמא ‪8.13‬‬
‫אופרטורים ופונקציות של ‪string‬‬
‫מחלקת מערך (‪ )vector‬של הספרייה הסטנדרטית‪:‬‬
‫דוגמא ‪8.14‬‬
‫אופרטורים ופונקציות של ‪vector‬‬
‫העמסה של אופרטורים ‪ -‬סיכום‬
‫‪‬‬
‫הדגמנו את האופרטורים הבאים‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪26‬‬
‫בינאריים (‪)...,-=,+=,/,*,-,+‬‬
‫קלט‪/‬פלט‬
‫אונריים (‪)-,+‬‬
‫השוואה (==‪)...,>=,<,>,!=,‬‬
‫השמה‬
‫[]‪)(,‬‬
‫המרה‬
‫‪--,++‬‬
‫העמסה של אופרטורים – סיכום‬
‫(המשך)‬
‫‪‬‬
‫הימנעו מהפתעות והטעיות‪:‬‬
‫;)‪// prefer s.push(5‬‬
‫;)(‪// prefer x = x.pop‬‬
‫‪‬‬
‫ספקו קבוצה שלמה של אופרטורים‪ ,‬שהקשר ביניהם טבעי‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫למשל אם מימשתם "‪ "-‬בינארי‪ ,‬אז הוסיפו גם את "=‪ "-" ,"-‬אונרי‪.‬‬
‫ההגדרות צריכות להיות עקביות‪ x -= y :‬שקול ל ‪.x = x-y‬‬
‫הקפידו על שימוש חוזר בעת מימוש אופרטורים דומים‪:‬‬
‫‪‬‬
‫‪27‬‬
‫;‪Stack s‬‬
‫;‪s += 5‬‬
‫;‪int x = s--‬‬
‫למשל מימוש האופרטורים "‪ "+=" ,"+‬יכול להתבסס אחד על השני (מי‬
‫יתבסס על מי?)‪.‬‬
‫העמסה של אופרטורים – סיכום‬
)‫(המשך‬
:‫מזערו את כמות האופרטורים להמרה‬

A::operator int)( const {…}
A::operator double)( const {…}
A a;
cout << a; // compilation error
‫שמו לב לכך שבנאי המקבל כארגומנט טיפוס בסיסי יחיד הוא גם‬
.‫אופרטור המרה‬
Array a(10);
a[3] = 5;
a = 5; // Can be compiled but probably a wrong action.
// a contents is lost.

28
‫העמסה של אופרטורים – סיכום‬
)‫(המשך‬
.‫הקומפיילר לא יבצע הרכבה של שתי המרות עקיפות‬

A::operator B)( const {…}
B::operator int)( const {…}
int calc)int x( {…}
A a;
calc(a);
B b(a);
calc(b);
int x = b;
calc(x);
// compilation error
// converting A to B and using copy constructor
// converting B to int
// converting B to int and using assignment operator
// fine
29
‫העמסה של אופרטורים – סיכום‬
‫(המשך)‬
‫‪‬‬
‫‪30‬‬
‫אופרטור השייך למחלקה לעומת אופרטור גלובלי‪:‬‬
‫‪‬‬
‫נעדיף אופרטור גלובלי לא חבר על פני אופרטור של מחלקה‪ ,‬בתנאי‬
‫שמשתמשים אך ורק בפונקציות גישה בסיסיות (עקרון האנקפסולציה)‪.‬‬
‫‪‬‬
‫נעדיף להגדיר אופרטורים גלובליים לטיפול באפשרויות רבות על פני הגדרות‬
‫של אופרטורים מקומיים רבים‪.‬‬
‫‪‬‬
‫רצוי לא להגדיר לכל שדה פונקציית גישה (נוגד את עקרון ההסתרה) אלא‬
‫לספק רק את אלו המשרתים את המחלקה‪.‬‬
‫‪‬‬
‫כאשר לא נוח או לא יעיל להגדיר אופרטור גלובלי לא חבר אז נגדיר אותו‬
‫כאופרטור של המחלקה (לעיתים "=‪ "+‬הוא אופרטור כזה)‪.‬‬
‫העמסה של אופרטורים – סיכום‬
‫(המשך)‬
‫‪‬‬
‫מקרים בהם נהיה מוכרחים להגדיר אופרטורים גלובליים‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫הטיפוס של הארגומנט הראשון אינו מחלקה‪:‬‬
‫;) ‪Vector operator*( double, Vector‬‬
‫הטיפוס של הארגומנט הראשון הוא מחלקה שלא ניתנת לשינוי‪:‬‬
‫;) ‪ostream& operator<<( ostream&, Vector‬‬
‫נדרשת המרה של הארגומנט הראשון‪:‬‬
‫;) ‪Fraction operator*( Fraction, Fraction‬‬
‫;‪Fraction b = 2*a‬‬
‫גם ההיפך נכון‪ ,‬קיימים אופרטורים שלא ניתן להגדיר אותם‬
‫כגלובליים (המרה ‪.)=, [], () , ->,‬‬
‫קדימויות של אופרטורים‪:‬‬
‫‪http://www.cppreference.com/wiki/operator_precedence‬‬
‫‪31‬‬