העמסת אופרטוריםOperator Overloading 

 

 

 


תוכן הפרק

 

פונקציות האופרטורים. 56

אופרטורים אונריים ובינאריים. 75

העמסה על ידי פונקציה חברה. 100

prefix  postfix   האופרטורים  ++  --. 125

משמעות מוגדרת מראש של האופרטורים     = & , 160

מתי להעמיס על ידי פונקציה חברה ומתי על ידי פונקציה חופשית. 179

הסבת טיפוסים על ידי המתכנת. 195

פונקציות אתחול  constructor  בתפקיד הסבת טיפוס. 230

אופרטורי הסבה. 274

מחלקות המכילות הקצאה דינאמית. 320

copy constructor 321

מה קורה אם שוכחים copy constructor   או  operator=. 404

שימוש ב explicit  לביטול הסבת טיפוס ע"י הבנאי. 427

אופרטור אינדקס [] 477

אופרטורי קלט/פלט   operator<<  operator>>. 516

קלט / פלט מקבצים. 567

file streams. 568

קלט / פלט בקבצים של טיפוס מוגדר על ידי המתכנת. 603

Fraction – final version. 638

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

 

               ·            הגדרת טיפוס פירושו הגדרת מושג מופשט הכולל תחום ערכים  ואוסף פעולות. לדוגמא,  הטיפוס  int  מגדיר ערכים מותרים שלמים ואוסף פעולות כגון  + - * /.

               ·            ישנם מקרים בהם מגדיר המשתמש טיפוס חדש אשר עבורו יש משמעות להגדיר פעולות דומות במשמעותן לאלו שקיימות עבור הטיפוסים המוגדרים בשפה עצמה. לדוגמא אם מגדירים מחלקה (טיפוס חדש) של מספרים מרוכבים או מטריצות הרי היה מאד נוח וברור עבורנו אם היה אפשר להגדיר את הפעולות + - * /  עבור טיפוסים חדשים אלה. אז היינו יכולים לכתוב קוד באותה מידה של בהירות ונוחות כפי שאנו כותבים קוד עבור טיפוסים קיימים כגון  int.

               ·            בשפת  C++  קיימת האפשרות להעמיס משמעות נוספת לאופרטור קיים (עבור טיפוס חדש-מחלקה). רצוי לעשות זאת אך ורק אם המשמעות של הפעולה החדשה תהיה ברורה לכל מי שקורא את התוכנית (דומה למשמעות המקורית) ולא רק לכותב.

 

 

לדוגמא:

·        אם רק נחליף את שם הפונקציה  Add  בשם   operator+ .

·        ואם נחליף את שם הפונקציה  Mul  בשם   operator*  נקבל את המבוקש:

 

class Fraction {

friend Fraction operator+(Fraction, Fraction);

friend Fraction operator*(Fraction, Fraction);

public:

    Fraction(int nn, int dd);

private:

    int n, d;

};

 

Fraction operator+(const Fraction& left, const Fraction& right)

{ return Fraction(left.n * right.d + left.d * right.n, left.d * right.d); }

Fraction operator*(const Fraction& left, const Fraction& right)

{    return Fraction(left.n * right.n, left.d * right.d);  }

 

void f()

{   Fraction a = Fraction(1, 3);

    Fraction b = Fraction(2, 5);

    Fraction c = b;

    a = b + c;

    b = b + c * a;

    c = a * b + Fraction(1, 2);

}

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

               ·            מותר להגדיר פונקציות אשר יגדירו מחדש את פעולתם של האופרטורים הבאים:

 

+

-

*

/

%

^

&

|

~

!

=

<

>

+=

-=

*=

/=

%=

^=

&=

|=

<<

>>

>>=

<<=

==

!=

<=

>=

&&

||

++

--

->*

,

->

[]

()

new

delete

כללים:

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

  2.  אי אפשר להמציא  tokens  חדשים. לדוגמא אי אפשר לההמציא אופרטור ** עבור חזקה.

  3.  אי אפשר להגדיר מחדש משמעות של אופרטורים עבור טיפוסים קיימים בשפה.

 

               ·            שימוש באופרטור המוגדר מחדש הוא בסך הכל קריאה בצורה נוחה לפונקצית האופרטור.

 

void f(Fraction a, Fraction b)

{

    Fraction c = a + b;           // shorthand

    Fraction d = operator+(a, b); // explicit call

}

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

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

·            אופרטור בינארי: אופרטור המקבל שני ארגומנטים

class Fraction {

friend Fraction operator+(Fraction, Fraction); // binary:  a + b

friend Fraction operator-(Fraction);           // unary:   -c

 

public:

    Fraction(int nn, int dd);

private:

    int n, d;

};

 

Fraction operator+(const Fraction& left, const Fraction& right)

{ return Fraction(left.n * right.d + left.d * right.n, left.d * right.d);}

Fraction operator-(const Fraction& f)

{    return Fraction(-f.n, f.d);  }

 

void f()

{   Fraction a(1, 3), b(2, 5);

    a = b + c;       // a = operator+(b, c)

    b = -b + c;      // b = operator+(operator-(b), c)

    c = a + -b + Fraction(1, 2);

}

 

העמסה על ידי פונקציה חברה 

·      כאשר ההעמסה מתבצעת על ידי פונקציה חברה, מניחים כי הארגומנט השמאלי הוא עצם

·      כאשר מעמיסים אופרטור על ידי פונקציה חברה:

  1. לאופרטור בינארי: רק ארגומנט אחד (הימני). השמאלי הוא העצם שעליו מופעל האופרטור.
  2. לאופרטור אונרי: אין ארגומנטים  (הארגומנט הוא העצם שעליו מפעל האופרטור).

class Fraction {

public:

    Fraction(int nn, int dd);

    Fraction operator+(Fraction right); // binary:  a + b

    Fraction operator-();               // unary:   -c

private:

    int n, d;

};

 

Fraction Fraction::operator+(const Fraction& right)

{ return Fraction(n * right.d + d * right.n, d * right.d);}

Fraction Fraction::operator-()

{    return Fraction(-n, d);  }

 

void f()

{   Fraction a(1, 3), b(2, 5);

    a = b + c;  // a = b.operator+(c)

    c = -b;     // c = b.operator-()  

}


 prefix  postfix   האופרטורים  ++  --     

·      צורת  prefix היא כאשר ה  ++  מופיע לפני לדוגמא:   ++a

  1. הערך שמחזיר האופרטור הוא  a+1    
  2. וה   side effect  הוא קידום  a.

·      צורת  postfix היא כאשר ה  ++  מופיע אחרי לדוגמא:  a++

  1. הערך שמחזיר האופרטור הוא  a    
  2. וה   side effect  הוא קידום  a.

 

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

·            בגרסת ה  prefix  כותבים את האופרטור כמו כל אופרטור אונרי רגיל.

·            בגרסת ה  postfix  מוסיפים לפונקציית האופרטור ארגומנט נוסף מסוג  int  (בכדי שהמהדר יוכל להבחין בין שתי הגרסאות).

 

class Fraction

{

public:

    Fraction(int nn = 0, int dn = 1);

    Fraction operator++();      // prefix  - ++a

    Fraction operator++(int);   // postfix - a++

};

 

Fraction Fraction::operator++()    // prefix - ++a

{

    n += d; // increment by 1.

    zimzum();

    return *this;  // return this fraction.

}

 

Fraction Fraction::operator++(int) // postfix - a++

{

    int nn = n;  // save "old" fraction

    int dd = d;

    n += d;      // increment by 1.

    zimzum();

    return Fraction(nn, dd);

}

משמעות מוגדרת מראש של האופרטורים    , = &

               ·            כל אופרטור מוגדר בפני עצמו והשפה לא עושה קומבינציות מאופרטורים המוגדרים מחדש:  עבור  int  הביטויים:   ++a a+=1 a=a+1  שקולים. אבל אם הגדרנו עבור המחלקה  Fraction  את האופרטורים  +  ו  =  זה אינו גורר הגדרת האופרטור  +=.

               ·            כאשר מגדירים מחלקה חדשה מייד מוגדרים בברירת מחדל האופרטורים:  = & ,  אפשר לבטל את האפשרות להשתמש באופרטורים אלו על ידי הגדרתם כ  private:

class X {

 

private:

   void operator=(const X&);

   void operator&();

   void operator,(const X&);

};

 

void f(X a, X b)

{

    a = b; // ERROR: operator= is private

    &a;    // ERROR: operator& is private

    a, b;  // ERROR: operator, is private

}

 


מתי להעמיס על ידי פונקציה חברה ומתי על ידי פונקציה חופשית

·            אי אפשר לשנות את המשמעות של אופרטור הקיים עבור טיפוסים בסיסיים. לדוגמא אי אפשר להגדיר מחדש אופרטור  int + int  

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

                     1.  אופרטור מוגדר להיות פונקציה חברה או המכיל לפחות ארגומנט אחד שהוא מחלקה (פרט לאופרטורים new delete). דבר זה נועד להבטיח כי המשתמש לא ישנה אופרטורים הקיימים בשפה. אין אפשרות להגדיר אופרטור שעובד רק על מצביעים.

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

 

aa  מטיפוס מחלקה X ומוגדר אoperator+() בתוך המחלקה אזי:

aa + 2  ok:     aa.operator+(2)

2 + aa  error:  2.operator+(aa)

 

אם נגדיר את operator+(X, int) operator+(int, X)    כפונקציות חופשיות אזי:

aa + 2  ok:  operator+(aa, 2)

2 + aa  ok:  operator+(2, aa)

 

חזרה להתחלה

 

הסבת טיפוסים על ידי המתכנת

 

class Fraction {

friend Fraction operator+(Fraction, Fraction);

friend Fraction operator+(Fraction, int);

friend Fraction operator+(int, Fraction);

 

friend Fraction operator-(Fraction, Fraction);

friend Fraction operator-(Fraction, int);

friend Fraction operator-(int, Fraction);

 

friend Fraction operator*(Fraction, Fraction);

friend Fraction operator*(Fraction, int);

friend Fraction operator*(int, Fraction);

 

public:

    Fraction(int r, int i);

    Fraction operator-()  // unary

    { return Fraction(-n,d); }

 

private:

    int n, d;

};

 

void f()

{

    Fraction a(1,2), b(2,3), c(3,4), d(4,5), e(5,6);

 

    a = -b - c;

    b = c * 2 * c;

    c = (d + e) * a;

}

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

 


פונקציות אתחול  constructor  בתפקיד הסבת טיפוס

               ·            אם מגדירים עבור מחלקה  constructor  שאפשר להפעילו עם ארגומנט יחיד בעצם מגדירים כיצד להסב ערך מהטיפוס של 

        הארגומנט לטיפוס של המחלקה במקרים:

1.      הסבה בזמן אתחול עצם

2.      הסבה בזמן העברת ארגומנט לפונקציה

3.      הסבה בזמן החזרת ערך מפונקציה

4.      הסבה בזמו השמת עך לעצם.

 

class Fraction {

public:

    Fraction(int nn) {n = nn; d = 1;}

 

private:

    int n, d;

};

 

Fraction z1 = Fraction(23); 

Fraction z2 = 23;    // conversion: int -> Fraction

 

void f(Fraction f);

 

1.      הסבה בזמן אתחול עצם Fraction a = 4;          

2.      הסבה בזמן העברת ארגומנט לפונקציהf(4) 

3.      הסבה בזמן החזרת ערך מפונקציה Fraction g() { return 4; }    

4.      הסבה בזמו השמת עך לעצם.f = 4;       

 

בכל המקרים הללו  4  מוסב להיות   Fraction(4)

 

אם נגדיר את המחלקה כך:

class Fraction {

friend Fraction operator+(Fraction, Fraction);

 

public:

    Fraction(int nn, int dd = 1);

    Fraction operator*(Fraction);

};

אזי משמעות הבטוי:

a = b + 2; // a = operator+(b, Fraction(2, 1))

a = 2 + b; // a = operator+(Fraction(2, 1), b)

a = b * 2; // a = b.operator+(Fraction(2, 1))

a = 2 * b; // Compile Error: a = 2.operator+(b) ???

a = 2 * 2; // a = Fraction(4, 1)

 


אופרטורי הסבה

שימוש ב  constructor  לשם השגת הסבת טיפוס הוא נוח אבל נושא בחובו מספר השלכות:

1.   אין אפשרות להסב טיפוס המוגדר על ידי המשתמש לטיפוס בסיסי.

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

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

 

               ·            את שתי הבעיות הראשונות ניתן לפתור על ידי הגדרת אופרטור הסבה:

X::operator T();

               ·            מגדירים פונקציה חברה במחלקה כך ש  T  הוא הטיפוס (טיפוס בסיסי או מחלקה) שאליו אנו רוצים להסב.

 

לדוגמא המחלקה  tiny  מגדירה מספרים קטנים כאשר יש בדיקה שאין גלישה.

 

class tiny {

public:

    tiny(int i) { assign(i); }

    tiny(const tiny& t) { v = t.v; } // no range checking

    tiny& operator=(const tiny& t) { v = t.v; return *this; }

    tiny& operator=(int i) { assign(i); return *this; }

    operator int() { return v;}

 

private:

    void assign(int i)

    {

        if (i < 0 || i > 63) { error("range error"); v = 0;}

        else

            v = i;

    }

    char v;

};

 

void main()

{

    tiny c1 = 2;

    tiny c2 = 62;

    tiny c3 = c2 - c1; // c3 = 60

    tiny c4 = c3;      // no range check

    int i = c1 + c2;   // i = 64;

    c1 = c2 + 2 * c1;  // range error: c1 = 0 (not 66)

    c2 = c1 - i;       // range error: c2 = 0;

    c3 = c2;           // no range check (not necessary)

}

 

               ·            שיטה זו טובה כאשר צריכים לחסוך מקום (במערכים).

               ·            שיטה זו טובה כאשר רוצים להחזיר את המספרים בשיטת ייצוג שונה (בסיסים שונים או שיטות ייצוג)


חזרה להתחלה

מחלקות המכילות הקצאה דינאמית

    copy constructor

class string {

    char* p;

    int   size; // of vector pointed by p

public:

    string(int sz) { p = new char[size=sz]; }

    string(char s[])

    {   p = new char[strlen(s)+1];

        strcpy(p, s);     // copy string s to p

    }

    ~string() { delete[] p; }

};

 

void f()

{

   string s1(10);

   string s2(20);

   s1 = s2;

} // deleting twice s1.p, s2.p is not deleted.

 

·      בעית השמה: הפתרון לבעיה זו הוא על ידי הגדרת אופרטור השמה

class string {

    char* p;

    int   size; // of vector pointed by p

public:

    string(int sz) { p = new char[size=sz]; }

    string(char s[])

    {   p = new char[strlen(s)+1];

        strcpy(p, s);     // copy string s to p

    }

    string& operator=(const string&);

    ~string() { delete[] p; }

};

 

string& string::operator=(const string& a)

{

    if (this != &a) { // beware s = s

        delete[] p;

        p = new char[size = a.size];

        strcpy(p, a.p);

    }

    return *this;

}

               ·            תיקון זה יבטיח כי הבעיה הקודמת תפתר.

               ·            החזרת   *this  מיועדת למקרים כגון:   s1 = s2 = s3;, שבהם משתמשים בערך שמחזיר האופרטור.

 

·      בעית אתחול: הבעיה דומה לבעיה הקודמת אך היא מתרחשת בזמו האתחול – בו עדיין אין מפעילים את  operator=.

void f()

{

    string s1(10);

    string s2 = s1; // initialization, not assignment

}

               ·            כאן ההבעיה שונה: לא מפעילים את ה  constructor  עבור s2  אלא מעתיקים את תוכן  s1  מבלי להפעיל את operator= .

               ·            אופרטור  =  אינו מופעל על עצם לא מאותחל.

               ·            חייבים להגדיר פונקציה נפרדת עבור אתחולים.

 

class string {

    char* p;

    int   size; // of vector pointed by p

public:

    string(int sz) { p = new char[size=sz]; }

    string(char s[])

    {   p = new char[size = strlen(s)+1];

        strcpy(p, s);     // copy string s to p

    }

    string(const string& s) // copy constructor

    {  p = new char[size = s.size];

        strcpy(p, s.p);     // copy string s.p to p

    }

    string& operator=(const string&);

    ~string() { delete[] p; }

};

 

               ·            הגדרת  copy constructor  נעשית באופן הבא:   X(const X&)

               ·            אתחול והשמה הן פעולות שונות !!

               ·            copy constructor מבצע:

1.      הקצאת שטח זכרון חדש.

2.      העתקת הנתונים אל השטח החדש.

               ·            operator=   מבצע:

1.      מחיקת שטח זכרון ישן

2.      הקצאת שטח זכרון חדש.

3.      העתקת הנתונים אל השטח החדש.

 

מה קורה אם שוכחים copy constructor   או  operator=

ישנם עוד שני מקרים בהם מופעל  copy constructor:

1.  כאשר מעבירים ארגומנט לפונקציה (call by value).

2.  כאשר פונקציה מחזירה ערך.

 

string g(string arg)

{

    return arg;

}

 

void main()

{    string s = "asdf";

 

 

 


    s           =         g(s);

}

1.  יצירת s:  string::string(char *)

2.  העברת s כארגומנט ל g():string::string(const string& ) 

3.  החזרת התוצאה מ  g():  string::string(const string& )  לתוך עצם זמני tmpObj.

4.  הפעלת string::~string()  על  arg.

5.  השמת  tmpObj  לתוך  s: string::operator=(const string&)

6.  הריסת העצם הזמני  tmpObj:   string::~string().

7.  הריסת העצם s:   string::~string().


שימוש ב explicit  לביטול הסבת טיפוס ע"י הבנאי.

·            לפעמים אין ברצוננו להשתמש בהסבת הטיפוס האוטומטית שמתבצעת באמצעות הבנאי.

class string {

   ...

public:

    string(int sz);

    string(char s[]);

    string(const string&);

    string& operator=(const string&);

    ~string();

};

 

void f(string s);

 

void main()

{

   string s1(10);

   string s2("abc");

 

   s1 = "efg";  // using string("efg")

   f("efg");

   s2 = 4;  // using string(4) – not a normal use of a string

   f(4);

}

·            ההמרה מ  int  ל  string  איננה הגיונית ןכן נחסום אותה באמצעות  explicit.

class string {

   ...

public:

    explicit string(int sz);

    string(char s[]);

    string(const string&);

    string& operator=(const string&);

    ~string();

};

 

void f(string s);

 

void main()

{

   string s1(10);    // OK

   string s2("abc");

 

   s1 = "efg";  // using string("efg")

   f("efg");    // OK

   s2 = 4;      // compile Error

   f(4);        // compile Error

   s2 = string(4);  // OK

   f(string(4));    // OK

}

חזרה להתחלה


אופרטור אינדקס []

·            נניח כי אני רוצה שעל עצמים מטיפוס המחלקה  string  אפשר יהיה להפעיל אינדקס כמו על מערך תווים רגיל.

class string {

    char* p;

    int   size; // of vector pointed by p

public:

    string(int sz);

    string(char s[]);

    string& operator=(const string&);

    ~string();

};

 

void main()

{

    char a[4] = "abc";

    string s = "abc";

 

    a[2] = 'e';  // fine

    s[2] = 'e';  // compile error!

}

·            הפתרון הוא להגדיר אופרטור אינדקס

class string {

    char* p;

    int   size; // of vector pointed by p

public:

    string(int sz);

    string(char s[]);

    string& operator=(const string&);

    char& operator{}(int i) { return p[i]; }

    ~string();

};

 

void main()

{

    string s = "abc";

    s[2] = 'e';  // fine:  s.operator[](2)  returns reference to s.p[2]

}

חזרה להתחלה

 


אופרטורי קלט/פלט   operator<<  operator>>

·            ראינו שאפשר לבצע קלט / פלט על משתנים המוגדרים בשפה:

int n;

char a[80];

 

cin >> n >> a;    // OK

cout << n << a;   // OK

 

Fraction f(3,4);

string   s("abc");

 

cin >> f >> s;  // compile Error

cout << f << s;  // compile Error

 

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

·            העצם   cout  הוא מטיפוס   ostream

·            העצם  cin  הוא מטיפוס  istream

 

·            בקריאה    cin >> f    צריך לשים לב כי הארגומנט השמאלי הוא מטיפוס   istream,  לכן פונקציית האופרטור חייבת להיות פונקציה חופשית.

 

·            חייבים להעביר ולהחזיר את העצמים מסוג   istream ostream  בהתיחסות. בכתי למנוע שיכפול של   cin cout.

 

 

#include<iostream.h>

class Fraction

{

friend istream& operator>>(istream& is, Fraction& f);

friend ostream& operator<<(ostream& os, const Fraction& f);

public:

    Fraction(int nn = 0, int dn = 1);

private:

    int n;  // top part

    int d;  // bottom part

};

 

istream& operator>>(istream& is, Fraction& f)

{

    char divSign;

    is >> f.n >> divSign >> f.d;

    if (f.d == 0)

        f.d = 1;

    f.zimzum();

    return is;

}

 

ostream& operator<<(ostream& os, const Fraction& f)

{

    return

        os << f.n << '/' << f.d;

}

חזרה להתחלה


קלט / פלט מקבצים

file streams

#include <iostream.h> // for cin & cout and cerr

#include <fstream.h>  // for ifstream & ofstream

#include <process.h>  // for exit() function

void main()

{

   ifstream infile("inp.txt"); // infile is an input file stream object

   if(!infile) { // error in openningfile

      cerr << "Input file cannot be opened" << endl;

      exit(1);

   }

 

   ofstream outfile("out.txt"); //outfile is an output file stream object

  

   if (!outfile) { // error in openning file

      cerr << "Output file cannot be opened" << endl;

      exit(1);

   }

 

   char one_char;

   while (infile) { // while it is not end of file, and input is correct

      infile >> one_char;

      outfile << one_char;

   } 

   infile.close();

   outfile.close();

}

·      פתיחת קובץ לקריאה מתבצעת על ידי יצירת עצם מטיפוס  ifstream   כאשר הארגומנט של הבנאי שלו הוא שם הקובץ ממנו רוצים לקרא נתונים.

·      פתיחת קובץ לכתיבה מתבצעת על ידי יצירת עצם מטיפוס  ofstream   כאשר הארגומנט של הבנאי שלו הוא שם הקובץ אליו רוצים לכתוב נתונים.

·      התוכנית פותחת שני קבצים אחד לקריאה ואחד לכתיבה, קוראת תו וכותבת אותו לקובץ השני..

·      העבודה עם עצם מסוג  ifstream זהה לצורת העבודה עם   cin.

·      העבודה עם עצם מסוג  ofstream זהה לצורת העבודה עם   cout.

·      יש לסגור קובץ מייד כאשר לא זקוקים לו (למרות שההורס גם סוגר אותו)

 


קלט / פלט בקבצים של טיפוס מוגדר על ידי המתכנת

·      אין צורך להגדיר פונקציות אופרטור  << >>  מעבר לאילו שהגדרנו עבור   cin cout.

·      מכיוון ש   ofstream  יורש מ  ostream   ו     ifstream  יורשת מ   istream אין צורך בהגדרות נוספות

#include <iostream.h> // for cin & cout and cerr

#include <fstream.h>  // for ifstream & ofstream

#include <process.h>  // for exit() function

void main()

{

   ofstream outfile("Fr.txt"); //outfile is an output file stream object

  

   if (!outfile) { // error in openning file

      cerr << "Output file cannot be opened" << endl;

      exit(1);

   }

 

   Fraction f1(1, 2), f2(4, 7), f3;

   outfile.close(); // must close file.

 

   ifstream infile("Fr.txt"); // infile is an input file stream object

   if(!infile) { // error in openningfile

      cerr << "Input file cannot be opened" << endl;

      exit(1);

   }

 

   // restore from ascii file.

   infile >> f1 >> f2 >> f3;

 

   infile.close();

}

 

 

 

 

.


Fraction – final version

//--------------- FRACTION.H ---------------

#include <iostream.h> // istream ostream

 

class Fraction

{

friend istream& operator>>(istream& is, Fraction& f);

friend ostream& operator<<(ostream& os, const Fraction& f);

friend Fraction operator+(const Fraction& f1, const Fraction& f2);

friend Fraction operator-(const Fraction& f1, const Fraction& f2);

friend Fraction operator*(const Fraction& f1, const Fraction& f2);

friend Fraction operator/(const Fraction& f1, const Fraction& f2);

 

public:

    Fraction(int nn = 0, int dn = 1);

    double ToDouble() { return double(n) / d; }

    Fraction operator++();      // prefix  - ++a

    Fraction operator++(int);   // postfix - a++

 

private:

    int           gcd(int i, int j);

    void         zimzum();

    int n;  // top part

    int d;  // bottom part

};

 

 

//------------- FRACTION.CPP-----------------

#include<iostream.h>

#include "FRACTION.H"

 

istream& operator>>(istream& is, Fraction& f)

{

    char divSign;

    is >> f.n >> divSign >> f.d;

    if (f.d == 0)

        f.d = 1;

    f.zimzum();

    return is;

}

 

ostream& operator<<(ostream& os, const Fraction& f)

{

    return

        os << f.n << '/' << f.d;

}

 

Fraction operator+(const Fraction& f1, const Fraction& f2)

{

    int nn = f1.n * f2.d + f1.d * f2.n;

    int dd = f1.d * f2.d;

    return Fraction(nn, dd);

}

 

Fraction operator-(const Fraction& f1, const Fraction& f2)

{

    int nn = f1.n * f2.d - f1.d * f2.n;

    int dd = f1.d * f2.d;

    return Fraction(nn, dd);

}

 

Fraction operator*(const Fraction& f1, const Fraction& f2)

{

    int nn = f1.n * f2.n;

    int dd = f1.d * f2.d;

    return Fraction(nn, dd);

}

 

Fraction operator/(const Fraction& f1, const Fraction& f2)

{

    int nn = f1.n * f2.d;

    int dd = f1.d * f2.n;

    return Fraction(nn, dd);

}

 

Fraction::Fraction(int nn, int dd) :

    n(nn), d(dd)

{

    if (d == 0)

        d = 1;

    zimzum();

}

 

Fraction Fraction::operator++()    // prefix - ++a

{

    n += d; // increment by 1.

    zimzum();

    return *this;  // return this fraction.

}

 

Fraction Fraction::operator++(int) // postfix - a++

{

    int nn = n;  // save "old" fraction

    int dd = d;

    n += d;      // increment by 1.

    zimzum();

    return Fraction(nn, dd);

}

 

 

int Fraction::gcd(int i, int j)

{

    if (i == 0 || j == 0)

        return i + j;   // if i or j is zero return the other value.

    while ((i %= j) != 0)// remainder replaces dividend.

    {                    // but remainder should replace divisor!

        int t = i;       // exchange dividend and divisor.

        i = j;

        j = t;

    }

    return j;

}

 

void Fraction::zimzum()

{

    int g = gcd(n, d);

    n /= g;

    d /= g;

}

 

 

 

 

 

//--------------- FRAC_USE.CPP ---------------

// Driver routine to test the Fraction class

 

#include <iostream.h>   // cout

#include <math.h>            // sqrt()

#include "FRACTION.H"   // for Fraction declarations

 

void main()

{

    // Try all three possible fraction constructors

    // and the input/output routines.

   

    Fraction f1(3, 2), f2(4), f3;

    cout << "\n The fraction f1 is " << f1 << endl;

    cout << "\n The fraction f2 is " << f2 << endl;

    cout << "\n The fraction f3 is " << f3 << endl;

   

    cout << "\n Now enter a fraction of your own: ";

    cin >> f1;

    cout << "\n And another one: ";

    cin >> f2;

   

    // Now try the overloaded operator.

    cout << "\n f1 + f2 is:" << (f1 + f2);

    cout << "\n f1 - f2 is:" << (f1 - f2);

    cout << "\n f1 * f2 is:" << (f1 * f2);

    cout << "\n f1 / f2 is:" << (f1 / f2);

    cout << "\n f1 * f1 + 2 * f1 * f2 + f2 * f2 is:"

         << (f1 * f1 + 2 * f1 * f2 + f2 * f2);

    cout << "\n first is:" << f1;

    cout << "\n ++first is:" << ++f1;

    cout << "\n first++ is:" << f1++;

    cout << "\n first is:" << f1;

 

    // using the conversion to double:

    Fraction f4(4, 9);

    double d = sqrt(f4.ToDouble());

    cout << "\n The sqrt of: " << f4 << "   is: " << d;

    cin >> d;

}

 

 

 

 

 

 

 

 

 


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

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

 

מהי התנהגות האופרטור

·                      מספר הארגומנטים - נתון לנו על ידי השפה ואינו נמצא בשליטנו עלינו רק לוודא כי אנו מגדירים מחדש את האופרטור

            הנכון (לדוגמא +a   ולא   a+b)

·                      סדר הקדימויות נתון על ידי השפה ואינו בשליטתנו.

·                      טיפוסי הנתונים של הארגומנטים:

לדוגמא:

a + b

תמיד חייבת להיות לנו האפשרות לחבר שני עצמים מאותו טיפוס

 

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

לדוגמא:

double < double

טיפוסי הארגומנטים  הם  double  אבל הטיפוס המוחזר על יד האופרטור הוא  bool (אמת או שקר)

bool operator<(X, X)

 

double + double

טיפוסי הארגומנטים  הם  double  אבל הטיפוס המוחזר על יד האופרטור הוא double

X operator+(X, X)

 

·                      אילו ארגומנטים הם  const,    על אילו  ארגומנטים יש  side effect

לדוגמא:

a + b

שני הארגומנטים לעולם לא משתנים

X operator+(const X&, const X&)

 

++a

על הארגומנט יש side-effect ולכן חייבת להיות אפשרות לשנותו.

X operator++(X&)

·                      מתי הערך המוחזר מאופרטור הוא  lvalue (מותר לעשות עליו השמה) ומתי הוא חייב להיות קבוע.

לדוגמא:

a = b

הארגומנט השמאלי חייב להשתנות והימיני לא, הערך של הביטוי לא יכול להשתנות מכיוון שאסור בשפה     (a = b) = c ולכן

const X& operator=(X&, const X&)

 

i = a[3] + 4  // const version

a[3] = 4      // reference version

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

const T& operator[](const X&, int)

 

T& operator[](X&, int)

·                      מתי אופרטור בינארי friend ומתי  member

דוגמא:

   כאשר האופרטור המקורי מאפשר הסבת טיפוס לארגומנט השמאלי -  friend  אבל אם באופרטור המקורי לא נהוגה הסבת טיפוס

   לארגומנט השמאלי  member.

friend:

X operator/(const X&, const X&)

 

member:

T X::operator[](int i)

T X::operator=(const X&)  //+= -= ...

X* X::operator&();  // address of

 

חזרה להתחלה