דוגמה והסבר מלא למתחילים (כולל כניסה לפרטים הטכניים) על ההצהרה switch בשפת התכנות C.
רקע: המטרה של switch
לעתים קרובות אנחנו רוצים שקוד התוכנה שלנו יבצע פעולות שונות במצבים שונים. המצבים האלה יכולים לנבוע, למשל, מהמשתמש (האם הוא לחץ "אישור" או "ביטול"?) ממערכת ההפעלה (האם קובץ קיים, קיים אבל ריק, או לא קיים בכלל?) ואפילו מדברים שהקוד עצמו עשה קודם.
ההצהרה if מאפשרת לקוד שלנו לבדוק תנאי אחד (פשוט או מורכב) ולהגיב באופן שונה אם התנאי הזה מתקיים או לא. אבל דמיינו תפריט כמו ששומעים לפעמים במענה טלפוני אוטומטי:
למחלקת מכירות הקישו 1 לשירות לקוחות הקישו 2 להנהלה הקישו 3 לחזרה לתפריט הראשי הקישו 9
כדי לזהות ולהגיב בעזרת if לתפריט כזה, נצטרך לכתוב הרבה הצהרות if נפרדות או משורשרות. הקוד יהפוך למסורבל ויהיה קשה לקרוא אותו או לעדכן אותו בעת הצורך. לכן המציאו את ההצהרה switch, שמאפשרת לבחור בצורה מסודרת תגובות למספר רב של ערכים.
דוגמה
למי שבא רק בשביל התכל'ס, הנה תוכנית קצרה ושלמה ב-C שמציגה את כל התכונות העיקריות של switch. היא מבקשת מהמשתמש מספר בין 1 ל-4 ומציגה טקסטים שונים בהתאם למספר שהתקבל:
הדברים שכדאי לשים לב אליהם בדוגמה הזו הם: הערכים הנבדקים (בין המילה case לבין הנקודתיים), והיעדר הפקודה break ב-case השלישי – מה יקרה אם המשתמש יכתוב 3, ומה יקרה אם יכתוב 4?
המבנה של switch
נתחיל ב"קליפה" של switch, שנראית ככה:
switch (expression) {
}
האותיות חייבות להיות קטנות (לא Switch). הסוגריים הרגילים הם חובה. המסולסלים לא באמת חובה אבל זה יהיה ממש מטומטם בלעדיהם, אז לצורך העניין גם הם חובה. ו-expression כאן זו לא מילה שנכתוב בקוד שלנו – זה פשוט המקום שבו נכתוב את הביטוי או המשתנה שאת הערך שלו אנחנו רוצים לבדוק, כמו x בדוגמה למעלה.
למעשה, מה שנכתוב בתור expression יכול להיות כל דבר, בתנאי שבשורה התחתונה זה ייצא משהו שאפשר לבטא כ-integer, כטיפוס של מספר שלם. כל תוצאה אחרת – מספר עם נקודה עשרונית, מערך, מצביע וכדומה – לא תתקבל ורק תגרום להודעת שגיאה. הנה כמה דוגמאות ל-expression חוקי ולא חוקי:
ועכשיו לְמה שבתוך הקליפה: בין הסוגריים המסולסלים של switch אנחנו יכולים לבדוק את התוצאות של expression, תוצאה אחת בלבד כל פעם, באמצעות מילת המפתח case. המבנה של כל בדיקה כזו הוא:
case const-expression: statements;
const-expression הוא המקום שבו נכתוב את הערך שאנחנו מחפשים. שימו לב שכאן אמרנו const-expression, לא סתם expression כמו קודם. הכוונה היא שחוץ ממשהו שאפשר לבטא כ-integer, זה חייב להיות גם משהו שהקומפיילר יוכל לדעת מראש, לפני שהתוכנית מתחילה לרוץ, מה יהיה הערך המדויק שלו.
זאת אומרת, מותר לשים שם מספרים שלמים (למשל 123), תווי ASCII בודדים (למשל 'A'), מספרים שלמים או תווים שהוגדרו כמאקרו (עם define#), וכן חישובים שמשלבים ביניהם. שימו לב שמשתנים שהוגדרו עם מילת המפתח const דווקא לא יעבדו. אגב, גם קבועי enum יתקבלו – אבל אם אתם יודעים מה זה אומר, אתם כנראה לא צריכים את הפוסט הזה בכלל 🙂
אחרי הנקודתיים יגיעו ה-statements, כל ההצהרות והפקודות שאנחנו רוצים שירוצו אם הערך של ה-expression ש-switch בודקת שווה ל-const-expression של ה-case. בעיקרון אפשר לכתוב כמה פקודות שרוצים, אבל בשביל הקריאוּת ונוחות התחזוקה של הקוד עדיף להצטמצם, ואם יש הרבה פעולות שצריך לעשות, לכתוב אותן בפונקציה נפרדת ולקרוא לה מה-case.
לא לשכוח את ה-break
אחרי כל הפקודות שכתבנו עבור כל case, חשוב להוסיף את הפקודה break (עם נקודה-פסיק אחריה כמובן). הפקודה הזו מקפיצה את התוכנית אל מחוץ ל"קליפה" של switch וממשיכה אל שאר הקוד. אם לא נכתוב break אחרי הפקודות, המחשב יריץ גם את הפקודות של ה-case הבא בתור, בלי קשר ל-const-expression שלו!
בקוד הדוגמה שבתחילת הפוסט לא כללתי break מיד אחרי הפקודה בשורה 19. זה אומר שאם המשתמש הזין את המספר 3, שורה 19 תבוצע (התוכנה תציג את המחרוזת "Three, which is less than "), ומיד לאחר מכן תבוצע גם שורה 20 (יוצג "Four").
במקרה הזה סידרתי את הקוד בכוונה כך שהפלט יהיה הגיוני (שלוש זה אכן פחות מארבע), אבל בעולם האמתי נדיר למצוא מקרים שבהם כדאי לעשות טריק כזה. מכל בחינה, עדיף להתרגל להוסיף break בסוף ה-statements של כל case ולא לחפש דרכים להתחכם.
ברירת המחדל – default
בכל case אנחנו בודקים ערך אחד ספציפי. לפעמים נרצה שהקוד שלנו יתייחס גם למקרה שבו אף אחד מהערכים שבדקנו לא מתאים. בדוגמה למעלה ביקשנו מהמשתמש מספר בין 1 ל-4, אבל מי מבטיח לנו שזה מה שנקבל? אולי הוא יכתוב 0, או 5, או 43823423? אי אפשר לשים case לכל מספר בעולם, אבל אפשר לתפוס את כל המספרים שאין להם case באמצעות מילת המפתח default. היא תבוא כמובן בלי const-expression, רק נקודתיים, ואחרי זה ה-statements הרלוונטיות.
בדרך כלל נכתוב את default בתור האפשרות האחרונה, אחרי כל ה-case, ולכן לא קריטי להוסיף שם break – אבל גם לא מזיק.
מתחת למכסה המנוע – למתקדמים
הזכרתי שאת default כותבים בדרך כלל בסוף התוכן של switch, זה הכי הגיוני מבחינת הקורא האנושי – אבל מבחינת הקומפיילר הסדר ממש לא משנה. זה יעבוד בדיוק אותו הדבר אם נכתוב את קטע ה-default בהתחלה או באמצע.
וזה מעלה עניין נוסף: באיזה סדר הקוד בודק את ערכי ה-case? האם בסדר שבו הקלדנו אותם? או לפי סדר עולה של הערכים עצמם? התשובה היא ששפת C לא מחייבת התנהגות מסוימת, וכל קומפיילר רשאי לבחור איך הוא מממש את switch – גם ברמה הגלובלית וגם ברמות שונות של אופטימיזציה שהמתכנת עשוי לבקש. אז אף על פי שפקודות ירוצו ברצף שבו נכתבו אם נשמיט את ה-break, עדיין אסור להניח שבדיקת הערכים תהיה בסדר מסוים ולהסתמך על זה.
תודה 🙂
אפרופו switch, יש טריק לביצוע unrolling של לולאות בשם Duff's device. טוב גם למקרים נוספים של alignement.
לפרטים ראו ויקיפדיה.
לא מומלץ למתחילים 😀