ערך אחד, הרבה קבועים
שרשרת else if שממשיכה להשוות את אותו משתנה מול קבועים היא דפוס שיש לו משפט ייעודי ב-C:
if (choice == 1) { /* ... */ }
else if (choice == 2) { /* ... */ }
else if (choice == 3) { /* ... */ }
else { /* ... */ }
switch אומר את זה ישירות יותר. הוא מחשב את הביטוי פעם אחת, ואז קופץ ישר לתווית המתאימה.
שנו את choice והריצו שוב. המבנה חושף במבט אחד את קבוצת הערכים החוקיים, וזו הסיבה האמיתית להעדיף אותו כאן.
תחביר והכללים לתוויות case
switch (integerExpression) {
case CONSTANT_1:
statements
break;
case CONSTANT_2:
statements
break;
default:
statements
break;
}
ארבע מגבלות ש-C אוכפת:
- לביטוי של ה-switch חייב להיות טיפוס שלם:
int,char,short,longאוenum. לאfloat, לאdoubleולא מחרוזת. - כל תווית
caseחייבת להיות ביטוי קבוע שידוע בזמן קומפילציה.case n:כש-nהוא משתנה זו שגיאה, ו-case 3 + 4:ו-case MAX:(קבוע של#defineאו של enum) תקינים. - לשני מקרים אסור שיהיה אותו ערך. כפילויות הן שגיאת קומפילציה, רשת ביטחון קטנה אבל אמיתית.
- case לא יכול לבטא טווח.
case 1 ... 5:היא הרחבה של GCC, לא C סטנדרטית: לטווחים השתמשו ב-if.
default הוא אופציונלי ויכול להופיע בכל מקום בבלוק, אם כי המוסכמה הכמעט אוניברסלית היא לשים אותו אחרון. בלי default, ערך שלא תאם פשוט מדלג על כל ה-switch.
break: למה הוא לא אופציונלי
הדבר המפתיע ביותר ב-switch של C הוא שתוויות case הן יעדי קפיצה, לא קופסאות. ברגע שהביצוע נוחת על תווית, הוא ממשיך דרך כל הפקודות שמתחתיה, גם מעבר לתוויות case אחרות, עד break או עד הסוגר המסולסל הסוגר.
ה-switch הראשון מדפיס one, two ו-three: הוא תאם את case 1 ואז הריץ את כל מה שאחריו. השני מדפיס רק one. ההתנהגות הזו נקראת fall-through, ושכחה של break היא באג ה-switch הקלאסי ב-C: התוכנית שגויה בשקט במקום להיכשל בקומפילציה.
קמפלו עם gcc -Wall -Wextra -Wimplicit-fallthrough והקומפיילר יצביע על כל case שנופל לתוך הבא.
ה-break ב-case האחרון מיותר מבחינה טכנית, כי הבלוק נגמר שם בכל מקרה. כתבו אותו בכל זאת, כדי שהוספה של case חדש מתחתיו בהמשך לא תיצור בשקט fall-through.
Fall-through מכוון
fall-through הוא יכולת כשהוא מכוון. ערימה של תוויות בלי פקודות ביניהן היא הדרך הרגילה לומר "הערכים האלה עושים את אותו הדבר":
שלוש תוויות חולקות גוף אחד, בלי קוד כפול ובלי תנאי ארוך עם ||. קומפיילרים לא מזהירים על הצורה הזו, כי אין פקודות ליפול דרכן.
fall-through עם פקודות באמצע, שבו case 1 רץ ואז ממשיך בכוונה לתוך case 2, נדיר יותר וראוי להערה מפורשת, כי צריך להגיד לקורא שה-break החסר היה החלטה:
switch (level) {
case 3:
printf("verbose\n");
/* fall through */
case 2:
printf("info\n");
/* fall through */
case 1:
printf("errors\n");
break;
}
/* fall through */ היא ההערה שהאזהרה של GCC מזהה, ולכן היא גם משתיקה את האבחון.
switch על תווים
מכיוון ש-char הוא טיפוס שלם, סיווג תווים מתאים באופן טבעי:
שימו לב ש-case 'a': משתמש בגרש בודד: זה קבוע תו, שהוא פשוט הערך השלם של התו. case "a": היה מחרוזת ולא היה מתקמפל.
switch על enums
הצירוף הטוב ביותר ב-C הוא switch עם enum. תוויות ה-case הופכות לשמות קריאים, וקומפיילרים יכולים להזהיר כשה-switch לא מטפל בכל ה-enumerators:
שני דברים ששווה להעתיק מכאן. כל case עושה return, ולכן לא צריך break: return יוצא מהפונקציה כולה, וזה מסיים גם את ה-switch. ואין default: עם -Wswitch (שכלול ב--Wall), השמטת default גורמת לקומפיילר להזהיר ביום שמישהו יוסיף מצב חמישי וישכח את הפונקציה הזו. default היה מסתיר את זה.
הצהרה על משתנים בתוך switch
גוף של switch הוא בלוק אחד, כך שהצהרה ב-case אחד נראית, אם כי לא בהכרח מאותחלת, ב-cases שאחריו. קפיצה מעל אתחול היא שגיאה ב-C:
switch (n) {
case 1:
int x = 10; // שגיאה ב-C: תווית לא יכולה לבוא לפני הצהרה בצורה הזו
printf("%d\n", x);
break;
case 2:
printf("%d\n", x); // x קיים כאן, אבל מעולם לא אותחל
break;
}
עטפו את ה-case בסוגריים מסולסלים משלו כשהוא צריך משתנים מקומיים:
switch (n) {
case 1: {
int x = 10;
printf("%d\n", x);
break;
}
case 2:
printf("two\n");
break;
}
switch או if-else?
פנו ל-switch כשכל התנאים האלה מתקיימים: אתם בודקים ביטוי אחד, יש לו טיפוס שלם, ואתם משווים אותו מול קבועים ידועים מראש. טיפול בתפריטים, מכונות מצבים עם enum, סיווג תווים וניתוב פקודות הם המקרים הקנוניים. הרווח הוא קריאות, בדיקה של ערכים כפולים, וקומפיילר שיכול להזהיר על enumerators שלא טופלו.
פנו ל-if-else כשהבדיקות כוללות טווחים (score >= 90), ערכים עשרוניים, מחרוזות (צריך strcmp), כמה משתנים שונים, או תנאים שמשולבים עם && ו-||. אף אחד מאלה לא ניתן לביטוי כתווית case.
ביצועים הם לעיתים רחוקות הגורם המכריע. קומפיילרים הופכים switch צפוף לטבלת קפיצות ו-switch דליל להשוואות, כך שבכמה מקרים בודדים אין הבדל בין שתי הצורות. בחרו את זו שמבטאת את הכוונה.
שאלות נפוצות
איך משפט switch עובד ב-C?
switch (expr) מחשב ביטוי שלם פעם אחת, קופץ לתווית ה-case שהקבוע שלה תואם, ורץ משם עד שהוא פוגש break או את סוף ה-switch. אם שום דבר לא תואם, הוא קופץ ל-default אם יש כזה, ואחרת מדלג על כל הבלוק.
למה צריך break ב-switch של C?
כי תוויות case הן יעדי קפיצה, לא בלוקים נפרדים. בלי break, הביצוע ממשיך ישר לפקודות של ה-case הבא, וזה נקרא fall-through. break חסר הוא באג ה-switch הנפוץ ביותר ב-C, ו-gcc -Wimplicit-fallthrough יסמן אותו.
אפשר לעשות switch על מחרוזת ב-C?
לא. switch ב-C דורש ביטוי שלם, כך שערכים של int, char ו-enum עובדים, אבל מחרוזות, מספרים עשרוניים וטווחים לא. כדי להסתעף לפי מחרוזת, השתמשו ב-strcmp בשרשרת if-else, או מפו קודם את המחרוזת ל-enum ועשו switch עליו.
מתי להשתמש ב-switch במקום if-else ב-C?
השתמשו ב-switch כשאתם משווים מספר שלם או תו אחד מול רשימה של קבועים: פקודות תפריט, מצבים של enum, סיווג תווים. השתמשו בשרשרת if-else כשהבדיקות כוללות טווחים, כמה משתנים, או כל דבר חוץ משוויון מול קבוע של זמן קומפילציה.