| 1. FEJEZET | Tartalom | 3. FEJEZET |
2. FEJEZET:
Típusok, operátorok és kifejezések
A változók és az állandók alkotják a programban feldolgozott alapvető adatobjektumokat. A program deklarációi felsorolják a felhasznált változókat, megadják a típusukat és néha még a kezdeti értéküket is. Az operátorok azt határozzák meg, hogy mit kell csinálni az adatokkal. A kifejezések a változókat és állandókat egy új érték előállítása érdekében kombinálják. Az objektum típusa meghatározza, hogy az objektum milyen értékeket vehet fel és milyen operátorok alkalmazhatók rá. Ebben a fejezetben ezekkel az alapelemekkel foglalkozunk.Az ANSI szabvány csak kis változásokat hozott az alapvető adattípusok és kifejezések terén, és inkább csak bővítette a lehetőségeket. Újdonság az összes egész típusú mennyiségre bevezetettsignedésunsignedforma, valamint az előjel nélküli (unsigned) állandó és a hexadecimális karakteres állandó bevezetése. Az új szabvány szerint a lebegőpontos műveleteket egyszeres pontossággal végzi a gép, és a nagyobb pontosság eléréséhez bevezették alongdoubleadattípust. A karakterlánc-állandók a fordítás során összekapcsolhatók. A felsorolt változók a nyelv részévé váltak a rég bevált tulajdonságok formalizálásával. Deklarálhatóvá vált aconsttípusú objektum, ami ezek változatlanságát jelzi. Az aritmetikai adattípusok közti automatikus konverziós kényszerek bővítik az adattípusok választékát.
2.1. Változónevek
Bár az1. fejezetbennem említettük, de van néhány megszorítás a változók és szimbolikus állandók neveit illetően. A nevek betűkből és számjegyekből állhatnak és az első karakterüknek betűnek kell lenni. Az aláhúzás-karakter ( _ ) betűnek számít, és alkalmazásával sokszor javítható a hosszú változónevek olvashatósága. Változónevet ne kezdjünk aláhúzás-karakterrel, mivel a könyvtári eljárások gyakran használnak ilyen neveket. A nagy- és kisbetűk különböznek egymástól, így azxésXkét különböző nevet jelent. A hagyományos C programozói gyakorlatban a változóneveket kisbetűvel írjuk és a szimbolikus állandókat csupa nagybetűvel.A belső neveknek legalább az első 31 karaktere szignifikáns. A függvények és külső változók neveinek hossza 31-nél kisebb, mert ezek külső nevek, amelyeket az assemblerek és loaderek használnak a nyelvtől függetlenül (ezekre nem vonatkoznak a C nyelv szabályai). A szabvány külső nevek esetén csak 6 karakterig garantálja a megkülönböztethetőséget. A nyelv kulcsszavai (pl.if,else,int,floatstb.) fenntartott szavak és nem lehetnek változónevek. A kulcsszavakat kisbetűvel kell írni.
Célszerű a programban olyan neveket választani, amelyek jelentenek valamit („beszélő nevek”) és írásmódjuk nem zavaró. Érdemes a helyi változókhoz (különösen a ciklusváltozóhoz) rövid, a külső változókhoz hosszabb neveket választani.
2.2. Adattípusok és méretek
A C nyelv viszonylag kevés alapvető adattípust használ:
| char | egyetlen bájt, a gépi karakterkészlet egy elemét tárolja |
| int | egész szám, mérete általában a befogadó számítógép egészek ábrázolásához használt mérete |
| float | egyszeres pontosságú lebegőpontos szám |
| double | kétszeres pontosságú lebegőpontos szám |
Ezekhez az alapvető adattípusokhoz még néhány minősíthető specifikáció járulhat. Ilyen az egész típusokhoz használhatóshortéslongminősítő, pl.
short int sh; long int counter;
Az ilyen deklarációkból azinttípusjelzés elhagyható, és általában el is hagyják.Ashortéslongminősítők bevezetésével az volt a cél, hogy ahol szükséges, két különböző hosszúságú egész álljon a programozó rendelkezésére. Szokásos módon azintmérete megegyezik a használt számítógép alap szóméretével. Ashorttípus általában 16 bites, alongpedig 32 bites, azinttípus 16 vagy 32 bites. Minden fordítóprogram a saját hardverének megfelelően választja meg az adatok méretét, és csak annyi megszorítás van, hogy ashortésintadattípus legalább 16, alongadattípus legalább 32 bites kell legyen, valamint, hogy ashortnem lehet hosszabb az int-nél és az nem lehet hosszabb a long-nál.
Asignedésunsignedminősítők acharés bármely egész adattípus esetén használhatók. Egyunsignedtípusú szám mindig pozitív vagy nulla, és a modulo 2naritmetika szabályai szerint használható, aholnaz adott típus ábrázolásához rendelt bitek száma, így pl. ha achartípust a gép 8 biten ábrázolja, akkor azunsignedchartípus értéke 0 és 255 között, amíg asignedchartípus értéke -128 és +127 között lehet (kettes komplemens kódú ábrázolás esetén). Az, hogy achartípusú adatsignedvagyunsigned-e a géptől függ, de a nyomtatható karakterek mindig pozitívak.
Alongdoubletípus növelt pontosságú lebegőpontos számot jelöl. Az egészekhez hasonlóan a lebegőpontos számok mérete is gépfüggő. Afloat,doubleéslongdoubletípus egyszeres, kétszeres és háromszoros méretet jelenthet.
A<limits.h>és<float.h>szabványos header állományok szimbolikus állandói között mindezen méretűek megtalálhatók, és ezek leírása a géptől, ill. a fordítóprogramtól függő tulajdonságokkal együtt aB. Függelékbentalálható.
2.1.gyakorlat. Írjunk programot, ami meghatározza asignedésunsignedminősítőjűchar,short,intéslongtípusú változók nagyságát a szabványos header állományokból vett megfelelő értékek kiírásával és közvetlen számítással! A feladat nehezebb, ha kiszámítjuk a nagyságokat és tovább nehezíthető, ha a lebegőpontos számok nagyságát is meg akarjuk határozni.
2.3. Állandók
Az1234formában leírt egész állandó minden külön jelzés nélkülinttípusú. Egylongtípusú egész állandó leírásánál viszont a számotl(el) vagyLbetűvel kell zárni, pl.123456789L. Ez a szám túl nagy ahhoz, hogyinttípusú legyen, ezértlonglesz. Az előjel nélküli (unsigned) számokat az utánuk írtuvagyUbetűvel jelöljük, és azulvagyULtoldalékunsignedlongtípust ír elő.A lebegőpontos állandók tizedespontot (pl.123.4) vagy kitevőt (pl.1e-2) vagy mindkettőt tartalmaznak, és alapértelmezésbendoubletípusúak. A lebegőpontos állandó után írtfvagyFfloat,lvagyLlongdoubletípust jelöl.
Egy egész szám értéke nem csak decimálisan, hanem oktális vagy hexadecimális alakban is megadható. A szám elé nullát írva az egész típusú állandó oktális alakú és ha0xjelzést írunk a szám elé, akkor hexadecimális alakú. Például a decimális31oktális alakban037és hexadecimális alakban0x1fvagy0X1F. Az oktális vagy hexadecimális állandó után írt Llongés Uunsignedtípust jelöl: pl.0XFULegy decimálisán15értékűunsignedlongtípusú állandó.
A karakter állandó egy egész típusú adat, amit aposztrófok közé írt egyetlen karakterrel (pl.‘x’) adunk meg. A karakterállandó értéke a karakter gépi karakterkészletbeni kódszáma. Például az ASCII karakterkészletben a‘0’karakterállandó értéke48, ami semmiféle kapcsolatban nincs a0számértékkel. Ha a48kódérték helyett‘0’karakterállandót írunk, akkor a program függetlenné válik a gépi karakterkészlettől és könnyebben olvasható. A karakterállandókat leggyakrabban más karakterekkel való összehasonlításra használjuk, de ugyanúgy részt vehetnek a numerikus műveletekben is, mint bármilyen egész adat.
Bizonyos karakterek a karakterállandókban vagy karaktersorozat-állandókban escape sorozattal adhatók meg, mint pl. a\n, ami az újsor-karaktert jelöli. Ezek az escape sorozatok leírva két karakternek látszanak, de valójában egyetlen karaktert jelölnek. Mindezeken kívül tetszőleges tartalmú, egy bájt méretű bitminta adható meg a
'\ooo'
specifikációval, aholoooegy max. háromjegyű oktális szám számjegyeit jelöli (csak a0…7számjegyek megengedettek), vagy a
'\xhh'
specifikációval, aholhhegy max. kétjegyű hexadecimális szám jegyeit jelöli (a számjegyek0…9,a…fvagyA…Flehetnek). Így minden további nélkül írhatjuk, hogy
#define VTAB '\013' /* a VTAB ASCII kódja */ #define BELL '\007' /* a BELL ASCII kódja */
vagy hexadecimálisan
#define VTAB '\xb' /* a VTAB ASCII kódja */ #define BELL '\x7' /* a BELL ASCII kódja */
Az escape sorozatok teljes listája a következő:
\a figyelmeztető jelzés (bell, csengő) \b visszalépés (backspace) \f lapdobás (formfeed) \n új sor (new line) \r kocsi vissza (carriage return) \t vízszintes tabulátor (horizontal tab, HTAB) \v függőleges tabulátor (vertical tab, VTAB) \\ fordított törtvonal (backlash) \? kérdőjel \' aposztróf \" idézőjel \ooo oktális szám \xhh hexadecimális szám
A‘\0‘karakterállandó egy nulla értékű karaktert, az ún. null-karaktert jelöli. A programokban gyakran írunk‘\0‘-t a0helyett, hogy ezzel is kihangsúlyozzuk a kifejezésen belül az érték karakteres természetét, de természetesen mindkét alak számértéke nulla.Azállandó kifejezéscsak állandókat tartalmaz. Az ilyen kifejezések kiértékelése még a fordítás során megtörténik, ezért bárhol szerepelhetnek, ahol állandó használata megengedett. Ilyen állandó kifejezés pl. a
#define MAXSOR 1000 char sor[MAXSOR+1];
vagy a
#define UGRAS /* pl. ugrás szökőévben */ int napok[31+18+UGRAS+31+30+31+30+31+31+30+31+30+31];
Akaraktersorozat-állandó (string-konstans)nulla vagy több karakterből áll, amelyek idézőjelek között helyezkednek el. Ilyen pl. a
"Ez egy karaktersorozat"
vagy a
"" /* ez egy üres karaktersorozat */
Az idézőjelek nem részei a karaktersorozatnak, csak határolják azt. A karaktersorozat-állandókban ugyanazok az escape sorozatok használhatók, mint a karakterállandóknál, ezért a\”egy idézőjelet jelent (mivel az idézőjel határolja a karaktersorozatot, ezért annak belsejében idézőjel csak a\”escape sorozattal íratható ki). A karaktersorozatállandók a fordítás során összekapcsolhatók (konkatenálhatók), így a
"Halló" "mindenki!"
egyenértékű a
"Halló mindenki!"
karaktersorozattal. Ez lehetőséget nyújt arra, hogy a forrásprogram túl hosszú sorait több, rövid sorra bontsuk.Gyakorlatilag a karaktersorozat-állandó egy karakterből álló tömb. Egy karaktersorozat belső ábrázolásában a sorozatot a‘\0‘null-karakter zárja, így a tárolásukhoz szükséges tárolóhelyek száma csak eggyel több, mint a karakterek száma. Ez a belső ábrázolásmód azt eredményezi, hogy nincs korlátozva a karaktersorozat hossza, de így a programoknak a teljes karaktersorozatot végig kell nézni ahhoz, hogy meghatározzák a tényleges hosszt. A standard könyvtárstrlen(s)függvénye visszatéréskor megadja a karaktersorozat típusúsargumentumának a hosszát (a záró‘\0‘null-karaktert nem számítja bele a hosszba). A függvény általunk írt változata:
/* strlen: az s karaktersorozat hosszát adja */ int strlen(char s[ ]) { int i; i = 0; while (s[i] != '\0') ++i; return i; }
Azstrlenés még több más karaktersorozat-kezelő függvény is a<string.h>szabványos headerben található.Ügyeljünk arra, hogy megkülönböztessük a karakterállandót és az egyetlen karaktert tartalmazó karaktersorozatot:‘x’nem azonos az„x”-szel! Az első egy egész mennyiség, amelynek számértéke azxbetű gépi karakterkészletben kódja, amíg a második egy egyetlen karaktert (azxbetűt) és a lezáró‘\0‘null-karaktert tartalmazó tömb.
Az állandók eddigiektől eltérő fajtája afelsorolt állandó. A felsorolt állandó egész értékek listájából áll, mint pl. az
enum boolean {NO, YES};
Azenumlistájában az első név értéke0, a másodiké1és így tovább, kivéve ha az értékeket explicit módon specifikáljuk. Ha a listában nem minden értéket specifikáltunk, akkor a nem specifikált elemek az utolsó specifikált elemtől kezdve folyamatosan a következő értéket kapják, mint ez az alábbik közül a második példában látható:
enum espaces { BELL = '\a', BACKSPACE = '\b', TAB = '\t', NEWKUBE = '\n', VTAB = '\v', RETURN = '\r' }; enum honapok { JAN = 1, FEB, MAR, APR, MAJ, JUN, JUL, AUG, SZEP, OKT, NOV, DEC }; /* így FEB = 2, MAR = 3 stb. */
A különböző felsorolt állandókban szereplő neveknek különbözniük kell, de egy felsoroláson belül az értékeknek nem kell különbözni.A felsorolás egy szokásos módja annak, hogy a nevekhez állandó értéket rendeljünk, és így lényegében a#defineutasítás helyettesítésére alkalmas, azzal az előnnyel, hogy a nevekhez rendelt értékek automatikusan generálhatók. Bár azenumtípusú változókat deklarálhatjuk, a fordítóprogramok nem ellenőrzik, hogy az így eltárolt változóknak van-e érvényes értékük a felsorolásban. Mindazonáltal a felsorolt változók jellegéből adódik az ellenőrzés lehetősége, így gyakran jobban használhatók, mint a#defineutasítással definiált változók. További előny, hogy egy megfelelő debugger program szimbolikus formában is képes kiírni a felsorolt változók értékét.
2.4. Deklarációk
A felhasználása előtt minden változót deklarálni kell, bár bizonyos deklarációk implicit módon, a programkörnyezet alapján is létrejöhetnek. A deklaráció egy típust határoz meg, és utána egy vagy több adott típusú változó felsorolása (listája) áll.Ilyen deklaráció pl.
int also, felso, lepes; char c, sor[1000];
A változók a deklarációk közt tetszőleges módon szétoszthatók, pl. a fenti deklarációs listákkal teljesen egyenértékű az
int also; int felso; int lepes; char c; char sor[1000];
Az utóbbi forma több helyet igényel, viszont előnye, hogy az egyes deklarációkhoz kényelmesen fűzhetők megjegyzések és maga a deklaráció is egyszerűen módosítható.A változók a deklaráció során kezdeti értéket is kaphatnak. Ha a nevet egyenlőségjel és egy kifejezés követi, akkor a kifejezés értéke lesz a kezdeti érték, mint pl. a következő deklarációban:
char esc = '\\'; int i = 0; int hatar = MAXSOR+1; float eps = 1.0e-5;
Ha a kérdéses változó nem automatikus, akkor a kezdeti értékadás csak egyszer, a program végrehajtásának kezdetén történik meg és a kezdő értéket megadó kifejezésnek állandó kifejezésnek kell lenni. Az explicit módon inicializált automatikus változók esetén a kezdeti értékadás minden alkalommal létrejön, amikor a vezérlés átkerül a végrehajtandó függvényhez vagy blokkhoz, és a kezdeti értéket megadó kifejezés tetszőleges lehet. Alapfeltételezés szerint a külső és statikus változók nulla kezdeti értéket kapnak. Az automatikus változók kezdeti értéke határozatlan, hacsak explicit módon nem kapnak értéket.
Bármely változó deklarációjában alkalmazható aconstminősítő, ami azt jelzi, hogy a változó értékét nem fogjuk megváltoztatni. Ilyen deklaráció pl.
const double e = 2.71828182845905; const char uzenet[ ] = "figyelem:";
Aconstminősítésű deklaráció tömb argumentumok esetén is használható, jelezve hogy a függvény nem változtatja meg a tömböt. Pl:
int strlen(const char [ ]);
Amennyiben a program aconstminősítésű változó értékének megváltoztatására mégis kísérletet tesz, akkor a hatás a használt számítógéptől és rendszertől függ.
2.5. Aritmetikai operátorok
A C nyelv kétoperandusú aritmetikai operátorai a+,–,*és/, valamint a%modulus operátor. Az egészek osztásakor a törtrészt a rendszer levágja, ezért van szükség a%modulus operátorra. Az
x % y
kifejezés azx/yegészosztás (egész) maradékát adja, és értéke nulla, haxoszthatóy-nal. Például egy adott év szökőév, ha az évszáma osztható néggyel és nem osztható százzal, kivéve a 400-zal osztható évszámokat, amik szintén szökőévek. Ezért annak eldöntése, hogy egy adott év szökőév-e vagy sem, az alábbi programrészlettel lehetséges:
if ((ev % 4 == 0 && ev % 100 != 0) || ev % 400 == 0) printf("%d szökőév.\n", ev); else printf("%d nem szökőév.\n", ev);
A%operátor nem alkalmazhatófloatésdoubletípusú adatokra. Negatív operandusok esetén az egészosztás hányadosának csonkítása, valamint a modulus előjele gépfüggő, és ugyanez igaz az esetlegesen előforduló túlcsordulásra és alácsordulásra is.Az egyoperandusú (unáris)+és–operátorok precedenciája a legmagasabb. A kétoperandusú+és–operátorok precedenciája kisebb a*,/és%precedenciájánál. Az aritmetikai operátorok mindig balról jobbra haladva hajtódnak végre (a precedencia figyelembevételével).
A fejezet végén lévő 2.1 táblázatban összefoglaltuk az összes operátor precedenciáját és asszociativitását.
2.6. Relációs és logikai operátorok
A C nyelv relációs operátorai:>,>=,<,<=. Ez a sorrend egyben a precedenciájuk sorrendje is. Ezeknél eggyel alacsonyabb precedenciájúak az egyenlőség operátorok:==,!=.A relációs operátorok precedenciája kisebb, mint az aritmetikai operátoroké, így pl. egy olyan kifejezés, minti < hatar-1úgy értékelődik ki, mint hai <(hatar-1)formában írtuk volna (amint ez elvárható).
Sokkal érdekesebb az&&és||logikai operátorok kérdése! Az&&és||operátorokkal összekapcsolt kifejezések kiértékelése balról jobbra történik, és a kiértékelés azonnal félbeszakad, ha az eredmény igaz vagy hamis volta ismertté válik. A legtöbb C nyelvű program kihasználja ezeket a tulajdonságokat. Így pl. az1. fejezetbenírtgetlinefüggvény ciklusszervezése is ennek alapján működik:
for (i = 0; i<lim-1 && (c = getchar ( )) != '\n' && c != EOF; ++i) s[i] = c;
Az új karakter beolvasása előtt meg kell vizsgálni, hogy van-e hely számára azstömbben, így először azi<lim-1ellenőrzéstkellvégrehajtani. Ha ennek az eredménye hamis, akkor a program már nem is megy tovább a következő karakter beolvasására. Ugyancsak nem volna szerencsés, ha ackaraktert az állomány vége feltételre vizsgálnánk agetcharhívása előtt, ezért a hívásnak és az értékadásnak meg kell előzni ackarakter vizsgálatát.Az&&precedenciája nagyobb, mint a||precedenciája, és mindkét operátor alacsonyabb precedenciájú, mint a relációs és egyenlőség operátorok, így az
i<lim-1 && (c = getchar( )) != '\n' && c != EOF
kifejezés nem tartalmaz felesleges zárójeleket. De mivel a!=precedenciája nagyobb, mint az értékadásé, ezért zárójelezés szükséges:
(c = getchar( )) != '\n'
Ezzel elérjük, hogy először az értékadás történjen meg, és csak ezután hasonlítsuk össze acértékét az‘\n‘karakterrel.Egy relációs vagy logikai kifejezés számértéke definíció szerint0, ha a kifejezés hamis, és1, ha igaz.
A!unáris (egyoperandusú) negáló operátor a nem nulla (igaz) operandust 0 értékűvé (hamissá), a 0 értékű (hamis) operandust 1 értékűvé (igazzá) alakítja. A!operátort gyakran használjuk olyan szerkezetekben, mint pl. az
if (!igaz)
az
if (igaz == 0)
kifejezés helyett. Nehéz általános esetben megmondani, hogy melyik változat a jobb. A!igazszerkezet általában jól olvasható („nem igaz”), de bonyolultabb esetben nehezen érthető.
2.2.gyakorlat. Írjunk az előzőforciklussal egyenértékű ciklust, ami nem használja az&&vagy||operátorokat!
2.7. Típuskonverziók
Ha egy operátor operandusai különböző típusúak, akkor a művelet végrehajtása előtt azokat egy közös típusra kell hozni. Az átalakításra néhány szabály vonatkozik. Általában az automatikus konverzió csak akkor jön létre, ha egy „keskenyebb” operandust egy „szélesebb” operandussá kell alakítani, mivel így biztosan nem vész el információ. Ilyen konverzió jön létre pl. azf+1alakú kifejezésekben, ahol aziegész lebegőpontossá alakul azflebegőpontos szám miatt. Az értelmetlen kifejezések, mint pl.floattípusú adat indexként való használata, nem megengedettek. Ha egy hosszabb egész típust egy rövidebbhez, vagy egy lebegőpontos típust egy egészhez rendelünk, akkor információ veszhet el, ezért figyelmeztető jelzést kapunk, de maga a művelet nem tilos.Achartípusú adatok kis egész számok, ezért az összes aritmetikai műveletben szabadon használhatók, ami nagymértékben egyszerűsíti és rugalmassá teszi a karakterkezelést. Erre jó példa azatoifüggvény, amely a számjegyekből álló karaktersorozatot a megfelelő értékű számmá alakítja. A függvény egyszerűsített változata:
/* atoi: az s karaktersorozat egész számmá alakitása */ int atoi(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; }
Amint azt az1. fejezetbenmár elmondtuk, azs[i]-‘0’kifejezés megadja azs[i]-ben tárolt karakter számértékét, mivel a‘0’, ‘1’stb. folyamatosan növekvő sorozatot alkot.Achartípusinttípussá alakítására egy másik példa alowerfüggvény, amely egy (kizárólag ASCII karakterkészletbeli) karaktert alakít kisbetűvé. Ha a karakter nem nagybetű, akkor a függvény változatlan formában adja vissza.
/* lower: a c ASCII karakter kisbetűssé alakitása */ int lower(int c) { if (c >= 'A' && c <= 'Z') return c + 'a' - 'A'; else return c; }
A függvény csak ASCII karakterkészlet esetén használható, mivel kihasználja, hogy a nagy- és kisbetűk számértéke (kódja) rögzített távolságra van egymástól, és a nagy-, ill. kisbetűs ábécék folyamatosak (azaz A és Z, ill. a és z között a betűkön kívül nincs más karakter). Ez az utóbbi kitétel nem igaz az EBCDIC karakterkészletre, így a lower nem működne helyesen (nem csak betűket alakítana át).AB. függelékbenleírt<ctype.h>standard header állományban számos függvény van, amelyek a karakterkészlettől független karakter-ellenőrzésre és -átalakításra használhatók. Például atolower(c)függvény ackarakter kisbetűs értékével tér vissza, ha az nagybetűs karakter volt, így általánosabb formában helyettesítheti alowerfüggvényünket.
Azatoifüggvényben használt
c >= '0' && c <= '9'
vizsgálat is helyettesíthető az
isdigit(c)
könyvtári függvénnyel. A továbbiakban a<ctype.h>standard header függvényeit használni fogjuk az egyes programokban.A karakterek egésszé alakításával kapcsolatban szólnunk kell még a C nyelv egy további finomságáról. A nyelv nem határozza meg, hogy achartípusú változó előjeles vagy előjel nélküli mennyiség-e. Vajonchartípusú adatinttípusúvá alakításakor létrejöhet-e negatív szám? A válasz gépről gépre változik, a felépítéstől függően. Néhány számítógépnél azok a karakterek, amelyek bal szélső (legnagyobb helyiértékű) bitje 1, negatív egész számmá konvertálódnak (előjel-kiterjesztés!). Más gépeken az átalakítás során keletkezőinttípusú adat balról nullákkal töltődik fel, ezért mindig pozitív marad.
A C nyelv definíciója garantálja, hogy a gép szabványos karakterkészletében lévő egyetlen nyomtatható karakter sem lesz negatív, így ezek a karakterek egy kifejezésben mindig pozitív mennyiségként szerpelnek. Ez természetesen nem igaz a karakteres változóban tárolt tetszőleges bitmintákra, amelyek néhány gépen negatív, más gépeken pozitív értékűek lehetnek. Az egyértelműség és hordozhatóság érdekében célszerű asignedvagyunsignedminősítőt használni, ha nem karakteres adatot tárolunkchartípusú változóban.
Az&&és||logikai műveletekkel összekapcsolti > jalakú relációs kifejezések és logikai kifejezések eredményének értékét úgy definiáljuk, hogy az1, ha az eredmény igaz, és0, ha hamis. Így a
d = c >= '0' && c <= '9'
értékadás adváltozóhoz1értéket rendel, hacszámjegy, és0értéket, ha nem. Másrészről azisdigithez hasonló függvények igaz esetben tetszőleges nem nulla értékkel térnek vissza. Az eltérő értelmezések okozta hibák kiküszöbölése érdekében azif,while,forstb. utasítások vizsgáló részében az „igaz” ugyanazt jelenti, mint a „nem nulla”, ezért a kétféle értékadás hatása között nincs különbség.Az implicit aritmetikai típuskonverziók sokkal inkább a várakozásnak megfelelően működnek. Ha egy kétoperandusú operátor (pl.+vagy*) operandusai különböző típusúak, akkor az „alacsonyabb” típus „magasabb” típussá alakul („előlép”) a művelet végrehajtása előtt, és az eredmény a „magasabb” típusnak megfelelő lesz. A pontos konverziós szabályokat azA. Függelék 6. pontjábanismertetjük, addig is néhány egyszerű szabályt adunk. Ha az operandusok nemunsignedminősítésűek, akkor:
Ha valamelyik operandus long double típusú, akkor a másik is long double típusúvá konvertálódik; különben, ha valamelyik operandus double típusú, akkor a másik is double típusúvá konvertálódik; különben, ha valamelyik operandus float típusú, akkor a másik is float típusúvá konvertálódik; különben a char és short típusú operandus int típusúvá konvertálódik; végül, ha valamelyik operandus long típusú, akkor a másik is long típusúvá konvertálódik.
Megjegyezzük, hogy egy kifejezésben afloattípusú operandusok nem automatikusan alakulnak átdoubletípusúvá, ami a C nyelv eredeti definíciójához képest eltérést jelent. Általában a matematikai függvények (mint a<math.h>standard header függvényei is) kétszeres pontosságú lebegőpontos adatokat használnak. Afloattípus használatának fő oka, hogy nagy tömbök esetén tárterület takarítható meg, vagy ritkábban, hogy a végrehajtási idő csökken, mivel a kétszeres pontosságú aritmetika viszonylag lassú.A konverziós szabályokunsignedminősítésű operandusok esetén sokkal bonyolultabbak. A problémát az előjeles és előjel nélküli számok összehasonlítása jelenti, mivel az értékek a géptől és a különböző egész típusok méretétől függenek. Például tegyük fel, hogy azint16 bites, along32 bites. Ekkor-1L < 1U, mert1U(ami egyinttípus) „előlép”signedlongtípussá. Másrészt viszont-1L>1UL, mert a-1Lunsignedlongtípussá „lép elő”, amivel nagy pozitív számmá válik.
Az értékadás is típuskonverzióval jár: a jobb oldal értéke a bal oldal típusának megfelelő típusúvá alakul és ez lesz az eredmény típusa is.
A karakterek mindig egésszé alakulnak (előjel-kiterjesztéssel vagy anélkül, ahogy erről már volt szó).
A hosszabb egészek rövidebb vagychartípusúvá alakulásakor a magasabb helyiértékű bitek elvesznek. Így az
int i; char c; i = c; c = i;
műveletsorban acértéke változatlan marad. Ez mindig igaz, függetlenül attól, hogy van-e előjel-kiterjesztés vagy sem.Az értékadás sorrendjének megfordítása információvesztéshez vezethet. Ha azxfloatés aziinttípusú, akkor azx = iési = xértékadásoknál mindkét esetben létrejön a típuskonverzió. Afloatinttípussá alakulása a törtrész elvesztésével jár. Hadoubletípust alakítunkfloattípussá, akkor az új érték a rendszertől függően kerekítéssel vagy levágással keletkezik.
Mivel függvényhíváskor az argumentum kifejezés is lehet, az argumentum átadásakor is létrejöhet típuskonverzió. Függvényprototípus hiányában acharésshorttípusokinttípussá, afloattípusdoubletípussá alakul. Ezért a függvények argumentumaitintésdoubletípusúnak deklaráltuk akkor is, ha a függvénytcharvagyfloattípusú argumentummal hívtuk.
Végül megjegyezzük, hogy tetszőleges kifejezésben kikényszeríthetjük az explicit típuskonverziót a rögzítő(cast)unáris típusmódosító operátorral. Az így kialakított szerkezet:
(típusnév) kifejezés
alakú, és hatására akifejezésa típusnévvel megadott típusúvá konvertálódik, a korábban elmondott szabályok szerint. A kényszerített típusmódosítás alapvetően úgy működik, mintha akifejezésértékét az előírt típusú változóhoz rendelnénk egy értékadással, majd ezután ezt a változót használnánk a kifejezés helyett. Például asqrtkönyvtári függvénydoubletípusú argumentumot vár, és értelmetlen eredményt ad, ha véletlenül valamilyen más típusú argumentumot kap. (Azsqrtfüggvény a<math.h>standard headerben található.) Így, ha aznváltozó egész típusú, akkor azsqrtfüggvény
sqrt((double) n)
formában használható, mivel ez a paraméterátadás előttdoubletípusúvá alakítjanértékét. Meg kell jegyeznünk, hogy bár a kényszerített típusmódosítás létrehozzanmegfelelő típusú értékét, de magát aznváltozót nem változtatja meg. A kényszerített típusmódosítás operátora a többi unáris operátorral azonos precedenciájú, mint az a fejezet végén lévő összefoglaló táblázatból látszik.Ha az argumentumot egy függvényprototípusban deklaráljuk (ami a szokásos megoldás), akkor a deklaráció bármilyen más, a függvény hívásakor felhasznált argumentumra rákényszeríti a megadott típust. Így pl. azsqrtprototípusát
double sqrt (double);
formában megadva megengedett a
gyok2 = sqrt(2)
alakú hívás, mivel a 2 automatikusandoubletípusú2.0értékké alakul, minden kényszerített típusmódosítás nélkül.A standard könyvtár tartalmaz egy hordozható pszeudovéletlenszám-generátort, valamint annak kezdőértékét beállító függvényt. A két függvény egyszerűsített programja jó példa a kényszerített típusmódosításra. A program:
unsigned long int next = 1; /* rand: egy 0 és 32767 közti pszeudo-véletlen számot */ /* ad vissza */ int rand(void) { next = next * 1103515245 + 12345; return (unsigned int) (next/65536) % 32768; } /* srand: a rand kezdőértékének beállítása */ void srand(unsigned int alap) { next = alap; }
2.3.gyakorlat. Írjunkhtoi(s)néven függvényt, amely egy hexadecimális számjegyekből álló karaktersorozatot (beleértve a0xvagy0Xkaraktersorozatot is) a megfelelő egész számmá alakít! A megengedett számjegyek0…9ésa…fvagyA…F.
2.8. Inkrementáló és dekrementáló operátorok
A C nyelv két szokatlan operátort használ a változók inkrementálására (eggyel való növelésére) és dekrementálására (eggyel való csökkentésére). A++inkrementáló operátor egyet ad az operandushoz, a—dekrementáló operátor pedig egyet kivon belőle. A++operátort gyakran használjuk változók növelésére, pl. az
if (c == '\n') ++nl
programrészletben. A++és—szokatlan vonatkozása, hogy prefix formában (a változó előtt elhelyezve, pl.++n) és postfix formában (a változó után elhelyezve, pl.n++) egyaránt létezik. A kétféle változat egyaránt növeli (vagy csökkenti) a változót, de a++na felhasználás előtt, azn++pedig utána növeli aznértékét (a—operátor hasonlóan működik). Ebből következően minden olyan esetben, amikor a változó értékét is felhasználjuk (nem csak a növelésre vagy csökkentésre, azaz számlálásra van szükség), a++nés azn++különbözik. Ha pl.nértéke5, akkor
x = n++;
hatásáraxértéke5lesz, amíg az
x = ++n;
hatásáraxértéke6lesz. Természetesennértéke mindkét esetben6lesz. Az inkrementáló vagy dekrementáló operátorok csak változókra alkalmazhatók, az(i+j)++formájú kifejezések tilosak.Olyan esetekben, amikor az értékre nincs szükség, csak a növelésre, mint pl. az alábbi példában:
if (c == '\n') nl++;
a prefix és postfix forma egyenértékű. Van viszont olyan feladat, amikor csak az egyiket vagy a másikat választhatjuk. Példaként vizsgáljuk meg asqueeze(s, c)függvényt, amely azskaraktersorozatból eltávolítja az összesckaraktert.
/* squeeze: az s-ből törli az összes c-t */ void squeeze(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'; }
A függvény minden alkalommal, amikorc-től különböző karaktert talál, azi-edik pozícióból átmásolja azt aj-edik (aktuális) pozícióba, és csak ezután inkrementáljajértékét, hogy kijelölje a következő karakter helyét. Ez teljesen egyenértékű a következővel:
if (s[i] != c) {
s[j] = s[i];
j++;
}
A másik példát az1. fejezetbenismertetettgetlinefüggvényből vettük:
if (c == '\n') { sor[i] = c; ++i; }
Ez a szerkezet helyettesíthető a sokkal tömörebb
if (c == '\n') sor[i++] = c;
szerkezettel. A harmadik példánk azstrcat(s, t)függvény, amely atkaraktersorozatot azskaraktersorozat végéhez illeszti (konkatenálja). Azstrcatfeltételezi, hogy azskaraktersorozat elegendően hosszú az összekapcsolt karaktersorozat befogadásához. Az itt közölt példaprogramban azstrcatvisszatéréskor nem ad értéket, standard könyvtári változata viszont egy mutatót ad vissza, ami kijelöli az eredményül kapott karaktersorozat helyét.
/* strcat: a t karaktersorozatot a s karaktersorozat végéhez kapcsolja, s elegendően hosszú */ void strcat(char s[], char t[ ] ) { int i, j; i = j = 0; while (s[i] != '\0') /* megkeresi s végét */ i++; while ((s[i++] = t[j++]) != '\0') ; /* átmásolja t-t */ }
A programtminden egyes karakteréneks-be másolása után a postfix++operátorral növeliiésjértékét, így azok a ciklusmag következő végrehajtásakor már a következő helyet jelölik ki.
2.4.gyakorlat. Írjuk meg asqueeze(s1, s2)olyan változatát, amely azs1karaktersorozatból töröl minden karaktert, ami azs2karaktersorozatban megtalálható!
2.5.gyakorlat. Írjunkany(s1, s2)néven függvényt, amely visszatérési értékként megadja azs1karaktersorozat azon legelső helyét, ahol azs2karaktersorozat bármelyik karaktere előfordul! A függvény visszatérési értéke legyen-1, has1egyetlens2-beli karaktert sem tartalmaz. (Azstrbrkstandard könyvtári függvény ugyanezt teszi, csak visszatérési értékként az adott helyet kijelölő mutatót adja.)
2.9. Bitenkénti logikai operátorok
A C nyelvben hat operátor van a bitenkénti műveletekre. Ezek az operátorok csak egész típusú adatokra, azazchar,short,intéslongtípusokra használhatók, akár előjeles, akár előjel nélküli változatban. Az egyes operátorok és értelmezésük a következő:
& bitenkénti ÉS-kapcsolat | bitenkénti megengedő (inkluzív) VAGY-kapcsolat ^ bitenkénti kizáró (exkluzív) VAGY-kapcsolat << balra léptetés >> jobbra léptetés ~ egyes komplemens képzés (unáris)
A bitenkénti ÉS operátort gyakran valamilyen bitminta kimaszkolására használják, pl. az
n = n & 0177;
művelet aznbináris értékében az alsó hét bit kivételével minden bitet nulláz. A bitenkénti VAGY operátort a bitek beállítására használják, pl. az
x = x | BEALL;
műveletxminden olyan bitjét1-re állítja, amely aBEALL-ban is1volt (függetlenül attól, hogy korábban milyen értékű volt).A kizáró VAGY operátor minden olyan helyen1-et ad, ahol a két operandus bitjei különböztek, és0-t, ha megegyeztek.
Az&és|bitenkénti, valamint az&&és||logikai operátorok közti legfontosabb különbség, hogy az utóbbiak az igazságtábla szerint, balról jobbra haladva végzik a kiértékelést, míg az előzőek egy lépésben, bitenként. Például, haxértéke1ésyértéke 2 , akkorx & yértéke nulla, viszontx && yértéke egy.
A<<és>>léptető operátorok a bal oldalukon lévő operandust a jobb oldalukon lévő pozitív értéknek megfelelő számú bittel jobbra vagy balra léptetik. Így pl. azx << 2műveletxértékét két hellyel balra lépteti, a jobb szélen keletkező két üres bináris helyet nullával tölti fel. A művelet megfelel a néggyel való szorzásnak. Egy előjel nélküli mennyiség jobbra léptetésekor a bal szélen felszabaduló bitek mindig nullával töltődnek fel. Előjeles számok jobbra léptetésekor a felszabaduló bitek az előjel bittel (aritmetikai léptetés), ill. néhány számítógép esetén nullával töltődnek fel (logikai léptetés).
A~unáris (egyoperandusú) operátor egy egész egyes komplemensét állítja elő, ami azt jelenti, hogy a szám minden1értékű bitjét0-ra, minden0értékű bitjét1-re állítja. Például az
x = x ~ 077
művelet hatásáraxutolsó hat bitje nulla értékű lesz. Vegyük észre, hogy azx ~ 077eredménye független azxgépi ábrázolásához használt szó hosszától, és így 16 bites szóhossz esetén (de csak akkor!) előnyösebb a vele egyenértékűx ~ 0177700művelet. A hordozható forma semmiféle többletbefektetést nem igényel, mivel az~077egy állandó kifejezés, aminek kiértékelése a fordítás során történik.Néhány bitenkénti operátor működésének bemutatására írjunkgetbits(x, p, n)néven függvényt, amely azxp-edik hellyel kezdődőnbites részét adja vissza, jobbra igazítva. Tételezzük fel, hogy a0.bitpozíció a jobb szélső, valamint azt, hogynéspértelmes pozitív mennyiségek. Például agetbits(x, 4, 3)x4., 3. és 2. pozícióján álló hárombites, jobbra igazított számmal tér vissza. A függvény:
/* getbits: a p-edik pozíciótól kezdődő n bitet adja */ unsigned getbits(unsigned x, int p, int n) { return (x >> (p+1-n)) & ~(~0 << n); }
azx >> (p+1-n)kifejezés a kiválasztott mezőt a szó jobb szélére mozgatja. A~0a teljes szóhosszon csupa1értékű bitet jelent, amit a~0 << nművelettelnlépéssel balra tolunk. Ennek hatására a szónszámú jobb szélső bitje nullával töltődik fel, és ebből komplementálással alakul ki azndarab jobb szélső bitet kiválasztó (csupa egyenesből álló) maszk.
2.6.gyakorlat. Írjuk meg asetbits(x, p, n, y)függvényt, amely egy olyanxértékkel tér vissza, amit úgy kap, hogy azxp-edik pozíciótól jobbra esőnbitje helyébe bemásoljayjobb szélsőnbitjét, a többi bitet változatlanul hagyva!
2.7.gyakorlat. Írjunk egyinvert(x, p, n)függvényt, amely azxp-edik pozíciótól kezdődőnbitjét invertálja (az1-eseket0-ra, a0-kat1-esekre változtatja), a többi bitet pedig változatlanul hagyja!
2.8.gyakorlat. Írjunk egyrightrot(x, n)függvényt, aminbittel jobbra rotálja azxegész mennyiséget! Jobbra rotálásnál a jobb szélen kilépő bitek a bal szélen visszakerülnek a szóba.
2.10. Értékadó operátorok és kifejezések
Az olyan kifejezéseket, mint
i = i + 2
amelyekben a bal oldalon lévő változó ismét megjelenik a jobb oldalon, az
i += 2
tömörebb formában is írhatjuk. Az új formában szereplő+=jelkombinációt értékadó operátornak nevezik. A legtöbb kétoperandusú operátor (pl, mint a+, amelynek bal és jobb oldalán is operandus áll) szerepelhet az értékadó operátorban, amelynek általános alakjaop=, aholopa
+ - * / % << >> & ^ |
operátorok egyike lehet. Hak1ésk2két kifejezés, akkor a
k1 op= k2
egyenértékű a
k1 = (k1) op (k2)
alakkal. A tömörebb forma előnye, hogy a gép a kikifejezést csak egyszer számolja ki. Összetett kifejezések esetén ügyeljünk a zárójelekre. Például az
x *= y + 1
alak az
x = x * (y + 1)
kifejezésnek felel meg, és nem az
x = x * y + 1
kifejezésnek. Az elmondottak megvilágítására nézzük a bitcount függvényt, amely megszámolja az egész típusú argumentumában lévő 1 értékű biteket.
/* bitcount: x 1 értékű bitjeinek száma */ int bitcount(unsigned x) { int b; for (b = 0; x != 0; x >>= 1) if (x & 01) b++; return b; }
A függvényxargumentumátunsignedtípusúnak deklaráltuk, hogy a jobbra léptetés során a felszabaduló helyekre garantáltan 0 értékű bitek lépjenek be, függetlenül a géptől, amelyen a program fut.A tömörségen túl az értékadó operátorok alkalmazásának további előnye, hogy jobban megfelel az emberi gondolkodásnak. Általában azt mondjuk, hogy „adj kettőt i-hez” vagy „növeld i-t kettővel”, ahelyett, hogy azt mondanánk „vedd i-t, adj hozzá kettőt, majd tedd az eredményt vissza az i-be”. Emiatt azi += 2kifejezés előnyösebb, mint azi = i+2. Bonyolult kifejezéseknél az értékadó operátor használatának még az is előnye, hogy érthetőbbé teszi a programot. Például egy
yyval[yypv[p3+p4] + yypv[p1+p2]] += 2
alakú kifejezésben nem kell nehézkes módon ellenőrizni, hogy a bal és jobb oldali kifejezések tényleg megegyeznek-e, ill. ha nem, akkor miért nem. Az értékadó operátorok a fordítóprogramot is segítik a hatékony kód előállításában.Mint korábban már láttuk, az értékadó utasításnak értéke van és előfordulhat kifejezésben, amire a legegyszerűbb példa a
while ((c = getchar()) != EOF)
utasítássor. Ennek mintájára más értékadó operátort (+=,-=stb.) tartalmazó értékadások is szerepelhetnek kifejezésekben, bár ez nem túl gyakori.Minden ilyen kifejezésben az értékadó kifejezés típusa megegyezik a bal oldali operandus típusával, és értéke az értékadás utáni érték.
2.9.gyakorlat. A kettes komplemens kódú aritmetikában azx &= (x-1)kifejezés törlixjobb szélső bitjét. Magyarázzuk meg, miért! Ezt kihasználva írjunk egy gyorsabb bitcount változatot!
2.11. Feltételes kifejezések
Az
if (a > b) z = a; else z = b;
programrészlet hatásárazazaésbértékek közül a nagyobbikat veszi fel. Ilyen és hasonló szerkezetek a C nyelv háromoperandusú?:feltételes kifejezésoperandusával egyszerűbben is leírhatók. Az operátor általános formája kifejezésekben:
kif1? kif2 : kif3
A szerkezet úgy működik, hogy először kiértékeli akif1kifejezést. Ha ennek értéke nem nulla (igaz), akkor akif2kiértékelése következik, egyébként pedig akif3kiértékelése. A programkif2éskif3közül csak az egyiket értékeli ki (akif1értékétől függően) és ez lesz a feltételes kifejezés értéke. Ígyzbeállítása azaésbközül a nagyobbik értékének megfelelően a
z=(a>b) ? a : b; /* z = max (a, b) */
utasítással történhet.Meg kell jegyeznünk, hogy a feltételes kifejezés egy valódi kifejezés, tehát ugyanúgy használható, mint bármilyen más kifejezés. Hakif2éskif3eltérő típusúak, akkor az eredmény típusát a korábban tárgyalt konverziós szabályok szerint lehet meghatározni. Például, haffloattípusú ésnint, akkor az
(n > 0) ? f : n
kifejezésfloattípusú lesz, függetlenül attól, hogynpozitív-e vagy sem.A feltételes kifejezésben az első kifejezést nem szükséges zárójelbe tenni, mivel a?:operátor precedenciája nagyon alacsony (csak eggyel nagyobb, mint az értékadásé). Másrészt zárójelezéssel a kifejezés feltételrésze egyértelművé tehető.
A feltételes kifejezések használata tömör és világos kódot eredményez. Ezt jól mutatja az alábbi programrészlet, amely egy tömb n elemét nyomtatja ki úgy, hogy soronként tíz értéket ír, azokat egy szóközzel választja el és a sort újsor-karakterrel zárja.
for (i = 0; i<n; i++) printf("%6d%c", a[i], (i%10==9 || i==n-1) ? '\n' : ' ');
A programrész minden tizedik sor ésn-edik elem után egy újsor-karaktert ír ki, minden más esetben pedig a számot szóköz követi. A példa elég trükkös, de mindenképpen sokkal tömörebb, mint azif–elseszerkezettel megvalósított változata. Egy másik példa*:
printf("You have %d item%s. \n", n, n==1 ? " " : "s");
2.10.gyakorlat. Írjuk át a nagybetűket kisbetűkké alakítólowerfüggvényt úgy, hogy azif–elseszerkezetet feltételes kifejezéssel helyettesítjük!
2.12. A precedencia és a kifejezés kiértékelési sorrendje
A 2.1. táblázatban összefoglaltuk az összes operátor (beleértve az eddig nem tárgyaltakat is) precedenciáját és asszociativitását (kiértékelési irányát). Az azonos sorban szereplő operátorok precedenciája azonos, a sorok csökkenő precedencia szerint követik egymást, és így pl. a*,/és%precedenciája azonos és nagyobb a kétoperandusú+és–precedenciájánál. A táblázatban a()operátor a függvényhívást jelöli, a->és.operátorok pedig struktúrák elemeihez való hozzáféréshez használhatók. Ezekkel a6. fejezetbenfoglalkozunk, csakúgy mint az objektum méretét megadósizeofoperátorral. A mutatókkal kapcsolatos indirekciós ( * ) operátort és az objektum címét megadó & operátort az5. fejezetben, a , (vessző) operátort pedig a3. fejezetbentárgyaljuk.
| Operátor | Asszociativitás |
| () [] -> | balról jobbra |
| ! ~ ++ — + – * & (típus) sizeof | jobbról balra |
| * / % | balról jobbra |
| + – | balról jobbra |
| << >> | balról jobbra |
| < <= > >= | balról jobbra |
| == != | balról jobbra |
| & | balról jobbra |
| ^ | balról jobbra |
| | | balról jobbra |
| && | balról jobbra |
| || | jobbról balra |
| ?: | jobbról balra |
| = += -= *= /= %= &= ^= |= <<= >>= | balról jobbra |
Megegyzés: az unáris (egyoperandusú)+,–és*operátorok nagyobb precedenciájúak, mint a kétoperandusú operátorok.
Megjegyezzük, hogy a bitenkénti&,^és|operátorok precedenciája ilyen sorrendben csökken és eleve kisebb az==és!=precedenciájánál. Emiatt az olyan bitvizsgáló kifejezéseket, mint
if ((x & MASZK) == 0)
a helyes eredmény érdekében gondosan zárójelezni kell.A C, több más nyelvhez hasonlóan, nem határozza meg az operátorok operandusainak kiértékelési sorrendjét. (Kivéve az&&,||,?:és,operátorokat.) Így pl. felmerül a kérdés, hogy az
x = f( ) + g( );
alakú kifejezésben azfkiértékelése agelőtt történik-e vagy sem. Ez azért érdekes, mertfvagygmegváltoztathatja a másik függvényben használt változókat, és emiattxértéke függhet a kiértékelés sorrendjétől. Ha valami miatt fontos a kiértékelés sorrendje, akkor a közbenső eredményeket egy segédváltozóban kell eltárolni és később felhasználni. Hasonlóan a függvényargumentumok kiértékelési sorrendje sincs meghatározva, ezért a
printf("%d %d\n", ++n, power(2, n)); /* hibás */
utasítás különböző fordítóprogramokkal más-más eredményt adhat, aszerint, hogy azninkrementálása apowerfüggvény hívása előtt vagy után történik-e. A biztos megoldás természetesen az, ha az utasítást
++n; printf("%d %d\n", n, power(2, n));
alakban írjuk.A függvényhívások, egymásba ágyazott értékadó utasítások és inkrementáló, ill. dekrementáló utasítások mellékhatásokat okozhatnak, azaz egy kifejezés kiértékelése közben néhány változó értéke (nem szándékosan) megváltozhat. Bármely kifejezés kiértékelése vezethet mellékhatásokhoz, és sok függhet attól, hogy a kifejezésben aktuálisan szereplő változókat milyen sorrendben dolgozza fel a gép. Egy szerencsétlen, de sajnos gyakori esetet példáz a következő utasítás:
a[i] = i++;
A kérdés az, hogy az index a régi vagy az újiérték-e? A fordítóprogramok a kérdést különböző módon kezelik, és az eredmény a felhasznált fordítóprogramtól függ. A szabvány a legtöbb ilyen kérdést szándékosan nyitva hagyja. A kifejezések kiértékelése során a mellékhatások kialakulását végezetül is mindig a fordítóprogram dönti el, mivel az optimális kiértékelési sorrend nagymértékben függ a számítógép felépítésétől.A kiértékelési sorrendtől függő programok írása minden nyelv esetén helytelen, ezért mindenképpen kerüljük és igyekezzünk kiküszöbölni a mellékhatásokat is. A mellékhatások elkerüléséhez jó, ha tudjuk, hogy adott helyzetbenmitcsinál a számítógép (vagy a fordítóprogram), viszont a tudatlanság sok esetben védelmet is jelenthet.
| 1. FEJEZET | Tartalom | 3. FEJEZET |





