V posledních dvou dílech (1, 2) jsme si představili nejdůležitější operátory, které můžeme v C/C++ využít. Dnes si jejich výčet dokončíme.
Operátory přiřazení
Operátor přiřazení zapisujeme „=“. Nalevo musí být místo v paměti (třeba proměnná), kam zapisujeme hodnotu napravo. Je to tedy například:
a = b;
Tento operátor jistě znáte. Je dobré si uvědomit, že přiřazení je v C/C++ (stejně jako ostatní operátory) výraz – tedy produkuje hodnotu. Proto je možná následující konstrukce:
int a, b; a = b = 10;
Nejdříve je do b přiřazena hodnota deset. Přiřazení nabývá hodnoty 10, která je dále uložena do proměnné a. Ze stejného důvodu jde použít i následující (a podobné):
char c; while((c = Serial.read()) != 0) { ... }
Je také zajímavé, že lze tento operátor kombinovat i s některými dalšími binárními operátory a dosáhnout tak kratšího zápisu. Například:
a = a + b;
lze psát zkráceně jako:
a += b;
Kromě operátoru pro sčítání lze použít následující:
Operátor | Popis | Použití | Význam |
+= | Plus | a += b | a = a + b |
-= | Minus | a -= b | a = a – b |
*= | Krát | a *= b | a = a * b |
/= | Děleno | a /= b | a = a / b |
%= | Modulo | a %= b | a = a % b |
<<= | Bitový posuv vlevo | a <<= b | a = a << b |
>>= | Bitový posuv vpravo | a >>= b | a = a >> b |
&= | Bitové AND | a &= b | a = a & b |
^= | Bitové XOR | a ^= b | a = a ^ b |
|= | Bitové OR | a |= b | a = a | b |
Další operátory
Do této skupiny patří operátory, které moc nelze zařadit do jiných skupin.
Operátor čárka
Prvním z nich je operátor čárka. Podívejte se na následující výstřižek kódu. Jaká bude hodnota proměnné c?
int a = 10; int b = 20; int c; c = (a, b); Serial.println(c);
Pokud si program spustíte, vypíše se na sériovou linku hodnota 20. Je tomu tak proto, protože operátor čárka vyhodnocuje své operandy zleva doprava a „vrátí“ hodnotu pravého. Použití tohoto operátoru je poměrně omezené a také zhoršuje čitelnost kódu, takže bych ho nedoporučoval. Využijeme ho ale například v případě, že chceme procházet 2D pole po diagonále.
int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; for(int i = 0, j = 0; i < 3; i++, j++) { Serial.println(a[j][i]); }
U tohoto operátoru je potřeba si dávat pozor na precedneci (viz závěr článku). Následující dva řádky mají totiž rozdílné chování:
c = (a, b); c = a, b;
První řádek má chování jesné. Nejdříve se vyhodnotí závorka a z ní se vezme hodnota b, která se přiřadí do proměnné c.
Ve druhém řádku je nejdříve vyhodnoceno přiřazení (tedy hodnota a se přiřadí do c) a až poté je vyhodnocen operátor čárka a celý řádek má hodnotu b.
Operátory přetypování
Slouží k převodům mezi datovými typy. Jejich použití dává smysl hlavně u číselných datových typů a u ukazatelů. Některé převody probíhají automaticky – takzvaně implicitně. Je tomu tak například, když provádíme operace s dvěma čísly různého datového typu (int + long, int + double, …). V některých případech je nutné uvést typ ručně, tedy explicitně. Jsou to často různé operace s ukazateli.
Přetypování je unární prefixový operátor, který zapisujeme pomocí kulatých závorek, do kterých uvedeme typ, na který chceme přetypovávat.
int i = 257; // hodnota typu int int* ip = &i; // ukazatel na hodnotu typu int byte* bp = (byte *) ip; // ukazatel na hodnotu typu byte // ip a bp ukazují na stejnou adresu, ale chování se liší Serial.println(*ip); // -> 257 Serial.println(*bp); // -> 1
Výše je jeden z možných příkladů použití operátoru přetypování. Ukazatel na int přetypujeme na ukazatel na byte. Poté si hodnoty těchto dvou ukazatelů vypíšeme. Pro ukazatel na int má hodnota 2 byte. Dojde tedy k jejich načtení z paměti a výpisu hodnoty. V případu s ukazatelem na byte je ale přečten z paměti jen jeden byte.
Operátor sizeof
I když by se mohlo zdát, že se jedná spíše o funkci, není tomu tak. Jedná se o unární operátor, který se podívá na datový typ svého operandu a vrátí jeho velikost v bytech. Sizeof jsme hojně používali ve článku o celočíselných datových typech. Setkáme se se dvěma způsoby zápisu:
int a; sizeof a; // =2 sizeof(a); // =2
Závorky kolem operandu zde nemusejí být, protože se nejedná o volání funkce. Je to podobná situace, jako u a+b vs. (a)+(b).
Přístup k prvku pole
Pro přístup k prvku pole používáme následující zápis:
int pole[] = {1, 2, 3}; // vytvoření pole pole[2] = 100; // přepsání hodnoty třetího prvku Serial.println(pole[1]); // přístup ke druhému prvku: 2
Indexování v poli probíhá od 0 (tedy první prvek pole má index 0).
Poznámka: Pole se do jisté míry chovají jako ukazatele. Adresa pole je totožná s adresou prvního prvku pole. Při indexaci tedy program zjistí adresu prvního prvku pole a velikost datového typu prvků pole a na základě těchto hodnot vypočte adresu požadovaného prvku.
Přístup k prvku struktury
Zapisujeme znakem „.“ (tečka). Pokud struktury neznáte, můžete si o nich přečíst například zde. V rychlosti struktury slouží ke sdružování dat, které patří k sobě. Například bod ve 3D prostoru by mohl být popsán následovně:
struct Bod3D { int x; int y; int z; };
Vytvoření proměnné datového typu struct Bod3D, nastavení jejích hodnot a přístup k hodnotám pomocí operátoru tečka vypadá takto:
struct Bod3D A; A.x = 10; A.y = 20; A.z = 30; Serial.println(A.x); // 10
Operátory pro práci s ukazateli
Ukazatelům byl věnován samostatný článek, takže jenom ve stručnosti:
Adresa proměnné
Pro zjištění adresy proměnné se používá operátor „&“ (ampersand). Je možné si ho zapamatovat pomocí mnemotechnické pomůcky „ampersand – adresa“. Tomuto operátoru se také říká reference.
Hodnota proměnné
Opačnou operací k referenci je dereference, tedy zjištění hodnoty proměnné. V C/C++ se používá k jeho zápisu symbol „*“.
Přístup k prvku struktury
Pokud máme ukazatel na strukturu, je možné k jejím položkám přistupovat následovně:
// vytvoření struktury a nastavení hodnot jejích položek struct Bod3D A; A.x = 10; A.y = 20; A.z = 30; // vytvoření ukazatele na strukturu struct Bod3D *p = &A; // práce s hodnotami (*p).y = 200;
Zápis (*p).y = 200; říká, že nejdříve přistoupíme k hodnotě ukazatele p a poté nastavíme její položku y na hodnotu 200. To může vypadat kostrbatě, proto byl zaveden i operátor „->“ pro přístup k prvku struktury, na kterou máme ukazatel. Následující dva zápisy jsou ekvivalentní:
p->x = 100; (*p).x = 100;
Precedence operátorů
Precedence operátorů slouží k určení pořadí vyhodnocování složitějších výrazů. Z matematiky například víme, že výraz 10+20*30 je vyhodnocen tak, že je nejdříve vypočten součin 20*30 a k výsledku je přičtena hodnota 10. Abychom byli schopni takto vyhodnotit i další operátory, mají operátory přiřazeny precedenci, tedy pořadí v jakém jsou vyhodnocovány. Ta je daná následující tabulkou (převzato z https://cs.cppreference.com/w/cpp/language/operator_precedence).
Precedence | Operátor | Popis | Asociativita |
---|---|---|---|
1 | ::
|
scope resolution | zleva |
2 | ++ --
|
postfixové operátory inkrementace a dekrementace | |
()
|
operátor volání funkce | ||
[]
|
operátor přístupu k prvku pole | ||
.
|
výběr členu přes referenci | ||
->
|
výběr členu přes ukazatel | ||
3 | ++ --
|
prefixové operátory inkrementace a dekrementace | zprava |
+ −
|
unární plus a mínus | ||
! ~
|
logická a bitová negace | ||
(type)
|
operátor přetypování | ||
*
|
operátor dereference | ||
&
|
získání adresy prvku | ||
sizeof
|
velikost prvku | ||
new , new[]
|
dynamická alokace paměti | ||
delete , delete[]
|
dynamicka dealokace paměti | ||
4 | .* ->*
|
ukazatel na člen | zleva |
5 | * / %
|
operátory násobení, dělení a zbytek po dělení | |
6 | + −
|
operátory součtu a rozdílu | |
7 | << >>
|
bitový posun doleva a doprava | |
8 | < <=
|
Relační operátory < a ≤ | |
> >=
|
Relační operátory > a ≥ | ||
9 | == !=
|
Relační operátory = a ≠ | |
10 | &
|
bitové AND | |
11 | ^
|
bitové XOR (výlučný součet) | |
12 | |
|
bitové OR | |
13 | &&
|
logické AND | |
14 | ||
|
logické OR | |
15 | ?:
|
ternární operátor[1] | zprava |
=
|
přímé přiřazení | ||
+= −=
|
přiřazení přičtením a odečtením | ||
*= /= %=
|
Přiřazení násobením, podílem a zbytkem | ||
<<= >>=
|
přiřazení bitovým posunem doleva a doprava | ||
&= ^= |=
|
přiřazení bitovou operací AND, XOR a OR | ||
16 | throw
|
operátor throw (pro výjimky) | |
17 | ,
|
operátor čárka | zleva |
Čím nižší je číslo precedence, tím dříve je operátor vyhodnocován (tj. má vyšší prioritu). Některé operátory v tabulce jsme ve článcích neprobrali, ale při programování Arduina se s nimi moc často nesetkáte.