תבניות פונקציה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 64 מתוך 104.
דמיין שאתה כותב פונקציה למציאת הערך המרבי מבין שני מספרים שלמים, ואז מבין שאתה זקוק לאותו היגיון עבור מספרים מסוג double, ושוב עבור מחרוזות. בלי תבניות, היית כותב פונקציות כמעט זהות עבור כל טיפוס. תבניות פונקציה פותרות זאת בכך שהן מאפשרות לך לכתוב את הלוגיקה פעם אחת, והמהדר יוצר באופן אוטומטי גרסאות ייחודיות לכל טיפוס.
תבנית פונקציה משתמשת במילת המפתח template ואחריה פרמטרים של תבנית בתוך סוגריים זוויתיים:
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << maximum(5, 3) << std::endl; // משתמש בגרסה מסוג int
std::cout << maximum(3.14, 2.71) << std::endl; // משתמש בגרסה מסוג double
std::cout << maximum('a', 'z') << std::endl; // משתמש בגרסה מסוג char
}המהדר בוחן כל קריאה ומייצר פונקציה קונקרטית עבור הסוג המסוים הזה. התהליך הזה נקרא התממשקות תבנית. אפשר גם לציין את הסוג במפורש כשצריך:
std::cout << maximum<double>(5, 3.14) << std::endl; // מאלץ שימוש בגרסת doubleלתבניות יכולים להיות כמה פרמטרים של טיפוס, וכך מתאפשרים עיצובים גמישים עוד יותר:
template <typename T, typename U>
void printPair(T first, U second) {
std::cout << first << ", " << second << std::endl;
}
printPair(42, "hello"); // T=int, U=const char*
printPair(3.14, 100); // T=double, U=intתבניות פונקציה מספקות פולימורפיזם בזמן הידור — הטיפוס נקבע בעת הידור הקוד, ולא בזמן הריצה. פירוש הדבר הוא שאין תקורה בזמן הריצה בהשוואה לפונקציות וירטואליות, ולכן תבניות אידיאליות לקוד גנרי קריטי לביצועים.
אתגר
קלבואו נבנה ערכת כלים שימושית באמצעות תבניות פונקציה ליצירת פעולות לשימוש חוזר שעובדות עם כל טיפוס תואם. תארגנו את הפונקציות הגנריות שלכם בקובץ כותרת ותדגימו את הגמישות שלהן בתוכנית הראשית.
תיצרו שני קבצים:
MathUtils.h: הגדירו אוסף של תבניות פונקציה שמבצעות פעולות נפוצות:minimum: פונקציית תבנית שמקבלת שני ערכים מאותו טיפוס ומחזירה את הקטן מביניהם.clamp: פונקציית תבנית שמקבלת שלושה פרמטרים: ערך, גבול תחתון וגבול עליון. היא מחזירה את הערך כשהוא מוגבל לטווח (מחזירה את הגבול התחתון אם הערך קטן ממנו, את הגבול העליון אם הערך גדול ממנו, ואחרת מחזירה את הערך עצמו).swapValues: פונקציית תבנית שמקבלת שתי הפניות מאותו טיפוס ומחליפה בין הערכים שלהן.main.cpp: קראו שישה קלטים (כל אחד בשורה נפרדת):- מספר שלם ראשון
- מספר שלם שני
- ערך מסוג double להגבלה לטווח
- גבול תחתון (double)
- גבול עליון (double)
- תו
הדגימו את התבניות שלכם באמצעות:
- מציאת הקטן מבין שני המספרים השלמים והדפסת:
Min of <a> and <b>: <result> - מציאת הקטן מבין התווים
'm'ותו הקלט שלכם, והדפסת:Min of m and <char>: <result> - הגבלת ערך ה-double לטווח והדפסת:
Clamp <value> to [<low>, <high>]: <result> - הגבלת המספר השלם הראשון לטווח [0, 100] והדפסת:
Clamp <value> to [0, 100]: <result> - החלפת שני המספרים השלמים והדפסת:
After swapValues: <a>, <b>
לדוגמה, עם הקלטים 25, 10, 3.7, 1.0, 5.0 ו-z:
Min of 25 and 10: 10
Min of m and z: m
Clamp 3.7 to [1, 5]: 3.7
Clamp 25 to [0, 100]: 25
After swapValues: 10, 25שימו לב כיצד כל תבנית פונקציה עובדת בצורה חלקה עם מספרים שלמים, ערכי double ותווים: המהדר יוצר את הגרסה המתאימה לכל טיפוס שבו תשתמשו. הפונקציה swapValues שלכם אמורה לשנות את המשתנים המקוריים באמצעות הפניות, וכך להדגים שתבניות עובדות עם פרמטרים שהם הפניות בדיוק כמו פונקציות רגילות.
נסו בעצמכם
#include <iostream>
#include "MathUtils.h"
using namespace std;
int main() {
// קרא את הקלטים
int a, b;
double value, low, high;
char ch;
cin >> a;
cin >> b;
cin >> value;
cin >> low;
cin >> high;
cin >> ch;
// TODO: השתמש בתבנית minimum כדי למצוא את המינימום מבין שני מספרים שלמים
// הדפס: "Min of <a> and <b>: <result>"
// TODO: השתמש בתבנית minimum כדי למצוא את המינימום מבין 'm' לבין תו הקלט
// הדפס: "Min of m and <char>: <result>"
// TODO: השתמש בתבנית clamp על הערך מסוג double
// הדפס: "Clamp <value> to [<low>, <high>]: <result>"
// TODO: השתמש בתבנית clamp על המספר השלם הראשון בטווח [0, 100]
// הדפס: "Clamp <value> to [0, 100]: <result>"
// TODO: השתמש בתבנית swapValues על שני המספרים השלמים
// הדפס: "After swapValues: <a>, <b>"
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++ אונליין