תבניות ארגומנטים משתנות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 67 מתוך 104.
מה אם תצטרכו פונקציה שמקבלת מספר כלשהו של ארגומנטים מכל סוג? תבניות מסורתיות מחייבות אתכם לציין בדיוק כמה פרמטרי טיפוס אתם צריכים. תבניות וריאדיות פותרות זאת בכך שהן מאפשרות לתבניות לקבל מספר שרירותי של ארגומנטים של תבנית.
התחביר משתמש ב־... (שלוש נקודות) כדי ליצור חבילת פרמטרים:
template <typename... Args>
void printAll(Args... args) {
// Args הוא חבילת פרמטרים של תבנית
// args הוא חבילת פרמטרים של פונקציה
}כדי לעבד את הארגומנטים, בדרך כלל משתמשים ברקורסיה עם מקרה בסיס. חבילת הפרמטרים מתרחבת בארגומנט אחד בכל פעם, עד שלא נשארים ארגומנטים:
// מקרה בסיס: לא נותרו ארגומנטים
void print() {
std::cout << std::endl;
}
// מקרה רקורסיבי: מעבדים את הארגומנט הראשון וקוראים לפונקציה רקורסיבית עם השאר
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // פריסת הארגומנטים הנותרים
}
print(1, 3.14, "hello", 'x'); // פלט: 1 3.14 hello xאפשר גם להשתמש בביטויי קיפול (C++17) לפעולות פשוטות יותר ללא רקורסיה מפורשת:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // ביטוי קיפול: מחבר את כל הארגומנטים
}
std::cout << sum(1, 2, 3, 4) << std::endl; // פלט: 10תבניות וריאדיות עומדות בבסיס תכונות רבות של הספרייה הסטנדרטית, כמו std::make_unique, std::tuple ו־std::function. הן מאפשרות ליצור פונקציות בטוחות מבחינת טיפוסים, שעובדות עם כל שילוב של טיפוסי ארגומנטים ומספרם.
אתגר
קלבואו נבנה מערכת רישום גמישה שמשתמשת בתבניות וריאדיות כדי לטפל בהודעות עם כל מספר של ארגומנטים. תיצרו כלי עזר שיוכלו לשרשר ערכים, לספור ארגומנטים ולהדפיס פלט מעוצב — הכול באמצעות חבילות פרמטרים.
תארגנו את הקוד שלכם בשני קבצים:
Variadic.h: הגדירו כאן את פונקציות התבניות הווריאדיות שלכם.צרו פונקציה בשם
printAllשמדפיסה את כל הארגומנטים שלה כשהם מופרדים ברווחים, ולאחריהם ירידת שורה. השתמשו בגישה הרקורסיבית עם מקרה בסיס שמדפיס רק ירידת שורה, ובמקרה הרקורסיבי מדפיסים את הארגומנט הראשון, רווח, ואז קוראים לפונקציה שוב עם שאר הארגומנטים.צרו פונקציה בשם
countArgsשמחזירה את מספר הארגומנטים שהועברו אליה. השתמשו ב-sizeof...כדי לקבל את הגודל של חבילת הפרמטרים.צרו פונקציה בשם
sumשמחברת את כל הארגומנטים המספריים ומחזירה את התוצאה. השתמשו בביטוי קיפול עם האופרטור+.צרו פונקציה בשם
productשמכפילה את כל הארגומנטים המספריים ומחזירה את התוצאה. השתמשו בביטוי קיפול עם האופרטור*.main.cpp: קראו ארבעה קלטים (כל אחד בשורה נפרדת):- מספר שלם
- מספר ממשי מסוג double
- מחרוזת
- תו
הדגימו את התבניות הווריאדיות שלכם באמצעות:
- קריאה ל-
printAllעם כל ארבעת הקלטים והדפסת התוצאה - הדפסת מספר הארגומנטים בקריאה ל-
countArgsעם המספר השלם, המספר מסוג double והתו:Argument count: <count> - חישוב הסכום של המספר השלם, המספר מסוג double והערכים 10 ו-5, ולאחר מכן הדפסה:
Sum: <result> - חישוב המכפלה של המספר השלם והערכים 2 ו-3, ולאחר מכן הדפסה:
Product: <result> - קריאה ל-
printAllעם המחרוזת בלבד (ארגומנט יחיד) - קריאה ל-
printAllללא ארגומנטים (צריכה להדפיס רק ירידת שורה)
לדוגמה, עם הקלטים 5, 2.5, Hello ו-X:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
שימו לב איך printAll מטפלת בכל מספר של ארגומנטים מסוגים מעורבים, מארבעה ארגומנטים ועד אפס. הפונקציות sum ו-product משתמשות בביטויי קיפול כדי לשלב באלגנטיות את כל הערכים, ללא רקורסיה מפורשת. הפונקציה countArgs שלכם מדגימה כיצד sizeof... נותן לכם את גודל החבילה בזמן הידור.
נסו בעצמכם
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// קרא את הקלטים
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: קרא ל־printAll עם כל ארבעת הקלטים
// TODO: הדפס את מספר הארגומנטים באמצעות countArgs עם intVal, doubleVal ו־charVal
// פורמט: "Argument count: <count>"
// TODO: חשב והדפס את הסכום של intVal, doubleVal, 10 ו־5
// פורמט: "Sum: <result>"
// TODO: חשב והדפס את המכפלה של intVal, 2 ו־3
// פורמט: "Product: <result>"
// TODO: קרא ל־printAll עם המחרוזת בלבד
// TODO: קרא ל־printAll ללא ארגומנטים
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++ אונליין