Menu

Boolean ב-C: stdbool.h, _Bool ומה נחשב אמת

ל-C לא היה טיפוס בוליאני עד C99. אפס הוא שקר, כל השאר אמת, השוואות מחזירות int, ו-stdbool.h מוסיף מעל זה את bool, true ו-false. ובנוסף, הבאג של = מול == שהעיצוב הזה מאפשר.

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

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

אפס הוא שקר, כל השאר אמת

אין ערך מיוחד של אמת או שקר בכללי התנאים של C. if, while, for, &&, || ו-! שואלים כולם שאלה אחת בדיוק: האם הערך הזה הוא אפס?

מספרים שליליים הם אמת. שברים הם אמת. מצביע שאינו null הוא אמת. רק אפס, בכל טיפוס, הוא שקר.

זו הסיבה שקוד C מלא בניבים שנראים תמציתיים עד שמכירים את הכלל:

if (count)            /* if count is not zero */
if (!count)           /* if count is zero */
if (p)                /* if p is not NULL */
if (!strcmp(a, b))    /* if the strings are equal - strcmp returns 0 for a match */

האחרון מפיל אנשים. strcmp מחזירה 0 כשהמחרוזות זהות, כך ש"שוות" נקרא כמו "לא" בתוך if. לכתוב if (strcmp(a, b) == 0) ברור יותר ועושה בדיוק אותו דבר.

השוואות מחזירות int

השוואה ב-C אינה סוג מיוחד של ביטוי. היא מחושבת ל-int רגיל עם הערך 1 או 0, ואפשר להדפיס אותו, לשמור אותו או לעשות איתו חשבון:

האופרטור ! עובד באותו אופן: הוא מחזיר 1 אם האופרנד שלו הוא אפס ו-0 אחרת. לכן !!x הוא ניב קלאסי שמנרמל כל ערך לבדיוק 0 או 1.

bool, true ו-false (C99)

C99 הוסיפה טיפוס בוליאני אמיתי בשם _Bool, וקובץ כותרת שנותן לו שם קריא:

<stdbool.h> זעיר: הוא מגדיר את bool כ-_Bool, את true כ-1 ואת false כ-0. זה כל הקובץ. אבל השימוש בו הופך את הכוונה לגלויה באופן ש-int flag = 1; אף פעם לא עושה.

ל-_Bool יש התנהגות ייחודית אחת באמת: הוא מאחסן רק 0 או 1. השמה של כל ערך שונה מאפס מומרת לבדיוק 1:

עדיין אין %b ב-printf עבור ערכים בוליאניים: הדפיסו אותם עם %d, או המירו אותם לטקסט בעצמכם:

ב-C23 זה נעשה פשוט עוד יותר: bool, true ו-false הפכו למילות מפתח אמיתיות, כך שה-include כבר לא נדרש. רוב הקוד היום עדיין מכוון ל-C17, אז השאירו את #include <stdbool.h>.

החזרת ערך בוליאני מפונקציה

כאן bool מצדיק את מקומו בצורה הברורה ביותר:

טיפוס החזרה bool אומר למי שקורא לפונקציה מה משמעות הערך. טיפוס החזרה int מפונקציה בשם check_file יכול להיות ערך בוליאני, ספירה או קוד שגיאה, ולפי המוסכמות של ספריות C הוא הרבה פעמים האפשרות השלישית, שבה 0 פירושו הצלחה. ציון הטיפוס מבטל את הניחוש.

הבאג של = מול ==

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

שני דברים השתבשו בבת אחת. התנאי השים 5 ל-x והרס את הערך שלו, ואז בדק את 5, שהוא שונה מאפס, ולכן הענף רץ. לתוכנית אין שגיאה ואין אזהרה כברירת מחדל.

גרוע מזה, הגרסה עם 0 לעולם לא מריצה את הענף שלה:

if (found = 0) { /* never taken - and found is now 0 */ }

שלוש הגנות, לפי סדר התועלת:

קמפלו עם אזהרות. -Wall תופס את זה:

warning: suggest parentheses around assignment used as truth value

להגיע לאפס אזהרות הוא התיקון האמיתי כאן, והוא עולה דגל אחד.

כתבו את הקבוע ראשון. "תנאי יודה" הופך את שגיאת ההקלדה לשגיאת קומפילציה:

if (5 == x)   /* correct */
if (5 = x)    /* error: not an lvalue - the compiler stops you */

יש צוותים שאוהבים את זה ויש כאלה שמוצאים את זה לא קריא; שתי העמדות סבירות, אבל זה עובד.

היו מכוונים כשזה מה שאתם מתכוונים. השמה בתוך תנאי שימושית באמת לקריאת קלט:

int c;
while ((c = getchar()) != EOF) { ... }

הסוגריים הנוספים סביב c = getchar() נדרשים בגלל סדר הקדימויות, והם גם מאותתים שההשמה מכוונת. זה הניב: אם ההשמה בתוך התנאי שלכם לא נראית ככה, כנראה מדובר בשגיאת הקלדה.

טעויות נפוצות עם ערכים בוליאניים

השוואה ל-true. if (flag == true) עובד עם stdbool.h, אבל נשבר ברגע ש-flag הוא int שמחזיק 42, כי 42 == 1 הוא שקר. כתבו if (flag).

השוואות משורשרות. if (0 < x < 10) תמיד אמת: 0 < x מחזיר 0 או 1, ושניהם קטנים מ-10. כתבו if (x > 0 && x < 10).

אופרטור ביטים במקום אופרטור לוגי. & ו-| אינם && ו-||. הם לא מקצרים את החישוב, והם משלבים ביטים:

שני האופרנדים "אמת", ובכל זאת a & b הוא 0. שימוש ב-& במקום שבו התכוונתם ל-&& יוצר תנאי ששגוי רק עבור ערכים מסוימים, וזה הסוג הקשה ביותר של באג למצוא.

השוואת מספרים עשרוניים לשוויון. if (0.1 + 0.2 == 0.3) הוא שקר. השוו מול סבולת במקום זאת; ראו טיפוסי נתונים כדי להבין למה.

שאלות נפוצות

האם יש ב-C טיפוס בוליאני?

מאז C99, כן: _Bool הוא טיפוס מובנה, והכללת <stdbool.h> נותנת לכם את השמות הידידותיים יותר bool, true ו-false. לפני C99 לא היה טיפוס בוליאני בכלל: תוכניות השתמשו ב-int עם 0 לשקר ו-1 לאמת, וזה עדיין עובד ועדיין נפוץ.

מה נחשב אמת ב-C?

כל ערך שונה מאפס. 1, -5, 0.01 ומצביע שאינו null הם כולם אמת בתנאי; רק 0, 0.0 ומצביע null הם שקר. אין בדיקה בוליאנית נפרדת: if (x) פשוט בודק אם x שונה מאפס.

איך משתמשים ב-bool ב-C?

הוסיפו #include <stdbool.h> ואז bool ready = true; עובד כמו שהייתם מצפים. מתחת למכסה המנוע bool הוא _Bool, שמאחסן רק 0 או 1: השמה של כל ערך שונה מאפס שומרת בדיוק 1.

מה ההבדל בין = ל-== ב-C?

= משים, == משווה. if (x = 5) משים 5 ל-x ואז בודק את 5, שהוא שונה מאפס, כך שהענף תמיד רץ. if (x == 5) בודק שוויון. הראשון מתקמפל בלי שגיאה, ולכן קיימים גם -Wall וגם ההרגל לכתוב if (5 == x).

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

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

להתחיל