משתנה הוא פיסת זיכרון עם שם שמחזיקה ערך שהתוכנית יכולה לקרוא ולשנות. ב-C תמיד אומרים מראש איזה סוג ערך הוא מחזיק, והבחירה הזו קבועה לכל אורך חייו של המשתנה.
הצהרה על משתנה
הצורה היא טיפוס, שם, נקודה-פסיק:
int age;
double price;
char grade;
כל שורה שומרת מספיק זיכרון עבור טיפוס הנתונים הזה ומצמידה לו שם. עדיין לא נשמר שום דבר: המקום קיים, אבל התוכן שלו הוא מה שהשאיר שם הקוד שרץ קודם.
אפשר לתת ערך באותו רגע, וזה נקרא אתחול:
שימו לב ל-format specifiers: %d עבור int, %f עבור double, %c עבור char. printf לא בודקת את הערכים שהיא מקבלת: היא סומכת על מחרוזת הפורמט שלכם, כך שאי התאמה מדפיסה שטויות במקום להחזיר שגיאה.
השמה אחרי הצהרה
ברגע שמשתנה קיים, משנים אותו עם =:
ב-C, = הוא השמה, לא שוויון. את score = score + 10 קוראים כך: "קחו את מה שיש ב-score, הוסיפו 10, והחזירו את התוצאה ל-score". אופרטור ההשוואה הוא ==, ובלבול ביניהם הוא באג כל כך נפוץ שיש לו פרק משלו בעמוד booleans.
הקיצור score += 10 עושה את אותו הדבר בפחות תווים; אופרטורי ההשמה המורכבים מכסים את כל האריתמטיקה.
מלכודת המשתנה הלא מאותחל
זה הבאג הנפוץ ביותר של מתחילים ב-C, והוא לא מייצר שום שגיאה:
הריצו את זה. אולי תראו 15, שזו התשובה הנכונה. אולי תראו מספר אקראי ענקי. אולי תראו משהו אחר בהרצה שנייה או במכונה אחרת. כל אלה התנהגות "נכונה", כי קריאה של משתנה מקומי לא מאותחל היא התנהגות לא מוגדרת: תקן C לא מטיל שום דרישה על מה שקורה.
בניגוד לשפות רבות, C לא מאפסת את המשתנים המקומיים שלכם. משתנה מקומי הוא חלק מהמחסנית, והמחסנית מלאה בשאריות מקריאות קודמות לפונקציות. לפעמים השאריות האלה הן במקרה אפס, וזה מה שהופך את הבאג הזה למסוכן כל כך: לרוב הוא עובד בבדיקות ונכשל בפרודקשן.
התיקון הוא תו אחד:
הפכו את זה להרגל: אתחלו כל משתנה במקום שבו מצהירים עליו. אם באמת עוד אין לכם ערך הגיוני, 0 או NULL עדיפים על זבל. וקמפלו עם -Wall, שתופס את רוב המקרים:
warning: 'total' is used uninitialized in this function
משתנים גלובליים ומשתני static הם החריג: הם כן מאותחלים לאפס אוטומטית. זה בסדר לסמוך על זה, אבל לכתוב = 0 בכל זאת מתעד את הכוונה שלכם.
הצהרה על כמה משתנים בבת אחת
משתנים מאותו טיפוס יכולים לחלוק הצהרה, עם או בלי אתחול:
זה חוקי ונפוץ לערכים קשורים מאוד כמו x, y, z. זה גם המקום שבו מסתתרת טעות קריאה קלאסית:
int a = 0, b = 0, c = 0; /* שלושתם 0 */
int d, e, f = 0; /* רק f הוא 0: d ו-e הם זבל */
האתחול מוצמד לשם אחד, לא לשורה. כשלערכים יש משמעויות שונות, תנו לכל אחד שורה משלו: זה נקרא טוב יותר ומייצר diffs ברורים יותר.
הצהרה על מצביעים בשורה אחת כוללת את אותה מלכודת בגרסה חדה יותר:
int *p, q; /* p הוא מצביע ל-int; q הוא int רגיל */
ה-* נקשר לשם, לא לטיפוס. הצהירו על מצביע אחד בכל שורה.
כללים ומוסכמות למתן שמות
הכללים של הקומפיילר:
- רק אותיות, ספרות וקווים תחתונים.
- אסור להתחיל בספרה:
total2תקין,2totalלא. - אסור שזו תהיה מילת מפתח של C: אין משתנה בשם
int,return,for,double. - הבחנה בין אותיות גדולות לקטנות:
count,Countו-COUNTהם שלושה משתנים נפרדים. - שמות שמתחילים בקו תחתון, או שמכילים קו תחתון כפול, שמורים למימוש. אל תיצרו כאלה.
int itemCount; /* חוקי */
int item_count; /* חוקי: הסגנון המקובל ב-C */
int _count; /* חוקי אבל שמור: עדיף להימנע */
int item-count; /* שגיאה: '-' הוא אופרטור החיסור */
int 3rd_place; /* שגיאה: מתחיל בספרה */
int float; /* שגיאה: מילת מפתח */
המוסכמות שרוב קוד ה-C עוקב אחריהן: snake_case למשתנים ולפונקציות, UPPER_CASE לקבועי מאקרו, ושמות שאומרים מה המשמעות של הערך ולא מה הטיפוס שלו. elapsed_ms עדיף על t; customer_count עדיף על n2.
שמות קצרים עדיין בסדר כשה-scope זעיר: i כמונה של לולאה מובן לכולם, ואף אחד לא רוצה for (int loop_iteration_index = 0; ...).
איפה משתנה חי
המקום שבו מצהירים על משתנה קובע מי יכול לראות אותו וכמה זמן הוא מחזיק. זה ה-scope, והגרסה הקצרה היא:
משתנה גלוי מרגע ההצהרה עליו ועד הסוגר המסולסל שסוגר את הבלוק שמכיל אותו. משתנים גלובליים חיים לאורך כל התוכנית וגלויים בכל מקום, מה שנשמע נוח, ובגלל זה הם עושים צרות: כל פונקציה יכולה לשנות אותם, כך שמציאת מי שינה הופכת לחיפוש בכל הקובץ. העדיפו משתנים מקומיים והעבירו ערכים לפונקציות כ-פרמטרים.
הצהרה בכל מקום (C99 ואילך)
ב-C89 המקורי, כל הצהרה הייתה חייבת לבוא לפני כל פקודה בבלוק:
int main(void) {
int i, sum = 0; /* כל ההצהרות קודם */
printf("Starting\n");
for (i = 0; i < 10; i++) { sum += i; }
return 0;
}
מאז C99 אפשר להצהיר במקום שבו צריכים את הערך לראשונה, כולל בכותרת של ה-for:
זה סגנון טוב יותר: ההצהרה יושבת ליד השימוש הראשון, ו-i לא יכול לדלוף לשאר הפונקציה. קמפלו עם -std=c17 (או כל הגדרה של C99 ומעלה) וזה עובד בכל מקום.
שאלות נפוצות
איך מצהירים על משתנה ב-C?
כותבים את הטיפוס, אחריו את השם, ואז נקודה-פסיק: int age;. אפשר לתת לו ערך באותו רגע, int age = 30;, וזה נקרא אתחול, וכמעט תמיד זה מה שרוצים.
מה ההבדל בין הצהרה על משתנה לבין אתחול שלו?
הצהרה שומרת מקום ונותנת לו שם (int count;). אתחול מכניס לתוכו ערך ברגע שהוא נוצר (int count = 0;). משתנה מקומי שהוצהר ולא אותחל מכיל את הבתים שכבר היו בכתובת הזו, וקריאה שלו היא התנהגות לא מוגדרת.
מה הכללים לשמות של משתנים ב-C?
רק אותיות, ספרות וקווים תחתונים; אסור להתחיל בספרה; אסור שזו תהיה מילת מפתח כמו int או for; ויש הבחנה בין אותיות גדולות לקטנות, כך ש-total ו-Total הם משתנים שונים. שמות שמתחילים בקו תחתון שמורים, עדיף להימנע מהם.
אפשר להצהיר על משתנים בכל מקום בפונקציה ב-C?
מאז C99, כן: הצהירו על כל משתנה בדיוק במקום שבו אתם צריכים אותו לראשונה. בתקן C89 המקורי כל הצהרה הייתה חייבת להופיע בראש הבלוק, ולכן בקוד ישן יש חומה של הצהרות לפני כל עבודה אמיתית.