Operatory
W ogólnym przypadku, operatory (zarówno arytmetyczne jak i logiczne) należy traktować jako specyficzny rodzaj funkcji, pobierającej jeden lub więcej argumentów i zwracającej nowo obliczoną wartość, w postaci wartości tymczasowej (w ang. temporary) lub zmieniającej wartość zmiennej wejściowej (przypisanie proste lub złożone - w ang. simple/complex assignment).

1.1 Operatory matematyczne

Do najczęściej stosowanych operatorów należą bez wątpienia operatory matematyczne, które zostały zebrane w tabeli numer 1. Wystarczają one do pełnego zapisu wszystkich możliwych równań matematycznych, nie mniej jednak stosowanie ich w pełnej formie jest nieco uciążliwe (patrz przykład 1). Celem uproszczenia składni co bardziej skomplikowanych równań oraz eliminacji irytującej konieczności wprowadzania pełnej linii kodu celem wykonania prostego przeliczenia wartości zmiennej (np. zwiększenia o 10), wprowadzono operatory przypisania złożonego, zebrane w tabeli numer 2. Nasz prosty przykład działań matematycznych został więc uproszczony do postaci pokazanej na przykładzie numer 2.


Tabela 1: Podstawowe operatory matematyczne


Operator Znaczenie
argument1 + argument2 dodaj argument1 i argument2
argument1 - argument2 odejmij argument2 od argument1
argument1 / argument2 podziel argument1 przez argument2
argument1 * argument2 pomnóż argument1 przez argument2
argument1 % argument2 reszta z dzielenia argument1 przez argument2 (modulo)
zmienna = argument przypisanie wartości argument do zmienna (równa się)



Przykład 1: Prosty przykład kodu z wykorzystaniem przypisań prostych i podstawowych operatorów matematycznych

int main(int argc, char* argv[])
{
int a,b,c;
a = 10;
b = 15;
c = (a + 15) / 10.0;
c = c + b / 5;
b = b + a + c;
a = a + 1;
b = b / 10;
(...)
return 0;
}


Tabela 2: Złożone operatory przypisania

Operator Znaczenie
zmienna += argument dodaj argument do zmienna i przypisz wartość wyjściową do zmienna
zmienna -= argument odejmij argument od zmienna i przypisz wartość wyjściową do zmienna
zmienna /= argument podziel zmienna przez argument i przypisz wartość wyjściową do zmienna
zmienna *= argument dodaj argument do zmienna i przypisz wartość wyjściową do zmienna
zmienna *= argument dodaj argument do zmienna i przypisz wartość wyjściową do zmienna


Przykład 2: Prosty przykład kodu z wykorzystaniem przypisań prostych i podstawowych operatorów matematycznych

int main(int argc, char* argv[])
{
int a,b,c;
c = ((a=10) + 15) / 10.0;
c += ((b=15) / 5);
b += (a + c);
a += 1;
b /= 10;
(...)
return 0;
}


Okazuje się jednak iż istnieje możliwość wprowadzenia dodatkowych operatorów matematycznych tzw. inkrementacji i dekrementacji (w ang. increment / decrement operator), najczęściej wykorzystywane przy zmianie wartości w pętlach zliczających. Idea wprowadzenia tego typu dodatkowych operatorów nie jest obecnie jasna, prawdopodobnie z założenia miały one ograniczyć długość wprowadzanych linii poleceń, przez co ułatwić analizę skomplikowanych fragmentów kodu jako że nie trzeba czytać bardzo długich linii naszpikowanych operatorami przypisania. Niestety, odbywa się to kosztem czytelności kodu dla osób postronnych a także osób migrujących z innych języków programowania, gdzie takowych operatorów nie ma.
Istnieją dwie wersje omawianych operatorów inkrementacji i dekrementacji, a mianowicie wersja przedrostkowa (prefiksowa) oraz przyrostkowa (postfiksowa) (w ang. prefix / postfix). Różnica między nimi jest bardzo często mylona i stanowi temat legend na temat skomplikowanej składni i nieczytelności kodu języka C/C++.
Operator inkrementacji ma postać podwójnego znaku dodawania [++] i oznacza "zwiększ wartość zmiennej o 1". Analogicznie, operator dekrementacji ma postać podwójnego znaku odejmowania [--]i oznacza "zmniejsz wartość zmiennej o 1". O ile znaczenie jest dość proste do zapamiętania, różnica pomiędzy formą prefiksową a postfiksową wymaga zrozumienia kolejności wykonywania poszczególnych operacji przez program (patrz tabela numer 3).

Tabela 3: Operatory inkrementacji oraz dekrementacji


Operator Znaczenie
zmienna++ wykonaj operację a potem zwiększ wartość zmienna o 1
++zmienna zwiększ wartość zmienna o 1 a potem wykonaj operację
zmienna-- wykonaj operację a potem zmniejsz wartość zmienna o 1
--zmienna zmniejsz wartość zmienna o 1 a potem wykonaj operację
zmienna *= argument dodaj argument do zmienna i przypisz wartość wyjściową do zmienna


W przypadku prostego przykładu (numer 3) różnica pomiędzy wersją prefiksową a postfiksową jest niezauważalna. W obu przypadkach na ekran zostanie wyprowadzona wartość 1, jednak wystarczy wykonywać jednocześnie wyświetlanie wartości zmiennej oraz zwiększać jej wartość, aby zobaczyć różnicę (patrz przykład 4). W pierwszym przypadku na ekran zostanie wyprowadzone 0, w drugim zaś 1. Dlaczego się tak dzieje? Przyczyna leży w kolejności wykonywania operacji przez program - w pierwszym przypadku najpierw zostanie pobrana wartość zmiennej a, wyświetlona na ekranie konsoli a dopiero następnie zwiększona o 1. W drugim przypadku natomiast w pierwszej kolejności zostanie zwiększona wartość zmiennej, następnie pobrana jej nowa wartość i dopiero wyświetlona na ekran konsoli. Identyczna zależność kolejności wykonywania operacji obowiązuje także dla operator dekrementacji [--] – patrz przykład 5. W pierwszym przypadku na ekran zostanie wyprowadzona wartość 1, w drugim 0.

Przykład 3: Operator prefiksowy i postfiksową inkrementacji - różnica

#include <iostream>
int main(int argc, char* argv[])
{
int a = 0;
a++;
std::cout << a << std::endl;
a = 0;
++a;
std::cout << a << std::endl;
return 0;
}


Przykład 4: Operator prefiksowy i postfiksową inkrementacji - różnica

#include <iostream>
int main(int argc, char* argv[])
{
int a = 0;
std::cout << a++<< std::endl;
a = 0;
std::cout << ++a << std::endl;
return 0;
}


Przykład 5: Operator prefiksowy i postfiksową dekrementacji - różnica

#include <iostream>
int main(int argc, char* argv[])
{
int a = 1;
std::cout << a-- << std::endl;
a = 1;
std::cout << --a << std::endl;
return 0;
}


Na koniec przykład wszystkie operatory matematyczne oraz przypisania w akcji:

Przykład 6: Operatory matematyczne i przypisania w akcji

// Operatory matematyczne w akcji
#include <iostream>
// definicja makro wyświetlającego dane na ekran
#define PRINT(STR, VAR)
std::cout << STR " = " << VAR << std::endl
 
int main(int argc, char* argv[])
{
int i, j, k;
double u, v, w;
std::cout << "Podaj wartość całkowitą: ";
std::cin >> j;
std::cout << "i jeszcze jedna wartość całkowita: ";
std::cin >> k;
PRINT("j",j); PRINT("k",k);
i = j + k; PRINT("j + k",i);
i = j - k; PRINT("j - k",i);
i = k / j; PRINT("k / j",i);
i = k * j; PRINT("k * j",i);
i = k % j; PRINT("k % j",i);
// % przyjmuje jedynie liczby całkowite (int) jako operatory
j %= k; PRINT("j %= k", j);
std::cout << "Wprowadź wartość niecałkowitą: ";
std::cin >> v;
std::cout << " Wprowadź jeszcze jedną wartość niecałkowitą:";
std::cin >> w;
PRINT("v",v); PRINT("w",w);
u = v + w; PRINT("v + w", u);
u = v - w; PRINT("v - w", u);
u = v * w; PRINT("v * w", u);
u = v / w; PRINT("v / w", u);
// następujące operacje działają dla typu int, double, float i pochodnych,
PRINT("u", u); PRINT("v", v);
u += v; PRINT("u += v", u);
u -= v; PRINT("u -= v", u);
u *= v; PRINT("u *= v", u);
u /= v; PRINT("u /= v", u);
return 0;
}


1.2 Operatory logiczne

UWAGA: przykłady programów podawane w tym dziale nie będą kompilowały się poprawnie pod kompilatorami C - powodem jest brak typu wbudowanego bool w standardzie C.
Argumenty logiczne jak sama nazwa wskazuje pozwalają na przeprowadzanie porównań i operacji logicznych, opartych na arytmetyce Boole`a (link na Wikipedie...). Istniejące operatory logiczne zebrano w tabeli 4.

Tabela 4: Operatory logiczne w C++


Operator Znaczenie
argument1 && argument2 koniunkcja logiczna (argument1 AND argument2)
argument1 || argument2 iloczyn logiczny (argument1 OR argument2)
!argument1 zaprzeczenie (NOT argument1)
argument1 == argument2 porównanie, sprawdzenie równości argument1 i argument2
argument1 != argument2 porównanie, sprawdzenie nierówności argument1 i argument2
argument1 < argument2 porównanie, sprawdzenie czy argument1 jest mniejszy od argument2
argument1 > argument2 porównanie, sprawdzenie czy argument1 jest większy od argument2
argument1 <= argument2 porównanie, sprawdzenie czy argument1 jest mniejszy/równy argument2
argument1 >= argument2 porównanie, sprawdzenie czy argument1 jest większy/równy argument2


Działanie operatorów logicznych najprościej prześledzić na następującym przykładzie (patrz przykład numer 7). Jedna kwestia wymaga nieco wyjaśnienia. Zamiast oczekiwanego true/false na ekran wyprowadzana jest wartość 1 lub 0, w zależności czy warunek jest spełniony lub nie. Zaszłość ta jest spowodowana koniecznością zachowania kompatybilności ze standardem C, w którym nie było zdefiniowanego typu bool, tak więc utarło się stosowanie zmiennych typu int do przechowywania wartości logicznych, przy czym 1 odpowiadała wartości true, zaś 0 wartości false. Jako że istnieje nadal sporo programów napisanych w standardzie C i wykorzystujących właśnie zmienne typu int do przechowywania wartości logicznych, kompilatory C++ dokonują zawsze konwersji typu int na bool w miejscach gdzie spodziewana jest wartość bool. Nie mniej jednak nie jest zalecane stosowanie zmiennych int do przechowywania wartości logicznych w programach pisanych w C++, jako że nowe wersje standardu mogą zaprzestać stosowania wbudowanej konwersji typów, zmuszające programistę do przepisania kodu.

Przykład 7: Operatory logiczne w akcji

// Operatory logiczne w akcji
#include <iostream>
#include <stdlib.h>
 
int main(int argc, char* argv[])
{
int i,j;
std::cout << "Wprowadź wartość całkowitą: ";
std::cin >> i;
std::cout << "I jeszcze jedną: ";
std::cin >> j;
std::cout << "warunek i > j jest " << (i > j) << std::endl;
std::cout << "warunek i < j jest " << (i < j) << std::endl;
std::cout << "warunek i >= j jest " << (i >= j) << std::endl;
std::cout << "warunek i <= j jest " << (i <= j) << std::endl;
std::cout << "warunek i == j jest " << (i == j) << std::endl;
std::cout << "warunek i != j jest " << (i != j) << std::endl;
std::cout << "warunek i && j jest " << (i && j) << std::endl;
std::cout << "warunek i || j jest " << (i || j) << std::endl;
std::cout << "warunek (i < 10) && (j < 10) jest " << ((i < 10) && (j < 10)) << std::endl;
system("pause");
return 0;
}


1.3 Operatory bitowe

Operatory bitowe jak sama nazwa wskazuje pozwalają na przeprowadzenie operacji bezpośrednio na polu bitowym reprezentującym daną zmienną w pamięci komputera. Jako że typy zmiennoprzecinkowe (float, double i pochodne) mają postać nietrywialną i przechowywane są w zapisie mantysowym, operatory bitowe (w ang. bitwise operators) współpracują jedynie z typami całkowitymi (int i pochodne), realizując operacje algebry Boole`a na polu bitowym a nie wartości zmiennej. Celem odróżnienia operatorów bitowych od logicznych, stosowany zapis zawiera pojedynczy znak operacyjny, jak przedstawiono w tabeli 5.

Tabela 5: Operatory bitowe w C/C++


Operator Znaczenie
argument1 & argument2 koniunkcja logiczna (argument1 AND argument2)
argument1 | argument2 iloczyn logiczny (argument1 OR argument2) na poszczególnych bitach argument1 oraz argument2
argument1 ^ argument2 wyłączony iloczyn logiczny (argument1 XOR argument2) na poszczególnych bitach argument1 oraz argument2
~argument1 zaprzeczenie (NOT argument1) - zmienia stan wszystkich bitów zmiennej na wartość przeciwną (0›1 oraz 1›0)
argument1 &= argument2 koniunkcja logiczna z przypisaniem - wykonuje koniunkcję logiczną na poszczególnych bitach argument1 oraz argument2 a wynik przypisuje do argument1
argument1 |= argument2 iloczyn logiczny z przypisaniem - wykonuje iloczyn logiczny na poszczególnych bitach argument1 oraz argument2 a wynik przypisuje do argument1
argument1 ^= argument2 wyłączony iloczyn logiczny z przypisaniem - wykonuje wyłączony iloczyn logiczny na poszczególnych bitach argument1 oraz argument2 a wynik przypisuje do argument1


Dodatkowo, celem realizacji dowolnych operacji mnożenia i dzielenia niezbędne jest przesuwanie zawartości pola bitowego o dowolną liczbę pozycji zarówno w lewo jak i prawo. Do tego celu służą tzw. operatory przesuwu bitowego (w ang. bit shift operators), zabrane w tabeli 6. Oczywistym jest iż operatory te mogą być także łączone z przypisaniem tworząc złożone operatory przesuwu bitowego [<<= oraz >>=]. Przykład numer 8 pokazuje jak wyświetlić bitową postać zmiennej typu unsigned int, która zwierać może maksymalnie 32 bity.

Tabela 6: Operatory przesuwu bitowego w C/C++

Operator Znaczenie
argument1 << argument2 przesuń pole bitowe argument1 o argument2 pozycji w lewo (mnożenie przez 2argument2)
argument1 >> argument2 przesuń pole bitowe argument1 o argument2 pozycji w prawo (dzielenie przez 2argument2)
argument1 <<= argument2 przesuń pole bitowe argument1 o argument2 pozycji w lewo (mnożenie przez 2argument2) a wynik przypisz do argument1
argument1 >>= argument2 przesuń pole bitowe argument1 o argument2 pozycji w prawo (dzielenie przez 2argument2) a wynik przypisz do argument1


Przykład 8: Operatory bitowe w akcji – przykład 1

// Operatory bitowe w akcji &#8211; wyświetlanie zmiennej w postaci bitowej,
// poczynając od najbardziej znaczącego bitu
#include <iostream>
#include <stdlib.h>
 
int main(int argc, char* argv[])
{
unsigned int zmienna;
std::cout << "Wprowadź wartość całkowitą <0,2^32>: ";
std::cin >> zmienna;
for(int i=0;i<32;i++)
  std::cout << ((zmienna & (1 << (31-i))) >> (31-i));
std::cout << std::endl;
system("pause");
return 0;
}


Przykład 9: Operatory bitowe w akcji – przykład 2

// Przykłady działania operatorów bitowych
#include <iostream>
#include <stdlib.h>
 
// funkcja wyświetlająca dane na ekran
void printBinary(const unsigned char val)
{
for(int i = 7; i >= 0; i--)
  std::cout << ((val & (1 << i)) >> i);
}
 
// makro do wyświetlania danych
#define PR(STR, EXPR)
std::cout << STR; printBinary(EXPR); std::cout << std::endl;
 
int main(int argc, char* argv[])
{
unsigned int wartosc;
unsigned char a, b;
std::cout << "Podaj liczbę z zakresu 0 i 255: ";
std::cin >> wartosc;
a = wartosc;
PR("a binarnie: ", a);
std::cout << "Podaj liczbę z zakresu 0 i 255: ";
std::cin >> wartosc;
b = wartosc;
PR("b binarnie: ", b);
PR("a | b = ", a | b);
PR("a & b = ", a & b);
PR("a ^ b = ", a ^ b);
PR("~a = ", ~a);
PR("~b = ", ~b);
// przykład ciekawego wzoru bitowego...
unsigned char c = 0x5A;
PR("c binarnie: ", c);
a |= c;
PR("a |= c; a = ", a);
b &= c;
PR("b &= c; b = ", b);
b ^= a;
PR("b ^= a; b = ", b);
system("pause");
return 0;
}


Autorem tekstu jest: Marek Hajduczenia
Materiał dodany przez użytkownika: marek_haj