1 / 28

נושאי התרגול :

נושאי התרגול :. Copy constructor Assignment operator המרת טיפוסים אוטומטיות. תכנות גנרי - Templates. Copy constructor & Assignment operator. מה קורה כאשר מבצעים s1 = s2 ? מה קורה כאשר מבצעים Stack s1 = s2 ? מה ההבדל? מה הסכנה? מה קורה כאשר קוראים לפונקציה : f(Stack s) ;

ila-calhoun
Download Presentation

נושאי התרגול :

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. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. נושאי התרגול: • Copy constructor • Assignment operator • המרת טיפוסים אוטומטיות. • תכנות גנרי - Templates

  2. Copy constructor & Assignment operator • מה קורה כאשר מבצעים s1 = s2? • מה קורה כאשר מבצעים Stack s1 = s2? מה ההבדל? מה הסכנה? • מה קורה כאשר קוראים לפונקציה: f(Stack s) ; f(Stack& s) ; • פתרון copy c’tor - לאתחול, assign. Op. - להשמה

  3. Copy constructor & Assignment operator • עבור כל מחלקה חדשה, מוגדרות באופן אוטומטי שתי פונקציות: • copy constructor- זהו c’tor המשמש לאתחול של עצם אחד של המחלקה ע"י עצם אחר של אותה מחלקה. פונקציה זו גם משמשת לאתחל פרמטריםבקריאה לפונקציות, כלומר כאשר מעבירים פרמטר מסוג מחלקה מסוימת by value, ערכי השדות של הפרמטרים מאותחלים ע"י קריאה לפונקציה זאת (כנ"ל לגבי החזרת ערכים). ה- prototype של פונקציה זו עבור מחלקה X הוא: X(const X&) מובטח שלא נשנה את הפרמטר המועבר אי אפשר להגדיר copy constructor באמצעות copy constructor

  4. Copy constructor & Assignment operator • אופרטור ההשמה (=) - פונקציה זו משמשת להשמה של עצמים מהמחלקה זה לזה פרט לאתחול של עצם אחד של המחלקה ע"י עצם אחר של אותה מחלקה (במקרה של אתחול ייקרא ה-copy c’tor, אפילו אם משתמשים בסימן = בביצוע האתחול). • בשני המקרים, בפונקצית ברירת המחדל ההעתקה היא איבר איבר. יש מקרים שבהם העתקה איבר איבר אינה טובה (למשל, במקרה בו יש מצביעים כשדות במחלקה). במקרים האלה, על המשתמש להגדיר פונקציות אלו מחדש (דוגמא להגדרה מחדש של אופרטורים אלו נראה במחלקה Stack בהמשך)

  5. Copy constructor & Assignment operator • כלל אצבע: בכל מחלקה בה יש שדות שהם מצביעים, יש להגדיר מחדש: • Copy Constructor • Assignment Operator • Destructor

  6. class Stack { int* array; int top_index, size; public: Stack (int s = 100) ; Stack (const Stack&) ; Stack& operator=(const Stack&); ~Stack() ; Result push(int e); Result pop() ; Result top(int& e); void print() ; }; The Final Stack

  7. Copy constructor Stack::Stack(const Stack & st) { array = new int [st.size]; if (array == 0) error(“out of memory”); // assumes error exits size = st.size; top_index = st.top_index; for (int top = 0 ; top<st.top_index; top++) array[top] = st.array[top]; }

  8. Assignment operator חמשת הצעדים: בדיקת הצבה עצמית שחרור משאבים הקצאת משאבים חדשים אתחול השדות החזרת this* Stack& Stack::operator=(const Stack& st) { if (this == &st) return *this; if (st.size != size) { delete[] array; array = new int[size = st.size]; } for (int top = 0 ; top<st.top; top++) array[top] = st.array[top]; top_index = top; return *this ; }

  9. User Defined Type Conversions ב ++C קיימים שני סוגי המרות טיפוסים אוטומטיות : • Conversion by constructor. • Conversion by conversion operator.

  10. Conversion by construction • בעזרת c’tors של מחלקה T אפשר להמיר כל מחלקה אחרת או טיפוסים פנימיים של השפה כך שיהפכו לאיבר מסוג T. • ההמרה מתבצעת ע”י הגדרת Constructor המקבל ארגומנט יחיד, מהטיפוס הנ”ל. T(class C); //converts from class C to T • ההמרה תתבצע אוטומטית על ידי הקומפיילר. • ההמרה מאפשרת לקרוא לפונקציות המצפות לקבל ארגומנט מטיפוס T עם ארגומנט מטיפוס C במקום. דוגמא: • במחלקה Complex : המרה מ-double (עוד מעט) • המרה מ-* const char למחלקה String (תרגול קודם)

  11. דוגמא - המחלקה Complex נניח כי רוצים להגדיר מחלקה של מספרים מרוכבים. היינו רוצים גם לאפשר את פעולות החשבון הרגילות בין מספרים מרוכבים ומספרים שלמים. דרך אחת לעשות את זה היא להגדיר את כל האפשרויות עבור כל אופרטור, בדומה לדוגמא הבאה: class complex { double re,im; public: complex (double r, double i) {re = r; im = i;} friend complex operator+(complex, complex); friend complex operator+(complex, double); friend complex operator+(double, complex); // Do the same for the rest of the operators… }; שיטה זו מייגעת ומלאה חזרות מיותרות. דרך נוספת לעשות את אותו הדבר היא להגדיר constructor ל- complex אשר מקבל פרמטר יחיד מטיפוס double. ההמרה מ double ל complex תתבצע אוטומטית באמצעות constructor זה.

  12. דוגמא - המחלקה Complex class complex { //... complex(double r) {re = r; im = 0;} friend complex operator+(complex,complex); }; המהדר יבצע את ההמרה באופן אוטומטי. לדוגמא: main() { complex a; complex b; a=b+23; } מה שמתבצע זה: 1. 23 מומר ל- complex ע"י ה- constructor. 2. מתבצע חיבור בין שני complex. 3. a מקבל את התוצאה ע"י אופרטור ההשמה המוגדר כברירת מחדל ע"י המהדר (העתקה איבר איבר).

  13. Conversion by conversion operator • בעזרת אופרטורים מיוחדים שמוגדרים במחלקה T אפשר להמיר את T לטיפוסים פנימיים של השפה או למחלקות אחרות שכבר הוגדרו. • הדרך היחידה להגדיר המרה לטיפוסים פנימיים של השפה (למה?). • המרה זו מבוצעת ע”י הגדרת conversion operator (מתודה של המחלקה T). operator C() ; // converts from T to C. • דוגמא: class Price { int Shekel, agorot ; … public : …. operator double() { return shekel + agorot / 100.0 ;} … }; • אם ניתן רצוי להיעזר ב Conversion by constructor (של C). • שימו לב שלאופרטור אין ערך החזרה אבל השתמשנו ב-return בתוכו עם ערך החזרה... מנפלאות הסינטקס של C++ (אז מה כן מחזירים?).

  14. בעיות עם המרות אוטומטיות class x { // ... x(int); x(char*); … }; class y{ //... y(int); …. }; class z {//... z(x); …. }; x f(x); y f(y); z g(z); void k1() { f(1); // Illegal: ambiguous f(x(1)) or f(y(1)) f(x(1)); f(y(1)); g("asdf") ;// Illegal: g(z(x("asdf"))) not tried g(z("asdf")); // O.k.: x(char*) is carried out } • פרט לכך קיימות שתי בעיות עם המרות: • מתבצעת רק המרה אחת • עלולה להיווצר דו משמעות (ambiguity)

  15. המרות ואופרטורים • כזכור operators הם בעצם פונקציות. לכן גם עבורם יכולה להתבצע המרה. לדוגמה, אופרטור החיבור במחלקה Complex יכול להיקרא: c2 = c1 + 5 ; אולם אם אופרטור מוגדר כ method לא תתבצע המרה על הארגומנט הראשון שלו. בגלל בעיה זו, עדיף בד"כ להגדיר אופרטור ע"י פונק' חיצונית. כך, הטיפול בשני האופרנדים יהיה סימטרי. class complex { ... public: complex(double r) {re = r; im = 0;} operator+(const complex&); friend operator-(const complex&, const complex&); }; main() { complex c2,c1; c1=2.0+c2; // Error: 2.0 will not be converted c2=2.0-c2; // O.k.: 2.0 will be converted }

  16. תכנות גנרי - Templates • משמש בעיקר כאשר בונים Containers - מחלקות אשר מכילות עצמים אחרים, אולם אין חשיבות לתכונותיו הייחודיות של אותו עצם: • אותן פעולות מוגדרות על ה Container ללא קשר לעצם שבו. לדוגמא עבור מחסנית תמיד יוגדרו : • push • pop • top ללא חשיבות אם זו מחסנית של int , char או String.

  17. int_stack.h: class int_stack { int top_index,size; int* array ; public: int_stack(int s) ; void pop() ; void push(int e); int top() ; int size(); }; char_stack.h: class char_stack { int top_index,size; char* array ; public: char_stack(int s) ; void pop() ; void push(char e); char top() ; int size(); }; דוגמת המחסנית String_stack.h: class string_stack { int top_index,size; String* array ; public: string_stack(int s) ; void pop() ; void push(String e); String top() ; int size(); };

  18. int_stack.cc (חלקי): int_stack::int_stack (int s) { top_index = 0 ; size = s ; array = new int[s]; } int int_stack::top() { return array[top_index-1]; } string_stack.cc (חלקי): string_stack::string_stack (int s) { top_index = 0 ; size = s ; array = new String[s]; } String string_stack::top() { return array[top_index-1]; } דוגמת המחסנית

  19. מחלקות גנריות • בכדי להימנע משכפול הקוד (ליתר דיוק: כדי לבצע שכפול קוד מבוקר) נעזר ב Template. • ה-Template מקבל טיפוס(ים) כפרמטר, ויכול להשתמש בפרמטר שכזה כאילו היה טיפוס רגיל. • ה Template הנו בעצם תבנית ליצירת מחלקות (מעין “מקרו” מתוחכם). לאחר שהעברנו ל-Template את הטיפוס עמו הוא עובד (ע”י הוספת < טיפוס > לשם ה-Template נקבל מחלקה רגילה לכל דבר.

  20. מה קורה בפועל? • אנחנו כותבים מחלקה שהיא Template כמו שאנחנו כותבים מחלקה רגילה, פרט לתוספת השורה template <class T>לפני הגדרת המחלקה. • כעת נוכל לכתוב את קוד המחלקה תוך שאנחנו מתייחסים ל-T כאל טיפוס כלשהו, שיועבר כפרמטר רק מאוחר יותר. • בתוך התוכנית, כאשר אנחנו רוצים להשתמש ב-Template עבור ערך מסוים של T, מעבירים את הערך הזה גם כן בסוגריים משולשים (ראו דוגמאות בהמשך). • מה שיקרה הוא שבזמן הקומפילציה יתבצע שכפול של הקוד שכתבנו עבור ה-Template ובכל מקום שבו מופיע T ייכתב הערך שהועבר ל-Template במקומו.

  21. stack.h: template <class T> class stack { int top_index,size; T* array ; public: stack(int s=100) ; void pop() ; void push(T e); T top() ; int size(); }; int_stack.h: class int_stack { int top_index,size; int* array ; public: int_stack(int s) ; void pop() ; void push(int e); int top() ; int size(); }; מחסנית גנרית

  22. stack.h (חלקי): template <class T> stack<T>::stack (int s) { top_index = 0 ; size = s ; array = new T[s]; } template <class T> T stack<T>:: top() { return array[top_index-1]; } int_stack.cc (חלקי): int_stack::int_stack (int s) { top_index = 0 ; size = s ; array = new int[s]; } int int_stack::top() { return array[top_index-1]; } מחסנית גנרית

  23. int main() { stack<int> si(100); stack<char*> sc(3); si.push(5); sc.push(“xxx”); sc.push(6); // error ! si.size(); sc.size(); } int main() { int_stack si(100); si.push(5); si.push(“xxx”); // error ! si.size(); } שמוש במחסנית הגנרית

  24. Templates • Templates הינם כלי מאוד שמושי לצורך הגדרת Containers. • למרבה הצער המימוש של Templates ב ++C מזכיר macro חכם ולכן כל הקוד של ה Template נמצא ב header file. כפי שהוצג או כ inline function. • ישנן לא רק מחלקות שהן גנריות אלא גם פונקציות גנריות.

  25. int major (int a , int b , int c ) { if (a == b || a == c) return a ; if (b == c) return b ; exit(1); } שימוש : int j = major (1,3,3); // ok? char c = major (‘w’,’w’,’w’);//ok? String s1 = major(s1,s2,s3); //ok ? int j = major (1,’a’,’a’); //ok ? template <class T> T major (T a ,T b , T c ) { if (a == b || a == c) return a ; if (b == c) return b ; exit(1); } שימוש : int j = major (1,3,3); // ok ? char c= major (‘w’,’w’,’w’); //ok String s1 = major(s1,s2,s3); // ok? int j = major (1,’a’,’a’); //ok ? פונקציות גנריות

  26. Templates - advanced issues • למרות ש ה Template לא “מתעניין” בתכונותיו המיוחדות של הטיפוס המועבר כפרמטר הוא יכול להניח הנחות לגבי קיומם של פונקציות או מתודות מסוימות בו. • אילו הנחות הניח major על T ? אילו stack ? • ניתן להעביר כמה טיפוסים שונים בפרמטר: template <class K, class D> class hashtable { bool find (const K &k, D &d) ; … };

  27. פרמטרים ל Template • ניתן להעביר ל Template פרמטר שאינו טיפוס. פרמטר יכול להיות מחרוזת, שם פונקציה או קבוע: • template <class T, int Dim> • class Vector { • T vec[Dim] ; • … • }; • Vector<int, 5> v ; • Vector < double, 12 > v1; • Vector <Complex, 3> v2; • Vector <int , 6> v3 ; • מה היתרון בכך שגודל ה buffer הנו חלק מהטיפוס ? • (רמז מה יקרה ב v=v3 ? )

  28. Templates - advanced issues טיפוס שנוצר ע”י template הינו טיפוס לכל דבר. אולם בדרך כלל כתיבת שמו המלא מסובכת לכן נעזרים בקיצור הבא: typedef stack<int> stack_int ; אם נרצה להגדיר מחסנית של מחסניות אזי נגדיר את הטיפוס typedef stack<stack_int> stack_stack_int; ונעזר בו: stack_int s1(100); stack_stack_int s2(5); s2.push(s1); // in this case push should have better // accepted const T& instead of T ...

More Related