Download presentation
Presentation is loading. Please wait.
2
הורשה ופולימורפיזם 1 עיגול ריבוע משושה צורה
3
מוטיבציה מנשק גרפי טיפוסי מורכב מ -Widgets 2 Entry Label Button Check Button
4
מוטיבציה לכל ה -Widgets יש תכונות משותפות : –מיקום – קואורדינטות x,y. –אורך ורוחב. –טקסט. מצד שני לכל סוג Widget תכונות ייחודיות לו : – Entry מאפשרת עריכת הטקסט שבתוכו. –כפתור מפעיל פונקציה כאשר הוא נלחץ. לבסוף יש תכונות משותפות ל -Widget שונים, אשר מתבטאות בצורה אחרת בכל Widget: –כל אחד מה -Widgets ניתן לציור, אבל המראה שלהם שונה. 3
5
מוטיבציה היינו רוצים להיות מסוגלים : –לממש את התכונות הזהות פעם אחת. –לממש לכל Widget התכונות הייחודיות לו. –בנוסף, אנחנו רוצים שקוד המנצל את התכונות המשותפות ייכתב פעם אחת בלבד. כלומר, בחלקים מסוימים של הקוד נוח לנו להתייחס לכל ה - Widgets כעצמים מאותו טיפוס. –בחלקים אחרים נרצה שההבדל ביניהם יבוא לידי ביטוי. למשל, עבור Button נרצה שהחלקים המטפלים בסידור וציור החלון יתייחסו אליו כאל Widget. –ובחלק אחר של הקוד נתייחס לתכונות הייחודיות לו ( למשל קישור לפעולה ) 4
6
הורשה - מוטיבציה עצם מטיפוס חלון (Window) מכיל רשימה של כל ה - Widgets הנמצאים בתוכו. דרכי מימוש : –ב -C: נצטרך ליצור רשימה לכל סוג אפשרי של Widget או רשימה של void*. מה החסרונות בכל אחת מהשיטות ? –ב -++C: נוכל ליצור רשימה של עצמים מטיפוס Widget אשר תוכל להכיל סוגים שונים Widgets. מדוע לא נוכל להשתמש בתבניות למטרה זו ? 5
7
הורשה - מוטיבציה נניח שצריך לצייר מחדש את החלון. –במקרה זה יש לצייר מחדש את כל ה -Widgets. פתרון : –ב -C: מבצעים קריאה מיוחדת לפונקציות הציור לכל סוג של Widget. –ב -++C: נעביר את פקודת הציור לכל אחד מה -Widgets והוא יידע לפעול בצורה המתאימה " בדרך קסם ". עוד על הקסמים הללו בעוד מספר שקפים. 6
8
הורשה is-a הורשה הינה הדרך בה מביעים יחסי is-a בתוכנית. נעזרים בהורשה לצורך שתי מטרות שונות : שימוש חוזר בקודשימוש חוזר בקוד - כאשר נרצה כי מספר מחלקות יהיו בעלות התנהגות זהה, כולן תירשנה ממחלקת אב משותפת אשר תממש התנהגות זו. בדרך זו נימנע משכפול של הקוד. התנהגות פולימורפית ( רב - צורתית )התנהגות פולימורפית ( רב - צורתית ) - כאשר נרצה כי מספר מחלקות יחשפו ממשק זהה אך יתנהגו בצורה שונה. בשני המקרים, ההורשה תאפשר לנו להתייחס לכל אובייקט בן כאילו היה מטיפוס אובייקט האב. 7
9
תחביר class Widget { int x, y, width, height; string text; // more fields common to all widgets public: void draw(); void resize(int width, int height); // more functionality common to all widgets }; class Button : public Widget { //... additions and specifications void onMouseClick(int x, int y); }; 8 התחביר המציין ש -Button הוא סוג של Widget בקורס נלמדת רק הורשה מסוג “public”
10
מה זה is-a? בשקף הקודם Button יורש מ -Widget. במקרה זה אומרים : A button is a widget בעברית : " כפתור הוא סוג של עצם גרפי ". –בהיעדר תרגום ל -Widget. פירוש הדבר הוא שבכל מקום שבו ניתן להשתמש ב -Widget, אפשר להשתמש גם ב -Button. למשל, אם יש לנו פונקציה שמקבלת Widget כפרמטר, ניתן להעביר Button בתור אותו פרמטר. הפונקציה תתייחס לכפתור כאל Widget ( כלומר, תתייחס לתכונות של הכפתור שמשותפות לכל ה -Widgets, ולא תתעניין בתכונות הייחודיות לכפתור ). כל זה נכון להורשה ציבורית (public) – היחידה שנלמד בקורס הזה. 9
11
הורשה למטרות שימוש חוזר בקוד נרצה להוסיף למחלקה Stack הידועה את הפונקציה : (popk(int k - אשר תוציא מהמחסנית k איברים. למחלקה המשופרת נקרא MultiStack. קיימות שלוש דרכים לבצע זאת : 1. לכתוב את MultiStack מהתחלה (copy & paste). 2. להיעזר ב -Stack כשדה. 3. לרשת מ -Stack. 10
12
מימוש בעזרת שדה ב -MultiStack 11 חסרונות / פגמים / ליקויים / מגרעות... עבור כל פונקציה מקורית של Stack נדרש לכתוב פונקצית מעטפת אשר תפנה את הקריאות לאובייקט המחסנית הפנימי. לקורא הקוד לא ברור מהתוכנית כי MultiStack היא סוג מיוחד של מחסנית. כיצד נראה המימוש של popk? –לא נוכל לייעל את העבודה ע ” י גישה ישירה למבני הנתונים של המחסנית ( האם friend הוא פתרון ?) class MultiStack { Stack s; public: MultiStack(int size) :s(size) {} ~MultiStack() {} void push(int i) { s.push(i); } void pop() { s.pop(); } int top() { return s.top();} bool isEmpty() { return s.isEmpty(); } void popk(int k); };
13
מימוש בעזרת שדה ב -MultiStack בעיה נוספת לשיטה זו : אם יש לנו קוד כתוב אשר נעזר במחסניות ( מקבל כפרמטר מצביע או reference לאובייקט מטיפוס Stack) לא נוכל לשלוח לו MultiStack כפרמטר כי MultiStack אינו Stack. int sumAndPop(Stack & stack) { int sum = 0; while (!stack.isEmpty()) { sum += stack.top(); stack.pop(); } return sum; } 12
14
הפתרון הקל והנכון – ירושה מ -Stack בכדי להביע כי MultiStack הינו סוג מיוחד של Stack נגדיר את MultiStack כיורש מ -Stack. באופן זה כל הפונקציות של Stack מוגדרות באופן אוטומטי ( עם אותה משמעות ) עבור המחלקה MultiStack. 13 class MultiStack : public Stack { public: MultiStack (int size) :Stack(size) {} ~MultiStack() {} void popk(int k); }; שימו לב לקריאה לבנאי של Stack
15
Protected fields ניגש למימוש בפונקציה popk: void MultiStack::popk(int k) { if (topIndex < k ) { k = topIndex ; } topIndex -= k; } רגע ! האם נוכל לגשת לשדות של מחלקת האב ? –במקרה שלנו אנוחנו רוצים לגשת ל -topIndex. 14
16
Protected fields אם topIndex מוגדר כ -private לא נוכל לגשת לשדה. אם topIndex מוגדר כ -public נוכל לגשת לשדה. –אבל ההסתרה אינה נשמרת ! מה ניתן לעשות ? –ניתן להגדיר שדות ומתודות כ -protected: שדות ומתודות המוגדרים כ -protected ניתנים לגישה ממחלקה היורשת. שדות ומתודות המוגדרים כ -protected אינם ניתנים לגישה מחוץ למחלקה. 15
17
Protected - הערות לא חייבים לאפשר גישה לשדה, ניתן להוסיף מתודה protected המשרתת את הנמחלקות היורשות. class Stack { public : //... protected : int* array; int topIndex, size ; }; כמו תמיד, נעדיף להסתיר את המימוש ככל הניתן ונבחר בהרשאות המינימליות המתאימות לצרכינו. –האם ניתן לפתור את מקרה המחסנית עם פחות הרשאות ? 16
18
סיכום הרשאות הגישה בהורשה 17 Stack Private Members Protected Members Public MembersStack Private Members Protected Members Public Members MultiMultiStack Private Members Protected Members Public MembersMultiMultiStack Private Members Protected Members Public Members MultiStack Private Members Protected Members Public MembersMultiStack Private Members Protected Members Public Members User
19
הורשה – בונים והורסים בכל פעם שיוצרים אובייקט מהסוג היורש אנו בעצם יוצרים גם אובייקט מסוג האב. זהו המימוש ב ++C. אתחול שדות האב ( ע ” י ה -C’tor של האב, שנקרא דרך שורת האתחול של הבן ) נעשה לפני יצירת שדה כלשהו של בנו, ובפרט לפני שנקרא ה -c’tor של הבן. הריסת האב נעשית לאחר הריסת בנו ובפרט אחרי ה -d’tor של הבן. 18 שדות של הבן שדות של האב אובייקט אב אובייקט בן
20
סיכום סדר הבניה של עצם סדר הבנאים בקריאה לבנאי של מחלקה ( כולל copy c’tor): 1. בנאי מחלקת אב ( אם יש כזאת ) 2. בַּנַאֵי השדות - לפי סדר ההצהרה עליהם במחלקה ( ולא לפי סדר הקריאה לבנאים שלהם ברשימת האתחול ) 3. גוף הבנאי של המחלקה. 19 הסדר בהריסה: הפוך.
21
הורשה פולימורפית בדוגמאות הקודמות האב והבן חלקו את אותה התנהגות. התנהגות זו הייתה תקפה לשתי המחלקות ולכן שתי המחלקות חלקו : –את אותו ממשק : אותה חתימה וערך מוחזר של הפונקציה. –את אותו מימוש : אותו קוד פעל בשניהם. לעיתים צורת התנהגות זו לא טובה לנו ונרצה שלמרות שניתן יהיה לפנות לבן כמו שפונים לאב ( ובפרט שישתפו את הממשק ) הבן יתנהג בצורה שונה. 20
22
קונפליקט של שמות הגדרת מתודה עם שם זהה למתודה שקיימת במחלקת האב " תבטל " (override) את המתודה של האב במחלקה היורשת. כיצד ניגש ל -member function( מתודה ) של מחלקת האב כאשר יש פונקציה עם שם זהה במחלקת הבן ? 21 void B::g(int x) { //... f(); // B גישה לפונקציה של A::g(x); // A גישה לפונקציה של מחלקת הבסיס //... } class B: public A { public: //... void f(); void g(int x); }; class A { public: //... void f(); void g(int x); };
23
מחלקות מעולם החי כדוגמא למחלקות בעלות התנהגות פולימורפית 22 חיה משקל, גובה, ת. לידה הדפס סוגי מזון השמע קול חיה משקל, גובה, ת. לידה הדפס סוגי מזון השמע קול
24
מחלקות בעלות התנהגות פולימורפית כל בעלי החיים חולקים דברים משותפים : –לכולם יש משקל, גובה, גיל –כולם אוכלים ומשמיעים קולות נרצה לתאר מחלקות מעולם החי בתוכניתנו ולהראות קשר זה יחד עם זאת לאפשר גמישות של התנהגות שונה כאשר הדבר נדרש 23 class Animal { public: int getAge(); void makeSound() const { cout << endl; } // no sound by default private: int weight, age; }; class Dog: public Animal { void makeSound() const; };
25
מחלקות בעלות התנהגות פולימורפית ניתן במחלקה היורשת ממחלקה אחרת להגדיר מחדש פונקציות אשר הוגדרו במחלקת האב. אולם הדבר יגרום לכך שהפונקציה שתיקרא תלויה בדרך בה קראנו לה : Dog* d = new Dog(); Animal* a = d; a->makeSound(); // Animal::makeSound called d->makeSound(); // Dog::makeSound called אם נרצה שההתנהגות השונה תחול גם כאשר ניגשים לפונקציה דרך מצביע לאב - נעזר בפונקציות ווירטואליות. 24
26
פונקציות וירטוראליות class Animal { public: int getAge(); virtual void makeSound() const { cout << endl; } // no sound by default private: int weight, age; }; class Dog: public Animal { void makeSound() const; }; 25 כדי לאפשר התנהגות פוליטמורפית עבור פונקציה מוסיפים את המילה virtual בתחילת חתימתה פונקציות שהן פולימורפיות במחלקת האב תמיד יהיו פולימורפיות במחלקה היורשת ניתן עדיין לרשום את המילה virtual כדי להקל על הקריאות פונקציות שהן פולימורפיות במחלקת האב תמיד יהיו פולימורפיות במחלקה היורשת ניתן עדיין לרשום את המילה virtual כדי להקל על הקריאות
27
פונקציות וירטואליות אם פונקציה מוגדרת כוירטואלית גורמים לפונקציה שתיקרא להיבחר בזמן ריצה. כאשר הפונקציה תיקרא על עצם ממחלקת האב, תיקרא הפונקציה מתאימה לטיפוס העצם המדויק. השימוש באובייקט האב נראה אחיד, אך בפועל ההתנהגות מותאמת בכל פעם למקרה הרצוי. 26 class Dog : public Animal { public: void makeSound() const { cout << "vuf vuf\n" << endl; } }; class Cat: public Animal { public: void makeSound() const { cout << "miao" << endl; } }; class Fish: public Animal { }; // the default makeSound is OK
28
פונקציות וירטואליות – אופן השימוש void foo() { const int arraySize = 3; Animal* animals[arraySize]; animals[0] = new Dog(); animals[1] = new Fish(); animals[2] = new Cat(); for (int i = 0; i < arraySize; ++i) { animals[i]->makeSound(); } 27 פלט התוכנית : vuf miao פלט התוכנית : vuf miao
29
פונקציות וירטואליות - הערות השימוש בפונקציות וירטואליות מעלים את השימוש ב -switch ו -if. –במקום לשאול איזה טיפוס יש לנו ולפעול בהתאם אנו פשוט מעבירים את ההודעה לעצם. –בשיטה זו ניתן להרחיב את הקוד לשימוש בטיפוסים חדשים. מה קורה באותו מקרה ב -C כאשר רוצים להוסיף טיפוס חדש ? שימו לב שכדי לנצל פונקציות וירטואליות צריך להשתמש במצביעים או משתנים מיוחסים. כאשר יש שימוש בפונקציות וירטואליות ה -D’tor צרריך להיות וירטואלי. 28
30
Abstract Class קיימות מחלקות המייצגות רעיון מופשט ויש להן משמעות רק כבסיס למחלקות אחרות class Shape { public: Shape(int x, int y) : center_x(x), center_y(y) {} virtual double area() const = 0; protected: int center_x, center_y; }; מחלקה עם מתודה Pure Virtual ( אחת או יותר ) היא מחלקה אבסטרקטית לא ניתן ליצור אובייקטים של מחלקה שכזו 29 Pure Virtual Method
31
דוגמה מסכמת class Shape { public: Shape(int x, int y) : center_x(x), center_y(y) {} virtual double area() const = 0; protected: int center_x, center_y; }; 30 class Circle: public Shape { public: Circle(int x, int y, int r) : Shape(x, y), radius(r) {} double area() const { return (PI * radius * radius); } private: int radius; }; class Rectangle: public Shape { public: Rectangle(int x, int y, int h, int w) : Shape(x, y), height(h), width(w) {} double area() const { return (height * width); } private: int height, width; };
32
דוגמה מסכמת int main() { Shape* shapes[N]; // an array of rectangles & circles // initialization //... double totalArea=0; for (int i = 0; i < N; ++i) { totalArea += shapes[i]->area(); } 31
33
Exceptions השיטה החדשה לטיפול בשגיאות 32 main() return BAD_ARG catch throw BAD_ARG func()
34
חריגות - מוטיבציה אם פונקציה a קוראת פונקציה b שקוראת לפונקציה c וכך הלאה והתגלתה שגיאה בפונקציה z אשר רק פונקציה a אחראית / יודעת איך לטפל בה, אנחנו כמתכנתים נצטרך לכתוב כמות ( עצומה ) של קוד " לטפל מבלי לטפל " בשגיאות ברמות הביניים, שאינן יודעות / מעוניינות לטפל בשגיאות. Function Calls: a() => b() => …. =>z() Error found in z() only possible/correct to deal with at a(). 33
35
חריגות מוטיבציה איזה ערך שגיאה תחזיר פונקציה שאינה מחזירה אף ערך ? –למשל, מה אמור להחזיר c’tor במקרה של כישלון בהקצאת זיכרון ( דינמי ) לשדותיו, לדוגמה ? דוגמה נוספת לפונקציה שקשה \ מסורבל \ לא הגיוני שתחזיר ערכי שגיאה : אופרטורים למיניהם. –איזה ערך מייצג שגיאה עבור אופרטור חילוק של שברים כדי לייצג שגיאת חלוקה ב -0? ואם האופרטור אמור להחזיר רפרנס, לאיזה אובייקט נחזיר רפרנס במקרה של שגיאה ? 34
36
חריגות – סמנטיקה הרעיון : לאפשר העברת ערך שגיאה לרמה שיכולה ומעוניינת לטפל, תוך דילוג על שלבי הביניים. בכל פעם שנרצה להודיע על שגיאה, " נזרוק " עצם שמייצג את השגיאה. זריקה זו תגרום לעלייה במעלה היררכיית הקריאה לפונקציות ( תוך שחרור משתנים מקומיים בעזרת ה -d’tor שלהם ) עד לבלוק ה -try-catch המכיל אותה הראשון שמתייחס לשגיאה זו. 35
37
חריגות - תחביר השימוש בחריגות נעשה ע " י 3 מילות מפתח חדשות : try - הגדרת בלוק שאנו מוכנים לכשלון בביצועו. catch – תפיסת חריגה מבלוק try. throw – זריקת חריגה. try { throw 20;// or more interesting code that might throw() } catch (int param) { cout << "int exception"; } catch (char param) { cout << "char exception"; } catch (...) { cout << "default exception"; } 36
38
חריגות - שימוש נתקן את אופרטור [] של String מתרגול 9: char& String::operator[](int index) { verify_index(index); return data[index]; } void String::verify_index(int index) const { if (index >= size() || index < 0) { error("Bad index"); } return; } בעזרת שימוש בחריגות : void String::verify_index(int index) const { if (index >= size() || index < 0) { throw “Bad index"; } 37
39
מה פלט התכנית ? #include using namespace std; void foo() { throw "ERROR"; cout << "Is this code executed ?" << endl; return; } int main() { try { foo(); } catch (const char* string) { cout << string << endl; } cout << "END OF PROGRAM" << endl; return 0; } 38
40
שימוש בחריגות לבדיקת הקצאות במקום לבדוק האם malloc מחזירה NULL כמו ב -C. ב -++C אם new נכשלת היא זורקת חריגה מסוג std::bad_alloc // bad_alloc standard exception #include using namespace std; int main() { try { for (;;) { new int[10000000]; } } catch (exception& e) { cout << "Standard exception: " << e.what() << endl; } 39
41
חריגות – משפטי catch מרובים במידה ויש כמה משפטי catch אחרי throw - תתבצע התאמה של הפרמטר של ה -catch לחריגה שנזרקת, לפי הסדר של משפטי catch 40
42
חריגות – משפטי catch מרובים מה פלט של התכנית הבאה ? class AException {}; class BException: public AException {}; class CException: public AException {}; int main() { try { throw CException(); } catch (BException& bException) { cout << "BException caught" << endl; } catch (CException& cException) { cout << "CException caught" << endl; } catch (AException& aException) { cout << "AException caught" << endl; } 41
43
חריגות – משפטי catch מרובים מה פלט של התכנית הבאה ? class AException {}; class BException: public AException {}; class CException: public AException {}; int main() { try { throw CException(); } catch (BException& bException) { cout << "BException caught" << endl; } catch (AException& aException) { cout << "AException caught" << endl; } catch (CException& cException) { cout << "CException caught" << endl; } 42
44
חריגות – משפטי catch מרובים 43 מה פלט של התכנית הבאה ? class AException {}; class BException: public AException {}; class CException: public AException {}; int main() { try { throw AException(); } catch (BException& bException) { cout << "BException caught" << endl; } catch (CException& cException) { cout << "CException caught" << endl; } catch (AException& aException) { cout << "AException caught" << endl; }
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.