תכנות מונחה עצמים א` – תש"ע
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