קבוע הוא ערך עם שם והבטחה שהוא לא ישתנה. 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 נועל את שניהם. קראו את ההצהרה מהשם החוצה.