Do czego służą operatory
Operator to symbol, który wykonuje działanie na jednej lub kilku wartościach, zwanych operandami. Jednego już używasz: =, który wiąże wartość ze zmienną. C++ ma bogaty zestaw operatorów do matematyki, porównań, logiki i operacji na bitach, a także kilka ostrych krawędzi, o które potykają się początkujący.
Na poprzedniej stronie widać było, jak const blokuje wartość. Operatory służą do obliczania wartości, które przechowujesz, niezależnie od tego, czy są stałe. Przejdźmy przez rodziny operatorów, których będziesz używać na co dzień.
Operatory arytmetyczne
Pięć operatorów arytmetycznych to +, -, *, / i % (modulo, czyli reszta z dzielenia):
Wielka pułapka kryje się w ilorazie. 17 / 5 wypisuje 3, a nie 3.4. Gdy oba operandy są liczbami całkowitymi, / wykonuje dzielenie całkowite i wyrzuca część ułamkową: obcina w stronę zera, a nie zaokrągla. Jeśli chcesz prawdziwy ułamek, przynajmniej jeden operand musi być zmiennoprzecinkowy:
Operator % działa tylko na liczbach całkowitych. Użycie % na double to błąd kompilacji: do reszty z dzielenia liczb zmiennoprzecinkowych użyj std::fmod z <cmath>.
Przypisanie i operatory złożone
Pojedyncze = przypisuje, a nie porównuje. C++ daje formy złożone, które łączą działanie z przypisaniem, żeby nie powtarzać nazwy zmiennej:
Klasyczny, bolesny błąd to napisanie = zamiast == w warunku. if (x = 0) przypisuje 0 do x, a potem sprawdza wynik (który jest fałszywy), zamiast porównywać. Nowoczesne kompilatory ostrzegają o tym, gdy włączysz ostrzeżenia: trzymaj włączone -Wall i traktuj to ostrzeżenie poważnie.
Operatory porównania
Operatory porównania zadają pytanie typu tak/nie i dają bool (true albo false):
Domyślnie cout wypisuje bool jako 1 albo 0. Wyślij raz do strumienia boolalpha, a do końca tego strumienia będą wypisywane słowa true/false.
Subtelna pułapka: nie łącz porównań w łańcuch, np. 1 < x < 10. To się parsuje jako (1 < x) < 10: pierwsze porównanie daje bool (0 albo 1), który jest potem porównywany z 10, więc całość jest prawie zawsze true. Zamiast tego napisz 1 < x && x < 10.
Operatory logiczne i obliczanie na skróty
&& (i), || (lub) oraz ! (nie) łączą wyrażenia logiczne. Dwa pierwsze działają na skróty: obliczanie kończy się, gdy tylko wynik jest znany.
Ani check("A"), ani check("B") nigdy się nie wykonuje: to jest obliczanie na skróty. To nie tylko optymalizacja, ale i narzędzie. Możesz bezpiecznie napisać if (ptr != nullptr && ptr->ready), bo do części ptr->ready dochodzi się tylko wtedy, gdy ptr nie jest null, co chroni przed dereferencją wiszącego wskaźnika.
Inkrementacja i dekrementacja
++ dodaje jeden; -- odejmuje jeden. Każdy ma formę przedrostkową i przyrostkową, a różnica ma znaczenie, gdy używasz wyniku:
Gdy chodzi ci tylko o efekt uboczny (na przykład w pętli for), wybieraj ++i. Dla zwykłego int to bez różnicy, ale dla cięższych typów, takich jak iteratory, postinkrementacja musi najpierw skopiować starą wartość, co jest zbędną pracą.
Jeszcze jedno ostrzeżenie: nie modyfikuj tej samej zmiennej dwa razy w jednym wyrażeniu, jak w i = i++ + 1; czy arr[i] = i++;. Kolejność tych zmian nie jest określona, a wynik to niezdefiniowane zachowanie. Każdą zmienną modyfikuj najwyżej raz na instrukcję.
Operatory bitowe i priorytet
Do pracy niskopoziomowej są operatory bitowe: & (i), | (lub), ^ (xor), ~ (nie) oraz przesunięcia << i >>.
Uwaga: << i >> to także operatory strumieni przy cout. W linii z cout przesunięcie bitowe zwykle wymaga nawiasów, inaczej kompilator odczyta je jako wstawianie do strumienia.
Na koniec priorytet decyduje, co wiąże się najpierw, gdy mieszasz operatory. * i / wiążą mocniej niż + i -, tak jak w matematyce, więc 2 + 3 * 4 to 14. Porównania wiążą słabiej niż arytmetyka, a logiczne &&/|| jeszcze słabiej. W razie wątpliwości nie ucz się całej tabeli na pamięć, tylko dodaj nawiasy. (a + b) * c jest czytelniejsze niż liczenie na to, że czytelnik pamięta reguły.
Dalej: rzutowanie typów
Wyżej widać było, że (double)a / b wymusiło dzielenie zmiennoprzecinkowe zamiast całkowitego. To rzutowanie, czyli celowa konwersja wartości z jednego typu na inny. Na następnej stronie omówimy narzędzia konwersji w C++, od niejawnych promocji po static_cast, oraz to, kiedy każde z nich jest bezpieczne.
Najczęściej zadawane pytania
Jakie są operatory w C++?
C++ dzieli operatory na arytmetyczne (+ - * / %), porównania (== != < > <= >=), logiczne (&& || !), przypisania (= += -= ...), inkrementacji i dekrementacji (++ --) oraz bitowe (& | ^ ~ << >>). Jest też operator trójargumentowy ?: i kilka innych, takich jak sizeof.
Dlaczego 5 / 2 daje 2 w C++?
Bo oba operandy są typu int, więc / wykonuje dzielenie całkowite, a część ułamkowa jest odrzucana, a nie zaokrąglana. Aby dostać 2.5, przynajmniej jeden operand musi być zmiennoprzecinkowy: 5.0 / 2 albo 5 / 2.0.
Czym się różni ++i od i++ w C++?
Oba dodają 1 do i. ++i (preinkrementacja) najpierw zwiększa i daje nową wartość; i++ (postinkrementacja) daje starą wartość, a potem zwiększa. Gdy zależy ci tylko na efekcie ubocznym, wybieraj ++i.