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.
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ů.
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ů.
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
long double
, je
rovněž druhý operand konvertován na tento typ. double
, je
rovněž druhý operand konvertován na tento typ. float
, je
rovněž druhý operand konvertován na tento typ. unsigned long
,
je rovněž druhý operand konvertován na tento typ. long
, a druhý
typu unsigned int
, je druhý operand
konvertován na typ long
(16-bitový
překladač), nebo jsou oba operandy konvertovány na typ unsigned long
(32-bitový překladač). long
, je
rovněž druhý operand konvertován na tento typ. unsigned int
,
je rovněž druhý operand konvertován na tento 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 v pořadí prvnímu.
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ů.
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)*/ /********************************************************************/ #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("\nnyni pozor:\n\t"); printf("%d + %d = %d\n", c1, c2, vc); return 0; }
|
|
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 */ /**********************************************************************/ #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
*/
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.
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í 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.
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.
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 zapomě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 */ /*******************************************************************/ #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-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-ti a 32-bitový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 */ /************************************************/ #include <stdio.h> int main(void) { unsigned int ui = ~0; int i = 1; while (ui >>= 1) i++; printf("prekladac pouziva %2d-ti bitovou reprezentaci celeho cisla\n", 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.
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);
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 */ /**********************************/ #include <stdio.h> int main(void) { int i, abs_i; printf("\nZadej cele cislo: "); scanf("%d", &i); abs_i = (i < 0) ? -i : i; printf("abs(%d) = %d\n", 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.
Čá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
.
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ě 16-ti bitového
překladače. 32-bitový 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.
Název: | Programování v jazyce C |
Autor: | Petr Šaloun |
Do HTML převedl: | Kristian Wiglasz |
Poslední úprava: | 19.11.1996 |