Presentation is loading. Please wait.

Presentation is loading. Please wait.

תכנות מכוון עצמים ו- C++ יחידה 11 תבניות - templates

Similar presentations


Presentation on theme: "תכנות מכוון עצמים ו- C++ יחידה 11 תבניות - templates"— Presentation transcript:

1 תכנות מכוון עצמים ו- C++ יחידה 11 תבניות - templates
קרן כליף

2 ביחידה זו נלמד: מוטיבציה לעבודה עם templates פונקציות template

3 מוטיבציה לשימוש ב- template
לפעמים יש פונקציות שעושות את אותה פעולה רק על טיפוסים שונים דוגמאות: swap, find, bubleSort, max וכד' כיום עלינו להעמיס את הפונקציה כך שכל פעם תקבל את הטיפוסים השונים בשפת C פתרנו זאת באמצעות void* בשפת C++ נפתור זאת באמצעות template: כתיבת פונקציה כללית ללא ציון טיפוס ספציפי

4 פונקצית ה- template: swap
#include <iostream> using namespace std; template <class T> void mySwap(T& x, T& y) { T temp = x; x = y; y = temp; } void main() int num1 = 15, num2 = 27; cout << "Before: num1=" << num1 << ", num2=" << num2 << endl; mySwap(num1, num2); cout << "After: num1=" << num1 << ", num2=" << num2 << endl << endl; char ch1 = 'a', ch2 = 'b'; cout << "Before: ch1=" << ch1 << ", ch2=" << ch2 << endl; mySwap(ch1, ch2); cout << "After: ch1=" << ch1 << ", ch2=" << ch2 << endl; האם הפונקציה תעבוד עבור מחרוזות? עבור כל פונקצית template נזהה מהן הדרישות מהטיפוס הגדרה שהפונקציה היא תבנית ומתן שם לטיפוס שאיתו עובדת הפונקציה הפונקציה משתמשת באופרטור= וב- copy c’tor של האובייקט Before: num1=15, num2=27 After: num1=27, num2=15 Before: ch1=a, ch2=b After: ch1=b, ch2=a

5 שליחת הפרמטר לפונקצית template
template <class T> T sum(T x, T y) { return x + y; } פונקציה כללית להחזרת סכום שני ערכים הקומפיילר יודע לזהות ששני הפרמטרים הם int ולכן יודע להסיק שה- T הוא int void main() { cout << "The sum is " << sum(3, 4) << endl; } The sum is 7 במקרה זה הקומפיילר לא יכול לקבוע באופן חד משמעי מה יהיה ה- T: int או double void main() { cout << "The sum is " << sum(3, 4.5) << endl; }

6 שליחת הפרמטר לפונקצית template (2)
הפתרון: במקרה של ambiguity יש לשלוח בתוך < > את הטיפוס T באופן מפורש void main() { cout << "The sum is " << sum<int>(3, 4.5) << endl; cout << "The sum is " << sum<double>(3, 4.5) << endl; } The sum is 7 The sum is 7.5

7 פונקציה המדפיסה את כל איברי המערך
void main() { int intArr[] = { 3,6,8,2 }; printArr(intArr, 4); Point pointsArr[3] = { {1,1}, {2,2}, {3,3} }; printArr(pointsArr, 3); Point* pointerToPoint[3] = { &pointsArr[0], &pointsArr[1], &pointsArr[2] }; printArr(pointerToPoint, 3); } דרישה שלטיפוס T יהיה אופרטור >> template<class T> void printArr(T arr[], int size) { for (int i = 0; i < size; i++) cout << arr[i] << " "; cout << endl; } class Point int x, y; public: Point(int x = 0, int y = 0) : x(x), y(y) {} friend ostream& operator<<(ostream& os, const Point& p) os << "(" << p.x << ", " << p.y << ")"; return os; }; (1, 1) (2, 2) (3, 3) 00AFF9BC 00AFF9C4 00AFF9CC

8 ההגבלות על הפונקציה במידה ובדוגמא הקודמת לא היה ממומש האופרטור >> עבור המחלקה Point, הייתה מתקבלת שגיאת הקומפילציה הבאה:

9 דוגמא נוספת להגבלות על הפונקציה
בדוגמא זו ההגבלות על הטיפוס T הן: שתהייה עבורו השיטה getArea שהיא const, שתחזיר משתנה מטיפוס שניתן לבצע עליו >> שתהייה עבורו השיטה getPerimiter שהיא const, שתחזיר משתנה מטיפוס שניתן לבצע עליו >> template<class T> void printDetails(const T& obj) { cout << "Area: " << obj.getArea() << endl; cout << "Perimiter: " << obj.getPerimiter() << endl; }

10 שימוש ב- template לעומת פולימורפיזם
בדוגמה הקודמת ראינו אלגוריתם כללי להדפסת נתוני צורה ניתן היה לבצע זאת גם באמצעות פולימורפיזם בעזרת שיטות וירטואליות template פולימורפיזם נפח הקוד גדול, מאחר ויש שכפול עבור כל טיפוס קטן, מאחר והאלגוריתם נמצא בבסיס פעם אחת בלבד זמן ריצה הקישור מתבצע בזמן קומפילציה, לכן טיפה יותר מהיר הקישור דינאמי, ולכן טיפה יותר איטי template פולימורפיזם נפח הקוד גדול, מאחר ויש שכפול עבור כל טיפוס קטן, מאחר והאלגוריתם נמצא בבסיס פעם אחת בלבד זמן ריצה הקישור מתבצע בזמן קומפילציה, לכן טיפה יותר מהיר הקישור דינאמי, ולכן טיפה יותר איטי

11 template<class T, class S>
הארות תמיד נתעד מהן הדרישות או ההגבלות על הטיפוסים שהם הפרמטר לפונקציה ניתן להגדיר פונקציית template עם יותר מטיפוס אחד: template<class T, class S> פונקצית template אינן פונקציה אחת, אלא אוסף של פונקציות בעלות שם זהה, המבצעות את אותן פעולות, על טיפוסים שונים עבור כל קריאה לפונקציית template עם טיפוסים שונים, הקומפיילר מייצר גירסא של הפונקציה עבור הטיפוס המבוקש (ניפוח ה- exe)

12 הצורך ב- Specialization
מן הסתם הפונקציה לא עובדת כראוי עבור מחרוזות.. (משווה כתובות ולא תוכן) template<class T> T myMax(T first, T second) { return first > second ? first : second; } void main() cout << myMax(3, 7) << endl; cout << myMax('a', 'A') << endl; char str1[] = "aaa", str2[] = "bbbb"; cout << myMax(str1, str2) << endl; cout << "str1 at: " << (void*)str1 << ", str2 at: " << (void*)str2 << endl; 7 a aaa str1 at: 006FFB64, str2 at: 006FFB54

13 specialization 7 a bbbb str1 at: 006FF75C, str2 at: 006FF74C
template<class T> T myMax(T first, T second) { return first > second ? first : second; } template<> char* myMax<char*>(char* first, char* second) return strcmp(first, second) > 0 ? first : second; void main() cout << myMax(3, 7) << endl; cout << myMax('a', 'A') << endl; char str1[] = "aaa", str2[] = "bbbb"; cout << myMax(str1, str2) << endl; cout << "str1 at: " << (void*)str1 << ", str2 at: " << (void*)str2 << endl; 7 a bbbb str1 at: 006FF75C, str2 at: 006FF74C

14 template<class T1, class T2> void f(T1, T2) { cout << "ver 1\n"; }
template<class T> void f(T) { cout << "ver 2\n"; } template<class T> void f(T, T) { cout << "ver 3\n"; } template<class T> void f(T*) { cout << "ver 4\n"; } template<class T> void f(T*, T) { cout << "ver 5\n"; } template<class T> void f(T, T*) { cout << "ver 6\n"; } template<class T> void f(int, T*) { cout << "ver 7\n"; } template<> void f<int>(int) { cout << "ver 8\n"; } void f(int, double) { cout << "ver 9\n"; } void f(int) { cout << "ver 10\n"; } template<class T> class complex {}; void main() { int i = 0; double d = 0; float ff = 0; complex<double> c; f(i); f<int>(i); f(i, i); f(c); f(i, ff); f(i, d); f(c, &c); f(i, &d); f(&d, d); f(&d); f(d, &i); f(&i, &i); } סדר עדיפויות הקריאה ver 10 ver 8 ver 3 ver 2 ver 1 ver 9 ver 6 ver 7 ver 5 ver 4 סדר עדיפויות הפונקציות במידה וישנן כמה פונקציות שמתאימות: פונקציה רגילה פונקצית template specialized פונקצית template

15 מדוע המימושים צריכים להיות ב- h ולא ב- cpp נפרד?
כי המימוש צריך להיות זמין בזמן קומפילציה. אבל מדוע? template<class T> T foo(T t) { return t*t; } void main() cout << foo(5) << endl; cout << foo("aaa") << endl; הקומפיילר צריך לתת שגיאת קומפילציה על השורה זו, כי אין פעולת כפל עבור מחרוזות אם המימוש היה ב- CPP נפרד, הקומפיילר היה מקמפל את המימוש בנפרד, ולא הייתה אינדיקציה לכך ששורה זו אינה מתקמפלת. תהליך הלינקר רק אמור לבצע קישורים ולא לבדוק תקינות.

16 מחלקת template ניתן להרחיב את השימוש ב- template גם עבור מחלקות שלמות
דוגמאות: המחלקה Array שיודעת להחזיק נתוני מערך. אין הבדל בתפעול בין מערך של מספרים, תווים או נקודות המחלקה List שיודעת להחזיק נתוני רשימה מקושרת. פעולות ההכנסה, הוצאה וכו' זהות עבור כל טיפוס בהמשך תראו שיש את ה- STL (Standard Template Library) אשר מממשת מבני-נתונים אלו בעזרת template

17 דוגמא: המחלקה Array /* T should have:
1- operator= > for using assignment and 'add' 2- operator << --> for printing with cout 3- default c'tor --> for creating the array */ template<class T> class Array { int physicalSize, logicalSize; char delimiter; T* arr; public: Array(int size = 10, char delimiter = ' '); Array(const Array& other); ~Array(); const Array& operator=(const Array& other); const Array& operator+=(const T& newVal); friend ostream& operator<<(ostream& os, const Array& arr) for (int i = 0; i < arr.logicalSize; i++) os << arr.arr[i] << arr.delimiter; return os; } }; דוגמא: המחלקה Array

18 שם המחלקה המלא הוא עם הטיפוס
דוגמא: המחלקה Array (2) template<class T> Array<T>::Array(int maxSize, char delimiter) : physicalSize(maxSize), logicalSize(0), delimiter(delimiter) { arr = new T[physicalSize]; } Array<T>::Array(const Array& other) : arr(NULL) *this = other; Array<T>::~Array() delete[]arr; כל הפונקציות ממומשות ב- h מתחת למחלקה, מאחר והקומפיילר צריך שהקוד יהיה נגיש בזמן קומפילציה כאשר מממשים את הפונקציות מתחת למחלקה יש לציין שוב שזוהי פונקציית template שם המחלקה המלא הוא עם הטיפוס

19 דוגמא: המחלקה Array (3) template<class T>
const Array<T>& Array<T>::operator=(const Array<T>& other) { if (this != &other) delete[]arr; physicalSize = other.physicalSize; logicalSize = other.logicalSize; delimiter = other.delimiter; arr = new T[physicalSize]; for (int i = 0; i < logicalSize; i++) arr[i] = other.arr[i]; } return *this; const Array<T>& Array<T>::operator+=(const T& newVal) if (logicalSize < physicalSize) arr[logicalSize++] = newVal;

20 שימוש במחלקה Array #include "array.h" void main() {
Array<int> intArr(3); intArr += 5; intArr += 2; intArr += 7; intArr += 1; cout << "The array is [" << intArr << "]\n"; } The array is [5 2 7 ]

21 שימוש במחלקה Array (2) class Point { int x, y; public:
Point(int x = 0, int y = 0) : x(x), y(y) {} friend ostream& operator<<(ostream& os, const Point& p) os << "(" << p.x << ", " << p.y << ")"; return os; } }; שימוש במחלקה Array (2) #include "array.h" void main() { Array<Point> pointsArr(4); pointsArr += Point(1, 1); pointsArr += Point(2, 2); pointsArr += Point(3, 3); cout << "The array is [" << pointsArr << "]\n"; } The array is [(1, 1) (2, 2) (3, 3) ]

22 פרמטר הטיפוס יכול להיות מורכב
כאשר הפרמטר הוא טיפוס temaplte, יש קומפיילרים (ישנים) שצריכים את הרווח בין 2 ה- << matArr מכיל 3 איברים ש- 'n\' מפריד בינהם בהדפסה, וכל איבר בהם הוא מערך של 10 int void main() { Array<Array<int>> mat(3, '\n'); Array<int> arr1(5); arr1 += 5; arr1 += 2; mat += arr1; Array<int> arr2(2); arr2 += 4; arr2 += 3; mat += arr2; arr1 += 9; cout << mat << endl; } 5 2 4 3 5 2 9 intArr1 מכיל 5 איברים ש- ' ' מפריד בינהם בהדפסה 5 2 5 2 9 intArr2 מכיל 2 איברים ש- ' ' מפריד בינהם בהדפסה 5 2 4 3 5 2 9 4 3

23 כיצד היה משתנה הפלט אם לא היה virtual d’tor??
template <class T> class Base { T val; public: Base(const T& t) : val(t) cout << "In Base::Base, val of type " << typeid(val).name() << endl; } virtual ~Base() {} friend ostream& operator<<(ostream& os, const Base& b) os << "My type is {" << typeid(b).name() << "}\n" << "My T's type is |" << typeid(b.val).name() << "|\n" << "val's value is [" << b.val << "]\n"; return os; }; template<class T> class Derived : public Base<T> Derived(const T& t) : Base(t) cout << "In Derived::Derived\n"; דוגמא מורכבת (1) כיצד היה משתנה הפלט אם לא היה virtual d’tor?? void main() { Derived<char> d('a'); cout << d << endl; } In Base::Base, val of type char In Derived::Derived My type is {class Derived<char>} My T's type is |char| val's value is [a]

24 template <class T>
class Base { T val; public: Base(const T& t) : val(t) cout << "In Base::Base, val of type " << typeid(val).name() << endl; } virtual ~Base() {} friend ostream& operator<<(ostream& os, const Base& b) os << "My type is {" << typeid(b).name() << "}\n" << "My T's type is |" << typeid(b.val).name() << "|\n" << "val's value is [" << b.val << "]\n"; return os; }; template<class T> class Derived : public Base<T> Derived(const T& t) : Base(t) cout << "In Derived::Derived\n"; דוגמא מורכבת (2) b (Base<Derived<int>>)  val (Derived<int>)  val (int) void main() { Derived<int> d(3); Base<Derived<int> > b(d); cout << b << endl; } In Base::Base, val of type int In Derived::Derived In Base::Base, val of type class Derived<int> My type is {class Base<class Derived<int> >} My T's type is |class Derived<int>| val's value is [My type is {class Derived<int>} My T's type is |int| val's value is [3] ]

25 template <class T>
class Base { T val; public: Base(const T& t) : val(t) cout << "In Base::Base, val of type " << typeid(val).name() << endl; } virtual ~Base() {} friend ostream& operator<<(ostream& os, const Base& b) os << "My type is {" << typeid(b).name() << "}\n" << "My T's type is |" << typeid(b.val).name() << "|\n" << "val's value is [" << b.val << "]\n"; return os; }; template<class T> class Derived : public Base<T> Derived(const T& t) : Base(t) cout << "In Derived::Derived\n"; דוגמא מורכבת (3) bd (Derived<Base<int>>)  val (Base<int>)  val (int) void main() { Base<int> b(8); Derived<Base<int> > bd(b); cout << bd << endl; } In Base::Base, val of type int In Base::Base, val of type class Base<int> In Derived::Derived My type is {class Derived<class Base<int> >} My T's type is |class Base<int>| val's value is [My type is {class Base<int>} My T's type is |int| val's value is [8] ]

26 דוגמא למחלקה המקבלת 2 טיפוסים
void main() { MyClass<char, int> mc1('a'); cout << mc1 << endl; MyClass<int, double> mc2(2); cout << mc2 << endl; MyClass<int, double*> mc3(3); cout << mc3 << endl; } a a a a template<class T, class S> class MyClass { T arr[sizeof(S)]; public: MyClass(const T& val) int numOfElements = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < numOfElements; i++) arr[i] = val; } friend ostream& operator<<(ostream& os, const MyClass& mc) os << mc.arr[i] << " "; return os; };

27 האם יתקמפל? אם כן מה הפלט, אחרת מהי השגיאה?
template<class T> class Tem { T t; public: Tem(const T& t1) { cout << "t=t1\n"; } ~Tem() { cout << "In Tem::d'tor\n"; } }; void main() Tem<Tem<A> > t1; cout << " \n"; Tem<Tem<A>> t2(t1); } לא יתקמפל מאחר יש דרישה של- Tem יהיה default c’tor

28 האם יתקמפל? אם כן מה הפלט, אחרת מהי השגיאה?
t1 (Tem<Tem<A>>)  t (Tem<A>)  t (A) נשים לב שזה לא copy c’tor!

29 האם יתקמפל? אם כן מה הפלט, אחרת מהי השגיאה?
לא יתקמפל כי הקומפיילר לא ידע להסיק מהו S לא יתקמפל כי הקומפיילר לא ידע להסיק מהו S: double או int

30 האם יתקמפל? אם כן מה הפלט, אחרת מהי השגיאה?
לא יתקמפל כי ל- Double אין בנאי המקבל int

31 ביחידה זו למדנו: מוטיבציה לעבודה עם templates פונקציות template

32 תרגול כתוב את המחלקה Pair כ- template אשר תחזיק 2 נתונים מטיפוסים כלשהם יש לספק למחלקה c'tor המקבל את שני הנתונים וכן default c'tor כתוב את המחלקה Map כ- template אשר תכיל מקסימום 10 זוגות של key- value (לצורך כך, המחלקה תחזיק מערך של איברים מטיפוס Pair, כך שהערך הראשון יהיה המפתח והשני הערך) יש לעמיס את האופרטור [ ] אשר יקבל משתנה מטיפוס המפתח ויחזיר משתנה מטיפוס הערך יש לממש את האופרטור >> בשקף הבא דוגמא ל- main >>

33 תרגול #include <iostream> using namespace std; #include "map.h" void main() { Map<int, char*> int2string; int2string[111] = "gogo"; int2string[222] = "momo"; int2string[333] = "yoyo"; cout << int2string; int2string[222] = "mama"; cout << endl << int2string; cout << " \n\n"; Map<char*, double> employeeToSalary; employeeToSalary["gogo"] = 1000; employeeToSalary["momo"] = 2000; employeeToSalary["yoyo"] = 3000; cout << employeeToSalary << endl; } /* 111 --> gogo 222 --> momo 333 --> yoyo Map is full 222 --> mama gogo --> 1000 momo --> 2000 yoyo --> 3000 Press any key to continue */ פתרון


Download ppt "תכנות מכוון עצמים ו- C++ יחידה 11 תבניות - templates"

Similar presentations


Ads by Google