תבניות מחלקה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 65 מתוך 104.
כשם שתבניות פונקציה מאפשרות לך לכתוב פונקציות שאינן תלויות בסוג, תבניות מחלקה מאפשרות לך ליצור מחלקות שאינן תלויות בסוג. כך פועלים מכלים כמו std::vector — מגדירים את המבנה פעם אחת, והמהדר יוצר גרסאות ספציפיות לכל סוג שבו משתמשים.
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << std::endl; // 42
std::cout << strBox.getValue() << std::endl; // שלום
}בניגוד לתבניות של פונקציות, בעת יצירת אובייקטים מתבניות של מחלקות, עליך לציין במפורש את הטיפוס באמצעות סוגריים זוויתיים. לאחר מכן, המהדר יוצר הגדרה מלאה של מחלקה עבור הטיפוס הספציפי הזה.
לתבניות מחלקה יכולים להיות כמה פרמטרים של טיפוס, ואפילו פרמטרים שאינם טיפוסים, כמו מספרים שלמים:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
T& operator[](int index) { return data[index]; }
int size() const { return Size; }
};
FixedArray<double, 5> arr; // מערך של 5 ערכי double
arr[0] = 3.14;כשמגדירים פונקציות חברות מחוץ למחלקה, יש לחזור על הצהרת התבנית:
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }תבניות מחלקה הן הבסיס לתכנות גנרי ב-C++, ומאפשרות לך לכתוב מבני נתונים לשימוש חוזר שפועלים עם כל טיפוס.
אתגר
קלבואו נבנה מערכת אחסון גנרית באמצעות תבניות מחלקה, כדי ליצור מכל גמיש שיכול להכיל כל סוג של נתונים. תארגנו את מחלקת התבנית בקובץ כותרת ותדגימו את הגמישות שלה עם סוגים שונים בתוכנית הראשית.
תיצרו שני קבצים:
Storage.h: הגדירו תבנית מחלקה בשםStorageשתשמש כמכל פשוט לערך יחיד עם כמה פעולות שימושיות:תבנית
Storageשלכם צריכה לכלול:- חבר פרטי לאחסון הערך
- בנאי שמקבל ערך התחלתי
- מתודה
getValue()שמחזירה את הערך המאוחסן - מתודה
setValue()שמעדכנת את הערך המאוחסן - מתודה
isEmpty()שמחזירהtrueאם הערך שווה לערך ברירת המחדל שנוצר עבור הטיפוס T, ו-falseאחרת
צרו גם תבנית מחלקה בשם
Pairעם שני פרמטרים של טיפוס, שמאחסנת שני ערכים קשורים:- חברים פרטיים לערך הראשון ולערך השני (שיכולים להיות מטיפוסים שונים)
- בנאי שמאתחל את שני הערכים
- מתודות
getFirst()ו-getSecond()לקבלת כל אחד מהערכים - מתודה
display()שמדפיסה:(<first>, <second>)
main.cpp: קראו ארבעה ערכים (כל אחד בשורה נפרדת):- ערך שלם
- ערך שלם חדש
- ערך מחרוזת
- ערך מסוג double
הדגימו את התבניות שלכם באמצעות:
- יצירת
Storage<int>עם המספר השלם הראשון, והדפסת:Int storage: <value> - עדכונו באמצעות המספר השלם השני בעזרת
setValue(), ולאחר מכן הדפסת:Updated: <value> - יצירת
Storage<std::string>עם קלט המחרוזת, והדפסת:String storage: <value> - בדיקה אם אחסון המחרוזת ריק, והדפסת:
Is empty: <true/false>(הדפיסוtrueאוfalse) - יצירת
Pair<std::string, double>עם ערכי המחרוזת וה-double, ולאחר מכן קריאה ל-display() - יצירת
Pair<int, int>עם שני המספרים השלמים שהתקבלו כקלט, ולאחר מכן קריאה ל-display()
לדוגמה, עם הקלטים 42, 100, Hello ו-3.14:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)שימו לב שאותה תבנית Storage פועלת בצורה חלקה עם מספרים שלמים ומחרוזות, וש-Pair יכולה לשלב יחד טיפוסים שונים. המהדר יוצר הגדרות מחלקה נפרדות עבור כל צירוף טיפוסים שבו אתם משתמשים. זכרו להשתמש בתחביר template <typename T> עבור תבניות עם טיפוס יחיד, ובתחביר template <typename T, typename U> עבור תבניות עם כמה פרמטרים של טיפוס.
נסו בעצמכם
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// קרא את הקלטים
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: צור Storage<int> עם המספר השלם הראשון והדפס: Int storage: <value>
// TODO: עדכן עם המספר השלם השני באמצעות setValue(), הדפס: Updated: <value>
// TODO: צור Storage<std::string> עם קלט המחרוזת, הדפס: String storage: <value>
// TODO: בדוק אם אחסון המחרוזת ריק, הדפס: Is empty: true/false
// TODO: צור Pair<std::string, double> עם המחרוזת והמספר מסוג double, קרא ל-display()
// TODO: צור Pair<int, int> עם שני המספרים השלמים, קרא ל-display()
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין