3.1 Operand, operátor,
výraz.
3.2. Rozdělení operátorů.
3.3. Operátor přiřazení, l-hodnota a
p-hodnota.
3.4. Aritmetické operátory - aditivní a
multiplikativní.
3.5. Logické operátory.
3.6. Relační operátory.
3.7. Bitové operátory.
3.8. Adresový operátor.
3.9. Podmíněný operátor.
3.10. Operátor čárka.
3.11. Přetypování výrazu.
3.1. Operand, operátor, výraz.
Zapíšeme-li v matematice například a + b
, hovoříme o
výrazu. Ten má dva operandy a
a b
a jeden
operátor +
. Jedná se sice o výraz velmi jednoduchý, nicméně
nám umožnil zopakování potřebných termínů.
3.2.Rozdělení operátorů.
Operátory rozdělujeme podle počtu operandů (arity) na operátory unární, binární a ternální. Binární operátory jsou aritmetické, relační, logické, bitové a operátory přiřazení a posuvu. Aritmetické operátory jsou aditivní a multiplikativní. Operátory mají svou prioritu a asociativitu. Priorita určuje, že například násobení se vyhodnotí dříve než třeba sčítání. Asociativita říká, vyhodnocuje-li se výraz zleva doprava, nebo naopak.
Operátory rovněž dělíme podle pozice jejich zápisu vzhledem k operandu(-ům). Takto rozlišujeme operátory prefixové, infixové a postfixové. Operátory v jednotlivých případech zapisujeme před operandy, mezi operandy, respektive za operandy. Druhá varianta je nám zřejmě nejbližší. Infixový způsob zápisu jsme používali již na základní škole.
Poznamejme, že v C uplatníme všechny zmíněné varianty operátorů.
Základní přehled operátorů jazyka C, rozlišených podle arity,
následuje:
Unární operátory:
+, - |
aritmetické plus a mínus |
& |
reference (získání adresy objektu) |
* |
dereference (získání objektu dle adresy) |
! |
logická negace |
~ |
bitová negace |
++, -- |
inkrementace resp. dekrementace hodnoty, prefixový i postfixový zápis |
(typ) |
přetypování na typ uvedený v závorkách |
sizeof |
operátor pro získání délky objektu nebo typu |
Binární operátory:
= |
přiřazení, možná je i kombinace s jinými operátory, např.
+=, -=, *=, /=, <<=, ^= |
+ |
sčítání |
- |
odčítání |
* |
násobení |
/ |
dělení |
% |
zbytek po celočíselném dělení (modulo) |
<<, >> |
bitový posun vlevo resp. vpravo |
& |
bitový součin (and) |
| |
bitový součet (or) |
^ |
bitový vylučovací součet (xor) |
&& |
logický součin (and) |
|| |
logický součet (or) |
. |
tečka, přímý přístup ke členu struktury |
-> |
nepřímý přístup ke členu struktury |
, |
čárka, oddělení výrazů |
< |
menší než |
> |
větší než |
<= |
menší nebo rovno |
>= |
větší nebo rovno |
== |
rovnost |
!= |
nerovnost |
Ternální operátor:
? : |
podmíněný operátor |
Při podrobnějším pohledu na přehled operátorů podle arity záhy objevíme
některé z operátorů, které jsou uvedeny jako unární i binární
současně. Příkladem uveďme -
, které může vystupovat jako unární
mínus i jako binární operátor odčítání.
Operátory s uvedením priority (v tabulce jsou řazeny sestupně od priority
nejvyšší k prioritě nejnižší) a asociativity:
operátor | typ operátoru | asociativita |
---|---|---|
[ ] ( ) . -> postfixové ++ postfixové --
|
výraz | zleva doprava |
prefixové ++ prefixové -- sizeof & * + - ~ ! |
unární | logické OR |
přetypování | unární | zprava doleva |
* / % |
násobení | zleva doprava |
+ - |
sčítání | zleva doprava |
<< >> |
bitového posunu | zleva doprava |
< > <= >= |
relační | zleva doprava |
== != |
rovnosti | zleva doprava |
& |
bitové AND | zleva doprava |
^ |
bitové vylučovací OR (XOR) | zleva doprava |
| |
bitové OR | zleva doprava |
&& |
logické AND | zleva doprava |
|| |
logické OR | zleva doprava |
?: |
podmíněné vyhodnocení | zprava doleva |
= *= /= %= += -= <<= >>= &= |= ^= |
jednoduché přiřazení a přiřazení s výpočtem | zprava doleva |
, |
postupné vyhodnocení | zleva doprava |
Unární operátory jsou prefixové s možným postfixovým použítím dekrementace a inkrementace. Binární operátory jsou infixové.
Operátory jsou rovněž []
, ()
ohraničující indexy, resp. argumenty
a #
, ##
, které zpracovává již preprocesor. Preprocesoru v tomto textu
věnujeme celou kapitolu. Užitečným operátorem je sizeof
, který v průběhu překladu
vyhodnotí paměťové nároky svého argumentu. Tento operátor je nezbytný
zejména při dynamické alokaci paměti, případně při operacích čtení/zápis z
binárních souborů.
3.3. Operátor přiřazení, l-hodnota a p-hodnota
Výrazy, jak již víme, jsou tvořeny posloupností operátorů a operandů. Výraz předepisuje výpočet adresy nebo hodnoty. Upravíme-li například známý vztah
do syntakticky správného zápisu1 v jazyce C
c = sqrt(a*a + b*b);
můžeme zřetelně ukázat některé významné vlastnosti operátoru
přiřazení =
2. Na pravé straně operátoru přiřazení se nachází výraz,
jehož vyhodnocením získáme hodnotu tohoto výrazu. Ovšem na levé straně se
nachází výraz (v našem případě je to "jen" proměnná), jehož
vyhodnocením získáme adresu. Na tuto adresu, představující začátek
paměťového místa pro umístění hodnoty proměnné c
, je umístěna
hodnota z pravé strany přiřazovacího operátoru.
Ještě, než si zadefinujeme zmíněné pojmy, nesmíme zapomenout na důležitou skutečnost. Výsledkem výrazu3 přiřazení je hodnota. Co to znamená? Například možnost elegantně řešit inicializaci více proměnných stejnou hodnotou, například
int a, b, c; a = b = c = -1;
Nezapomínejme, že přiřazovací operátor je asociativní zprava doleva. Nejprve se tedy
vyhodnotí c = -1
, výsledkem je hodnota -1
, ta tvoří pravou
stranu přiřazení b =
, jehož výsledkem je opět -1
. A jak se
uvedená hodnota dostane do proměnné a
, není jistě třeba popisovat. Vraťme se
však k nastíněným pojmům.
Adresový výraz (lvalue - l-hodnota) je výraz, jehož
výpočtem se získá adresa v paměti. Například je-li P
nějaký
výraz vyhodnocený jako nenulový ukazatel, pak *P
je l-hodnota. V souvislosti s
modifikátorem const
rozlišujeme modifikovatelnou a nemodifikovatelnou l-hodnotu.
Hodnotový výraz (rvalue - p-hodnota) je výraz, jehož
výpočtem se získá hodnota jistého typu. Typ je jednoznačně určen typem operandů. Protože
se zejména při číselných výpočtech často setkáváme s operandy
různých typů, uveďme si pravidla, jež určují typ výsledku. Poznamenejme, že naplnění
pravidel testujeme v uvedeném pořadí:
Alespoň jeden z operandů je racionálního typu, pak
- je-li jeden z operandů typu
long double
, je rovněž druhý operand konvertován na tento typ; - je-li jeden z operandů typu
double
, je rovněž druhý operand konvertován na tento typ; - je-li jeden z operandů typu
float
, je rovněž druhý operand konvertován na tento typ.
V opačném případě (operandy jsou celočíselných typů4):
- je-li jeden z operandů typu
unsigned long
, je rovněž druhý operand konvertován na tento typ; - je-li jeden z operandů typu
long
, a druhý typuunsigned int
, je druhý operand konvertován na typlong
(16bitový překladač), nebo jsou oba operandy konvertovány na typunsigned long
(32bitový překladač); - je-li jeden z operandů typu
long
, je rovněž druhý operand konvertován na tento typ; - je-li jeden z operandů typu
unsigned int
, je rovněž druhý operand konvertován na tento typ; - není-li splněna žádná z předchozích podmínek, jsou oba operandy převedeny
na typ
int
.
Výrazy mohou vyvolávat i vedlejší efekty. ANSI norma nedoporučuje používat výrazy, které během vyhodnocení způsobují vícenásobnou změnu obsahu jednoho paměťového místa. Například:
cc = cc++ + 1.
Některé vedlejší efekty mohou být implementačně závislé, třeba výraz
a[i] = i++.
Operátoru přiřazení jsme se věnovali jako prvnímu v pořadí. Důvod je prostý.
Bez tohoto operátoru nemůžeme uchovat výsledky v proměnných. Současně nám tento
operátor bude sloužit i v následujícím výkladu látky, včetně popisu
dalších operátorů.
3.4. Aritmetické operátory - aditivní a multiplikativní.
Aritmetické operátory + - * / %
představují základní
matematické operace sčítání, odčítání, násobení,
dělení a zbytku po (celočíselném) dělení.
Nejlepší ukázkou bude jistě příklad.
/********************************************************************/ /* program op_int01.c */ /* celociselne nasobeni, deleni, zbytek po deleni */ /* navic je ukazano celociselne preteceni (jen pro 16-ti bitove int)*/ /********************************************************************/ #includeint 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 = %dn", o1, o2, v1); printf("%d / %d = %dn", o3, 2, v2); printf("%d %% %d = %dn", o3, 2, v3); vc = c1 + c2; printf("nnyni pozor:nt"); printf("%d + %d = %dn", c1, c2, vc); return 0; }
/* vystup BC31 123 * 456 = -9448 295 / 2 = 147 295 % 2 = 1 nyni pozor: 20000 + 20001 = -25535 */ |
/* vystup MWC + COHERENT 123 * 456 = 56088 295 / 2 = 147 295 % 2 = 1 nyni pozor: 20000 + 20001 = 40001 */ |
Příklad ukazuje nejen inicializaci hodnot proměnných "operandů" o1
o2
a o3
, ale po prvních očekávaných výsledcích i
neočekávané hodnoty5. Ty jsou způsobeny faktem aritmetického přetečení. Dále je vhodné
zdůraznit, že vzhledem k typu operandů int je i typ výsledku stejného typu (viz pravidla
uvedená dříve). Z toho důvodu je i výsledek dělění celočíselný.
Podívejme se nyní na výsledky aritmetických operací, v nichž argumenty jsou opět celočíselné, ale levá strana je racionálního typu.
/**********************************************************************/ /* program op_int_f.c */ /* zakladni aritmeticke operace a prirazeni vysledky jsou sice i float*/ /* ale vypocty jsou provadeny jako int a teprve pote prevedeny */ /**********************************************************************/ #includeint main() { int i, j; float r, x; j = i = 5; j *= i; r = j / 3; x = j * 3; printf("i=%dtj=%dtr=%ftx=%fn", i, j, r, x); return 0; }
/* vystup BC31 i=5 j=25 r=8.000000 x=75.000000 */
Rovněž v tomto příkladu vidíme, že výpočet probíhá s hodnotami typů podle
zmíněných pravidel, a teprve poté je získaná p-hodnota konvertována do
typu odpovídajícího l-hodnotě. Proto podíl 25/3
dává
8.0
a nikoliv 8.333
.
Stejným způsobem probíhají aritmetické operace s racionálními hodnotami.
Chceme-li změnit pořadí vyhodnocení jednotlivých částí výrazu,
použijeme k tomuto "pozměnění priority" kulatých závorek6.
3.5. Logické operátory
Logické operátory představují dvě hodnoty, pravda a nepravda. ANSI norma C říká, že hodnota nepravda je představována 0 (nulou), zatímco pravda 1 (jedničkou)7. Ve druhém případě se ovšem jedná o doporučení, neboť užívaným anachronismem je považovat jakoukoliv nenulovou hodnotu za pravdu.
Logické operátory jsou && || !
, postupně and or a not. Provádějí
výpočet logických výrazů tvořených jejich operandy. Pravidla pro určení
výsledku známe z Booleovy algebry. Logické výrazy často obsahují i
stanovení (a ověření) podmínek tvořených relačními operátory.
.3.6 Relační operátory
Relační operátory jsou < > <= >= == !=
. Pořadě menší,
větší, menší nebo rovno, větší nebo rovno, rovno a nerovno. Jsou
definovány pro operandy všech základních datových typů8. Jejich výsledkem jsou
logické hodnoty pravda a nepravda tak, jak jsou popsány v předchozím odstavci.
3.7. Bitové operátory
Jak sám název napovídá, umožňují provádět operace nad jednotlivými bity. Tuto možnost zdaleka nemají všechny programovací jazyky označované jako vyšší. Jazyk C jí oplývá zejména proto, že byl vytvořen jako nástoj systémového programátora (OS Unix). Použití bitových operátorů vyžaduje znalosti o uložení bitů v paměti, způsobu kódování čísel ... .
Bitové operátory jsou: << >> & | ~ ^
, tedy posun vlevo, posun vpravo,
and, or, not a xor. Bitové operace jsou možné pouze s celočíselnými hodnotami.
Podívejme se nyní na jednotlivé zástupce bitových operátorů.
Při bitovém posunu vlevo (vpravo) <<
, ( >>
) se
jednotlivé bity posouvají vlevo (vpravo), tedy do pozice s (binárně)
vyšším (nižším) řádem. Na nejpravější
(nejlevější) posunem vytvořenou pozici je umístěna nula. Posuny ovšem
probíhají aritmeticky. To znamená, že uvedené pravidlo neplatí pro posun vpravo
hodnoty celočíselného typu se znaménkem. V takovém případě se
nejvyšší bit (znaménkový), zachovává. Takto se při posunu doplňuje
do bitového řetězce nový bit. Naopak před posunem nejlevější (nejpravější)
bit je odeslán do "říše zapomnění".
Bitový posun o jeden (binární) řád vpravo, respektive vlevo, má stejný
význam jako celočíselné dělení, respektive násobení, dvěma. Je-li
bitový posun o více než jeden řád, jedná se o násobení (dělění)
příslušnou mocninou dvou.
Bitové and &
, or |
,
a
xor ^
provádí příslušnou binární operaci s
každým párem odpovídajících si bitů. Výsledek je umístěn do pozice
stejného binárního řádu výsledku. Výsledky operací nad
jednotlivými bity jsou stejné jako v Booleově algebře9. Bitové not ~
je
operátorem unárním, provádí negaci každého bitu v bitovém řetězci
jediného operandu. Tomuto operátoru se často říká bitový doplňek.
/*******************************************************************/ /* program op_bit01.c */ /* ukazuje bitove posuny, a zakladni bitove operace and, or, xor */ /* a bitovy doplnek */ /*******************************************************************/ #includeint main() { printf("1 << 1 = t%dt%#xn", 1 << 1, 1 << 1); printf("1 << 7 = t%dt%#xn", 1 << 7, 1 << 7); printf("-1 >> 1 = t%dt%#xn", -1 >> 1, -1 >> 1); printf("1024 >> 9 = t%dt%#xn", 1024 >> 9, 1024 >> 9); printf("13 & 6 = t%dt%#xn", 13 & 6, 13 & 6); printf("13 | 6 = t%dt%#xn", 13 | 6, 13 | 6); printf("13 ^ 6 = t%dt%#xn", 13 ^ 6, 13 ^ 6); printf("2 & 1 = t%dt%#xn", 2 & 1, 2 & 1); printf("2 | 1 = t%dt%#xn", 2 | 1, 2 | 1); printf("2 ^ 1 = t%dt%#xn", 2 ^ 1, 2 ^ 1); return 0; }
/* BC31 - 16-ti 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
*/
Jestliže jsme v úvodu k bitovým operátorům naznačili jejich systémovou orientaci, ukažme ji na příkladu. Často popisujeme rozdíly mezi 16 a 32bitovým kódem (překladačem, který kód generuje). Následující program nám umožní zjistit, s jakým překladačem máme čest.
/************************************************/ /* soubor int_size.c */ /* zjisti kolika bitove int pouziva prekladac */ /************************************************/ #includeint main(void) { unsigned int ui = ~0; int i = 1; while (ui >>= 1) i++; printf("prekladac pouziva %2d-ti bitovou reprezentaci celeho cislan", i); return 0; }
Povšimněme si použití bitového doplňku ui = ~0
. Tak snadno (a zejména
přenositelně) získáme bitový řetězec tvořený samými jedničkami.
3.8. Adresový operátor
Tento operátor &
je unární. Jak již název adresový
operátor napovídá, umožňuje získat adresu objektu, na nějž je aplikován. Adresu
objektu můžeme použít v nejrůznějších situacích, obvykle je to ale v souvislosti s
ukazateli. Bez tohoto operátoru bychom nebyli schopni pracovat se soubory a ani standardní vstup
bychom nebyli schopni číst jinak než po znacích. Takto například můžeme přečíst hodnoty
dvou proměnných jedinou funkcí pro formátovaný vstup:
int i; float f; scanf("%d %f", &i, &f);
3.9. Podmíněný operátor
Podmíněný operátor je poměrně nezvyklý. Proto bude vhodné, objasníme-li si jeho význam. Mějme například výpočet, který potřebujeme provést, v závislosti na nějaké podmínce, jednou ze dvou variant (pochopitelně odlišných). Výsledek výpočtu přiřazujeme vždy stejné proměnné. Pokud navíc je část výrazu popisující výpočet obou variant shodná, jedná se o typický příklad využití podmíněného výrazu.
Buďme však raději konkrétnější. Chceme-li vypočíst absolutní hodnotu nějakého čísla, použijeme velmi pravděpodobně podmíněný operátor. Výpis zdrojového textu takového výpočtu následuje:
/**********************************/ /* soubor op_cond.c */ /* ukazuje pouziti podmineneho */ /* operatoru - absolutni hodnota */ /**********************************/ #includeint main(void) { int i, abs_i; printf("nZadej cele cislo: "); scanf("%d", &i); abs_i = (i < 0) ? -i : i; printf("abs(%d) = %dn", i, abs_i); return 0; }
Další použití (ternálního) podmíněného operátoru ukazuje následující řádek.
return((znak == 0x0) ? getch() + 0x100 : znak);
Tento řádek zajišťuje případné načtení a překódování libovolné stisknuté klávesy na tzv. rozšířené klávesnici IBM PC AT. Základní klávesy této klávesnice produkují kód odpovídající jejich pozici v ASCII tabulce. Rozšířené klávesy produkují dvojici celočíselných kódů, z nichž první je nula. Než si popíšeme jeho činnost, doplňme i předcházející příkaz pro načtení znaku:
znak = getch(); return((znak == 0x0) ? getch() + 0x100 : znak);
Pomocí podmínky (znak == 0x0)
otestujeme, jednalo-li se o rozšířenou
klávesu. Jestliže ano, je proveden první příkaz následující za
podmíněným operátorem, getch() + 0x100
. Je jím načten tzv. scan kód
rozšířené klávesy, který je dále zvětšen o hodnotu 100hex. Jestliže
podmínka splněna nebyla, je vykonán příkaz za dvojtečkou. Návratovou hodnotou je pak
neupravená hodnota načtená do proměnné znak
těsně před ternálním
operátorem.
3.10. Operátor čárka
Čárka je operátorem postupného vyhodnocení. Má nejnižšší
prioritu ze všech operátorů a vyhodnocuje se zleva doprava10. Čárkou můžeme oddělit jednotlivé
výrazy v místě, kde je očekáván jediný výraz, například v cyklu
for
.
3.11. Přetypování výrazu
Jazyk C nám umožňuje přetypovat výrazy podle naší potřeby. Přetypování má přirozeně svá omezení, ta však často plně odpovídají zdravému rozumu. Těžko se například vyskytne potřeba přetypovat znak na typ ukazatel na double.
Úvodní úvaha postrádá konkrétní ukázku, z níž by
vyplýval syntaktický zápis přetypování. Připomeňme si příklad
op_int_f.c
, v němž se vyskytoval příkaz
r = j / 3;
který byl ovšem vyhodnocen celočíselně a teprve poté konvertován na
float
. Chceme-li, aby již podíl proběhl v racionálním oboru, musíme pravou
stranu upravit. S přetypováním může vypadat pravá strana takto:
r = (float) j / 3;
Přetypování provádíme tak, že před hodnotu, kterou chceme přetypovat, napíšeme typ, který chceme získat, v kulatých závorkách. Syntakticky tedy zapíšeme přetypování takto:
(type) expression
Výsvětlivky:
1 Předpokládejme, že použité proměnné jsou vhodného typu, například
double
..
2 Nesmíme se nechat svést jinými programovacími jazyky. Jedná se o jeden z
nejčastěji se vyskytujících operátorů, proto mu náleží zápis
jediným symbolem.
3 Podrobněji se výrazům budeme věnovat v kapitole 6. Řízení chodu programu.
Zatím tolik, že příkaz je výraz zakončený středníkem.
4 Celočíselné typy jsou jak všechny varianty typu int
, tak za
celočíselný typ považujeme typ char
. Jak jsme v předchozí kapitole viděli, můžeme
i u měj použít modifikátory unsigned
a signed
. Ve výrazech je
konvertován na typ int
plus případné znaménko.
5 V případě 16bitového překladače. 32bitový dává správné
výsledky.
6 Je-li potřeba více úrovní závorek, používáme vždy jen
závorek kulatých. Hranaté a složené závorky představují operátory
jazyka C s jiným významem, než je stanovení pořadí vyhodnocení.
7 Jedná se o celočíselné hodnoty.
8 Ovšem například pro ukazatele mají smysl pouze operátory rovnosti a
nerovnosti.
9 Připomeňme alespoň, že xor dává výsledek 1 jen v případě různosti hodnot
operandů.
10 Tento operátor zajišťuje pořadí vyhodnocení zleva doprava. Výsledkem je
hodnota nejpravějšího výrazu ze seznamu výrazů oddělených čárkami.
petr.saloun@ vsb.cz
DOWNLOAD & Odkazy
- Domovská stránka autora - http://www.cs.vsb.cz/saloun/publik/c4zelenace/
- Další užitečné zdroje týkající se tématu - index.html
- Obsah kurzu - index.html