next up previous contents
Elõre: Konverziók Fel: Kifejezések Vissza: Elsõdleges kifejezések

Operátorok

A C nyelv egyik erõssége más nyelvekhez képest a kifejezésekben használható operátorok gazdag választéka. Az eddig említett operátorokat (típusmódosító operátorok, indexelõ, illetve függvényaktivizáló operátorok) csak a C-ben nevezik operátornak. A következõkben a hagyományos értelemben vett operátorokat ismertetjük. Ezek többnyire elemi típusú adatokon végeznek mûveleteket, de egyik-másik közülük származtatott, illetve bonyolultabb, összetett típusú tárolási egységekre is alkalmazható. Az egyes operátorok felhasználásának lehetõségét a C++ az objektum-orientált tulajdonságai révén még tovább szélesíti. Ezt majd az ún. operator overloading kapcsán tekintjük át részletesen a 2. fejezetben.

A következõkben tehát leírjuk a C nyelvben hagyományos értelemben vett operátorokat, ismertetjük azok alkalmazását.

Az operátoroknak különbözõ precedenciájuk lehet, a kiértékelés sorrendje ezektõl függ. A fejezet végén ezért majd összefoglalóan felsoroljuk az egyes precedencia-osztályokat, és megadjuk azt is, hogy mi a teendõ, ha azonos precedenciájú operátorok együtt fordulnak elõ. A legtöbb kétoperandusú operátor megkívánja, hogy mindkét operandusa azonos típusú legyen. Például a /-rel jelölt osztási mûvelet mást jelent egész típusú operandusokra alkalmazva (maradékos egész osztás), mint lebegõpontosakra, és kevert operandusok esetén nem dönthetõ el, milyen algoritmust kell használni. Hasonló - bár nem ennyire látványos - a probléma különbözõ méretû operandusok esetén is. Nem lenne praktikus elvárás, hogy a C fordító minden elképzelhetõ operanduspárosra adjon eljárást, de nem lenne célravezetõ a teljes uniformizálás sem (mint például egyes BASIC rendszereknél, ahol minden mûveletet lebegõpontosan hajtanak végre). Ezért a C nyelv tervezõi úgy döntöttek, hogy a gépi szónál rövidebb operandusokat gépi szó méretûre bõvítik, valamint számûzik a rövid lebegõpontos mûveleteket. Ha a mûveletben résztvevõ két operandus nem azonos típusú, akkor az egyik átalakul, hogy megegyezzenek, mindig a sorszámozott típusú alakulva lebegõpontossá, a rövidebb változat a hosszabbra, az elõjeles az elõjel nélkülire. A pontos szabályokat az 1.6-os szakasz tartalmazza, és a továbbiakban ezekre mint "szokásos aritmetikai konverziók"-ra fogunk utalni.

Az ismertetést az egyoperandusú operátorokkal kezdjük, majd folytatjuk a kétoperandusúakkal, és végül kitérünk a C egyetlen, háromoperandusú operátorára is.

Az egyoperandusú opertárok közé tartozik a [ ] indexelõ, a ( ) függvényaktivizáló operátor, és az elsõdleges kifejezést képzõ operátor, az egyszerû zárójelpár. Ezeket az elõzõ, 1.5.1. pontban már ismertettük, itt csak a teljesség kedvért említjük meg õket újra.

Az egyoperandusú * operátor, az ún. dereference operator indirekciót jelez, az operandusa mutató kell, hogy legyen, eredményül a mutató által megcímzett értéket kapjuk. Az indirekció operátor inverze az egyoperandusú & operátor, amely az operandusa címét szolgáltatja ('address of' operator). Ha az 'address of' operátor operandusának típusa T, akkor az eredmény típusa "mutató T-re" lesz. Például a korábbi deklarációkat figyelembe véve a p = &hiba[3]; értékadás után *p értéke 'a' lesz. Az 'address of' operátor nem alkalmazható register tárolási osztályú változókra.

Az egyoperandusú - (minusz) operátor az operandus 2-es komplemensét szolgáltatja, a szokásos aritmetikai konverziók elvégzése után. Elõjel nélküli egészekre alkalmazva ezt az operátort, az eredményre igaz lesz, hogy hozzáadva az eredeti operandust az összeg 2n, ahol n az operandus szélessége bitben. Például -40000u értéke 25536 lesz. A BORLAND C++ megengedi az egyoperandusú + (plusz) operátor használatát: a fordítóprogram egyszerûen figyelmen kívül hagyja azt.

A logikai tagadás operátora a !, eredménye int típusú 1 vagy 0, attól függõen, hogy az operandus 0 volt-e vagy sem. Alkalmazható lebegõpontos számokra és mutatókra is.

A bitenkénti komplementálás operátorának jele ~ (a tilde karakter), ami a - kötelezõen sorszámozott típusú - operandusán elvégzett szokásos aritmetikai konverziók után kapott bitminta 1-es komplemensét adja eredményül, például ~ 0xFFFE értéke 1 lesz (16 bites gépen).

Az elõtagként alkalmazott (prefix) egyoperandusú ++ operátor operandusának az értékét megnöveli 1-gyel, és eredményül ezt a megnövelt értéket szolgáltatja olyan típusban, mint amilyen az operandusé. Ez az operátor tehát nemcsak visszaad egy értéket, de mellékhatása is van az operandusára. Ha x értéke 3, akkor az y = ++x * 2 eredményeként az x felveszi a 4 értéket, y pedig 8 lesz. Analóg módon létezik egyoperandusú prefix dekrementáló operátor is, jele -. Ez operandusa értékét csökkenti 1-gyel, és ezt adja eredményül.

Utótagként (postfix) is alkalmazható az egyoperandusú ++ operátor. Ekkor operandusának az értékét 1-gyel megnöveli, de eredményül a növelés elõtti értékét szolgáltatja olyan típusban, mint amilyen az operandusé. Ennek az operátornak tehát szintén mellékhatása van az operandusára, de az eredményben ez nem jelentkezik. Ha x értéke 3, akkor az y = x++ * 2 eredményeként az x felveszi a 4 értéket, y pedig 6 lesz. (Most már érthetõvé válik a C++ elnevezés szimbólikus jelentése: olyan C, ami egy fokkal jobb.) Analóg módon létezik egyoperandusú postfix dekrementáló operátor is, jele -. Ez operandusa értékét csökkenti 1-gyel, és eredményül a csökkentés elõtti értéket szolgáltatja.

A típuskonverzió (type cast) operátora az adott operandus elõtt zárójelben álló típusnév (absztrakt deklarátor). Az eredmény értéke - a lehetõségek határain belül - megegyezik az operanduséval, típusa a megnevezett új típus. Például (int)3.14 értéke 3, (long)0 megegyezik 0L-val, (unsigned short)-1 értéke 65535, (long *)p pedig egy olyan mutatót eredményez, ami ugyanarra a tárterületre mutat, mint p, de a megcímzett memóriarészt a fordító hosszú egésznek tekinti. Ez utóbbi talán a legjellemzõbb a típuskonverzió operátorának alkalmazására. Igy alkíthatjuk át az általános - ismeretlen típusú változóra mutató - void* "típusú" mutatókat adott típusú mutatókká. Erre a késõbbiekben egy példát is be fogunk muatatni.

A sizeof egyoperandusú operátor az operandusaként szereplõ változó (vagy zárójelben álló típus) byte-okban megadott méretét szolgáltatja. Ennek segítségével lehet gépfüggetlen módon tárterületigényeket meghatározni. Például sizeof(int) eredménye a BORLAND C++-ban 2, és minden implementációnál igaz, hogy sizeof(char) értéke 1.

Itt jegyezzük meg, hogy mind a C, mind a C++ nyelv definicója csak annyit közöl az egyes elemi típusok méreteirõl, hogy azok minden implementációban meg kell hogy feleljenek az alábbi relációknak:

1tex2html_wrap_inline11323sizeof(char)tex2html_wrap_inline11325sizeof(short)tex2html_wrap_inline11325 sizeof(int)tex2html_wrap_inline11325sizeof(long),

illetve

sizeof(float)tex2html_wrap_inline11325sizeof(double)

A konkrét méretek a nyelv adott implementációjától függenek. (A BORLAND C++-ra vonatkozó méret információkat a 1.2. táblázatban foglatuk össze.) Általában nem érdemes a fentieknél többet feltételezni az elemi típusok méreteirõl. Például nem mindig igaz, hogy az egész típus elég nagy ahhoz, hogy pointerek is elférjenek benne (lásd a BORLAND C++ esetében a near és a far, illetve huge pointereket). A korábbi deklarátorokat figyelembe véve sizeof(p) értéke memóriamodelltõl függõen 2 vagy 4, sizeof(hiba) értéke 11, sizeof(vektor[2])-é pedig 4. A fenti példákból is látszik, hogy az implementáció-független, portábilis C, illetve C++ programozási stílus kialakításában a sizeof operátornak kulcsszerepe van.

A kétoperandusú * operátor a szorzás mûvelete, a / operátor pedig az osztásé. A szokásos aritmetikai konverziók megtörténnek. A maradékképzés operátora a %, amelynek operandusai kötelezõen sorszámozott típusúak. Egészek osztásánál, ha bármelyik operandus negatív, a csonkítás iránya gépfüggõ, és hasonlóképpen gépfüggõ az is, hogy a maradék az osztó vagy az osztandó elõjelét örökli-e. Pozitív egészek osztásánál mindig lefelé csonkítás történik. Minden esetben fennáll azonban, hogy (a/b)*b + a%b megegyezik a-val, ha b nem nulla.

A kétoperandusú + és - operátor a szokásos összeadást, illetve kivonást végzi el, a szokásos aritmetikai konverziók után. Speciális esetként lehet mutató operandusuk is, ezt a pointerekkel foglalkozó 1.9.4. fejezetben tárgyaljuk majd részletesen.

A << és >> kétoperandusú operátorok bitenkénti eltolás ( shift) mûveletet végeznek balra, illetve jobbra. Mindkét operandusnak sorszámozott típusúnak kell lenni, és a szokásos aritmetikai konverziók után az eredmény típusa a bal oldali operanduséval egyezik meg. A kilépõ bitek elvesznek, balra léptetésnél az üres helyekre 0-ák lépnek be. Jobbra történõ eltolásnál a belépõ bitek garantáltan 0-ák, ha a bal oldali operandus elõjel nélküli értelmezésû, egyébként értékük gépfüggõ (a BORLAND C++ esetében az elõjeles jobbraléptetésnél a belépõ bitek az eredeti érték elõjelbitjével egyeznek meg). Például 2 << 3 értéke 16, 0xFFFDu >> 2 eredménye 0x3FFFu, míg -3 >> 1 értéke a BORLAND C++ esetén -1.

A relációs és egyenlõség-vizsgáló operátorok a következõk: kisebb <, nagyobb >, kisebb vagy egyenlõ <=, nagyobb vagy egyenlõ >=, egyenlõ == és nem egyenlõ !=. Értelmezésük a szokásos, eredményül int 1-et vagy 0-át adnak, attól függõen, hogy a vizsgált feltétel teljesül-e vagy sem.

A bitenkénti operátorok sorszámozott típusú operandusaikon végeznek a szokásos aritmetikai konverziók után bitmûveleteket. Ezek a bitenkénti ÉS (AND) operátor: a kétoperandusú &, a bitenkénti kizáró VAGY (XOR) operátor, a ^ és a bitenkénti VAGY (OR) operátor, a |.

A logikai ÉS operátor (&&) és a logikai VAGY operátor ( || ) viszont logikai értékû operandusokat vár (0 - hamis, nem 0 - igaz). Eredményül int 0-át vagy 1-et szolgáltatnak a megfelelõ logikai mûvelet elvégzése után. Vegyük példaként azt az esetet, hogy a értéke 2, b értéke 1. Ekkor a & b, a | b, a && b és a || b értéke rendre 0, 3, 1 és 1 lesz, mivel logikailag mindkét operandus igaz értékû.

A feltételes ( ?  : ) operátor az egyetlen háromoperandusú operátor a C nyelvben, alakja kif1 ? kif2 : kif3. A kifejezés feloldása a kif1 kifejezés kiértékelésével kezdõdik. Ha az eredmény nem 0, akkor a kif2, egyébként pedig a kif3 kifejezés értékelõdik ki, és ez utóbbi érték adja a mûvelet eredményét és típusát. kif2-nek és kif3-nak a szokásos aritmetikai konverziók után azonos típusúaknak kell lenniük, kivéve, ha az egyik mutató, a másik pedig konstans 0. Ekkor az eredmény típusa a mutató típusa lesz. Garantált, hogy futás közben kif2 és kif3 közül csak az egyik kerül kiértékelésre.

A C nyelvben explicit értékadó utasítás nincs; az értékadás operátorokon keresztül valósul meg, kifejezések kiértékelésének mellékhatásaként. A leggyakrabban használt értékadó operátor az egyszerû értékadás operátora, jele =. Például: y = x. Kiértékelésre kerül a jobboldali kifejezés (szükség szerint konvertálva a baloldal típusának megfelelõen), majd beíródik a baloldalon meghatározott változóba, felülírva annak korábbi tartalmát. Az egyszerû értékadó operátor baloldalán álló kifejezést az angol terminológia alapján balértéknek (lvalue), a jobboldalán álló kifejezést pedig jobbértéknek (rvalue) nevezzük. A kifejezés eredménye és típusa az értékadás során átadott értéknek megfelelõ. Példaként tekintsük a pelda.c-ben szereplõ alábbi feltételt:

            (c = getchar()) != EOF
Mivel a zárójelek közt álló kifejezés elsõdleges kifejezés, ezért ennek kiértékelése történik legelõször. A getchar() függvény hívása által szolgáltatott visszatérési érték beíródik a c nevû változóba, és ugyanez az érték lesz a zárójelezett kifejezés értéke is, ami végül összehasonlításra kerül az EOF szimbólummal, 0-át eredményezve, ha megegyeznek, és 1-et, ha nem. A c változó tehát a feltétel kiértékelése közben mellékhatásként kapott értéket. A további értékadó operátorok a következõk:

        +=, -=, *=, /=, %=, >>=, <<=, &=, ^ =, |=
 
Látható, hogy mindegyik egy kétoperandusú operátorból és az értékadás jelébõl tevõdik össze. Egy kif1 op= kif2 formájú kifejezés kiértékelését úgy tekinthetjük, mintha a helyén kif1 = kif1 op (kif2) alakú értékadás állna - ahol op a fenti kétoperandusú operátorok bármelyike lehet - fontos különbség azonban, hogy kif1 kiértékelésére csak egyszer kerül sor. Például ha x értéke 1, akkor az x += 2 kifejezés értéke 3, és mellékhatásként x is 3 lesz.

A vesszõoperátor (comma operator) nevét jelérõl, a , karakterrõl kapta. A vesszõoperátorral elválasztott kifejezéspár sorban egymás után kiértékelõdik, és az eredmény értéke és típusa megegyezik a második (jobb oldali) kifejezésével. Ha a vesszõ egy adott kontextusban másképp is elõfordulhat (függvény paramétereinek elválasztásánál, kezdeti értékek listájánál), akkor a vesszõoperátorral felépített kifejezést zárójelekkel kell védeni, például:

            fugg(a, (t = 3, t + 2), c)
A fenti függvénynek három paramétert adunk át, amelyek közül a középsõ értéke 5.

Az elõzõekben felsorolt operátorok elõfordulási sorrendje megegyezik a prioritásuk sorrendjével, az egyértelmûség kedvéért azonban álljanak itt az egyes prioritási szintek csökkenõ sorrendben a hozzájuk tartozó asszociativitási iránnyal együtt:

1.
Elsõdleges kifejezések. Az elsõdleges kifejezések balról jobbra csoportosítanak, tehát például a.k[3] értelmezése (a.k)[3]. Itt a . (a pont karakter) az ún. mezõkiválasztó operátor. Errõl részletesen az struktúrákkal foglakozó 1.8. pontban szólunk.
2.
Egyoperandusú operátorok: *, &, -, +, !, ~ , ++, -, típusmódosítás, sizeof

Az egyoperandusú operátorok csoportosítása jobbról balra történik, azaz például *p++ értelmezése *(p++).
3.
Multiplikatív operátorok: *, /, %

A multiplikatív operátorok balról jobbra csoportosítanak.
4.
Additív operátorok: +, -

Az additív operátorok balról jobbra csoportosítanak.
5.
Biteltoló (shift) operátorok: <<, >>

A Biteltoló operátorok balról jobbra csoportosítanak.
6.
Relációs operátorok: <, <=, >, >=

A relációs operátorok balról jobbra csoportosítanak.
7.
Egyenlõség-vizsgáló operátorok: ==, !=

Az egyenlõség-vizsgáló operátorok balról jobbra csoportosítanak.
8.
Bitenkénti ÉS operátor: &

Lásd a felsorolás utáni 1. és 3. megjegyzést.
9.
Bitenkénti kizáró VAGY operátor: ^

Lásd a felsorolás utáni 1. megjegyzést.
10.
Bitenkénti VAGY operátor: |

Lásd a felsorolás utáni 1. megjegyzést.
11.
Logikai ÉS operátor: &&

Lásd a felsorolás utáni 2. megjegyzést.
12.
Logikai VAGY operátor: ||

Lásd a felsorolás utáni 2. megjegyzést.
13.
Feltételes operátor: ? :

A feltételes kifejezések balról jobbra csoportosítanak.
14.
Értékadó operátorok: =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^ =, |=

Az értékadó operátorok jobbról balra csoportosítanak, azaz például x = y = 1 hatására mind x, mind y az 1 értéket veszi fel.
15.
Vesszõoperátor: ,

A vesszõoperátor garantálja a balról jobbra való sorrendben történõ kiértékelést.
1. Megjegyzés: a kétoperandusú *, +, &, ^ és az | operátorok asszociatívak. Ha egy kifejezésben azonos szinten több azonos asszociatív operátor szerepel, akkor a fordítónak jogában áll a kiértékelési sorrendet tetszõlegesen megválasztani. Ez - a részkifejezések mellékhatásai miatt - kihathat az eredmény értékére is, ezért kerülendõk az olyan kifejezések, amelyek nem definit kiértékelési sorrendtõl függenek. Például az a++ + b[a] kifejezésben a b tömb indexe attól függ, hogy az elsõ vagy a második tag kiértékelése történik elõbb, azaz az indexeléshez az eredeti vagy a megnövelt a-t használjuk. Ugyanez a helyzet áll fenn a függvényhívások paramétereinél, ugyanis ezek kiértékelése sem kötött sorrendû. Például a fugg(i++, a[i]) gépfüggõen választja meg adott i érték mellett az átadandó tömbelemet.

2. Megjegyzés: A logikai operátorok (&&, ||) garantálják a balról jobbra történõ kiértékelési sorrendet. Ezenkívül azt is biztosítják, hogy a második kifejezés kiértékelését csak akkor végzik el, ha az elsõ operandusuk alapján az eredmény nem egyértelmû. Például a && b esetében b kiértékelésére nem kerül sor, ha az a 0, hiszen ekkor az eredmény már biztosan hamis logikai érték lesz. Ennek jelentõségére igyekszik rávilágítani a következõ feltétel: b != 0 && a/b < 5. Ha nem lenne garantált a fenti kiértékelési stratégia, akkor ez a feltétel hibás lenne, mert b 0 értéke esetén bekövetkezne az elkerülni kívánt 0-val történõ osztás. Ügyeljünk arra, hogy a logikai kifejezésekben fellépõ mellékhatásokat ne használjuk ki, ugyanis egy logikai kifejezés esetleg lerövidült kiértékelése következtében a várt mellékhatások egy része elmaradhat.

3. Megjegyzés: a bitenkénti operátorok precedenciája alacsonyabb, mint az egyenlõség-vizsgáló operátoroké. Gyakori hiba a következõ alakú vizsgálat: c & 0xF == 8. Ez garantáltan mindig 0-át ad eredményül. Helyes megoldás: (c & 0xF) == 8.

4. Megjegyzés: a feltételes operátor kif1 feltételrészében, és minden egyéb helyen, ahol feltételt kell megadnunk, tetszõleges kifejezés szerepelhet, és annak 0, illetve nem 0 értéke jelenti a feltétel hamis, illetve igaz voltát. Különösen veszélyes ez akkor, ha tévedésbõl összekeverjük az egyszerû értékadó operátor = jelét az egyenlõség-vizsgáló operátor == jelével. Az a = b alakú feltétel ugyanis szintaktikailag teljesen helyes, de nem a két mennyiség egyenlõségét vizsgálja, hanem b 0 vagy nem 0 volta szerint szolgáltatja az eredõ feltételt, egyben b értékével a-t is felülírva. Szerencsére a BORLAND C++ az ilyen feltételeknél - ha engedélyezzük - figyelmeztetést ad (a Possibly incorrect assignment üzenettel). Ha viszont ténylegesen a fenti esetre van szükségünk, akkor a figyelmeztetés elkerülése - és az érthetõbb felírási forma - kedvéért írjuk azt az (a = b) != 0 alakba.


next up previous contents
Elõre: Konverziók Fel: Kifejezések Vissza: Elsõdleges kifejezések