תוכן הפרק
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