Menu

Operatory w C: arytmetyczne, logiczne i priorytety

Wszystkie operatory C, które warto znać: arytmetyczne, przypisania, porównania, logiczne i inkrementacji, a do tego pułapka dzielenia całkowitego, zachowanie modulo dla liczb ujemnych i tabela priorytetów, której można ufać.

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

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:

PoziomOperatoryŁą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
10bitowe ORod lewej do prawej
11&&od lewej do prawej
12logiczne ORod lewej do prawej
13?:od prawej do lewej
14= += -= *= /= %= i pokrewneod 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ