Download presentation
Presentation is loading. Please wait.
1
C++: יוצרים, הורסים ואופרטורים
2
המחלקה Stack - תזכורת class Stack { private: int* array; int size; int topIndex; public: Result init (int size); void destroy(); Result push (int element); Result pop (); Result top(int& element) const; Result print() const; }; מה קורה אם המשתמש שוכח לקרוא ל -init או ל -destroy? 2
3
יוצרים והורסים - מוטיבציה הבעיההבעיה - בהגדרת הטיפוס אנו משאירים פתח לטעויות של המשתמש : –ניתן לשכוח לאתחל עצמים ( לא לקרוא ל -init) – התכנית תתנהג בצורה לא צפויה. –ניתן לשכוח להרוס אובייקט וכך לגרום לדליפת זיכרון. יתר על כן, פעמים רבות נוצרים עצמים זמניים. –למשל, בהעברת והחזרת פרמטרים מפונקציות –אין למשתמש יכולות לאתחל עצמים אלו בצורה מפורשת. 3
4
יוצרים והורסים הפתרון - פונקציות אתחול והריסה שנקראות אוטומטית כאשר האובייקט נוצר וכאשר הוא אמור להיהרס. מעתה, בכל פעם שנגדיר משתנה מטיפוס Stack נידרש להעביר ליוצר את הפרמטרים הדרושים לצורך אתחול האובייקט בכל פעם שהאובייקט לא יהיה נגיש יותר הקומפיילר יקרא להורס אשר ישחרר את האובייקט. שמות היוצרים (constructors) כשם המחלקה. שם ההורס (destructor) כשם המחלקה ולפניה ~. class Stack { private: int* array; int size; int topIndex; public: Stack (int size); ~Stack();... }; class Stack { private: int* array; int size; int topIndex; public: Stack (int size); ~Stack();... }; הכרזה במחלקה 4
5
מימוש Consturctors ו -Destructors Stack::Stack(int initSize) { array = new int[initSize]; topIndex = 0; size = initSize ; } Stack::~Stack() { delete[] array; } Stack::Stack(int initSize) { array = new int[initSize]; topIndex = 0; size = initSize ; } Stack::~Stack() { delete[] array; } מימוש הבנאים וההורסים : ( בקובץ.cpp) שימו לב כי Constructor (להלן "C ’ tor") ו-Destructor (להלן "D ’ tor") לא מחזירים ערכים! הערות: –אם איננו מגדירים יוצרים והורסים, מוגדרים אוטומטית יוצרים והורסים ברירת מחדל (default C’tor and D’tor). –כמעט לכל מחלקה נדרש להגדיר מפורשות יוצרים לאתחול שדותיה. –הגדרת הורסים תתבצע תמיד עבור מחלקות המקצות זיכרון דינמי בעצמן. במקרה זה על ההורס לשחררו (לכן עליו להיות מוגדר מפורשות). בקריאה ל -new ו -delete נקראים ה -c’tor וה -d’tor, בהתאמה. 5
6
שימוש במחסנית שיש לה יוצרים והורסים #include “Stack.h” int main() { Stack s(100); // the c’tor is called with size 100 Stack* ps = new Stack(100); // same here s.push(1); s.push(213); s.pop (); int i=0; s.top(i); ps->push(1); ps->push(2); ps->pop(); delete ps; // the d’tor is called for ps return 0; } // the d’tor is called for s 6
7
זמני קריאה של יוצרים והורסים קיימות 4 דרכים להקצאת משתנים. –זמני הקריאה של היוצרים וההורסים תלויים באופן הקצאת העצם. משתנים לוקאליים היוצר נקרא בכל פעם שהתוכנית מגיעה להכרזת המשתנה. ההורס בכל פעם שהתוכנית יוצאת מהתחום בו הוגדר המשתנה. משתנים לוקאליים היוצר נקרא בכל פעם שהתוכנית מגיעה להכרזת המשתנה. ההורס בכל פעם שהתוכנית יוצאת מהתחום בו הוגדר המשתנה. משתנים גלובאליים היוצר נקרא עם תחילת התוכנית (לפני ה-main). ההורס עם סיום התוכנית (לאחר סיום main). משתנים גלובאליים היוצר נקרא עם תחילת התוכנית (לפני ה-main). ההורס עם סיום התוכנית (לאחר סיום main). משתנים דינמיים היוצר נקרא בכל פעם שמוקצה אובייקט ע”י new. ההורס בכל פעם שאובייקט משוחרר ע”י delete. משתנים דינמיים היוצר נקרא בכל פעם שמוקצה אובייקט ע”י new. ההורס בכל פעם שאובייקט משוחרר ע”י delete. משתנים סטאטיים היוצר נקרא בפעם הראשונה שהתוכנית מגיע לתחום בו מוגדר המשתנה. ההורס עם סיום התוכנית. משתנים סטאטיים היוצר נקרא בפעם הראשונה שהתוכנית מגיע לתחום בו מוגדר המשתנה. ההורס עם סיום התוכנית. 7
8
דוגמה לזמני קריאה #include “Stack.h” Stack globals(100); // globals c’tor is called int main() { Stack locals(50); // locals c’tor is called Stack* ps = new Stack(600); //ps c’tor is called Stack* ps2 = new Stack(600); //ps2 c’tor is called delete ps; // ps destructor is called return 0; // locals destructor is called } // globals destructor is called // ps2 destructor is never called! 8
9
רשימות אתחול בעת יצירת עצם יש לאתחל את כל השדות שלו אתחול השדות נעשה לפני הכניסה לגוף הקוד של ה -C’tor אתחול השדות יכול להיות לא מפורש - קריאה ל -C’tor חסר פרמטרים של השדה ברשימת האתחולאתחול השדות יכול להיות מפורש - הוספת קריאה ל -C’tor של השדה ברשימת האתחול class TwoStack { Stack s1, s2; public : TwoStack(int size);... }; TwoStack::TwoStack(int size) : s1(100), s2(size*5) {... } class TwoStack { Stack s1, s2; public : TwoStack(int size);... }; TwoStack::TwoStack(int size) : s1(100), s2(size*5) {... } רשימת אתחול : אתחול שדות לפני גוף ה -C’tor 9
10
רשימות אתחול מייד אחרי הצהרת היוצר ולפני גוף היוצר ( החלק שבתוך ה -{}) מופיעות נקודתיים ואז רשימה של השדות הפנימיים, כשהם מופרדים על ידי פסיקים. כל מופע של שדה ברשימת האתחול הוא למעשה קריאה לאחד מהיוצרים של השדה. לכל שדה כותבים בסוגריים את הערכים שמעבירים ליוצר שלו –ערכים אלו יכולים להיות תלויים בפרמטרים שהועברו ליוצר הראשי class TwoStack { Stack s1, s2; public : TwoStack(int size);... }; TwoStack::TwoStack(int size) : s1(100), s2(size*5) {... } class TwoStack { Stack s1, s2; public : TwoStack(int size);... }; TwoStack::TwoStack(int size) : s1(100), s2(size*5) {... } רשימת אתחול : אתחול שדות לפני גוף ה -C’tor 10
11
רשימות אתחול סדר הפעלת היוצרים אינו הסדר בו הם מופיעים ברשימת האתחול אלא בו השדות מופיעים בהגדרת המחלקה. רשימת אתחול עדיפה על פני השמה בתוך הפונקציה מאחר ונחסך האתחול המיותר –השדות יאותחלו ע " י הקומפיילר בעזרת C’tor חסר פרמטרים אוטומטית class TwoStack { Stack s1, s2; public : TwoStack(int size);... }; TwoStack::TwoStack(int size) : s1(100), s2(size*5) {... } class TwoStack { Stack s1, s2; public : TwoStack(int size);... }; TwoStack::TwoStack(int size) : s1(100), s2(size*5) {... } רשימת אתחול : אתחול שדות לפני גוף ה -C’tor 11
12
מערכים בעת יצירת מערך לא ניתן לשלוח פרמטרים ליצירת כל אחד מהעצמים לכן על מנת ליצור מערך של עצם מסוג מסוים חובה שיהיה יוצר חסר פרמטרים תזכורת : הקומפיילר מספק יוצר שכזה רק אם לא כתבנו אף יוצר בעצמנו ! class Stack {... public: Stack(int size = 100);... };... Stack array[10];... class Stack {... public: Stack(int size = 100);... };... Stack array[10];... 12
13
העמסת אופרטורים – Operator overloading המטרה :המטרה : לאפשר עבודה טבעית ככל האפשר עם מחלקות שהגדיר המשתמש. ע ” י שימוש באופרטורים, המחלקות שהמשתמש מגדיר יוכלו לעבוד עם אופרטורים כמו טיפוסים פנימיים. אם האופרטורים מממשים פעולה טבעית של המחלקה, הדבר יכול לשפר את הקריאות של הקוד. הגדרת אופרטורים מתבצעת באופן הבא : operator ( ); אופרטורים יכולים להיות methods של המחלקה או פונקציות חיצוניות. אם האופרטור מוגדר כ -method אזי הארגומנט הראשון הנו תמיד האובייקט של המחלקה בו הוא מוגדר ואין צורך להעבירו. 13
14
Operator overloading פונקציות עם שם מיוחד : =+ operator Stack& Stack::operator+=(int element) { push(element); return *this; } צורות הקריאה לפונקציה : s.operator+=(5) ; s += 5 ; 14
15
The matrix example class M { double matrix[5][5]; public:... M& operator+=(const M&); M operator*(const M &) const;... }; M operator+(const M& m1,const M& m2){... } int main() { M m1, m2, m3 ; //... m1+= m2 ; //calls m1.operator+=(m2); m2 = m1 + m3 ; //calls operator+(m1,m3); m3 = m2 * m2; //calls m2.operator*(m2); } 15
16
Operator overloading – what can we overload? + - * / % ^ & | ! = +=-=*=/=%= ^=&=|= > >=== != =&&||++--, ->*->()[ ] What not? What not?..* :: ? : sizeof post & pre 16
17
מגבלות Operator overloading רק אופרטורים שכבר קיימים ( ולא למשל אופרטור $%$) האופרטורים מקבלים את אותו מספר משתנים אותו סדר עדיפות ואותה אסוציאטיביות הערות בנוגע ל - post & pre :: –ניתן להגדיר את שני סוגי הגדלה / הקטנה עצמית ע ” י שימוש בפרמטר דמה : x.operator++() (pre: ++num) x.operator++(int) (post: num++) 17
18
ערכי החזרה של אופרטורים אופרטור עבור המחלקה T יחזיר בדרך כלל איבר זמני מסוג T או רפרנס לאובייקט שעליו נקרא : T&. איבר זמני יוצרת איבר חדשהאופרטור יחזיר איבר זמני כאשר הפעולה יוצרת איבר חדש שאותו אנו רוצים לקבל, למשל a+b. רוצים לקבל את האובייקט לאחר השינויהאופרטור יחזיר רפרנס כאשר אנו רוצים לקבל את האובייקט לאחר השינוי. למשל, (a+=2)+=3 דורש שהפעולה += תחזיר רפרנס ( ואז ניתן יהיה להפעיל אותה מספר פעמים ברצף על אותו אובייקט ). הפעולה a++ אמורה להחזיר איבר זמני, ואילו ++a אמורה להחזיר רפרנס ( מדוע ?) 18
19
קלט / פלט ב -++C - תזכורת #include int i = 17, j; double d; fprintf(stdout,"%s %d", "A string", i); fscanf(stdin, "%d %lf", &j, &d); fprintf(stderr, "Error!\n"); #include int i = 17, j; double d; fprintf(stdout,"%s %d", "A string", i); fscanf(stdin, "%d %lf", &j, &d); fprintf(stderr, "Error!\n"); #include using std::cin; using std::cout; using std::cerr; using std::endl; int i = 17, j; double d; cout << "A string " << i; cin >> j >> d; cerr << "Error!" << endl; #include using std::cin; using std::cout; using std::cerr; using std::endl; int i = 17, j; double d; cout << "A string " << i; cin >> j >> d; cerr << "Error!" << endl; 19
20
העמסת אופרטורים של I/O ניתן לבצע overload של אופרטורי הקלט והפלט בכדי לאפשר לבצע קלט אל ופלט מאובייקטים מטיפוסים שהוגדרו ע " י המשתמש בדומה לצורה שבה הדבר אפשרי עבור טיפוסים פנימיים int main() { int x; someclass y; otherclass z; cin >> x >> y >> z; cout << x << y << z; } 20
21
העמסת אופרטורים של I/O class someclass { int k, j ; //... friend ostream& operator<<(ostream& os, const someclass& s1); friend istream& operator>>(istream& is, someclass& s1); }; ostream& operator<<(ostream& os,const someclass& s1) { os << "(" << s1.k << "," << s1.j << ")"; return os ; } istream& operator>> (istream& is, someclass& s1) { is >> s1.k >> s1.j ; return is ; } למה האופרטורים הוגדרו כ-friend? האם זה מתחייב? (רמז: לא). כיצד ניתן לשנות זאת ? 21
22
העמסת אופרטורים של I/O int main() { someclass s; int i; cin >> i >> s ; cout << s << i; // assume the user typed 1, 2 and 3 // What does the program print? } שימו לב כי את האופרטורים האלה לא ניתן להגדיר כפונקצית member מכיוון שהארגומנט הראשון שלו צריך עצם השייך למחלקה אחרת (ערוץ) על הפונקציה המחזירה את האופרטור להחזיר כ-reference את הערוץ כדי שיהיה אפשר להמשיך ולהזרים לו נתונים 22
23
class String { int length; char* data; char* allocate_and_copy(const char* data, int size); void verify_index(int index) const; public: String(const char* str = ""); // String s1; or String s1("aa"); String(const String& str); // String s2(s1); ~String(); int size() const; String& operator=(const String& str); // s1 = s2; String& operator+=(const String& str); // s1 += s2; const char& operator[](int index) const; // c = s1[5] char& operator[](int index); // s1[5] = 'a' friend ostream& operator<<(ostream&,const String&); // cout << s1; friend bool operator==(const String&, const String&); // s1==s2 friend bool operator<(const String&, const String&); // s1<s2 }; bool operator!=(const String& str1, const String& str2); bool operator<=(const String& str1, const String& str2); bool operator>(const String& str1, const String& str2); bool operator>=(const String& str1, const String& str2); String operator+(const String& str1, const String& str2); המחלקה String - String.h 23
24
String.cpp void error(const char* str) { cerr << "Error: " << str << endl; exit(0); } char* String::allocate_and_copy(const char* str, int size) { return strcpy(new char[size+1], str); } טיפול בשגיאות ב -++C נעשה בעזרת מנגנון ה -Exceptions שיילמד בהמשך. בינתיים נסתפק בכך. טיפול בשגיאות ב -++C נעשה בעזרת מנגנון ה -Exceptions שיילמד בהמשך. בינתיים נסתפק בכך. פונקצית עזר פשוטה לביצוע כל ההקצאות פונקצית עזר פשוטה לביצוע כל ההקצאות 24
25
String.cpp String::String(const char* str) : length(strlen(str)), data(allocate_and_copy(str, length)) { } String::String(const String& str) : length(str.size()), data(allocate_and_copy(str.data, length)) { } String::~String() { delete[] data; } int String::size() const { return length; } שימו לב לכך שביוצרים אין קוד מלבד רשימת האתחול קל להחליף את השדה length בקריאה ל -strlen אם יש צורך קל להחליף את השדה length בקריאה ל -strlen אם יש צורך 25
26
String.cpp String& String::operator=(const String& str) { if (this == &str) { return *this; } delete[] data; data = allocate_and_copy(str.data, str.size()); length = str.length; return *this; } String& String::operator+=(const String& str) { char* new_data = allocate_and_copy(data, str.size() + size()); strcat(new_data, str.data); delete[] data; length += str.length; data = new_data; return *this; } בדיקת הצבה עצמית מה קורה עבור s=s; לולא שורה זו ? ( עוד על כך בתרגול הבא ) בדיקת הצבה עצמית מה קורה עבור s=s; לולא שורה זו ? ( עוד על כך בתרגול הבא ) 26
27
String.cpp void String::verify_index(int index) const { if (index >= size() || index < 0) { error("Bad index"); } return; } const char& String::operator[](int index) const { verify_index(index); return data[index]; } char& String::operator[](int index) { verify_index(index); return data[index]; } " פעמיים זו פעם אחת יותר מדי " ולכן כדאי לשים את בדיקה זו בפונקציה נפרדת ( פרטית כמובן ) " פעמיים זו פעם אחת יותר מדי " ולכן כדאי לשים את בדיקה זו בפונקציה נפרדת ( פרטית כמובן ) האם החזרת char by value משנה משהו ? 27
28
String.cpp bool operator==(const String& str1, const String& str2) { return strcmp(str1.data, str2.data) == 0; } ostream& operator<<(ostream& os, const String& str) { return os << str.data; } bool operator<(const String& str1, const String& str2) { return strcmp(str1.data, str2.data) < 0; } פונקציות אלו הוגדרו כ -friend 28
29
String.cpp bool operator!=(const String& str1, const String& str2) { return !(str1 == str2); } bool operator<=(const String& str1, const String& str2) { return !(str2 < str1); } bool operator>(const String& str1, const String& str2) { return str2 < str1; } bool operator>=(const String& str1, const String& str2) { return str2 <= str1; } String operator+(const String& str1, const String& str2) { return String(str1) += str2; } מדוע פונקציות אלו אינן מוגדרות כ -friend? 29
30
המחלקה String הקלה משמעותית בכתיבת קוד המטפל במחרוזות : –מחרוזות מתנהגות כטיפוס מובנה –שרשור מחרוזות מתבצע בקלות –אין בעיות של ניהול זיכרון יותר ! בפועל, המחלקה הזו מיותרת : –ב -++C יש מחלקה std::string המוגדרת בקובץ string. –דוגמה זו מראה כיצד יש לכתוב ולממש אופרטורים שונים. הרעיון הכללי זהה תמיד. 30
31
ערוצי קבצים שימוש בערוצי קבצים דומה לשימוש בערוצי הקלט / פלט הסטנדרטיים. כל הפעולות המוגדרות על הערוצים הסטנדרטיים מוגדרות גם עבור ערוצי הקבצים. –ע " י שימוש בהורשה – תכונה של ++C שתילמד בהמשך fstreamמחלקות אלו מוגדרות בקובץ fstream, ולכן על מנת להשתמש בהם צריכה להופיע בתחילת הקובץ השורה : #include #include constructorערוצי הקלט מקבצים מוגדרים ע " י המחלקה ifstream. למחלקה זו יש constructor המקבל כקלט מחרוזת ופותח את הקובץ ששמו ערך המחרוזת. constructorערוצי הפלט לקבצים מוגדרים ע " י המחלקה ofstream. למחלקה זו יש constructor המקבל כקלט מחרוזת ופותח את הקובץ ששמו ערך המחרוזת לשתי המחלקות יש הורסים שסוגרים את הקבצים. 31
32
דוגמה לעבודה עם קבצים #include using std::ifstream; using std::ofstream; using std::cerr; using std::endl; void copyFile(char * fromName, char* toName) { ifstream from(fromName); if (!from) { cerr << "cannot open file " << fromName << endl; return; } ofstream to(toName); if (!to) { cerr << "cannot open file " << toName << endl; return; } while (!from.eof()) { char c; from >> c; to << c; } 32 מדוע אין צורך לסגור את קובץ הקלט ?
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.