.pl 60 .po 12 .mt 2 .mb 2 .pn 29 KAPITOLA 2: TYPY, OPERATORY A VYRAZY ------------------------------------ Promenne a konstanty jsou zakladni datove objekty, se kte- rymi se v programu pracuje. V deklaracich jsou vyjmenovany promenne, ktere budou pouzivany a je urcen jejich typ a nekdy i jejich pocatecni hodnoty. Operatory urcuji, jak s nimi bude nalozeno. Vyrazy obsahuji promenne a operatory a vysledkem je nova hodnota. To je predmetem teto kapitoly. 2.1 Nazvy promennych -------------------- Prestoze jsme si to jeste nerekli,tak pro nazvy promennych a symbolickych konstant plati jista omezeni. Nazvy se skladaji z pismen a cislic, prvni znak musi byt pisme- no.Podtrzeni "_" je chapano jako pismeno: je uzitecne pro zpre- hledneni dlouhych jmen nekterych promennych. Velka a mala pis- mena se od sebe lisi: v jazyku C je tradici pouzivat mala pis- mena pro jmena promennych a velka pismena pro symbolicke konstanty. Jen prvnich osm znaku jmen je vyznamnych, ackoliv jich muze byt pouzito vice. Pro externi nazvy,jako jsou jmena funkci a promennych, musi byt nekdy pocet znaku mensi, protoze jsou tato jmena pouzivana ruznymi assemblery a sestavovacimi pro- gramy. Podrobnosti jsou v dodatku A. Klicova slova, jako jsou if, else, int, float,atd. jsou rezervovana a nelze je pouzivat pro jmena promennych /musi byt psana malymi pismeny/. Je prirozene vyhodne volit jmena promennych tak, aby byl jasny jejich vyznam a aby nebylo snadne je zamenit navzajem. 2.2 Typy dat a jejich delka --------------------------- V jazyku C je jen nekolik zakladnich typu dat: char jeden byte, muze obsahovat jeden znak mistniho souboru znaku int celociselna promenna, delka odpovida delce celych cisel na danem pocitaci float cislo s pohyblivou desetinou carkou a jednoduchou presnosti double cislo s pohyblivou desetinou carkou a dvojnasobnou presnosti Navic je jeste nekolik kvalifikatoru, ktere mohou byt aplikovany na typ int: short, long a unsigned. short a long odpovidaji ruznym delkam celociselnych promennych. Cisla unsigned splnuji pravidla aritmeticke funkce modulo 2**n, kde n je pocet bitu promenne int. Tato cisla jsou vzdy kladna. Deklaracni prikazy vypadaji takto Š.po 3 short int x; long int y; unsigned int z; Slovo int muze byt vynechano a obycejne v takovych pripa- dech nebyva. Presnost techto typu promennych zalezi na konkretnim typu pocitace. V tabulce jsou uvedeny nektere typy. DEC PDP-11 Honeywell 6000 IBM 370 Interdata 8/32 ASCII ASCII EBCDIC ASCII char 8 bitu 9 bitu 8 bitu 8 bitu int 16 36 32 32 short 16 36 16 16 long 32 36 32 32 float 32 36 32 32 double 64 72 64 64 short a long by mely byt ruzne dlouhe tam, kde je to vyhodne. Int obycejne predstavuje "prirozenou" delku pro dany typ pocitace. Jak vidite, kazdy prekladac jazyka C interpretuje delku long a short ruzne v zavislosti na typu pocitace. Jedine, s cim muzete pocitat je, ze short neni delsi nez long. 2.3 Konstanty ------------- Konstanty typu int a float jsme jiz pouzivali, takze upozorneme na nezvykle tvary 123.456e-7 nebo 0.12E3 Tento tvar konstant se nazyva "vedecka" notace. Kazda konstanta s pohyblivou radovou carkou je chapana jako double, takze nota- ce "e" slouzi jak pro float, tak pro double. Dlouha cela cisla se pisi s priponou L. Obycejna celo- ciselna konstanta, ktera je delsi nez short je rovnez chapana jako long. Existuje take moznost zapisu oktalovych a hexadecimal- nich konstant: je-li na prvni pozici konstanty 0 /nula/, tak je konstanta brana jako oktalova; jsou-li na zacatku znaky 0x nebo 0X, tak se jedna o hexadecimalni promennou. Napr. dekadicke cislo 31 muze byt psano oktalove jako 037 a hexadecimalne 0xlf nebo 0X1F. Oktalove a hexadecimalni konstanty mohou mit rovnez priponu L pro oznaceni typu long. Znakova konstanta je jeden znak napsany v apostrofech, napr. 'x'. Hodnota znakove konstanty je numericka hodnota toho- Š.po 12 to znaku v souboru znaku daneho pocitace. Napr. ve znakovem souboru ASCII ma znak nula, nebo '0' hodnotu 48 a v EBCDIC '0' je 240. Oboji je velice odlisne od ciselne hodnoty 0. Napiseme-li v programu '0' misto 48 nebo 240, tak ucinime program nezavislym na konkretni reprezentaci. Znakova konstanta se muze zucastnit ciselnych operaci jako ostatni cisla, presto- ze se pouziva spise pro porovnavani s ostatnimi znaky. V nasle- dujici casti se hovori o konverznich pravidlech. Urcite netistitelne znaky mohou byt reprezentovany se- kvenci, ktera zacina obracenym lomitkem napr. \n (znak nove ra- dky)/, \t (tabelator), \0 (prazdny znak), \\ (obracene lo- mitko), \' (apostrof) atd., ktere vypada jako dva znaky, ale ve skutecnosti to je jen jeden znak. Navic muze byt libovolny vzor bitu v bytu generovan sekvenci: '\ddd' kde ddd jsou jedno az tri oktalova cisla, jako napr. #define FORMFEED '\014' (# ASCII Form Feed) Znakova konstanta '\0' predstavuje znak s nulovou hodnotou '\0' piseme casto misto 0, abychom zduraznili vlastnost urci- teho vyrazu. Konstantni vyraz je vyraz, ktery obsahuje pouze konstanty a operatory. Takovy vyraz je vyhodnocovan jiz ve fazi prekla- du a ne pri behu programu a muze byt pouzit vsude tam, kde se muze objevit konstanta, jako napr. #define MAXLINE 1000 char line [MAXLINE + 1]; nebo v seconds = 60*60*hours; Znakova konstanta je sekvence nekolika znaku jako napr. "I am a string" "" /*prazdny retezec*/ Uvozovky nejsou soucasti retezce, slouzi jenom k jeho omeze- ni. Sekvence s obracenym lomitkem muze byt soucasti retezce: napr. \" reprezentuje znak uvozovky. Z technickeho hlediska je retezec pole, jehoz prvky jsou jednotlive znaky. Prekladac automaticky ukoncuje kazdy rete- zec prazdnym znakem \0, takze program muze snadno detekovat konec retezec. Tato reprezentace tudiz nijak neomezuje delku retezce, ale program musi cele pole projit, aby urcil jeho delku. Skutecna pamet potrebna pro retezec je o jednu pozici delsi, coz je fakticka delka retezce. Nasledujici funkce s t r l e n (s) urcuje delku retezce s a nebere pri tom v uvahu znak \0. strlen (s) /*zjisteni delky retezce*/ char s []; { int i; i = 0; while (s[i] != '\0') Š.po 3 ++i; return (i); } Budte opatrni pri rozlisovani znakove konstanty a retezce, kte- ry obsahuje jeden znak: 'x' neni totez jako "x". 'x' je jeden znak, ktery je v tomto tvaru ciselnou reprezentaci znaku x v souboru znaku pocitace. "x" je znakovy retezec, ktery obsahu- je znak x /pismeno x/ a znak \0. 2.4. Deklarace -------------- Vsechny promenne musi byt pred pouzitim deklarovany, ackoliv nektere deklarace mohou byt podle kontextu implicitni. Deklara- ce specifikuje typ a za nim nasleduje seznam jedne nebo vice promennych tohoto typu, jako napr.: int lower, upper, step; char c, line [1000]; Promenne mohou byt umisteny v deklaracich v libovolnem poradi, takze usporadani muze byt i nasledovane: int lower; int upper; int step; char c; char line [1000]; Tento zpusob zapisu zabira sice vice mista textu pro- gramu, ale je vyhodny, chceme-li ke kazde promenne pridat ko- mentar nebo chceme-li provadet modifikace. Promenne mohou byt rovnez v deklaracich inicializovany. Inicializece se provadi timto zpusobem: char backslash = '\\'; int i = 0; float eps = 1.0e-5; Jestlize je inicializovana promenna externi nebo staticka, ini- cializace je provedena jen jednou, a to pred zacatkem cinnosti programu. Automaticke promenne jsou inicializovany vzdy, kdyz jsou funkce vyvolany. Automaticke promenne, ktere nejsou expli- citne definovany, maji nedefinovanou hodnotu. Externi a static- ke promenne maji implicitne nulovou hodnotu, ale je dobrym stylem je stejne inicializovat. Podrobneji se problemem ini- cializace budeme zabyvat dale. 2.5. Aritmeticke operatory --------------------------- Binarni aritmeticke operatory jsou +, -, *, / a operator modulo %. Existuje unarni - ale neexistuje unarni +. Š.po 12 Celociselne deleni x/y odrezava desetinnou cast. Vyraz x % y produkuje zbytek po deleni x%y a je nula tehdy, kdyz x je delitelne y. Napr. rok je prechodny, kdyz je delitelny 4 a neni delitelny 100. Roky delitelne 400 jsou take prechodne. Proto if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0) je prechodny rok ---------------- else neni prechodny rok ------------------- Operator % nemuze byt pouzit pro promenne float nebo double. Operatory + a - maji stejnou prioritu, ktera je nizsi nez priorita operatotu *, / a %. Unarni minus ma prioritu nejvyssi. Aritmeticke operatory stejne priority se vykonavaji zleva doprava /na konci kapitoly je uvedena tabulka, ktera shrnuje prioritu a asociativnost vsech operatoru/. Sled vycislovani ne- ni urcen pro asociativni a komutativni operatory jako jsou + a -. Prekladac proto muze vyrazy s temito operetory presku- pit. Proto vyraz a+(b+c) muze byt vycislen ve tvaru (a+b)+c. Druhy zpusob je vyhodnejsi, protoze neni potreba pouzivat pomocnou promennou. 2.6. Relacni operatory ---------------------- Relacni operatory jsou > >= < <= Vsechny maji stejnou prioritu. Nizsi a navzajem shodnou prioritu maji operatory rovnosti a nerovnosti. == != Relacni operatory maji nizsi prioritu nez aritmeticke operatory, takze vyrazy jako i < lim-1 jsou chapany jako i < (lim-1), jak muze byt nakonec ocekavano. Dalsimi operatory jsou && a ||. Vyrazy s temito operatory jsou vyhodnocovany zleva doprava a vyhodnocovani skonci tehdy, je-li zrejme, ze vyraz je pravdivy nebo nepravdivy. Tuto vlast- nost je si treba uvedomit pri psani programu. Podivame-li se na prikaz cyklu funkce getline, kterou jsme vytvorili v kap. 1 for(i=0; i < lim-1 && (c=getchar()) != '\n' && c != EOF; ++i) s[i] = c; je zrejme, ze test i < lim-1 m u s i byt vykonan drive nez nacteme dalsi znak. A ne jenom to. Kdyz totiz neni tato podmin- ka splnena, n e s m i m e cist dalsi znak. Obdobne budeme v nesnazich, pokusime-li se porovnavat znak c s EOF predtim, nez zavolame getchar. Volani se musi objevit predtim, nez testujeme konec souboru. Priorita operatoru && je vyssi nez priorita operatoru ||. Oba tyto oparatory maji nizsi prioritu nez relacni operatory Š.po 3 a operatory rovnosti a nerovnosti, takze vyraz: i < lim-1 && (c=getchar()) != '\n' && c != EOF nepotrebuje dalsi zavorky, protoze priorita != je vyssi nez prirazeni, zavorky jsou nutne v (c=getchar()) != '\n' abychom dosahli pozadovaneho efektu. Unarni operator negace ! prevadi nenulove, nebo-li pravdi- ve operatory, na 0, a nulove, nebo-li nepravdive operatory, na 1. Rozsirenym pouzitim operatoru ! je vyraz jako if (! inword) spise nez if (inword == 0) Je tezke stanovit obecna pravidla, ktera forma je lepsi. Vyraz jako ! inword, lze snadno cist /"jestlize neni slovo"/, ale komplikovanejsim vyrazum je nekdy tezke porozumet. C v i c e n i 2-1. Napiste prikaz cyklu ekvivalentni cyklu for v predchozi ukazce bez uziti &&. 2.7. Konverze typu ------------------ Jestlize se ve vyrazech objevuji promenne ruznych typu, tak jsou konvertovany na spolecny typ, podle nekolika pravidel. Automaticky jsou provadeny pouze konverze, ktere maji smysl, jako je konvertovani cisla typu int na typ float ve vyrazu f+i. Vyrazy, ktere nemaji smysl, jako napr. uziti float jako indexu, nejsou povoleny. Typy char a int mohou byt v aritmetickych vy- razech libovolne promichany; kazda promenna char je konvertova- na na int. To umoznuje dostatecnou flexibilitu v urcitych dru- zich znakove transformace. To je uzito ve funkci a t o i , ktera konvertuje retezec cislic na ciselny ekvivalent: atoi(s) /*konvertovani s na cela cisla*/ char s[]; { int i, n; n = 0; for(i=0; s[i] >= '0' && s[i] <= '9'; ++i) n = 10 * n + s[i] - '0'; return(n); } Jak bylo ukazano v kapitole 1, vyraz: s[i] - '0' Š.po 12 je numerickou hodnotou znaku ulozeneho v s[i], protoze hodnoty znaku '0', '1',... tvori vzestupnou, neprerusenou radu kladnych cisel. Jinym prikladem konverze char na int je funkce l o w e r , ktera prevadi velka pismena na mala /plati pouze pro ASCII!/. Jestlize znak neni velke pismeno, vraci jej funkce lower nezme- neno. lower(c) /*prevod c na male pismeno, pouze pro ASCII*/ int c; { if(c >= 'A' && c <= 'Z') return(c + 'a' - 'A'); else return(c); } Tato fukce muze pracovat pouze se znakovym souborem ASCII, pro- toze velka a mala pismena maji od sebe konstantni vzdalenost a ciselne hodnoty obou abeced jsou ve vzestupnem poradi a mezi 'A' a 'Z' neni nic jineho nez pismena. Posledni podminka nepla- ti pro EBCDIC /IBM 360/370/. Jeden problem je ale s konverzi velkych pismen na mala spo- jen. V jazyku c neni definovano, zda je promenna char se zna- menkem nebo bez neho. Konvertujeme-li char na int, bude vysle- dek k l a d n y nebo z a p o r n y ? Nanestesti se to lisi- podle typu pocitace. Na nekterych /napr. PDP-11/ je promenna char, jejiz levy bit je 1, konvertovana na zaporne cele cislo /rozsireni znamenka/. Na jinych pocitacich je char doplnen zle- va nulami a vysledek je proto kladny. Definice jazyka C garantuje, ze libovolny znak v souboru znaku pocitace nebude nikdy zaporny, takze tyto znaky mohou byt volne pouzity ve vyrazech jako kladna cisla. Naproti tomu pro- menna, ktere jsme priradili nejaky bitovy obrazec, muze byt na nekterych typech pocitacu zaporna, na jinych kladna. Casto se tato situace vyskytuje v pripade je-li pro EOF po- uzita hodnota -1. Uvazme pripad: char c; c = getchar(); if (c == EOF) ... Na pocitacich, ktere nemaji rozsirena znamenka, je c vzdy klad- ne, protoze je char a EOF zaporne. Vysledkem je to, ze test c == EOF nemuze byt nikdy pravdivy. Abychom se tomuto problemu vyhnuli musime pro promennou, ktera obsahuje hodnotu danou funkci getchar, deklarovat misto char typem int. Pravy duvod pro uziti int misto char nema co delat s otazkou pravdepodobneho rozsireni znamenka. Je to proto, ze funkce getchar musi vracet vsechny mozne znaky /aby mohla byt pouzita pro cteni libovolneho vstupu/ a navic odlisne hodnoty pro EOF. Proto promenna, ktere je funkce prirazena, n e s m i byt typu char, ale musi byt definovana jako int. Jinou uzitecnou formou automaticke konverze typu je to, ze relace jako i>j a logicke vyrazy s operatory && a || maji hod- Š.po 3 notu 1 jsou-li pravdive a 0 nejsou-li pravdive; proto prirazeni: isdigit = c >= '0' && c <= '9'; nastavuje promennou isdigit na 1, je-li c cislice, a na 0, neni-li cislici. /V podminkach prikazu if, while, for atd. "pravdivy" znamena "nenulovy"./ Implicitni aritmeticke konverze funguji prevazne tak, jak ocekavame. Obecne receno, v operacich s binarnimi operatory /+, * atd./ je operand "nizsiho" typu konvertovan na "vyssi" predtim, nez dojde k vycisleni. Vysledek je rovnez vyssiho ty- pu. Pro kazdy aritmeticky operatator plati nasledujici soubor pravidel: char a short jsou konvertovany na int, float je konver- tovano na double. Potom, jestlize je nejaky operand double, jsou ostatni operandy konvertovany na double a vysledek je double. Jinak, jestlize je nejaky operand long, ostatni jsou konvertovany na long a vysledek je long. Jinak, jestlize je nejaky operand unsigned, ostatni jsou konvertovany na unsigned a vysledek je unsigned. Jinak operandy musi byt int a vysledek je int. Uvedomte si, ze vsechny promenne float jsou konvertovany na double; v jazyku C se uplatnuje jedine aritmetika s dvojnasob- nou presnosti. Konverze se rovnez uplatnuji pri porizovani; hodnota prave strany je konvertovana na typ promenne na leve strane, coz je typem vysledku. Znak je konvertovan na cele cislo, at uz plati pravidlo rozsireni znamenka nebo ne, jak uz bylo psano drive. Opacne prirazeni - int na char je bez problemu. Prebytecne bity vyssich radu jsou vypusteny. Proto v int i; char c; i = c; c = i; je hodnota c nezmenena, at jiz plati pravidlo rozsireni znamen- ka nebo ne. Jestlize je x float a i je int, potom jsou vyrazy x = i a i = x konvertovany; float na int odrezava desetinnou cast, double na float je konvertovano zaokrouhlenim, long jsou konvertovany na int nebo char odriznutim hornich bitu. Protoze argument funkce je vyraz, je rovnez provadena konverze pri predavani: char a short na int, float na double. Proto tedy deklarujeme argumenty funkce jako int a double, i kdyz potom funkci volame s char nebo float. Š.po 12 Na zaver budiz receno, ze konverze lze provadet rovnez ex- plicitne zpusobem zvanym v l a s t n o s t . V konstrukci (nazev typu) vyraz je v y r a z konvertovan na vyjmenovany typ podle vyse uvede- nych pravidel. Presny vyznam v l a s t n o s t i je v tom, ze hodnota vyrazu je prirazena promenne specifikovaneho typu a ta je potom pouzita misto celeho vyrazu. Napr. argument knihovni funkce s q r t je promenna typu double a jestize vyvolame tuto funkci s argumentem jineho typu, dostaneme nesmy- slny vysledek. Proto, je-li n cele cislo, tak sqrt ((double)n) prevadi n na double predtim, nez je argument predan funkci sqrt /uvedomnte si, ze vlastnost vytvari h o d n o t u n spravne- ho typu; skutecny obsah promenne n je nezmenen/. Operator vlastnosti ma stejnou prioritu jako ostatni zname operatory, jak je shrnuto v tabulce na konci teto kapitoly. 2.8. Operatory pricteni a odecteni jednicky ------------------------------------------- V jazyce C jsou dva nezvykle operatory pro prirustek a zmen- seni promennych. Operator prirustku ++ pridava ke svemu ope- randu 1; operator zmenseni ubira 1. Operator ++ jsme casto uzivali pro zvetsovani promennych, jako napr. if(c == '\n') ++nl; Neobvyklym rysem je to, ze ++ a -- muhou byt pouzity bud pred operandem /++n/ nebo za operandem /n++/. V obou pripadech je operand zvetsen o 1. Vyraz ++n zvetsuje n p r e d pouzitim jeho hodnoty a n++ zvetsuje n p o t o m, co jeho hodnota byla pouzita. To znamena, ze v kontextu, kde ma byt pouzita hodnota promenne tak konstrukce ++n a n++ jsou odlisne. Jestize n je 5 potom po x = n++; je x 5, zatim co po x = ++n; je x 6. Operatory ++ a -- mohou byt aplikovany pouze na promen- ne. V pripadech, ze nepotrebujeme ve vyrazech pouzivat hodnotu promenne, jako v if(c == '\n') n1++; si muzeme zvolit bud ++nl, nebo nl++. Vyrazy jako x=(i+j)++ jsou nepripustne. Š.po 3 Jsou ovsem situace, kdy potrebujeme pouzivat prave jednu z techto moznosti. Podivejme se napr. na funkci squeeze(s, c), ktera vymazava znak c z retezce s. sgueeze(s, c) /*vymaze vsechna c ze s*/ char s[]; int c; { int i, j, for(i = j = 0; s[i] != '\0'; i++) if (s[i] != c) s[j++]=s[i]; s[j] = '\0'; } Kazdy znak, ktery neni pismenem c je kopirovan na pozici j, a tehdy je take promenna zvetsena o jednicku a je pripra- vena na dalsi znak. To je ekvivalentni prikazum if(s[i] != c) { s[j]=s[i]; j++; } Jinym prikladem je cast funkce getline, kterou jsme vytvo- rili v kapitole 1, kde muzeme prikazy if (c == '\n') { s[i] = c; ++i; } prepsat kompaktneji takto: if (c == '\n') s[i++] = c; Ve tretim priklade funkce strcat ( s, t) pripojuje retezec t na konec retezce s. Predpokladame, ze v retezci s je dostatek mista pro vysledek: strcat(s,t) /*pripojeni t na konec s*/ char s[], t[]; /*s musi byt dostatecne velke*/ { int i, j; i = j = 0; while(s[i] != '\0') /*nalezeni konce s*/ i++; while((s[i++]=t[j++]) != '\0') /*kopiruj t*/ ; } Kdyz jsou znaky kopirovany z t do s, tak operater ++ za pro- mennymi i, j pripravuje bezprostredne kopirovani dalsiho znaku. Š.po 12 C v i c e n i 2-3. Napiste modifikaci funkce squeeze (s1, s2), ktera zrusi vsechny znaky v s1, ktere jsou shodne se znaky v retezci s2. C v i c e n i 2-4. Napiste funkci any (s1, s2), ktera vrati pozici prvniho vyskytu retezce s2 v retezci s1; nebo -1, jestlize s1 neobsahuje s2. 2.9 Bitove logicke operatory ---------------------------- V jazyku C jsou k dispozici operatory pro manipulaci s bity. Tyto operatory nemohou byt aplikovany na promenne float nebo double. & bitove AND | bitove OR ^ bitove exkluzivni OR << posun doleva >> posun doprava ` jednickovy doplnek Bitove AND, &, je casto pouzivano pro vynulovani urcitych bitu. Napr.: c = n & 0177; vynuluje vsechny bity promenne n krome nejnizsich 7 bitu. Bitove OR, |, je pouzivano pro nastaveni bitu x = x | MASK; nastavi v x na jednicku ty bity, ktere jsou jednotkove v MASK. S opatrnosti rozlisujte bitove operatory & a | od logickych operatoru && a ||, ktere provadeji vyhodnocovani pravdivosti odleva doprava. Napr. jestlize x je 1 a y je 2, potom x & y je nula, zatimco x && y je jedna /proc?/. Operatory posunu << a >> provadeji posun bitu doleva nebo doprava v levem operandu o pocet bitu, udanych operandem v pra- vo. Proto x << 2 posouva bity promenne x o dve pozice doleva a vyplnuje prazdna mista nulami - to odpovida nasobeni cislem 4. Posun doprava promenne bez znamenka plni prazdna mista nula- mi. Posun doprava promenne se znamenkem vypli prazdna mista na nekterych pocitacich /jako je PDP-11/ jednickovym bitem /aritmeticky posun/, na jinych nulami /logicky posun/. Vysledkem unarni operace operatorem ` je doplnek celeho cis- la; to znamena, ze prevadi bity 0 na 1 a naopak. Tyto operatory se obvykle pouzivaji ve vyrazech jako x & `077 ktery nuluje poslednich sest bitu. Uvedomte si, ze operace x & `077 je nezavisla na delce slova a proto je lepsi ji pou- zivat misto operaci x & 0177700, ktera predpoklada, ze x je sestnactibitove. Vyraz `077 nestoji pri vypoctu zadny cas Š.po 3 navic, protoze je jako konstantni vycislovan jiz pri prekladu. Abychom si ilustrovali pouziti nekterych bitovych operatoru, napiseme funkci g e t b i t s (x, p, n), ktera vraci pole n-bitu promenne x, ktere zacinaji na pozici p. Predpokladejme, ze bitova pozice 0 je vpravo a ze n a p maji rozumne kladne hodnoty. Napr. getbits(x, 4, 3) vraci tri bity na pozicich 4, 3 a 2 zarovnane vpravo. getbits( x, p, n) /*ziskani n bitu od pozice p*/ unsigned x { return((x >> (p+1-n)) & `(`0 << n)); } x>>(p+1-n) presouva pozadovane pole bitu na pravy konec slova. Protoze jsme deklarovali x jako unsigned, bez znamenka, tak prazdna mista jsou vyplnena nulami a nezalezi na typu pocitace, ktery pouzivame. `0 je promenna, ktera ma vsechny bity 1; posun o nb bitu doleva prikazem `0<> & ^ | Jestlize (e1) a (e2) jsou vyrazy, potom e1 op= e2 Š.po 12 je ekvivalentni e1 = (e1) op (e2) s tou vyjimkou, ze (e1) je vycislovano jen jednou. Uvedomte si, ze e2 je v zavorkach. To znamena, ze x *= y+1 je x = x*(y+1) a ne x = x*y+1 Jako priklad uvedeme fukci bitcount, ktera pocita jednickove bity: bitcount(n) /*pocet jednickovych bitu v n*/ unsigned n; { int b; for(b = 0; n != 0; n >>= 1) if (n & 01) b++; return(b); } Prirazovaci operatory maji vyhodu hlavne v tom, ze jsou blizsi mysleni cloveka. Rikame spise "pridej 2 k i" nebo "zvetsi i o 2" nez "vezmi i, pridej 2 a vysledek vloz do i". Proto i += 2. Navic pri slozitych vyrazech jako yyal[yypv[p3+p4]+yypv[p1+p2]] += 2 je tomuto zapisu lepe rozumet, protoze ctenar nemusi otrocky kontrolovat, zda vyrazy na obou stranach jsou shodne nebo ne. Navic prirazovaci operator umoznuje prekladaci generovat efektivnejsi kod. Jiz drive jsme vyuzivali skutecnosti, ze prirazovaci prikaz ma hodnotu, kterou lze pouzivat ve vyrazech. Typickym prikladem je while (c=getchar()) != EOF) Prirazeni uzivajici operator prirazeni / +=, -= , adt./ se rovnez muze objevit ve vyrazech, prestoze je to mene obvykle. Typ operatoru prirazeni je shodny s typem na leve strane. C v i c e n i 2-9. Systemu s dvojkovym doplnkem vyraz x &(x-1) nuluje pravy bit promenne x /proc?/. Pouzijte teto skutecnosti a napiste rychlejsi verzi funkce bitcount. .pa Š.po 3 2.11. Podminene vyrazy ---------------------- Prikaz if(a>b) z=a; else z=b; pocita zajiste maximum z cisla a a b. P o d m i n e n y v y r a z psany s operatorem "?:", umoznuje jinym zpusobem napsat podobnou konstrukci. Ve vyrazu e1?e2:e3 je vyraz e1 spocitan nejdrive. Jestilze je nulovy /pravdivy/, potom je vycislen vyraz e2, ktery je hodnotou podmineneho vy- razu. Jinak je vycislen vyraz e3, ktery je potom hotnotou vy- razu. Jenom jeden z vyrazu e2 a e3 je vyhodnocen. Proto vypo- cet maxima z z a a b muzeme napsat - - - z=(a>b)?a:b; /*z=max(a,b)*/ Dluzno poznamenat, ze podmineny vyraz je skutecny vyraz a muze byt pouzit jako jine vyrazy. Jestlize e2 a e3 jsou odlisneho typu, potom je vysledek zkonvertovan podle pravidel uvedenych v teto kapitole. Je-li napr. f typu float a n je int, potom vyraz (n>0)?f:n je typem double at jiz je n kladne nebo ne. Zavorky kolem prvniho vyrazu nejsou nezbytne, protoze priorita operatoru ?: je velmi mala, prave vyssi nez prirazeni. Je vsak vhodne je pro prehlednost psat. Podminene vyrazy vedou k velmi hutneho kodu. Napr.tento cyklus tiskne N prvku pole, 10 na radku oddelene jednou mezerou a kazda radka /i posledni/ je ukoncena presne jednim znakem pro novou radku. for(i=0;i . zleva doprava | - ++ -- ` (typ) * & zprava doleva * / % zleva doprava + - zleva doprava << >> zleva doprava < <= > >= zleva doprava == != zleva doprava & zleva doprava ^ zleva doprava ! zleva doprava && zleva doprava || zleva doprava ? : zprava doleva = += -= atd. zprava doleva , /kapitola 3/ zleva doprava Operatory -> a . jsou pouzivany pro pristup ke clenum struktur; budou popsany v kapitole 6 spolu se sizeof /velikost objektu/. V kapitole 5 jsou uvedeny operatory * a &. Uvedomte si, ze priorita bitovych operatoru &, ^ a | je nizsi nez == a !=. Z toho vyplyva, ze pri testovani bitu ve vyrazu jako: if((x & MASK) == 0) ... jsou zavorky nezbytne. Jak uz jsme se zminili, vyrazy obsahujici jeden z aso- ciativnich a komutativnich operatoru /*, +, &, ^, |/ mohou byt prekladacem preskupeny i kdyz jsou pouzity zavorky. Ve vetsine pripadu to nevadi; v situacich, kdy by to vadit mohlo, musi byt explicitne pouzity pomocne promenne, aby poradi vycislova- ni probihalo tak, jak si prejeme. Jazyk C, ostatne jako vetsina ostatnich jazyku, nespecifiku- je, v jakem poradi budou operandy operatoru vycisleny. Napr. v prikazu x=f() + g(); muze byt f vycislena drive nez g nebo take naopak; proto jestli jedna z funkci f nebo g meni externi promenne, na kterych zavi- si druha z funkci, muze hodnota x zalezet na poradi vycislova- ni. Znovu opakujeme, ze mezivysledek muze byt ulozen v pomocne promenne, abychom si zajistili spravne poradi vycisleni. Obdobne neni stanoveno poradi, v jakem jsou vycislovany Š.po 3 argumenty funkci, takze prikaz pritf("%d %d \n", ++n, power(2,n)); /*chyba*/ muze produkovat /a take produkuje/ ruzne vysledky na ruznych pocitacich, coz zalezi na tom, je-li n zvetseno pred volanim funkce power nebo ne. Spravny zapis vypada takto: ++n; printf("%d\n", n, power(2,n)); Volani funkci a operatory ++ a -- v prirazovacich prikazech zpusobuji "postranni efekty" - nektera promenna je zmenena jako "vedlejsi produkt" pri vycislovani vyrazu. Ve vyrazech, kde se projevuji vedlejsi efekty, zalezi na tom, jak jsou ulozeny pro- menne, ktere vystupuji ve vyrazu. Jedna nestastna situace vypa- da takto: a[i] = i++; Otazkou je, zda index je stara nebo nova hodnota promenne i. Prekladac muze s timto vyrazem nalozit ruznym zpusobem. Jestlize se projevuji vedlejsi efekty, jsme vydani na milost a nemilost prekladaci, protoze nejlepsi zpusob prekladu zalezi vzdy na architekture pocitace. Moralnim zaverem teto diskuze je to, ze psani programu, kde zalezi na poradi vyhodnocovani, je spatnym programatorskym sty- lem ve vsech jazycich. Je prirozene nutne vedet, kterym vecem se mame vyhnout, ale nevite-li, jak jsou na kterych pocitacich implementovany, pak vas tato nevedomnost muze i zachranit.