שפת C למתחילים: כמה זה x = 3?

בדוגמאות הקוד בפוסט הקודם בסדרה נתתי ערכים למשתנים, וגם שיניתי את הערכים הללו, בעזרת ביטויים כמו-

x = 1;
x = x + 2;

הביטויים האלה נראים כל-כך מובנים מאליהם, שאף אחד לא תהה מה בעצם קורה שם; אך מאחורי הקלעים של הקוד הזה עומד מנגנון מתוחכם ורב-עוצמה, שמהווה את אחת מאבני היסוד של התכנות בכלל – וגם מאפשר לנו, ספציפית בשפת C, לבצע טריקים שימושיים מאד ואפילו מרהיבים מכל מיני סוגים. בפוסט הנוכחי נגרד את קצה הקרחון של המנגנון הזה, וגם נפתור את התעלומה כמה זה x = 3. מוכנים?

בשפת C, התו "=" הוא אופרטור השמה. השמה מלשון לשים, הוא שם את הערך שמימין לו בתוך המשתנה שמשמאל. אם כתוב x = 1, אז המשתנה x יקבל את הערך המספרי 1. אבל מה זה אופרטור?

אופרטור (Operator) הוא סימן שמציין פעולה בסיסית. למשל, התו "=" מציין השמה, והתו כוכבית ("*") מציין כפל חשבוני1. כמובן, אין משמעות לכפל בלי מספרים שאותם מכפילים, ואין משמעות להשמה בלי משתנה וערך. האלמנטים האלה, שהאופרטור עובד איתם, נקראים אופרנדים (Operands). רוב האופרטורים עובדים על שני אופרנדים, אם כי יש יוצאי דופן. לדוגמה, הסימן "-", כשהוא בא לפני מספר ולא כחלק מפעולת חיסור, הוא בעצם אופרטור שעובד על אופרנד אחד בלבד והופך לו את הסימן.

הפעולה של אופרטור כמו "=" אינטואיטיבית לנו, כי אנחנו מבינים מה קורה בסיומה: המשתנה מכיל את הערך. לעומת זאת, מה קורה בסיום הפעולה של האופרטור "+"? האם תוצאת החיבור מרחפת לה בחלל הריק עד שמשהו כמו "=" תופס אותה? ובכן, זה די קרוב למציאות: האופרטור הזה, ולמעשה כל אופרטור, "מחזיר" לקוד תשובה, והקוד – בהתאם לאיך שאנו כותבים אותו כמובן – משתמש בתשובה הזו או לא, איך שבא לו.

הסתכלו, למשל, על התוכנית המשונה הזו לארדואינו:

void setup() {
  1 + 2;
  3 > 4;
  4 < 5;
}

void loop() {
}

אם תנסו לקמפל אותה ולהריץ אותה, היא תעבוד בלי שום בעיה. האופרטורים שבשלוש ה"פקודות" המוזרות עושים את פעולתם ומחזירים תשובה לחלל האוויר, בלי שיהיה מי שיתפוס אותה, אבל זה לא מפריע.

לעומת זאת, אם נכתוב

void setup() {
  x = 1 + 2;
  y = 3 > 4;
  z = 4 < 5;
}

אז המשתנים x, y, z "יתפסו" את התשובות של האופרטורים ההם: x יקבל את הערך המספרי 3, המשתנה y יקבל את הערך 0 (אפס, שמקביל בשפת C ל"שקר" או false), והמשתנה z יקבל את הערך 1 ("אמת" או true), כי ארבע באמת יותר קטן מחמש. זיכרו את הקשר הזה בין אפס ואחד ל"שקר" ו"אמת", הוא מאד מאד חשוב להמשך.

בואו נפרק את הפקודה הראשונה בקוד לעיל, בצורה גרפית, לאופרנדים ואופרטורים:

הפקודה x = 1 + 2 בחלוקה גרפית
הפקודה x = 1 + 2 בחלוקה גרפית

ראיתם איך התשובה של האופרטור "+" הופכת להיות אחד האופרנדים של האופרטור "="? זה בדיוק מה שמאפשר לנו לכתוב ביטויים מורכבים, שיש בהם יותר מאופרטור אחד, למשל

Index = lastIndex + (10 - userSelection) * 64;

החץ האדום באיור למעלה מסמן את הפעולה שהאופרטור "=" מבצע, אם כי זו לא התשובה שהוא מחזיר לקוד (ושמסומנת בסימן שאלה).  איזו תשובה הוא מחזיר? את אותו הערך שהוא נתן למשתנה. במילים אחרות, אחרי הרצה של הפקודה

a = 1 + (b = 2);

המשתנה b יהיה בעל ערך 2, והמשתנה a יהיה בעל ערך 3! וזו גם התשובה לחידה שבכותרת: בשפת C, הערך של הביטוי x = 3 הוא המספר 3.

רגע. אם "=" הוא אופרטור בדיוק כמו "+", איך הקומפיילר יודע מי מביניהם לבצע קודם? לשם כך נקבע סדר הקדימויות (Precedence) של האופרטורים. זוכרים שבתרגילי חשבון, אם אין סוגריים, אנחנו מבצעים את פעולות הכפל והחילוק לפני פעולות החיבור והחיסור, בלי קשר להיכן הן נמצאות? כך בדיוק גם בתכנות. לכל שפת תכנות יש טבלה מוגדרת מראש שקובעת את הקדימות של כל אופרטור, ולפיה הביטויים מחושבים. לדוגמה, מבין האופרטורים המתמטיים/לוגיים בשפת C, ל-"=" יש את הקדימות הנמוכה ביותר: אם כתוב x = a + b, אז האופרטור "+" יבוצע ראשון, והתוצאה תישלח כאופרנד ל-"=" שהמתין בסבלנות לתורו.

וזה לא הכל. הסתכלו על הפקודה

a = b = 1;

מה יקרה אם ננסה להריץ אותה? בהנחה שאופרטורים בעלי אותה קדימות מבוצעים לפי סדר הופעתם, משמאל לימין, אז a יקבל קודם כל את הערך שב-b, והאופרטור "=" יחזיר כתשובה את אותו ערך. שימו לב: הוא לא יחזיר את המשתנה a או את b, אלא אך ורק את הערך ש-a קיבל! אם הערך של b היה 7, אז אחרי ביצוע האופרטור הראשון, הביטוי המקורי יהפוך למשהו כמו

7 = 1;

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

אך להזכירכם, התחלנו מההנחה שאופרטורים בעלי אותה קדימות מבוצעים משמאל לימין, וזה לא תמיד נכון! אופרטורי השמה ב-C (כן, יש יותר מאחד) מבוצעים דווקא מימין לשמאל, וזה מאפשר לנו לכתוב בלי שום בעיה ביטוי כמו

a = b = c = d = e = f = 0;

שפשוט מאפס את המשתנים a-f.

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

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

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

להרשמה
הודע לי על
8 Comments
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

נפלא- עוד חור קטן הושלם ונסתם.
תודה רבה!

פשוט כיף לקרוא את הפוסטים שלך !

יש רק נושא אחד מעצבן – בRSS מקבלים רק את 2 השורות הראשונות ולא את כל הכתבה .
אשמח אם תוכל לשנות את זה .

כנראה שזה תלוי באדם ובקורא הRSS .
אני משתמש בGoogle Reader ושם זה ממש נוח לעבור מאייטם לאייטם .
נחייה עם זה ככה ..

אם כבר דיברנו על זה, יש איפהשהו RSS לתגובות של כל הפוסטים? לא הצלחתי למצוא.

זה כל כך כיף לראות בRSS שלי כל פעם שיש שיעור חדש אצלך. משלים חורים, חוזר על חומר, לומד דברים חדשים. תענוג אמיתי.
ועכשיו שאלה:
למה a = b = 1 לא הופך את הערכים של a ו b ל1, כמו ש –
a = b = c = d = e = f = 0 הופך את הערכים של כל המשתנים ל0 ?
(או שזה רק בגלל שהנחת שb שווה 7 לשם הדוגמא?)

מדריך מצויין תודה רבה!