1 / 31

תכנות מונחה עצמים והנדסת תוכנה – תשע"ד

תכנות מונחה עצמים והנדסת תוכנה – תשע"ד. העמסה של אופרטורים ( Operator overloading ). מבוא. מטרה: להכליל אופרטורים של טיפוסים בסיסיים לפעול על עצמים. למשל, הכללת האופרטור "+" עבור מספר מרוכב, וקטור ורשימה משורשרת. מתקבל קוד ברור. האופרטורים מתפקדים בקוד כרגישים להקשרם.

oki
Télécharger la présentation

תכנות מונחה עצמים והנדסת תוכנה – תשע"ד

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. תכנות מונחה עצמים והנדסת תוכנה – תשע"ד העמסה של אופרטורים (Operator overloading)

  2. מבוא מטרה: להכליל אופרטורים של טיפוסים בסיסיים לפעול על עצמים. למשל, הכללת האופרטור "+" עבור מספר מרוכב, וקטור ורשימה משורשרת. מתקבל קוד ברור. האופרטורים מתפקדים בקוד כרגישים להקשרם. ניתן להכליל את מרבית האופרטורים הקיימים אך לא ניתן להוסיף אופרטורים חדשים (Deitel and Deitel chapter 8). רשימה חלקית של אופרטורים הניתנים להכללה: +,-,/,%,^,&,|,~,=,<,>,=+,=-,>>,<<,==,new,delete,<-. אופרטורים שאינם ניתנים להכללה: .,::,: ?. 2

  3. מבוא (המשך) קוד ללא שימוש באופרטורים: 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

  4. מבוא (המשך) שימוש באופרטורים: 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  z3 = z1.operator+(z2) cout << z3  operator<<( cout,z3 ) 4

  5. אופרטורים כפונקציות מחלקה ניתן להגדיר אופרטורים כפונקציות מחלקה או כפונקציות גלובליות. אופרטור כפונקצית מחלקה: 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

  6. אופרטורים גלובליים אופרטור כפונקציה גלובלית חברה: 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

  7. אופרטורים גלובליים (המשך) אופרטור כפונקציה גלובלית לא חברה (תוך שימוש בפונקציות גישה): 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  z3 = operator+(z1,z2) z1+z2+z3  operator+( operator+(z1,z2) , z3 ) 7

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

  9. אופרטורים גלובליים (סיכום ביניים) קיימים מקרים בהם מוכרחים להגדיר אופרטורים גלובליים: למשל: double + Complex ,operator<< גם ההיפך נכון, קיימים אופרטורים שלא ניתן להגדיר אותם כגלובליים (המרה =, [], (), ->, ). הגדרת שני אופרטורים לאותה הפעולה (אחד במחלקה ואחד גלובלי) תגרום לשגיאת קומפילציה (ambiguous error). 9

  10. אופרטורים של קלט ופלט 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

  11. אופרטורים של קלט ופלט (המשך) העמסת האופרטור << עבור מחלקת Complex. הפעלה ע"י std::cout<<z מחייבת הגדרה גלובלית של האופרטור. הגדרת האופרטור: std::ostream& operator<<( std::ostream& os, const Complex& z) { os << z.getReal() << “+” << z.getImag() << “*i”; return os; } std::cout << z1 << z2  operator<<( operator<<(std::cout,z1) , z2 ) לצורך שרשור שימוש באופרטור << ביחס לטיפוסים: double, מחרוזות 11

  12. אופרטורים של קלט ופלט (המשך) אופרטור הקלט >> יוגדר באותו האופן. דוגמא 8.3. 12

  13. אופרטורים אונריים אופרטור הפועל על עצם יחיד. אופרטור המוגדר במחלקה הוא ללא ארגומנטים. אופרטור המוגדר גלובלית מקבל ארגומנט יחיד. דוגמא: 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

  14. אופרטורים של השוואה אינו מוגדר באופן ברירת מחדל. דוגמא: 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

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

  16. דוגמא: מחלקת מערך (המשך) • מימוש מחלקת מערך כולל: • בדיקת תחום. • אופרטורים של השוואה. • אופרטור השמה ובנאי העתקה. • אופרטור כתיבה וקריאה. • עצם ממחלקת מערך יכיר את גודלו. • אופרטור המתייחס לתא במערך (אופרטור [] כפי שקיים עבור מערכים). • דוגמא 8.4_6 (חלוקת האופרטורים בדוגמא לגלובליים/פונקציות מחלקה/חברות אינה תמיד תואמת את הכללים שלנו). 16

  17. דוגמא: מחלקת מערך (המשך) הערות: • עבור מחלקות המבצעות הקצאות דינאמיות רצוי להגדיר: בנאי ברירת מחדל, פונקציה הורסת, בנאי העתקה ואופרטור השמה. • הגדרת אופרטור השמה: Array& Array::operator=(const Array& right) • החזרת הפניה היא יעילה יותר (ביחס להחזרת ערך). • בדוגמא של Deitel אופרטור ההשמה החזיר const& זאת בכדי למנוע את הפעולה המלאכותית הבאה: (x=y)=z. בספרייה הסטנדרטית זה לא נהוג. • החזרת הפניה מאפשרת שרשור עבור אופרטורים הפועלים משמאל לימין (למשל האופרטור >> אבל לא אופרטור =).

  18. דוגמא: מחלקת מערך (המשך) הגדרת אופרטור []: 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(חלוקת האופרטורים בדוגמא לגלובליים/פונקציות מחלקה/חברות אינה תמיד תואמת את הכללים שלנו).

  20. אופרטורים של המרות • המרות בין מחלקה A לטיפוס בסיסי. • המרות בין מחלקה A למחלקה B. • המרות בין טיפוס בסיסי אחד לטיפוס בסיסי שני – לא ניתן. • מימוש בעזרת בנאי ואופרטור המרה. • דוגמא: A::A(int n) // cast from int to A A::operator int() const // cast from A to int • אין צורך בהגדרת ערך החזרה. • ניתן להגדיר אך ורק במחלקה (ולא באופן גלובלי).

  21. אופרטורים של המרות (המשך) • שימוש באופרטור המרה: A a; cout << (int)a; // C syntax cout << int(a); // C++ syntax cout << a.operator int(); // direct syntax • שימוש עקיף באופרטור המרה: cout << a; • הקומפיילר ידאג לקרוא לאופרטור המרה. • אילו האופרטור << היה ממומש לעצם ממחלקה A אז הייתה קריאה אליו. • אילו היו ממומשים כמה אופרטורי המרה (ל-int וגם ל-double) אז הייתה מתקבלת הודעת שגיאה עבור: cout << a; • הקומפיילר לא יבצע הרכבה של שתי המרות עקיפות (למשל מ-B ל-A ואחר כך מ-A ל-int).

  22. אופרטורים של המרות (המשך) • המרות בין שתי מחלקות שאינן בסיסיות: A::A(const B& b) // cast from B to A A::operator B() const // cast from A to B

  23. אופרטור ++,-- • אופרטור prefix (++x) – מחזיר את הערך שהתקבל אחרי ההוספה. • אופרטור postfix (x++) – מחזיר את הערך שהיה לפני ההוספה. • מוגדרים באופן שונה כדי שהקומפיילר יבדיל ביניהם. • הגדרת אופרטור prefix: Date& Date::operator++() // local Date& operator++(Date&) // global • מחזיר הפניה (לעצם המעודכן). • שימוש באופרטור prefix: Date d; ++d; d.operator++(); // using local operator operator++(d); // using global operator

  24. אופרטור ++,-- (המשך) • הגדרת אופרטור 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

  25. מחרוזת ומערך של הספרייה הסטנדרטית • מחלקת מחרוזת (string) של הספרייה הסטנדרטית: דוגמא 8.13 אופרטורים ופונקציות של string • מחלקת מערך (vector) של הספרייה הסטנדרטית: דוגמא 8.14 אופרטורים ופונקציות של vector

  26. העמסה של אופרטורים - סיכום • הדגמנו את האופרטורים הבאים: • בינאריים (+,-,*,/,=+,=-,...) • קלט/פלט • אונריים (+,-) • השוואה (==,=!,>,<,=>,...) • השמה • [],() • המרה • ++,--

  27. העמסה של אופרטורים – סיכום (המשך) • הימנעו מהפתעות והטעיות: Stack s; s += 5; // prefer s.push(5); int x = s--; // prefer x = x.pop(); • ספקו קבוצה שלמה של אופרטורים, שהקשר ביניהם טבעי: • למשל אם מימשתם "-" בינארי, אז הוסיפו גם את "=-", "-" אונרי. • ההגדרות צריכות להיות עקביות: x -= y שקול ל x = x-y. • הקפידו על שימוש חוזר בעת מימוש אופרטורים דומים: • למשל מימוש האופרטורים "+", "=+" יכול להתבסס אחד על השני (מי יתבסס על מי?).

  28. העמסה של אופרטורים – סיכום (המשך) • מזערו את כמות האופרטורים להמרה: 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.

  29. העמסה של אופרטורים – סיכום (המשך) • הקומפיילר לא יבצע הרכבה של שתי המרות עקיפות. A::operator B() const {…} B::operator int() const {…} int calc(int x) {…} A a; calc(a); // compilation error B b(a); // converting A to B and using copy constructor calc(b); // converting B to int int x = b; // converting B to int and using assignment operator calc(x); // fine

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

  31. העמסה של אופרטורים – סיכום (המשך) • מקרים בהם נהיה מוכרחים להגדיר אופרטורים גלובליים: • הטיפוס של הארגומנט הראשון אינו מחלקה: Vector operator*( double, Vector ); • הטיפוס של הארגומנט הראשון הוא מחלקה שלא ניתנת לשינוי: ostream& operator<<( ostream&, Vector ); • נדרשת המרה של הארגומנט הראשון: Fraction operator*( Fraction, Fraction ); Fraction b = 2*a; • גם ההיפך נכון, קיימים אופרטורים שלא ניתן להגדיר אותם כגלובליים (המרה=, [], () , ->, ). • קדימויות של אופרטורים: http://www.cppreference.com/wiki/operator_precedence

More Related