Menu

קבועים ב-C: const, #define ו-enum

שלוש דרכים לתת שם לערך שלעולם לא משתנה ב-C: מילת המפתח const, מאקרו של #define וקבועי enum. מה כל אחד מהם הוא בפועל, ובמה לבחור ומתי.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

קבוע הוא ערך עם שם והבטחה שהוא לא ישתנה. C נותנת לכם שלושה מנגנונים לכך, ומתחת למכסה המנוע הם באמת דברים שונים: אחד הוא משתנה, אחד הוא החלפת טקסט ואחד הוא טיפוס של מספר שלם.

למה בכלל לתת שם לערך

השוו את שתי השורות האלה:

double area = 3.14159 * r * r;
double area = PI * r * r;

השנייה טובה יותר משלוש סיבות שכולן מתגלות בהמשך. היא אומרת מה המספר אומר. היא מופיעה במקום אחד בדיוק, כך ששינוי הדיוק הוא עריכה אחת ולא חיפוש. ושגיאת הקלדה ב-PI היא שגיאת קומפילציה, בעוד ששגיאת הקלדה ב-3.14195 היא תשובה שגויה.

מספרים בלי שם שמפוזרים בקוד נקראים מספרי קסם (magic numbers), והסרתם היא אחד השיפורים הזולים ביותר שאפשר לעשות בתוכנית C.

const

מילת המפתח const מסמנת משתנה כקריא בלבד:

הוציאו את ההשמה מההערה והקומפיילר מסרב: assignment of read-only variable 'PI'. השגיאה הזו בזמן הבנייה היא כל העניין.

משתנה const הוא משתנה אמיתי. יש לו טיפוס, כך שהקומפיילר בודק איך אתם משתמשים בו. הוא מכבד טווח (scope), כך ש-const בתוך פונקציה לא דולף החוצה. יש לו כתובת, כך שאפשר לקחת אליו מצביע. ודיבאגר יכול להראות לכם את השם והערך שלו.

const חייב להיות מאותחל במקום ההצהרה, ואין הזדמנות שנייה:

const int limit;       /* error (or garbage forever): nothing can assign to it later */
const int limit = 50;  /* correct */

#define

החלופה של קדם-המעבד היא מאקרו:

זה לא משתנה. לפני שהקומפיילר רץ, קדם-המעבד מחליף כל מופע של PI בקוד המקור בתווים 3.14159265358979. הקומפיילר אף פעם לא רואה את השם בכלל, וזו בדיוק הסיבה של-#define אין טיפוס והוא לא יכול להופיע בדיבאגר.

שימו לב שאין נקודה ופסיק ואין =. מאקרו הוא טקסט גולמי, כך שהטעות הנפוצה הזו:

#define MAX_USERS 100;        /* note the semicolon */

int limit = MAX_USERS + 1;    /* expands to: int limit = 100; + 1;  -> error */

מייצרת שגיאה בשורה שלא נראית בכלל כמו ההגדרה. מוסכמת השמות באותיות גדולות למאקרו קיימת בדיוק כדי שקוראים ידעו שמתרחשת החלפה.

לפי המוסכמה, מאקרו נכתבים ב-UPPER_CASE; משתני const בדרך כלל הולכים באותה דרך כשהם מייצגים את אותו רעיון.

קבועי enum

הצורה השלישית נותנת לכם קבוצה של מספרים שלמים עם שמות:

הערכים מתחילים ב-0 וגדלים באחד אלא אם משימים להם ערך, כך ש-MON הוא 0 ו-FRI הוא 4. enum { ... } אנונימי הוא ניב נפוץ לקבועים שלמים פשוטים.

לקבועי enum יש יתרון אמיתי על שתי החלופות: הם קבועים שלמים בזמן קומפילציה שהקומפיילר מבין, והם גלויים לדיבאגר. הסיפור המלא נמצא בעמוד על enum ב-C.

במה להשתמש

צורךבמה להשתמש
קבוע עם טיפוס, טווח ואפשרות דיבאגconst
גודל מערך, תווית case, רוחב שדה ביטיםenum או #define
קבוע מחרוזת#define או const char *
קבוע נקודה צפהconst double
קבוצה של ערכים שלמים קשוריםenum
כל מה שקדם-המעבד חייב לראות (#if, שומרי הכללה)#define

ברירת המחדל צריכה להיות const. פנו ל-#define כשהערך צריך להתקיים לפני הקומפילציה, ול-enum כשצריך מספר שלם בזמן קומפילציה או קבוצה עם שם.

המקום היחיד שבו זה באמת נושך הוא גדלי מערכים:

const int ב-C הוא משתנה לקריאה בלבד, לא ביטוי קבוע בזמן קומפילציה. בתוך פונקציה C99 מקבלת int local[const_size] בכך שהיא הופכת אותו למערך באורך משתנה; בטווח הקובץ, בתוך struct או כתווית case הוא נדחה מיד. enum ו-#define עובדים בכל מקום.

(זה אחד ההבדלים האמיתיים מ-C++, שבה const int הוא ביטוי קבוע. קוד שעובר בין השפות נתקל בזה.)

const עם מצביעים

const ומצביעים משתלבים בשלוש דרכים, והמשמעויות אינן ניתנות להחלפה. קראו את ההצהרה מהשם החוצה:

const char *p;          /* p points at chars that must not be modified */
char * const q;         /* q itself cannot be re-pointed; the chars can change */
const char * const r;   /* neither the pointer nor the chars can change */

הצורה const char * היא זו שתכתבו הכי הרבה, כי זה הטיפוס הנכון לפרמטר של פונקציה שקוראת מחרוזת בלי לשנות אותה:

הוספת const לפרמטר לקריאה בלבד לא עולה כלום ומתעדת את החוזה באופן שהקומפיילר אוכף. כדאי לעשות את זה בכל מקום.

מחרוזות מילוליות הן למעשה const

מלכודת עדינה ש-const קיים כדי לתפוס:

char *s = "hello";   /* legal C, but s points into read-only memory */
s[0] = 'H';          /* undefined behavior - usually a crash */

מחרוזות מילוליות נמצאות באזור לקריאה בלבד של קובץ ההרצה. כתיבה דרך המצביע הזה בדרך כלל גורמת ל-segfault. הצהרה על המצביע כ-const char *s = "hello"; גורמת לקומפיילר לדחות את ההשמה במקום לתת לה לקרוס בזמן ריצה.

אם צריך מחרוזת שאפשר לשנות, העתיקו אותה למערך:

שאלות נפוצות

איך מצהירים על קבוע ב-C?

הוסיפו const להצהרה ותנו לה ערך: const double PI = 3.14159;. מעכשיו הקומפיילר דוחה כל ניסיון להשים לה ערך. החלופה הוותיקה יותר היא מאקרו של קדם-המעבד, #define PI 3.14159, שמבצע החלפת טקסט במקום זאת.

מה ההבדל בין const ל-#define ב-C?

const יוצר משתנה אמיתי עם טיפוס שהקומפיילר מכיר, ולכן הוא נבדק מבחינת טיפוסים, מופיע בדיבאגר ומכבד טווח. #define הוא החלפת טקסט טהורה שמתבצעת לפני הקומפילציה: אין לו טיפוס, אין לו טווח, ואי אפשר לבחון אותו בזמן דיבאג. העדיפו const, אלא אם צריך את הערך בהקשר שדורש קבוע בזמן קומפילציה.

אפשר להשתמש במשתנה const כגודל של מערך ב-C?

לא עבור מערך בגודל קבוע בטווח הקובץ. const int N = 10; int arr[N]; הוא מערך באורך משתנה ב-C99 (מותר בתוך פונקציה, לא בטווח הקובץ ולא בתוך struct). לגודל אמיתי בזמן קומפילציה, השתמשו ב-#define N 10 או ב-enum { N = 10 };.

מה פירוש const char *p?

זה מצביע ל-char קבוע: מותר לשנות לאן p מצביע, אבל לא את התו שהוא מצביע עליו. char * const p הוא ההפך: מצביע קבוע ל-char שאפשר לשנות. const char * const p נועל את שניהם. קראו את ההצהרה מהשם החוצה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל