 
 
 
 
תוכן הפרק
 
prefix  postfix   האופרטורים  ++  --
משמעות מוגדרת מראש של האופרטורים     =
& ,
מתי להעמיס על ידי פונקציה חברה ומתי על ידי פונקציה חופשית
פונקציות אתחול  constructor  בתפקיד הסבת טיפוס
מה קורה אם שוכחים copy constructor   או  operator=
שימוש ב explicit  לביטול הסבת טיפוס ע"י הבנאי.
אופרטורי קלט/פלט   operator<<  operator>>
קלט / פלט בקבצים של טיפוס מוגדר על ידי המתכנת
ניתוח (חלקי) כיצד לבחור את צורת ההגדרה מחדש של אופרטור
 
              
·           
הגדרת
טיפוס פירושו הגדרת מושג מופשט הכולל תחום ערכים  ואוסף פעולות. לדוגמא, 
הטיפוס  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);
}
 
·     
כאשר
ההעמסה מתבצעת על ידי פונקציה חברה, מניחים כי הארגומנט השמאלי הוא עצם
·     
כאשר
מעמיסים אופרטור על ידי פונקציה חברה:
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 היא
כאשר ה  ++  מופיע לפני לדוגמא:   ++a
·     
צורת  postfix היא
כאשר ה  ++  מופיע אחרי לדוגמא:  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 
שאפשר להפעילו עם ארגומנט יחיד בעצם מגדירים כיצד להסב ערך מהטיפוס של  
        הארגומנט לטיפוס של
המחלקה במקרים: 
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)
}
 
              
·           
שיטה
זו טובה כאשר צריכים לחסוך מקום (במערכים).
              
·           
שיטה
זו טובה כאשר רוצים להחזיר את המספרים בשיטת ייצוג שונה (בסיסים שונים או שיטות
ייצוג)
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:
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().
·           
לפעמים
אין ברצוננו להשתמש בהסבת הטיפוס האוטומטית שמתבצעת באמצעות הבנאי.
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]
}
 
·           
ראינו
שאפשר לבצע קלט / פלט על משתנים המוגדרים בשפה:
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;
}
#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.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