בשביל מה switch
switch משווה ערך אחד מול רשימה של אפשרויות קבועות ומריץ את הענף שמתאים. כשאתם מוצאים את עצמכם כותבים שרשרת ארוכה של if/else if שבודקת שוב ושוב את אותו משתנה מול קבועים שונים, switch מבטא את הכוונה בצורה ברורה יותר, ולעיתים קרובות מתקמפל לטבלת קפיצות מהירה יותר.
הוא בודק רק שוויון מול קבועים בזמן קומפילציה, על ערך מטיפוס שלם. הוא לא יכול לבדוק טווחים, להשוות מחרוזות או לשלב תנאים. לכל אלה, הישארו עם if/else.
switch בסיסי
נותנים ל-switch ערך, ואז רושמים תוויות case לערכים שמעניינים אתכם. כל ענף מסתיים ב-break:
הערך 3 מתאים ל-case 3:, אז "Wednesday" מודפס ו-break קופץ החוצה מה-switch. ענף ה-default תופס את כל השאר: הוא רץ כשאף case לא מתאים. הוא אופציונלי, אבל כדאי לכלול אותו אלא אם אתם בטוחים שכל ערך מטופל.
שימו לב שתוויות ה-case הן קבועים חשופים ואחריהם נקודתיים, לא תנאים. כותבים case 3:, אף פעם לא case day == 3:.
break ו-fall-through
זו המלכודת החשובה ביותר של switch ב-C++. אחרי ש-case מתאים, הביצוע לא עוצר ב-case הבא: הוא ממשיך לרוץ ישר עד שהוא פוגש break או את הסוגר המסולסל הסוגר. שימו לב מה קורה כשמשפטי ה-break חסרים:
אולי ציפיתם רק ל-"one". במקום זה כל ארבע השורות מודפסות: ההתאמה ל-case 1: נכנסת ל-switch שם ואז נופלת (falls through) דרך כל התוויות שמתחתיה. הוסיפו break; אחרי כל ענף ותקבלו את השורה היחידה שרציתם. שכחה של break היא המקור הקלאסי לבאגים מסוג "למה ה-switch שלי מריץ יותר מדי קוד?".
fall-through מכוון לקיבוץ מקרים
fall-through הוא לא תמיד טעות: זו הדרך האידיומטית לגרום לכמה מקרים לחלוק גוף אחד. השאירו את ה-cases ריקים (בלי משפטים, בלי break) וכולם יזרמו לבלוק הבא:
'A', 'B' ו-'C' מגיעים כולם לאותה שורת "Pass", כי שני ה-cases הראשונים ריקים ונופלים לשלישי. זה נקי ומכוון. כשאתם כן מתכוונים ליפול הלאה אחרי שהרצתם קוד, תעדו את זה בהערה, או ב-C++17 ומעלה השתמשו בתכונה [[fallthrough]];, שאומרת למהדר "כן, התכוונתי לזה" ומשתיקה אזהרות fall-through.
switch על enums
switch משתלב באופן טבעי עם enums, כי enum הוא בדיוק "אחד מתוך קבוצה קבועה של ערכים". המהדרים גם יכולים להזהיר אתכם אם שכחתם לטפל באחד הערכים:
עם enum class בעל scope חייבים להוסיף את שם הטיפוס לכל תווית (Direction::East). מכיוון שכל ערך מכוסה, אין צורך ב-default, ומהדרים רבים יזהירו אם בהמשך תוסיפו כיוון חמישי ותשכחו להוסיף לו case. העזרה הזו מהמהדר היא סיבה גדולה להעדיף switch על פני שרשראות if/else כשעובדים עם enums.
מלכודת: הצהרות בתוך case
אי אפשר להצהיר על משתנה עם ערך התחלתי ב-case אחד ולגרום לו להיות נגיש בשאר ה-cases בלי לתחום אותו. זו שגיאת קומפילציה נפוצה:
switch (x) {
case 1:
int n = 10; // שגיאה: הקפיצה ל-case 2 מדלגת על האתחול הזה
cout << n;
break;
case 2:
cout << "two";
break;
}
המהדר דוחה את זה כי נפילה ל-case 2: הייתה מדלגת על האתחול של n בזמן ש-n עדיין בטווח ההכרה. הפתרון הוא לעטוף את גוף ה-case בסוגריים מסולסלים משלו, וכך המשתנה מקבל בלוק משלו:
בכל פעם ש-case צריך משתנה מקומי משלו, תנו לו סוגריים מסולסלים. זה גם מונע מהמשתנה לדלוף ל-cases שמתחתיו.
הבא בתור: לולאת for
switch ו-if מאפשרים לתוכנית לבחור איזה קוד להריץ פעם אחת. אבל הרבה עבודה פירושה לעשות את אותו דבר הרבה פעמים: לספור, לעבור על רשימה, לחזור עד שתנאי משתנה. לולאת for היא סוס העבודה לזה, והיא הדף הבא.
שאלות נפוצות
מתי כדאי להשתמש ב-switch במקום if-else ב-C++?
השתמשו ב-switch כשמשווים ערך אחד מטיפוס שלם מול הרבה אפשרויות קבועות, כמו בחירה בתפריט, מספר יום או enum. הוא קריא יותר משרשרת ארוכה של if/else if ומאפשר למהדר לבצע אופטימיזציה לקפיצה. הישארו עם if/else כשהתנאים כוללים טווחים (x > 10), ערכי נקודה צפה, מחרוזות או כמה משתנים: switch לא יכול לעשות אף אחד מאלה.
למה צריך break במשפט switch ב-C++?
ברגע ש-case מתאים, C++ ממשיכה להריץ את ה-cases שאחריו עד שהיא פוגשת break או את סוף ה-switch. זה נקרא fall-through. break עוצר את זה. שכחה של break היא באג קלאסי: מתאימים ל-case 1: ובטעות מריצים גם את הקוד של case 2, case 3 ושל ה-default.
על אילו טיפוסים אפשר לעשות switch ב-C++?
רק על טיפוסים שלמים או טיפוסי מנייה: int, char, short, long, bool, enum עם או בלי scope, וכל דבר שמומר לאחד מהם. אי אפשר לעשות switch על double, float או std::string: בשבילם השתמשו ב-if/else. כל תווית case חייבת להיות קבוע בזמן קומפילציה.