next up previous contents
Elõre: Inicializált függvényparaméterek Fel: Új elemek a C++-ban Vissza: Alternatívák a #define direktíva

Cím szerint nyilvántartott típusú, vagy referencia típusú változók

A 1.9.3-as részben, a hagyományos C nyelv ismertetésénél említettük, hogy a függvények érték szerint veszik át az aktuális paramétereiket. (A tömböket ebbõl a szempontból tekintsük olyan pointereknek, amelyek adott számú értékes adat számára lefoglalt tárterületre mutatnak - lásd a 1.9.6-os szakaszt. Igy a tömbök is beleférnek az "érték szerint" fogalmába, hiszen a mutatókat tényleg érték szerint adjuk át.) Ha egy változót cím szerint akartunk paraméterként átadni, akkor a formális paraméterlistában az adott típusra mutató pointert kellett deklarálnunk, a függvénytörzsben az indirekció operátorát kellett alkalmaznunk, és a függvény meghívásakor az aktuális paraméterlistában magunknak kellett explicit módon gondoskodnunk arról, hogy a megfelelõ paraméterhelyre a megfelelõ változó címe kerüljön. Ennek a dolognak az a nagy hátránya, hogy a függvénytörzsben nem különülnek el szintaktikailag az igazi tömbök és a cím szerint átadott skalárjellegû változók.

A C++-ban ilyen, és hasonló jellegû problémák áthidalására bevezették az ún. cím szerint nyilvántartott, vagy referencia típus (reference type) fogalmát és ehhez definiálták a & típusmódosító operátort. Igy például a

            int& r;
deklaráció azt jelenti, hogy r olyan változó, amely egy egész típusú változóra vonatkozó referenciát tartalmazhat. Úgy is felfoghatjuk a dolgot, hogy egy ilyen fajta változó egy olyan konstans pointer-kifejezés, amelyre vonatkozólag automatikusan végrehajtódik egy indirekció-mûvelet, amikor az adott referencia típusú változóra hivatkozunk. Ez három dolgot von maga után. Az egyik, hogy a fenti példa szerinti r változó minden olyan helyen állhat, ahol egy int típusú változó is állhat, azaz egész típusú kifejezésekben akár balérték, akár jobbérték lehet. A második, hogy a referencia típusú változókkal semmilyen mûvelet nem végezhetõ, hiszen minden hivatkozás alkalmával minden egyebet megelõz az implicit indirekció mûvelet (dereference operation, lásd a 1.5.2 alatt az egyoperandusú * operátorról leírtakat). Ezzel áll szoros összefüggésben a harmadik fontos dolog, hogy nincs értelme egy cím szerint nyilvántartott típusú változót inicializálás nélkül definiálni. Tehát csak az alábbihoz hasonló definíciónak van értelme:
            int  ii = 0;
            int& rr = ii;
Ekkor az rr++; utasítás szintaktikailag ugyan helyes, de nem az rr változó inkrementálódik, hanem az az int típusú tárterületfoglaló tárolási egység, amelyiknek a címét rr tartalmazza. Ez a fenti példában éppenséggel az ii változó. Ez az értelmezés triviális akkor, amikor az inicializáló kifejezés egy balérték, de nem kötelezõ, hogy az inicializátor balérték legyen, sõt, az sem kötelezõ, hogy a referncia típus alaptípusába tartozzon. Ilyen esetekben
a)
elõször típuskonverzió hajtódik végre, ha az szükséges,
b)
aztán a típuskonverzió eredménye egy ideiglenes változóba kerül,
c)
és végül ennek az ideiglenes változónak a címe kerül felhasználásra az adott referencia típusú változó inicializálásához.
Tekintsük az alábbi deklarációt:
            double& dr = 1;
Ez a fentiek alapján a következõképpen értelmezhetõ:
            double* drp;
            double temp;
            temp = (double)1;
            drp = &temp;
A használat során a (*drp) kifejezés egyenértékû a dr-rel.

A referencia típus igazán kellemes felhasználási területe a bevezetõben is említett függvényparaméter-deklaráció. Ez azt jelenti, hogy a formális paraméterlistában már lehetõségünk van arra, hogy egy paramétert ne a reá mutató pointer segítségével adjunk át cím szerint, hanem jelezzük, hogy egy olyan változóról van szó, amit cím szerint kell átadnuk (mert például kimenõ paraméterként is szükségünk lesz rá), ugyanakkor a függvénytörzsben a többi, érték szerint átadott változóhoz hasonló módon - ugyanolyan szintaktikával - szeretnénk kezelni. A 1.9.3-as részben közölt egyszerû példa a referncia típus felhasználásával így néz ki:

            void f1(long& a)
            {
               a += 2L;
            }
Ekkor az
            alfa = 0L; f1(alfa);
kódrészlet szintaktikailag helyes, és hatására alfa értéke 2L lesz. A különbözõ paraméterátadási lehetõségeket szemlélteti a következõ kis program:
#include <stdio.h>
// **********************************************************
int any_function(int  par_by_value,  //Ertek szerinti int
                 int* use_for_arrays,//Ertek szerinti pointer
                 int& par_by_address)//Cim szerinti int
// **********************************************************
{  int   work;
   work = par_by_value;
   par_by_value *= 3;
   *use_for_arrays = par_by_address * work;
   par_by_address = work * work;
   return par_by_value;
}
// **********************************************************
main()
// **********************************************************
{
   int x = 2, y[ ] = { 1, 2 }, z = 10, w = 0;
   w = any_function(z,y,x);
   printf("%d %d %d %d\n",x,y[0],z,w);
}
Az any_function függvény harmadik paraméterét deklaráltuk referencia típusúnak, erre a típusnév (int) után álló & típusmódosító operátor utal. Tekintsük át a fenti program mûködését.

A main-ben any_function meghívása elõtt az x, y[0], z és w változók értéke rendre 2, 1, 10 és 0, a w-nek függvényhívással történõ értékadás eredményeképpen (mellékhatásként) pedig x, y[0], z, valamint w a 100, 20, 10 és 30 értékeket veszik fel; a szabványos kimeneten ezek a számok fognak sorra megjelenni. Látható, hogy az érték szerint átadott paraméter (z) nem változott meg, y[0] új értéket kapott, hiszen a reá mutató pointeren keresztül indirekt módon címezve beleírtunk, és a cím szerint átadott skalár, x is új értékkel bír a függvényhívás után. Látható az is, hogy par_by_value és par_by_address használata szintaktikailag azonos.

Az any_function deklarációjakor a par_by_address parméternél használt megoldás hasonlít ahhoz, amit a Pascal nyelv alkalmaz. Pascal-ban is alapértelmezés szerint érték szerint adódnak át a paraméterek; ott a VAR kulcsszóval jelezhetjük a cím szerinti paraméterátadást, ugyanakkor a FUNCTION, vagy PROCEDURE törzsében az érték, vagy cím szerint átadott paraméterek használatában nincs szintaktikai különbség.

A referencia típust függvények visszatérési típusaként is nagyon jól fel lehet használni. Gondoljunk csak a 1.12-es pontban a balértékek kapcsán deklarált char *get_buff_pos(int i) függvényre. Ennek a függvénynek balértékként való alkalmazása így nézett ki:

            *get_buff_pos(1) = 'b';
Ha függvényünket char & típusú visszatérési értékkel deklaráljuk, akkor a referencia típus tulajdonságai következtében a get_buff_pos(1) kifejezés minden további nélkül érvényes balérték kifejezés lesz, tehát a fenti értékadó utasítást így írhatjuk:
            get_buff_pos(1) = 'b';
Vegyük észre, hogy a referencia típusú visszatérési érték miatt a char& get_buff_pos(int i) függvény egy értékadó utasítás mindkét oldalán állhat. A referencia típusról itt elmondottak elsõsorban a fogalom megértetését szolgálják. A referencia típus leges-legfontosabb szerepet azonban a felhasználó által definiált típusokra vonatkozó operátor-függvényeknek definiálásánál játssza - ezek visszatérési értéke ugyanis az adott típusra vonatkozó referencia-típusú.

Vegyük észre, hogy a referencia típust képzõ & operátor természetes kiegészítése a hagyományos C típusmódosító operátorainak: míg a * indirekció operátornak, a [ ] indexelõ operátornak és a ( ) függvényaktivizáló operátornak volt párja a típusmódosító operátorok között, addig az 'address of' operátornak (egyoperandusú &) nem volt.


next up previous contents
Elõre: Inicializált függvényparaméterek Fel: Új elemek a C++-ban Vissza: Alternatívák a #define direktíva