Close

C++ operátory – 3. díl

Programovací jazyky - Wordcloud

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átorPopisPoužitíVýznam
+=Plusa += ba = a + b
-=Minusa -= ba = a – b
*=Kráta *= ba = a * b
/=Dělenoa /= ba = a / b
%=Moduloa %= ba = a % b
<<=Bitový posuv vlevoa <<= ba = a << b
>>=Bitový posuv vpravoa >>= ba = a >> b
&=Bitové ANDa &= ba = a & b
^=Bitové XORa ^= ba = a ^ b
|=Bitové ORa |= ba = 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.

Zbyšek Voda

Napsat komentář