Operatory to czasowniki języka C: biorą wartości i tworzą nowe. Jest ich około 40, ale kilkanaście obejmuje większość kodu, a dwa z nich mają pułapki na tyle ostre, że zasługują na osobne sekcje.
Operatory arytmetyczne
(%% w łańcuchu formatu wypisuje dosłowny znak procentu; samo % rozpoczęłoby specyfikator formatu.)
Pięć dwuargumentowych operatorów arytmetycznych to + - * / %, a do tego jednoargumentowy - do negacji i +, który w praktyce nic nie robi.
Pułapka dzielenia całkowitego
a / b dla dwóch liczb całkowitych wykonuje dzielenie całkowite i wyrzuca resztę. Nie ma zaokrąglania: wynik jest obcinany w stronę zera.
Ten ostatni przypadek to błąd, który dożywa produkcji. Przypisanie do double nie pomaga: dzielenie total / count jest już zakończone, i to jako int, zanim nastąpi przypisanie. Rozwiązaniem jest uczynienie jednego operandu zmiennoprzecinkowym wewnątrz wyrażenia, zwykle przez rzutowanie.
Dwie pokrewne zasady: dzielenie liczb całkowitych przez zero to niezdefiniowane zachowanie i zwykle kończy się awarią programu, a INT_MIN / -1 powoduje przepełnienie.
Operator modulo
% daje resztę i działa tylko na liczbach całkowitych:
Sprawdzanie parzystości przez x % 2 == 0 i zawijanie indeksu wokół tablicy przez % n to dwa codzienne zastosowania.
Dla liczb ujemnych % w C przyjmuje znak dzielnej:
Dlatego x % 2 == 1 to błędny test nieparzystości, gdy x może być ujemne: -7 % 2 to -1. Używaj zamiast tego x % 2 != 0.
Dla reszt zmiennoprzecinkowych % to błąd kompilacji; użyj fmod() z math.h.
Przypisanie i przypisanie złożone
= zapisuje wartość. Formy złożone łączą operację z zapisem:
x += 5 oznacza x = x + 5, ale oblicza x tylko raz, co ma znaczenie, gdy celem jest coś w rodzaju arr[compute_index()].
Przypisanie samo jest wyrażeniem, które daje przypisaną wartość, i dlatego działa a = b = c = 0 (przypisuje od prawej do lewej), a pomyłka = zamiast == kompiluje się po cichu.
Inkrementacja i dekrementacja
++ dodaje jeden, -- odejmuje jeden. Każdy ma formę przedrostkową i przyrostkową:
Jako osobne instrukcje obie formy są wymienne, a i++ to zwyczajowy wybór w nagłówku for bez głębszego powodu niż tradycja.
Nie wolno natomiast używać zmiennej więcej niż raz w wyrażeniu, w którym jest też modyfikowana:
int i = 5;
int x = i++ + i++; /* niezdefiniowane zachowanie */
arr[i] = i++; /* niezdefiniowane zachowanie */
printf("%d %d", i++, i);/* niezdefiniowane zachowanie */
C nie definiuje kolejności obliczania tych podwyrażeń, więc wynik nie jest tylko nieokreślony: niezdefiniowane jest zachowanie całego programu. Podziel instrukcję na dwie, a niejednoznaczność zniknie.
Operatory porównania
Jest ich sześć i wszystkie dają int: 1 dla prawdy i 0 dla fałszu.
To, że wynik jest zwykłym int, a nie osobnym typem logicznym, to cecha definiująca C; co z tego wynika, łącznie z błędem = zamiast ==, opisuje strona o wartościach logicznych w C.
Dwa błędy porównań są typowe dla C. Łączenie w łańcuch nie działa tak, jak sugeruje matematyka: if (1 < x < 10) jest zawsze prawdą, bo 1 < x daje 0 albo 1, a to jest potem porównywane z 10. Pisz if (x > 1 && x < 10). A porównywanie łańcuchów przez == porównuje wskaźniki, a nie tekst: używaj strcmp z string.h.
Operatory logiczne
&& to AND, || to OR, ! to NOT. Wszystkie trzy traktują każdą niezerową wartość jako prawdę.
Działają skrótowo: && zatrzymuje się, gdy tylko jeden operand jest fałszem, a ||, gdy tylko jeden jest prawdą. Reszta nigdy nie jest obliczana i to nie tylko optymalizacja: to gwarancja, na której można polegać dla bezpieczeństwa:
Zamień kolejność tych dwóch testów, a wywołanie z NULL wyłuska wskaźnik pusty i się wysypie. Najpierw zabezpieczenie, potem użycie: kolejność w łańcuchu && jest częścią logiki.
Operator warunkowy
Jedyny trójargumentowy operator C wybiera między dwiema wartościami:
condition ? value_if_true : value_if_false. To wyrażenie, więc pasuje tam, gdzie instrukcja if nie może stać: w argumencie printf albo w inicjalizatorze. Niech będzie krótkie; zagnieżdżone warunki szybko stają się nieczytelne.
Operatory bitowe w skrócie
Działają na pojedynczych bitach liczby całkowitej:
Służą do flag, masek i rejestrów sprzętowych. Dwie przestrogi: & i | to nie && i || (nie działają skrótowo i operują bit po bicie), a przesunięcie wartości ze znakiem albo przesunięcie o więcej niż szerokość typu jest niezdefiniowane. Do pracy na bitach używaj typów bez znaku.
Priorytety
Gdy wyrażenie nie ma nawiasów, o tym, co z czym się wiąże, decyduje ta tabela. Najwyższy priorytet jest pierwszy:
| Poziom | Operatory | Łączność |
|---|---|---|
| 1 | () [] -> . x++ x-- | od lewej do prawej |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (type) | od prawej do lewej |
| 3 | * / % | od lewej do prawej |
| 4 | + - | od lewej do prawej |
| 5 | << >> | od lewej do prawej |
| 6 | < <= > >= | od lewej do prawej |
| 7 | == != | od lewej do prawej |
| 8 | & | od lewej do prawej |
| 9 | ^ | od lewej do prawej |
| 10 | bitowe OR | od lewej do prawej |
| 11 | && | od lewej do prawej |
| 12 | logiczne OR | od lewej do prawej |
| 13 | ?: | od prawej do lewej |
| 14 | = += -= *= /= %= i pokrewne | od prawej do lewej |
| 15 | , | od lewej do prawej |
Praktyczne konsekwencje:
a + b * c /* oznacza a + (b * c) */
a < b == c < d /* oznacza (a < b) == (c < d): rzadko o to chodzi */
x & 1 == 0 /* oznacza x & (1 == 0), czyli x & 0: klasyczny błąd */
*p++ /* oznacza *(p++): wyłuskaj p, potem je przesuń */
Trzecią linię warto zapamiętać: == wiąże mocniej niż &, więc testy masek bitowych potrzebują nawiasów: (x & 1) == 0.
Nikt nie pamięta wszystkich piętnastu poziomów i nikt nie musi. Zapamiętaj, że * wygrywa z +, że porównania wygrywają z &&, a ten z ||, a wszystko inne dostaje nawiasy:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
Ta linia ma nadmiarowe nawiasy i jest dzięki nim lepsza.
Najczęściej zadawane pytania
Co robi % w C?
% to operator modulo: daje resztę z dzielenia całkowitego. 17 % 5 to 2. Działa tylko na liczbach całkowitych: użycie go na float albo double to błąd kompilacji, a zmiennoprzecinkowym odpowiednikiem jest fmod() z math.h.
Dlaczego 5 / 2 daje w C 2?
Bo oba operandy są liczbami całkowitymi, więc C wykonuje dzielenie całkowite i odrzuca część ułamkową. Żeby dostać 2.5, niech jedna strona będzie liczbą zmiennoprzecinkową: 5 / 2.0, albo zrzutuj jeden operand przez (double)a / b.
Czym różni się i++ od ++i?
Oba dodają jeden do i. ++i (przedrostkowy) najpierw zwiększa i daje nową wartość; i++ (przyrostkowy) daje starą wartość i zwiększa potem. Jako osobna instrukcja są identyczne; różnica ma znaczenie tylko wtedy, gdy wynik jest używany, jak w int b = a++; kontra int b = ++a;.
Czym jest priorytet operatorów w C?
Kolejnością, w jakiej operatory wiążą, gdy wyrażenie nie ma nawiasów. *, / i % wiążą mocniej niż + i -, te mocniej niż porównania, porównania mocniej niż &&, potem ||, a na końcu przypisanie. W razie wątpliwości dodaj nawiasy: nic nie kosztują i usuwają pytanie.