slide1
Download
Skip this Video
Download Presentation
תכנות מונחה עצמים והנדסת תוכנה – תשע"ד

Loading in 2 Seconds...

play fullscreen
1 / 31

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


  • 189 Views
  • Uploaded on

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

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' תכנות מונחה עצמים והנדסת תוכנה – תשע"ד' - oki


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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide1

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

העמסה של אופרטורים (Operator overloading)

slide2
מבוא

מטרה: להכליל אופרטורים של טיפוסים בסיסיים לפעול על עצמים.

למשל, הכללת האופרטור "+" עבור מספר מרוכב, וקטור ורשימה משורשרת.

מתקבל קוד ברור.

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

ניתן להכליל את מרבית האופרטורים הקיימים אך לא ניתן להוסיף אופרטורים חדשים (Deitel and Deitel chapter 8).

רשימה חלקית של אופרטורים הניתנים להכללה: +,-,/,%,^,&,|,~,=,<,>,=+,=-,>>,<<,==,new,delete,<-.

אופרטורים שאינם ניתנים להכללה: .,::,: ?.

2

slide3
מבוא (המשך)

קוד ללא שימוש באופרטורים:

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

slide4
מבוא (המשך)

שימוש באופרטורים:

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

slide5
אופרטורים כפונקציות מחלקה

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

אופרטור כפונקצית מחלקה:

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

slide6
אופרטורים גלובליים

אופרטור כפונקציה גלובלית חברה:

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

slide7
אופרטורים גלובליים (המשך)

אופרטור כפונקציה גלובלית לא חברה (תוך שימוש בפונקציות גישה):

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

slide8
אופרטורים גלובליים (סיכום ביניים)

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

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

בנוסף, נעדיף להגדיר אופרטורים גלובליים לטיפול באפשרויות רבות על פני הגדרות של אופרטורים מקומיים רבים.

8

slide9
אופרטורים גלובליים (סיכום ביניים)

קיימים מקרים בהם מוכרחים להגדיר אופרטורים גלובליים:

למשל: double + Complex ,operator<<

גם ההיפך נכון, קיימים אופרטורים שלא ניתן להגדיר אותם כגלובליים (המרה =, [], (), ->, ).

הגדרת שני אופרטורים לאותה הפעולה (אחד במחלקה ואחד גלובלי) תגרום לשגיאת קומפילציה (ambiguous error).

9

slide10
אופרטורים של קלט ופלט

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

slide11
אופרטורים של קלט ופלט (המשך)

העמסת האופרטור << עבור מחלקת 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

slide12
אופרטורים של קלט ופלט (המשך)

אופרטור הקלט >> יוגדר באותו האופן.

דוגמא 8.3.

12

slide13
אופרטורים אונריים

אופרטור הפועל על עצם יחיד.

אופרטור המוגדר במחלקה הוא ללא ארגומנטים.

אופרטור המוגדר גלובלית מקבל ארגומנט יחיד.

דוגמא:

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

slide14
אופרטורים של השוואה

אינו מוגדר באופן ברירת מחדל.

דוגמא:

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

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

15

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

16

slide17
דוגמא: מחלקת מערך (המשך)

הערות:

  • עבור מחלקות המבצעות הקצאות דינאמיות רצוי להגדיר: בנאי ברירת מחדל, פונקציה הורסת, בנאי העתקה ואופרטור השמה.
  • הגדרת אופרטור השמה:

Array& Array::operator=(const Array& right)

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

הגדרת אופרטור []:

int& Array::operator[](int subscript)

const int& Array::operator[](int subscript) const

מאפשר שימוש דומה למערך.

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

18

slide19
דוגמא: מחלקת מחרוזת
  • ניתן לייצג מחרוזת ע"י:
    • char* (שפת C).
    • הגדרת מחלקה String (שפת C++).
    • שימוש במחלקה קיימת std::string (הספרייה הסטנדרטית של C++).
  • הגדרת אופרטור ():

String String::operator() (int,int) const

    • התייחסות לעצם כאל פונקציה (function object).
  • דוגמא 8.7_9(חלוקת האופרטורים בדוגמא לגלובליים/פונקציות מחלקה/חברות אינה תמיד תואמת את הכללים שלנו).
slide20
אופרטורים של המרות
  • המרות בין מחלקה A לטיפוס בסיסי.
  • המרות בין מחלקה A למחלקה B.
  • המרות בין טיפוס בסיסי אחד לטיפוס בסיסי שני – לא ניתן.
  • מימוש בעזרת בנאי ואופרטור המרה.
  • דוגמא:

A::A(int n) // cast from int to A

A::operator int() const // cast from A to int

    • אין צורך בהגדרת ערך החזרה.
    • ניתן להגדיר אך ורק במחלקה (ולא באופן גלובלי).
slide21
אופרטורים של המרות (המשך)
  • שימוש באופרטור המרה:

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).
slide22
אופרטורים של המרות (המשך)
  • המרות בין שתי מחלקות שאינן בסיסיות:

A::A(const B& b) // cast from B to A

A::operator B() const // cast from A to B

slide23
אופרטור ++,--
  • אופרטור 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

slide24
אופרטור ++,-- (המשך)
  • הגדרת אופרטור 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
slide25
מחרוזת ומערך של הספרייה הסטנדרטית
  • מחלקת מחרוזת (string) של הספרייה הסטנדרטית:

דוגמא 8.13

אופרטורים ופונקציות של string

  • מחלקת מערך (vector) של הספרייה הסטנדרטית:

דוגמא 8.14

אופרטורים ופונקציות של vector

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

Stack s;

s += 5; // prefer s.push(5);

int x = s--; // prefer x = x.pop();

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

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.

slide29
העמסה של אופרטורים – סיכום (המשך)
  • הקומפיילר לא יבצע הרכבה של שתי המרות עקיפות.

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

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

Vector operator*( double, Vector );

    • הטיפוס של הארגומנט הראשון הוא מחלקה שלא ניתנת לשינוי:

ostream& operator<<( ostream&, Vector );

    • נדרשת המרה של הארגומנט הראשון:

Fraction operator*( Fraction, Fraction );

Fraction b = 2*a;

  • גם ההיפך נכון, קיימים אופרטורים שלא ניתן להגדיר אותם כגלובליים (המרה=, [], () , ->, ).
  • קדימויות של אופרטורים: http://www.cppreference.com/wiki/operator_precedence
ad