Kimenő paraméterek
Kérdés: tudunk-e a paraméterváltozókon keresztül értéket visszaadni?
Erre sajnos a C#-ban nem egyszerű válaszolni. A helyzet igen bonyolult mindaddig, amíg a referencia típusú változókkal alaposan meg nem ismerkedünk.
Addig is használjuk az alábbi szabályokat: amennyiben a paraméterváltozók típusa a nyelv alaptípusai (int, double, char, string, …) közül valamelyik, úgy nem tudunk értéket visszaadni a hívás helyére, ha a paraméter típusa tömb, akkor a tömbelemeken keresztül azonban igen.
Pl.:
{
Console.WriteLine("A {0}+{1}={2}",a,b,a+b);
a = a+b;
}
Ha a fenti eljárást az alábbi módon hívjuk meg ...
Kiiras(x,20);
Console.WriteLine("A hivas utan az x erteke x={0}",x);
... ekkor kiíráskor az ’x’ változó értéke még mindig 12 lesz. Mivel a paraméterátadás során a híváskori érték (12) átadódik az eljárás ’a’ változójába mint kezdőérték (a=12), de utána a hívás helyén szereplő ’x’ változó, és a fogadó oldalon szereplő ’a’ változó között minden további kapcsolat megszűnik. Ezért hiába helyezünk az ’a’ változóba az eljáráson belül más értéket (a+b), az nem fogja befolyásolni az ’x’ értékét, marad benne a 12.
A fenti technikát érték szerinti paraméterátadásnak nevezzük. Ekkor az aktuális paraméterlistában feltüntetett értéket a fogadó oldal átveszi – a nélkül, hogy a kapcsolatot fenntartaná. Az érték egyszerűen átmásolódik a fogadó oldal változóiba.

Mint az ábrán is látszik, az ’x’ aktuális értéke (12) átmásolódik a memória külön területén helyet foglaló ’a’ változóba. Ezért az a=a+b értékadás eredménye is ezen a külön területen kerül tárolásra, és nem zavarja, nem változtatja meg az ’x’ értékét.
Amennyiben azonban a paraméterátadás-átvétel során tömböket adunk át, úgy a helyzet megváltozik:
{
tomb[0] = 10;
}
// --
// használat
// --
static int[] kisTomb=new int[10];
Feltoltes( kisTomb );
Console.WriteLine(”A 0. tombelem={0}”, kisTomb[0] );
A hívás helyén szereplő kis tömb még feltöltetlen, amikor átadjuk a ’Feltoltes’ eljárásnak. Az eljárás fogadja a tömböt a ’tomb’ változóban, majd megváltoztatja a ’tomb’ elemeinek értékét. Ezen változás azonban beleíródik a ’kisTomb’ által reprezentált tömb-be is, ezért az eljárás lefutása után a kisTomb[0] értéke már 10 lesz.
Az ilyen jellegű paraméterátadást referencia-szerinti átadásnak nevezzük!
A referencia típus nem jelentkezik külön a változó deklarációja során mint külön kulcsszó, vagy egyéb jelzés, ezért nehéz felismerni. Egyelőre fogadjuk el szabályként, hogy azok a változók lesznek referencia típusúak, amelyeknél a ’new’ kulcsszót kell használni az érték megadásakor (példányosítás).

A referencia-típusú változóknál dupla memóriafelhasználás van. Az elsődleges területen ilyenkor mindig egy memóriacímet tárol a rendszer. Ezen memóriacím a másodlagos memóriaterület helyének kezdőcíme lesz. Ezen másodlagos memóriaterületet a ’new’ hozza létre.
A nyelvi alaptípusok (int, double, char, …) értékének képzésekor nem kell a ’new’ kulcsszót használni …
- de a tömböknél igen:
Az ilyen jellegű változókat általában az jellemzi, hogy viszonylag nagy memória-igényük van. Egy ’int’ típusú érték tárolása csak 4 byte, míg a fenti tömb tárolása 40 byte.
Az érték szerinti paraméterátadás során az érték az átadás pillanatában két példányban létezik a memóriában (lemásolódik):
Kiiras(x);
static void Kiiras(int a)
{
...
}

A referencia típusú változók esetén azonban nem másolódik le az érték még egyszer a memóriában (ami a tömb esetén a tömbelemek lemásolását jelentené), hanem csak a szóban forgó tárterület címe (memóriacíme) adódik át. Ekkor a fogadó oldal megkapja a memóriaterület címét, és ha beleír ebbe a memóriaterületbe, akkor azt a küldő oldal is észlelni fogja:

{
tomb[0] = 10;
}
// --
// használat
// --
static int[] kisTomb=new int[10];
Feltoltes( kisTomb );
A fenti ábrán látszik, hogy a ’kisTomb’ változónk aktuális értéke átmásolódik a ’tomb’ aktuális értékébe. Csakhogy a ’kisTomb’ értéke egy memóriacím, ez az, ami valójában átmásolódik a ’tomb’ váltózóba, és nem a tömb elemei.
Mivel a ’tomb’ változó is ismerni fogja a tömbelemek tényleges helyét a memóriában, ezért amennyiben megváltoztatja azokat, úgy a változtatásokat a ’kisTomb’-ön keresztül is el tudjuk majd érni, hiszen mindkét esetben ugyanazokra a tömbelemekre hivatkozunk a memóriában.
Ez az oka annak, hogy a tömbelemek értékének módosítása a fogadó oldalon maradandó nyomot hagy a memóriában – amikor az eljárás már befejezte a futását, az értékek akkor is hozzáférhetőek.
A referencia-szerinti paraméterátadás is érték szerinti paraméterátadás, a változó értéke ilyenkor egy memóriacím, mely mint érték másolódik át a paraméterváltozókba. Az már más kérdés, hogyha a fogadó oldal megkapja a terület kezdőcímét, akkor azt meg is változtathatja. Mivel az eredeti változó is ugyanezen területre hivatkozik, ezért a változásokat a küldő oldalon később észlelni, (pl: kiolvasni) lehet.
Amennyiben a fogadó oldalon referencia típusú paramétert fogadunk, úgy a küldő oldalon csak olyan kifejezés szerepelhet, melynek végeredménye egy referencia:
Az előzőekben bemutatott kifejezés létrehoz egy 20 elemű int tömböt. A new foglalja le számára a memóriát, majd visszaadja a terület kezdőcímét. Ezt a kezdőcímet általában egy megfelelő típusú változóban tároljuk, de amennyiben erre nincs szükség, úgy a memóriacímet tárolás nélkül átadhatjuk egy eljárásnak paraméterként.
{
for(int i=0;i<tomb.Length;)
{
Console.Write("A tomb {0}. eleme=",i);
string strErtek=Console.ReadLine();
int x = Int32.Parse( strErtek );
if (x<0) continue;
tomb[i] = x;
i++;
}
return tomb;
}
Az előzőekben bemutatott függvény paraméterként megkapja egy int típusú tömb kezdőcímét. A tömb elemeit billentyűzetről tölti fel oly módon, hogy csak pozitív számot fogad el. A feltöltött tömb címét (referenciáját) visszaadja.
Mivel a függvény ugyanazt a memóriacímet adja vissza, mint amit megkapott, ennek nem sok értelme látszik. Figyeljük meg azonban a hívás helyét:
A hívás helyén a frissen elkészített tömb memóriacímét nem tároljuk le, hanem rögtön átadjuk a függvények. A tömb a hívás helyén készül el, üresen (csupa 0-val inicializálva), a függvény feltölti ezt a tömböt elemekkel, majd visszaadja az immár feltöltött tömb címét.
A fenti megoldás azért szép, mert egy sorban oldja meg a tömb létrehozását, feltöltését. Amennyiben a Feltoltes() csak egy eljárás lenne, úgy az alábbi módon kellene meghívni: