3. Operátory a výrazy 3.1 Operand, operátor, výraz. 3.2. Rozdelenie operátorov. 3.3. Operátor priradenie, l-hodnota a p-hodnota. 3.4. Aritmetické operátory - aditívne a multiplikatívne. 3.5. Logické operátory. 3.6. Relačné operátory. 3.7. Bitové operátory. 3.8. Adresový operátor. 3.9. Podmienený operátor. 3.10. Operátor čiarka. 3.11. Pretypovanie výrazu. 3.1. Operand, operátor, výraz. Ak zapíšeme v matematike napríklad a + b, hovoríme o výraze. Ten má dva operandy a a b a jeden operátor +. Ide síce o výraz veľmi jednoduchý, umožnil nám však zopakovanie potrebných termínov. 3.2. Rozdelenie operátorov. Operátory rozdeľujeme podľa počtu operandov (arity) na operátory unárne, binárne a ternálne. Binárne operátory sú aritmetické, relačné, logické, bitové a operátory priradenia a posunu. Aritmetické operátory sú aditívne a multiplikatívne. Operátory majú svoju prioritu a asociativitu. Priorita určuje, že napríklad násobenie sa vyhodnotí skôr, než napríklad sčítanie. Asociativita hovorí, či sa vyhodnocuje výraz, alebo naopak. Operátory rovnako delíme podľa pozície ich zápisu vzhľadom k operandu(-om). Takto rozlišujeme operátory prefixové, infixové a postfixové. Operátory v jednotlivých prípadoch zapisujeme pred operandy, medzi operandy, respektíve za operandy. Druhý variant je nám zrejme najbližší. Infixový spôsob zápisu sme používali už na základnej škole. Poznamejme, že v C uplatníme všetky zmienené varianty operátorov. Základný prehľad operátorov jazyka C, rozlíšených podľa arity, je v nasledujúcej tabuľke: Unárne operátory: +, - aritmetické plus a mínus & referencia (získanie adresy objektu) * dereferencia (získanie objektu podľa adresy)! logická negácia ~ bitová negácia ++, -- inkrementácia resp. dekrementácia hodnoty, prefixový aj postfixový zápis
(typ) sizeof pretypovanie na typ uvedený v zátvorkách operátor na získanie dĺžky objektu alebo typu Binárne operátory: = priradenie, možná je aj kombinácia s inými operátormi, napr. +=, -=, *=, /=, <<=, ^= + Sčítanie - Odčítanie * Násobenie / Delenie % Zbytok po celočíselnom delení (modulo) <<, >> Bitový posun vľavo resp. vpravo & Bitový súčin (and) Bitový súčet (or) ^ && Bitový vylučovací súčet (xor) Logický súčin (and) Logický súčet (or). Bodka, priamy prístup k členovi štruktúry -> Nepriamy prístup k členovi štruktúry, Čiarka, oddelenie výrazov < Menší než > Väčší než <= Menší alebo rovný >= Väčší alebo rovný == Rovnosť!= Nerovnosť Ternálny operátor:? : podmienený operátor Pri podrobnejšom pohľade na prehľad operátorov podľa arity hneď objavíme niektoré z operátorov, ktoré sú uvedené ako unárne aj binárne súčasne. Ako príklad uveďme -, ktoré môže vystupovať ako unárne mínus ale aj ako binárny operátor odčítania.
Operátory s uvedením priority (v tabuľke sú zoradené zostupne od priority najvyššej k priorite najnižšej) a asociativity: Operátor typ operátora asociativita [ ] ( ). -> postfixové ++ postfixové -- Výraz prefixové ++ prefixové -- sizeof & * + - ~! pretypovanie Unárny Unárny * / % Násobenia + - Sčítania << >> bitového posunu < > <= >= Relačný ==!= Rovnosti & ^ Bitové AND Bitové OR && Bitové vylučovacie OR (XOR) logické AND logické OR?: podmienené vyhodnotenie = *= /= %= += -= <<= >>= &= = ^= jednoduché priradenie a priradenie s výpočtom, postupné vyhodnotenie logické OR Sprava doľava sprava doľava sprava doľava Unárne operátory sú prefixové s možným postfixovým použitím dekrementácia a inkrementácia. Binárne operátory sú infixové. Operátory sú rovnako aj [], () ohraničujúce indexy resp. argumenty a #, ##, ktoré spracováva už preprocesor. Preprocesoru v tomto texte venujeme celú kapitolu. Užitočným operátorom je sizeof, ktorý v priebehu prekladu vyhodnotí pamäťové nároky svojho argumentu. Tento operátor je nepostrádateľný najmä pri dynamickej alokácii pamäte, prípadne pri operáciách čítanie/zápis z binárnych súborov.
3.3. Operátor priradenia, l-hodnota a p-hodnota. Výrazy, ako už vieme, sú tvorené postupnosťou operátorov a operandov. Výraz predpisuje výpočet adresy alebo hodnoty. Ak upravíme napríklad známy vzťah do syntakticky správneho zápisu v jazyku C c = sqrt(a*a + b*b); môžeme zreteľne ukázať niektoré významné vlastnosti operátora priradenie =. Na pravej strane operátoru priradenie sa nachádza výraz ktorého vyhodnotením získame hodnotu tohto výrazu. Avšak na ľavej strane sa nachádza výraz (v našom prípade je to "len" premenná), ktorého vyhodnotením získame adresu. Na túto adresu, predstavujúcu začiatok pamäťového miesta pre umiestnenie hodnoty premennej c, je umiestnená hodnota z pravej strany priradzovacieho operátora. Ešte než si zadefinujeme uvádzané pojmy, nesmieme zabudnúť na dôležitú skutočnosť ato: výsledkom výrazu priradenia je hodnota. Čo to znamená? Napríklad možnosť elegantne riešiť inicializáciu viacerých premenných rovnakou hodnotou: int a, b, c; a = b = c = -1; Nezabúdajme, že priraďovací operátor je asociatívny sprava doľava. Najprv sa teda vyhodnotí c = -1, výsledkom je hodnota -1, ta tvorí pravú stranu priradenia b =, ktorého výsledkom je opäť -1. A ako sa uvedená hodnota dostane do premennej a iste nie je treba popisovať. Vráťme sa však k naznačeným pojmom. Adresový výraz (lvalue - l-hodnota) je výraz, ktorého výpočtom sa získa adresa v pamäti. Napríklad, ak je P nejaký výraz vyhodnotený ako nenulový ukazovateľ, tak *P je l-hodnota. V súvislosti s modifikátorom const rozlišujeme modifikovateľnú a nemodifikovateľnú l-hodnotu. Hodnotový výraz (rvalue - p-hodnota) je výraz, ktorého výpočtom sa získa hodnota istého typu. Typ je jednoznačne určený typom operandov. Pretože sa najmä pri číselných výpočtoch často stretávame s operandami rôznych typov, uveďme si pravidlá, ktoré určujú typ výsledku. Poznamenajme, že naplnenie pravidiel testujeme v uvedenom poradí:
Ak aspoň jeden z operandov je racionálneho typu, potom Ak je jeden z operandov typu long double, je rovnako aj druhý operand konvertovaný na tento typ. Ak je jeden z operandov typu double, je rovnako aj druhý operand konvertovaný na tento typ. Ak je jeden z operandov typu float, je rovnako aj druhý operand konvertovaný na tento typ. Ak je jeden z operandov typu unsigned long, je rovnako aj druhý operand konvertovaný na tento typ. Ak je jeden z operandov typu long, a druhý typu unsigned int, je druhý operand konvertovaný na typ long (16-bitový prekladač), alebo sú oba operandy konvertované na typ unsigned long (32-bitový prekladač). Ak je jeden z operandov typu long, je rovnako aj druhý operand konvertovaný na tento typ. Ak je jeden z operandov typu unsigned int, je rovnako aj druhý operand konvertovaný na tento typ. Ak nie je splnená žiadna z predchádzajúcich podmienok, sú obidva operandy prevedené na typ int. Výrazy môžu vyvolávať aj vedľajšie efekty. ANSI norma nedoporučuje používať výrazy, ktoré behom vyhodnotenia spôsobujú viacnásobnú zmenu obsahu jedného pamäťového miesta. Napríklad: cc = cc++ + 1. Niektoré vedľajšie efekty môžu byť implementačne závislé, napr. výraz a[i] = i++. Operátoru priradenia sme sa venovali ako prvému v poradí. Dôvod je jednoduchý. Bez tohoto operátoru nemôžeme uchovať výsledky v premenných. Súčasne nám tento operátor bude slúžiť aj v nasledujúcom výklade látky, vrátane popisu ďalších operátorov. 3.4. Aritmetické operátory - aditívne a multiplikatívne. Aritmetické operátory + - * / % predstavujú základné matematické operácie sčítanie, odčítanie, násobenie, delenie a zvyšku po (celočíselnom) delení. Najlepšou ukážkou bude iste príklad.
/********************************************************************/ /* program op_int01.c */ /* celociselne nasobenie, delenie, zvysok po deleni */ /* naviac je ukazane celociselne pretecenie (len pre 16 bitove int) */ /********************************************************************/ #include <stdio.h> int main() { int o1 = 123, o2 = 456, o3 = 295, v1, v2, v3; int c1 = 20000, c2 = 20001, vc; v1 = o1 * o2; v2 = o3 / 2; v3 = o3 % 2; printf("%d * %d = %d\n", o1, o2, v1); printf("%d / %d = %d\n", o3, 2, v2); printf("%d %% %d = %d\n", o3, 2, v3); vc = c1 + c2; printf("\nteraz pozor:\n\t"); printf("%d + %d = %d\n", c1, c2, vc); return 0; } /* vystup BC31 123 * 456 = -9448 295 / 2 = 147 295 % 2 = 1 teraz pozor: 20000 + 20001 = -25535 */ /* vystup MWC + COHERENT 123 * 456 = 56088 295 / 2 = 147 295 % 2 = 1 teraz pozor: 20000 + 20001 = 40001 */ Príklad ukazuje nielen inicializáciu hodnôt premenných "operandov" o1 o2 a o3, ale po prvých očakávaných výsledkoch aj neočakávané hodnoty. Tie sú spôsobené faktom aritmetického pretečenia. Ďalej je vhodné zdôrazniť, že vzhľadom na typ operandov int, je aj typ výsledku rovnakého typu (viď pravidla uvedené skôr). Z tohoto dôvodu je aj výsledok delenia celočíselný. Pozrime sa teraz na výsledky aritmetických operácii, v ktorých argumenty sú opäť celočíselné, ale ľavá strana je racionálneho typu.
/* program op_int_f.c */ /* zakladne aritmeticke operacie a priradenia vysledky su sice i float*/ /* ale vypocty su prevadzane ako int a az potom prevedene */ /**********************************************************************/ #include <stdio.h> int main() { int i, j; float r, x; j = i = 5; j *= i; r = j / 3; x = j * 3; printf("i=%d\tj=%d\tr=%f\tx=%f\n", i, j, r, x); return 0; } /* vystup BC31 i=5 j=25 r=8.000000 x=75.000000 */ Rovnako aj na tomto príklade vidíme, že výpočet prebieha s hodnotami typov podľa spomínaných pravidiel a až potom je získaná p-hodnota konvertovaná do typu zodpovedajúceho l-hodnote. Preto podiel 25/3 dáva 8.0 a nie 8.333. Rovnakým spôsobom prebiehajú aritmetické operácie s racionálnymi hodnotami. Ak chceme zmeniť poradie vyhodnotenia jednotlivých častí výrazu, použijeme na "pozmenenie priority" okrúhle zátvorky. 3.5. Logické operátory. Logické operátory predstavujú dve hodnoty, pravda a nepravda. ANSI norma C hovorí, že hodnota nepravda je predstavovaná 0 (nulou), a pravda 1 (jednotkou). V druhom prípade však ide o doporučenie, lebo zaužívaným anachronizmom sa považuje akákoľvek nenulová hodnota za pravdu. Logické operátory sú &&!, postupne and or a not. Vykonávajú výpočet logických výrazov tvorených ich operandami. Pravidlá pre určenie výsledku poznáme z Booleovej algebry. Logické výrazy často obsahujú aj stanovenie (a overenie) podmienok tvorených relačnými operátormi. 3.6. Relačné operátory. Relačné operátory sú < > <= >= ==!=. V poradí menší, väčší, menší alebo rovné, väčší alebo rovné, rovné a nerovné. Sú definované pre operandy všetkých základných dátových typov. Ich výsledkom sú logické hodnoty pravda a nepravda tak, ako sú popísané v predchádzajúcom odstavci.
3.7. Bitové operátory. Ako samotný názov napovedá, umožňujú vykonať operácie nad jednotlivými bitmi. Túto možnosť zďaleka nemajú všetky programovacie jazyky označované ako vyššie. Jazyk C ich má najmä preto, že bol vytvorený ako nástoj systémového programátora (OS Unix). Použitie bitových operátorov vyžaduje vedomosti o uložení bitov v pamäti, spôsobe kódovania čísel,.... Bitové operátory sú: << >> & ~ ^, teda posun vľavo, posun vpravo, and, or, not a xor. Bitové operácie sú možné iba s celočíselnými hodnotami. Pozrime sa na jednotlivé zástupné znaky bitových operátorov. Pri bitovom posune vľavo (vpravo) <<, ( >> ) sa jednotlivé bity posúvajú vľavo (vpravo), teda do pozície s (binárne) vyšším (nižším) rádom. Na najpravejšiu (nejľavejšiu) posunom vytvorenú pozíciu je umiestnená nula. Posuny však prebiehajú aritmeticky. To znamená, že uvedené pravidlo neplatí pre posun vpravo hodnoty celočíselného typu so znamienkom. V takomto prípade sa najvyšší bit (znamienkový), zachováva. Takto sa pri posune dopĺňa do bitového reťazca nový bit. Naopak pred posunom najľavejší (najpravejší) bit je poslaný do "ríše zabudnutia". Bitový posun o jeden (binárny) rád vpravo, respektíve vľavo, má rovnaký význam, ako celočíselné delenie, respektíve násobenie, dvoma. Ak je bitový posun o viac než jeden rád, jedná sa o násobenie (delenie) príslušnou mocninou dvoch. Bitové and &, or, a xor ^ vykonáva príslušnú binárnu operáciu s každým párom odpovedajúcich si bitov. Výsledok je umiestnený do pozície rovnakého binárneho rádu výsledku. Výsledky operácií nad jednotlivými bitmi sú rovnaké, ako v Booleovej algebre. Bitové not ~ je operátorom unárnym, prevádza negáciu každého bitu v bitovom reťazci jediného operandu. Tomuto operátoru sa často hovorí bitový doplnok. /*******************************************************************/ /* program op_bit01.c */ /* ukazuje bitove posuny, a zakladne bitove operacie and, or, xor */ /* a bitovy doplnok */ /*******************************************************************/ #include <stdio.h> int main() { printf("1 << 1 = \t%d\t%#x\n", 1 << 1, 1 << 1); printf("1 << 7 = \t%d\t%#x\n", 1 << 7, 1 << 7); printf("-1 >> 1 = \t%d\t%#x\n", -1 >> 1, -1 >> 1); printf("1024 >> 9 = \t%d\t%#x\n", 1024 >> 9, 1024 >> 9); printf("13 & 6 = \t%d\t%#x\n", 13 & 6, 13 & 6); printf("13 6 = \t%d\t%#x\n", 13 6, 13 6); printf("13 ^ 6 = \t%d\t%#x\n", 13 ^ 6, 13 ^ 6); printf("2 & 1 = \t%d\t%#x\n", 2 & 1, 2 & 1); printf("2 1 = \t%d\t%#x\n", 2 1, 2 1); printf("2 ^ 1 = \t%d\t%#x\n", 2 ^ 1, 2 ^ 1); return 0; }
/* BC31-16 bitovy kod 1 << 1 = 2 0x2 1 << 7 = 128 0x80-1 >> 1 = -1 0xffff 1024 >> 9 = 2 0x2 13 & 6 = 4 0x4 13 6 = 15 0xf 13 ^ 6 = 11 0xb 2 & 1 = 0 0 2 1 = 3 0x3 2 ^ 1 = 3 0x3 */ Ak sme v úvode k bitovým operátorom naznačili ich systémovú orientáciu, ukážme ju na príklade. Často popisujeme rozdiely medzi 16 a 32-bitovým kódom (prekladačom, ktorý kód generuje). Nasledujúci program nám umožní zistiť, s akým prekladačom máme česť. /************************************************/ /* soubor int_size.c */ /* zisti kolko bitove int pouziva prekladac */ /************************************************/ #include <stdio.h> int main(void) { unsigned int ui = ~0; int i = 1; while (ui >>= 1) i++; printf("prekladac pouziva %2d bitovu reprezentaciu celeho cisla\n", i); return 0; } Všimnime si použitie bitového doplnku ui = ~0. Tak ľahko (a najmä prenesitelne) získame bitový reťazec tvorený samými jednotkami. 3.8. Adresový operátor. Tento operátor & je unárny. Ako už názov adresový operátor napovedá, umožňuje získať adresu objektu, na ktorý je aplikovaný. Adresu objektu môžeme použiť v najrôznejších situáciách, obvykle je to ale v súvislosti s ukazovateľmi. Bez tohoto operátoru by sme neboli schopní pracovať so súbormi a ani štandardný vstup by sme neboli schopní čítať inak, než po znakoch. Takto napríklad môžeme prečítať hodnoty dvoch premenných jedinou funkciou pre formátovaný vstup: int i; float f; scanf("%d %f", &i, &f);
3.9. Podmienený operátor. Podmienený operátor je pomerne neobvyklý. Preto bude vhodné, ak si objasníme jeho význam. Máme napríklad výpočet, ktorý potrebujeme previesť, v závislosti na nejakej podmienke, jednu z dvoch variant (pochopiteľne odlišných). Výsledok výpočtu priradzujeme vždy rovnakej premennej. Pokiaľ naviac je časť výrazu, popisujúca výpočet oboch variant, zhodná, jedná sa o typický príklad využitia podmieneného výrazu. Buďme však radšej konkrétnejší. Ak chceme vypočítať absolútnu hodnotu nejakého čísla, použijeme veľmi pravdepodobne podmienený operátor. Výpis zdrojového textu takého výpočtu: /**********************************/ /* soubor op_cond.c */ /* ukazuje pouzitie podmieneneho */ /* operatoru - absolutna hodnota */ /**********************************/ #include <stdio.h> int main(void) { int i, abs_i; printf("\nzadaj cele cislo: "); scanf("%d", &i); abs_i = (i < 0)? -i : i; printf("abs(%d) = %d\n", i, abs_i); return 0; } Ďalšie použitie (ternálneho) podmieneného operátoru ukazuje nasledujúci riadok. return((znak == 0x0)? getch() + 0x100 : znak); Tento riadok zaisťuje prípadné načítanie a prekódovanie ľubovolnej stlačenej klávesy na tzv. rozšírenej klávesnici IBM PC AT. Základné klávesy tejto klávesnice vytvárajú kód odpovedajúci ich pozícii v ASCII tabuľke. Rozšírené klávesy vytvárajú dvojicu celočíselných kódov, z ktorých prvá je nula. Než si popíšeme jeho činnosť, doplňme aj predchádzajúci príkaz pre načítanie znaku: znak = getch(); return((znak == 0x0)? getch() + 0x100 : znak); Pomocou podmienky (znak == 0x0) otestujeme, či sa jedná o rozšírenú klávesu. Ak áno, je prevedený prvý príkaz nasledujúci za podmieneným operátorom, getch() + 0x100. Je ním načítaný tzv. scan kód rozšírenej klávesy, ktorý je ďalej zväčšený o hodnotu 100hex. Ak podmienka splnená nebola, je vykonaný príkaz za dvojbodkou. Návratovou hodnotou je potom neupravená hodnota, načítaná do premennej znak tesne pred ternálnym operátorom.
3.10. Operátor čiarka. Čiarka je operátorom postupného vyhodnotenia. Má najnižšiu prioritu zo všetkých operátorov a vyhodnocuje sa. Čiarkou môžeme oddeliť jednotlivé výrazy v mieste, kde je očakávaný jediný výraz. Napríklad v cykle for. 3.11. Pretypovanie výrazu. Jazyk C nám umožňuje pretypovať výrazy podľa našej potreby. Pretypovanie má prirodzene svoje obmedzenia, tie však často plne zodpovedajú zdravému rozumu. Ťažko sa napríklad vyskytne potreba pretypovať znak na typ ukazovateľ na double. Úvodnej úvahe chýba konkrétna ukážka, z ktorej by vyplýval syntaktický zápis pretypovania. Pripomeňme si príklad op_int_f.c, v ktorom sa vyskytoval príkaz r = j / 3; ktorý bol vyhodnotený celočíselne a až potom konvertovaný na float. Ak chceme, aby už podiel prebehol v racionálnom obore, musíme pravou stranu upraviť. S pretypovaním môže vyzerať pravá strana takto: r = (float) j / 3; Pretypovanie vykonávame tak, že pred hodnotu, ktorú chceme pretypovať, napíšeme typ, ktorý chceme získať v okrúhlych zátvorkách. Syntakticky teda zapíšeme pretypovanie takto: (type) expression Predchádzajúca kapitola Obsah Začiatok Nasledujúca kapitola