Menu

Typ bool w C: stdbool.h, _Bool i prawdziwość wartości

C nie miało typu logicznego aż do C99. Zero to fałsz, wszystko inne to prawda, porównania zwracają int, a stdbool.h dokłada bool, true i false. Do tego klasyczny błąd = zamiast ==, na który ten projekt pozwala.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Przez pierwsze 27 lat C nie miało typu logicznego. To nie przeoczenie, tylko decyzja projektowa z konsekwencjami, na które trafiasz pierwszego dnia, oraz jeden słynny błąd, który z niej bezpośrednio wynika.

Zero to fałsz, wszystko inne to prawda

W regułach warunków w C nie ma specjalnej wartości prawda/fałsz. if, while, for, &&, || i ! zadają dokładnie jedno pytanie: czy ta wartość jest zerem?

Liczby ujemne są prawdziwe. Ułamki są prawdziwe. Niepusty wskaźnik jest prawdziwy. Fałszywe jest tylko zero, w dowolnym typie.

Dlatego kod w C jest pełen idiomów, które wyglądają zwięźle, dopóki nie znasz tej reguły:

if (count)            /* jeśli count nie jest zerem */
if (!count)           /* jeśli count jest zerem */
if (p)                /* jeśli p nie jest NULL */
if (!strcmp(a, b))    /* jeśli stringi są równe: strcmp zwraca 0 przy zgodności */

Ten ostatni łapie wiele osób. strcmp zwraca 0, gdy stringi są równe, więc w if "równe" czyta się jak "nie". Zapis if (strcmp(a, b) == 0) jest czytelniejszy i robi dokładnie to samo.

Porównania zwracają int

Porównanie w C nie jest specjalnym rodzajem wyrażenia. Daje zwykły int o wartości 1 albo 0, który możesz wypisać, zapisać albo użyć w obliczeniach:

Operator ! działa tak samo: zwraca 1, jeśli jego argument jest zerem, a 0 w przeciwnym razie. Dlatego !!x to klasyczny idiom, który sprowadza dowolną wartość do dokładnie 0 albo 1.

bool, true i false (C99)

C99 dodało prawdziwy typ logiczny o nazwie _Bool oraz nagłówek, który nadaje mu czytelną nazwę:

<stdbool.h> jest malutki: definiuje bool jako _Bool, true jako 1 i false jako 0. To cały nagłówek. Ale jego użycie pokazuje intencję w sposób, w jaki int flag = 1; nigdy tego nie zrobi.

_Bool ma jedno naprawdę odrębne zachowanie: przechowuje tylko 0 albo 1. Przypisanie dowolnej niezerowej wartości zamienia ją na dokładnie 1:

W printf nadal nie ma %b dla wartości logicznych: wypisuj je przez %d albo samodzielnie zamieniaj je na tekst:

W C23 jest jeszcze prościej: bool, true i false stały się prawdziwymi słowami kluczowymi, więc dołączanie nagłówka nie jest już potrzebne. Większość dzisiejszego kodu wciąż celuje w C17, więc zostaw #include <stdbool.h>.

Zwracanie wartości logicznej z funkcji

Tu bool najwyraźniej zasługuje na swoje miejsce:

Typ zwracany bool mówi wywołującemu, co oznacza wartość. Typ int zwracany przez funkcję o nazwie check_file może być wartością logiczną, licznikiem albo kodem błędu, a w konwencjach biblioteki C często jest tym trzecim, gdzie 0 oznacza sukces. Nazwanie typu usuwa zgadywanie.

Błąd = zamiast ==

Ponieważ przypisanie jest wyrażeniem, które zwraca przypisaną wartość, a każda wartość niezerowa jest prawdą, to się kompiluje:

Dwie rzeczy poszły źle jednocześnie. Warunek przypisał 5 do x, niszcząc jego wartość, a potem sprawdził 5, które jest niezerowe, więc gałąź się wykonała. Domyślnie program nie ma ani błędu, ani ostrzeżenia.

Co gorsza, wersja z 0 nigdy nie wykonuje swojej gałęzi:

if (found = 0) { /* nigdy nie wykonana, a found ma teraz wartość 0 */ }

Trzy sposoby obrony, w kolejności przydatności:

Kompiluj z ostrzeżeniami. -Wall to wyłapie:

warning: suggest parentheses around assignment used as truth value

Dojście do zera ostrzeżeń to tutaj prawdziwe rozwiązanie, a kosztuje jedną flagę.

Pisz stałą jako pierwszą. "Warunek Yody" zamienia literówkę w błąd kompilacji:

if (5 == x)   /* poprawnie */
if (5 = x)    /* błąd: to nie lvalue, kompilator cię zatrzyma */

Niektóre zespoły to uwielbiają, a inne uważają za nieczytelne; obie postawy da się obronić, ale to działa.

Rób to świadomie, gdy naprawdę tego chcesz. Przypisanie w warunku jest naprawdę przydatne przy czytaniu wejścia:

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

Dodatkowe nawiasy wokół c = getchar() są wymagane ze względu na priorytet operatorów, a przy okazji sygnalizują, że przypisanie jest zamierzone. To jest ten idiom: jeśli twoje przypisanie w warunku tak nie wygląda, to prawdopodobnie literówka.

Częste błędy z wartościami logicznymi

Porównywanie z true. if (flag == true) działa ze stdbool.h, ale psuje się w chwili, gdy flag jest int o wartości 42, bo 42 == 1 jest fałszem. Pisz if (flag).

Łańcuchowe porównania. if (0 < x < 10) jest zawsze prawdziwe: 0 < x daje 0 albo 1, a oba są mniejsze od 10. Pisz if (x > 0 && x < 10).

Operatory bitowe zamiast logicznych. & i | to nie && i ||. Nie działają na zasadzie krótkiego spięcia i łączą bity:

Oba argumenty są "prawdziwe", a mimo to a & b wynosi 0. Użycie & tam, gdzie chodziło o &&, daje warunek, który jest błędny tylko dla niektórych wartości, a to najtrudniejszy do znalezienia rodzaj błędu.

Porównywanie liczb zmiennoprzecinkowych na równość. if (0.1 + 0.2 == 0.3) jest fałszem. Zamiast tego porównuj z tolerancją; dlaczego, wyjaśnia artykuł o typach danych.

Najczęściej zadawane pytania

Czy C ma typ logiczny?

Od C99 tak: _Bool jest typem wbudowanym, a dołączenie <stdbool.h> daje przyjaźniejsze nazwy bool, true i false. Przed C99 nie było żadnego typu logicznego: programy używały int z 0 jako fałszem i 1 jako prawdą, co nadal działa i wciąż jest popularne.

Co jest prawdą w C?

Każda wartość różna od zera. 1, -5, 0.01 i wskaźnik różny od NULL są w warunku prawdziwe; fałszywe są tylko 0, 0.0 i wskaźnik pusty. Nie ma osobnego sprawdzenia logicznego: if (x) po prostu sprawdza, czy x jest różne od zera.

Jak używać bool w C?

Dodaj #include <stdbool.h> i wtedy bool ready = true; działa tak, jak się spodziewasz. Pod spodem bool to _Bool, który przechowuje tylko 0 albo 1: przypisanie dowolnej niezerowej wartości zapisuje dokładnie 1.

Jaka jest różnica między = a == w C?

= przypisuje, == porównuje. if (x = 5) przypisuje 5 do x, a potem sprawdza 5, które jest niezerowe, więc gałąź wykonuje się zawsze. if (x == 5) sprawdza równość. Pierwsza wersja kompiluje się bez błędu, dlatego istnieje zarówno -Wall, jak i nawyk pisania if (5 == x).

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ