אופרטורים הם הפעלים של C: הם לוקחים ערכים ומפיקים ערכים חדשים. יש בערך 40 כאלה, אבל תריסר מהם מכסים את רוב הקוד, ולשניים מהם יש מלכודות חדות מספיק כדי להצדיק חלק משלהן.
אופרטורים אריתמטיים
(%% במחרוזת הפורמט מדפיס סימן אחוז מילולי: % לבד היה פותח format specifier.)
חמשת האופרטורים האריתמטיים הבינאריים הם + - * / %, ועוד - אונרי לשלילה ו-+ שלמעשה לא עושה כלום.
המלכודת של חילוק שלמים
a / b עם שני מספרים שלמים מבצע חילוק שלמים וזורק את השארית. אין עיגול: התוצאה נחתכת לכיוון אפס.
המקרה האחרון הוא הבאג ששורד עד הפרודקשן. השמה ל-double לא עוזרת: החילוק total / count כבר הסתיים, וכבר הוא int, לפני שההשמה קורית. התיקון הוא להפוך אופרנד אחד לנקודה צפה בתוך הביטוי, בדרך כלל עם המרה.
שני כללים קשורים: חילוק באפס עם מספרים שלמים הוא התנהגות לא מוגדרת ובדרך כלל מקריס את התוכנית, ו-INT_MIN / -1 גולש.
אופרטור המודולו
% נותן את השארית ועובד רק על מספרים שלמים:
בדיקה של x % 2 == 0 לזוגיות ושימוש ב-% n כדי לגלגל אינדקס סביב מערך הם שני השימושים היומיומיים.
עם מספרים שליליים, % של C הולך לפי הסימן של המחולק:
לכן x % 2 == 1 היא בדיקה שבורה לאי-זוגיות כש-x יכול להיות שלילי: -7 % 2 הוא -1. השתמשו במקום זאת ב-x % 2 != 0.
עבור שאריות בנקודה צפה, % הוא שגיאת קומפילציה; השתמשו ב-fmod() מ-math.h.
השמה והשמה מורכבת
= שומר ערך. הצורות המורכבות משלבות פעולה עם השמירה:
x += 5 פירושו x = x + 5, אבל הוא מחשב את x רק פעם אחת, וזה חשוב כשהיעד הוא משהו כמו arr[compute_index()].
השמה היא בעצמה ביטוי שמחזיר את הערך שהושם, ולכן a = b = c = 0 עובד (הוא משים מימין לשמאל), ולכן הטעות של = מול == מתקמפלת בשקט.
הגדלה והקטנה
++ מוסיף אחד, -- מחסיר אחד. לכל אחד יש צורת prefix וצורת postfix:
כפקודה עצמאית שתי הצורות ניתנות להחלפה, ו-i++ הוא הבחירה המקובלת בכותרת של for בלי סיבה עמוקה יותר ממסורת.
מה שאסור לעשות הוא להשתמש במשתנה יותר מפעם אחת בביטוי שבו הוא גם משתנה:
int i = 5;
int x = i++ + i++; /* התנהגות לא מוגדרת */
arr[i] = i++; /* התנהגות לא מוגדרת */
printf("%d %d", i++, i);/* התנהגות לא מוגדרת */
C לא מגדירה את הסדר שבו תת-הביטויים האלה מחושבים, כך שהתוצאה היא לא רק לא מוגדרת: ההתנהגות של כל התוכנית לא מוגדרת. פצלו את הפקודה לשתיים והעמימות נעלמת.
אופרטורי השוואה
יש שישה, וכולם מפיקים int: 1 עבור אמת ו-0 עבור שקר.
העובדה שהתוצאה היא int רגיל ולא טיפוס בוליאני ייעודי היא תכונה מגדירה של C: ראו את העמוד על בוליאנים ב-C למה שנובע מזה, כולל הבאג של = מול ==.
שתי טעויות השוואה ייחודיות ל-C. שרשור לא עובד כמו שהמתמטיקה מרמזת: if (1 < x < 10) תמיד אמת, כי 1 < x מחושב ל-0 או 1 ואז זה מושווה ל-10. כתבו if (x > 1 && x < 10). והשוואת מחרוזות עם == משווה מצביעים, לא טקסט: השתמשו ב-strcmp מ-string.h.
אופרטורים לוגיים
&& הוא AND, || הוא OR, ! הוא NOT. שלושתם מתייחסים לכל ערך שונה מאפס כאמת.
הם מבצעים short-circuit: && עוצר ברגע שאופרנד אחד שקר, ו-|| עוצר ברגע שאחד אמת. השאר אף פעם לא מחושב, וזו לא רק אופטימיזציה: זו הבטחה שאפשר לסמוך עליה בשביל בטיחות:
החליפו את הסדר של שתי הבדיקות האלה והקריאה עם NULL תבצע dereference למצביע null ותקרוס. קודם שמירה, אחר כך שימוש: הסדר בשרשרת && הוא חלק מהלוגיקה.
האופרטור המותנה
האופרטור היחיד ב-C עם שלושה אופרנדים בוחר בין שני ערכים:
condition ? value_if_true : value_if_false. זה ביטוי, ולכן הוא מתאים במקומות שבהם פקודת if לא יכולה להופיע: בתוך ארגומנט של printf, באתחול. שמרו אותו קצר; ביטויים מותנים מקוננים הופכים לבלתי קריאים מהר.
אופרטורים על ביטים, בקצרה
אלה עובדים על הביטים הבודדים של מספר שלם:
משתמשים בהם לדגלים, למסכות ולרגיסטרים של חומרה. שתי אזהרות: & ו-| הם לא && ו-|| (הם לא מבצעים short-circuit והם פועלים ביט אחרי ביט), והזזה של ערך עם סימן או הזזה ביותר מהרוחב של הטיפוס היא לא מוגדרת. השתמשו בטיפוסים בלי סימן לעבודה עם ביטים.
סדר קדימויות
כשלביטוי אין סוגריים, הטבלה הזו קובעת מה נקשר למה. הקדימות הגבוהה ביותר ראשונה:
| רמה | אופרטורים | אסוציאטיביות |
|---|---|---|
| 1 | () [] -> . x++ x-- | מההתחלה לסוף |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (type) | מהסוף להתחלה |
| 3 | * / % | מההתחלה לסוף |
| 4 | + - | מההתחלה לסוף |
| 5 | << >> | מההתחלה לסוף |
| 6 | < <= > >= | מההתחלה לסוף |
| 7 | == != | מההתחלה לסוף |
| 8 | & | מההתחלה לסוף |
| 9 | ^ | מההתחלה לסוף |
| 10 | OR על ביטים | מההתחלה לסוף |
| 11 | && | מההתחלה לסוף |
| 12 | OR לוגי | מההתחלה לסוף |
| 13 | ?: | מהסוף להתחלה |
| 14 | = += -= *= /= %= והדומים להם | מהסוף להתחלה |
| 15 | , | מההתחלה לסוף |
ההשלכות המעשיות:
a + b * c /* פירושו a + (b * c) */
a < b == c < d /* פירושו (a < b) == (c < d): כמעט אף פעם לא מה שהתכוונתם */
x & 1 == 0 /* פירושו x & (1 == 0), כלומר x & 0: באג קלאסי */
*p++ /* פירושו *(p++): dereference ל-p, ואז קידום שלו */
את השורה השלישית שווה לזכור: == נקשר חזק יותר מ-&, ולכן בדיקות של מסכות ביטים צריכות סוגריים: (x & 1) == 0.
אף אחד לא זוכר את כל חמש עשרה הרמות, ואף אחד לא צריך. למדו ש-* גובר על +, שהשוואות גוברות על && שגובר על ||, ושכל השאר מקבל סוגריים:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
בשורה הזו יש סוגריים מיותרים, והיא טובה יותר בזכותם.
שאלות נפוצות
מה עושה % ב-C?
% הוא אופרטור המודולו: הוא נותן את השארית של חילוק שלמים. 17 % 5 הוא 2. הוא עובד רק על מספרים שלמים: שימוש בו על float או double הוא שגיאת קומפילציה, ו-fmod() מ-math.h הוא המקביל לנקודה צפה.
למה 5 / 2 נותן 2 ב-C?
כי שני האופרנדים הם מספרים שלמים, ולכן C מבצעת חילוק שלמים ומשליכה את השבר. הפכו צד אחד לערך נקודה צפה כדי לקבל 2.5: 5 / 2.0, או המירו אופרנד אחד עם (double)a / b.
מה ההבדל בין i++ ל-++i?
שניהם מוסיפים אחד ל-i. ++i (prefix) מגדיל קודם ומחזיר את הערך החדש; i++ (postfix) מחזיר את הערך הישן ומגדיל אחר כך. כפקודה עצמאית הם זהים: ההבדל חשוב רק כשמשתמשים בתוצאה, כמו ב-int b = a++; לעומת int b = ++a;.
מה זה סדר קדימויות של אופרטורים ב-C?
הסדר שבו אופרטורים נקשרים כשלביטוי אין סוגריים. *, / ו-% נקשרים חזק יותר מ-+ ו--, שנקשרים חזק יותר מהשוואות, שנקשרות חזק יותר מ-&&, אחר כך ||, ואחר כך השמה. כשיש ספק, הוסיפו סוגריים: הם לא עולים כלום ומבטלים את השאלה.