fel
le

Értékadó utasítás

Az változóink deklarálása nem elég. Egy deklarált változó önnmagában nem képvisel értéket, ebből a szemponból definiálatlannak tekinthetjük. Egy söröskorsó nem hordozza a sört, csak a lehetőséget.

A változóinkba értéket tudunk elhelyezni az értékadó utasítással. Ennek jele a = jel (más programozási nyelvekben néha := jellel jelölik).

int a;
a=12; // az 'a' értéke legyen 12
...
 

A fenti példában deklarálunk egy a nevű, int típusú változót, mely deklaráláskor még értékkel nem rendelkezik. A következő utasítás segítségével ezen változó aktuális értékét beállítjuk, egy egész szám literál segítségével 12-re.

Az értékadás után az a változónk értéke 12 lesz. Ezen értéket felhasználhatjuk további értékadásokban:

int b;
b=a; // a 'b' értéke legyen egyenlő az 'a' aktuális értékével
...
 

Most deklaráltunk egy b változót (szintén int), és értékét beállítjuk ugyanarra az értékre, mint az a értéke ebben a pillanatban, vagyis 12-re.

int c;
c=b*2; // 'c' értéke legyen a 'b' értékének kétszerese
...
 

Ezen példában a c változó már 24 értékre áll be.

Az értékadó utasítás általános alakja szerint bal oldalán egy (korábban deklarált) változó neve szerepel, jobb oldalán egy kifejezés, amely értékét a számítógép futás közben kiszámolja, majd a kapott értéket elhelyezi a változó által lefoglalt memóriaterületen. Ezt a folyamatot értékadásnak, az új értéket e pillanattól kezdve a változó aktuális értékének nevezzük.
b=8;
...
 

A b változó, amely korábban a 12 értéket tárolta, most felveszi a 8 értéket. A továbbiakban már nem emlékszik a régi értékére, csak az újra. A c változót ez már nem érinti, ő megkapta a 24 értéket korábban, és mivel nem adtunk ki rá újabb értékadó utasítást, ez nem változik meg.

Egy változó értékét a programunk futása során tetszőleges sokszor beállíthatjuk, módosíthatjuk. A változó mindíg csak a legutolsó értékét képes tárolni, korábbi értékeit felülírtuk, elveszett. Egy változó értékének beállítása nem lehet hatással más változók értékeire. Egyetlen értékadó utasítás csak egyetlen változó értékét képes módosítani.

Kezdőérték beállítás

Nagyon gyakori az, hogy a változó deklarációját követően azonnal be kívánjuk állítani annak értékét. A változóinkat a korábban elmondott okoknál fogva ugyanis a lehető legkésőbbi pillanatban deklaráljuk, leginkább akkor, amikor már ténylegesen szükség is van rá.

int x;
x=7;
...
 

A fenti két utasítás összevonható egyetlen lépésbe:

int x = 7;
...
 

Ennek során nem csak deklarálunk egy új x nevű változót, de azonnal be is állitjuk annak kezdőértékét. A továbbiakban az x változónk beállított értékkel rendelkező (definiált) változó.

Típusegyezőség

Amikor értékadó utasítást fogalmazunk, ügyelnünk kell a baloldal és a jobboldal típusára.

A bal oldalon kötelezően egy változó áll, melyet deklaráltunk, és eközben explicit módon megadtuk a típusát. A bal oldal típusa tehát egyértelműen ismert.

A jobb oldalon minden esetben egy kifejezés áll, mégha az a kifejezés egyetlen literálból vagy konstansból áll is. Az egyszerű kifejezések típusa könnyedén meghatározható, az összetett kifejezéseké (operátorokat, függvényhívásokat is tartalmaz) kicsit több gondolkodással, de szintén meghatározható (lásd a numerikus kifejezések fejezetet).

Ameddig a két oldali típus egyezik, addig nincs semmi gond. Ez az értékadó utasítás biztosan helyes, és végrehajtható.

Amennyiben a két oldal típusa nem egyezik meg tökéletesen, úgy három lehetőség van:

  • létezik implicit típuskonverzió,
  • létezik explicit típuskonverzió,
  • egyik sem létezik.

Implicit típuskonverzió

Vegyük az alábbi példát:

double a = 12;
 

A bal oldal típusa double, a jobb oldal típusa int. A két típus nem egyezik meg, ezért a fenti három eset valamelyike fog bekövetkezni.

Az implicit típuskonverzió olyan típuskonverzió, amelyet a fordítóprogram automatikusan alkalmazhat, amikor szükségét érzi (maga a típuskonverzió ténye nincs belegépelve látható módon a forráskódba, de a típusok elemzése során kimutatható).

A fenti értékadó utasítás úgy működik, hogy a jobb oldali értéket (12) az értékadás végrehajtása során double típussá alakítja a futtató rendszer (12.0), majd mivel mostmár a jobb oldalon is double áll, végrehajtó az értékadó utasítás.

Milyen implicit típuskonverziók léteznek? Ez a téma elsősorban a numerikus típusok (egész, tört) között merül fel. Az implicit típuskonverziók témakörét a második féléves OOP tananyagban újratárgyaljuk, bővítve a lehetőségeket.

Amikor a fordítóprogram látja, hogy van egy A típusa (pl: int), és kell(ene) belőle csinálni egy B típust (pl: double), akkor a következőképpen jár el (ha A és B is egész számtípus):

  • ha A tárolási igénye kisebb mint B tárolási igénye, akkor elvégzi a típuskonverziót (pl: byte -> ushort működik)
  • ha A tárolási igénye nagyobb mint B tárolási igénye, akkor nincs típuskonverzió (pl: int -> sbyte nem működik)
  • ha A és B tárolási igénye egyenlő, egyikük előjeles, a másik előjel nélküli, akkor nincs típuskonverzió (pl: int -> uint, sem uint->int)

Ha nem mindkettő egész szám típus, akkor:

  • ha A egész, B lebegőpontos, akkor van típuskonverzió (pl: int -> float)
  • ha A lebegőpontos, B egész, akkor nincs típuskonverzió (pl: double -> int)

A fenti szabályok első olvasásra bonyolultaknak, és megjegyezhetetleneknek tűnnek. Pedig teljesen logikusan működnek. Akkor van t ípuskonverzió, ha a konvertálandó értéket egy olyan típussá alakítjuk, ami garantáltan képes azt tárolni. Egy kisebb egészből egy nagyobb egészbe konvertálás garantáltan működik, mivel a nagyobb egész típus nagyobb számokat képes kezelni, tehát a kis értéket gond nélkül át tudja venni (byte -> short).

Nagyobb típusból kisebb típusba nem biztos a siker. Ha egy int típusú értékünk van, pl: a 12, az mondjuk épp beleférhetne egy byte típusú változóba, de az int nagyon nagy értéket is felvehet, és általában nem fér el egy byte-on. Ezért ezen irányú típuskonverzió csak bizonyos esetekben működne jól, ezért összességében végrehajtása tilos, nem létezik.

Bármilyen egész típusú érték mindíg konvertálható lebegőpontos értékké, mivel azok elég nagy egész értéket is kezelnek. De fordítva nem, mivel egy tört érték sosem fér be egy egész típusba.

Implicit (automatikus) típuskonverziót a fordító akkor alkalmaz csak,

ha biztos benne, hogy nem okoz vele adatvesztést. Amennyiben a valamilyen típusú érték nem garantált, hogy a fogadó típus értéktartományába belefér - a fordító nem alkalmazza a típuskonverziót.

Ha a fordító implicit módon nem képes a jobb oldali típus átalakítani a bal oldal típussá, akkor az értékadó utasítást típus-helytelennek minősíti, és szintaktikai (fordítási) hibával leáll. A programunk ebben a formában nem indítható el.

Ekkor még nem feltétlenül van nagy baj, mert elképzelhető, hogy explicit típuskonverzióval az ellentmondás feloldható.

Megj.: a második féléves anyagban tárgyalni fogjuk hogyan készíthetünk saját típust, ahhoz saját implicit és explicit típuskonverziókat. Be fogjuk látni, hogy az első félévben hivatkozott típuskonverziók valójában nem részei a nyelvnek, ezeket a fordítóprogram nem önhatalmúlag ismeri és használja. A megadott típusokhoz (int, double, stb.) a típus készítői írták meg az implicit és explicit típuskonverziókat. A fordítóprogram ezeket a típusok definíciójával beolvassa, és alkalmazza ahol kell. Tehát pl. az, hogy nincs string->int típuskonverzió, azért nem a fordítóprogramot kell hibáztatni, hanem a string vagy az int típusok készítőit.

Explicit típuskonverzió

Amennyiben egy A típusunkat egy B típussá kívánjuk átalakítani, alkalmazhatunk explicit típuskonverziót is.

Az explicit típuskonverziót könnyű felismerni - ugyanis a program szövegébe bele kell írni (a programozó belegépeli a szövegbe):

double a = 12;
int x = a; // hibás, nincs double -> int
 

Az a változónk típusa double, e miatt az értékadó utasítás jobb oldala is double, amely nem alakítható át implicit (automatikus) módon int-é. A fordító cannot implicitly convert type 'double' to 'int'. Explicit conversion exists (are you missing a cast?) üzenettel jelzi a hibát.

Az eplicit konverzió során nekünk kell megadni a cél típus nevét, melyet gömbölyű zárójelpárban írunk a konvertálandó érték elé:

double a = 12;
int x = (int)a; // rendben van
 

Az (int)a a fordítóprogramnak szól: alakítsd át bármi áron az a változóban lévő értéket int-é. Az átalakítás után a jobb oldalon egy int fog állni, s innentől kezdve a két oldal típusa egyezik - az értékadó utasítás típushelyes, végrehajtható.

De hogyan történik a típusátalakítás? Amennyiben pl az a váltózónkban a 12.3 érték lenne, úgy az int-é alakítás után már csak 12 maradna ezen törtértékből. Tehát eközben információt veszthetünk.

Ezen veszteséget a programozó tudomásul kell vegye, és ennek ellenére kérte az átalakítást. Innentől a felelősség nem a fordítóprogramot terheli, amennyiben ez valamifél rossz számolási végeredményhez vezetne.

Ritkán, de előfordul, hogy az explicit típuskonverzió során nem közvetlenül a céltípusba konvertálunk:

double a = 300.234;
int x = (byte)a; // rendben van, de mennyi az eredmény?
 

A fenti esetben a 300.234 értéket explicit típuskonverzióval byte típussá alakítjuk. Eközben nem 300 lesz belőle, mivel a byte típus értéktartománya [0..255]. Megmagyarázható (most nem megyünk bele), de 44 lesz az eredmény. Ezek után a jobb oldal típusa byte lesz, melyből vezet implicit típuskonverzió int-re, ezt a fordító automatikusan alkalmazza is, így alakul ki a végeredmény - a fenti értékadó utasítás típushelyes. Az x változó értéke 44 lesz.

Amennyiben egyből int-é alakítottuk volna a double-t, ekkora veszteség nem állt volna fenn:

double a = 300.234;
int x = (int)a; // rendben van
 

E fenti esetben az x változóba a 300 érték került volna be.

Egyik sem létezik

Amennyiben a jobb oldal típusára nem alkalmazható implicit típuskonverzió, sem explicit típuskonverzió, akkor az értékadó utasítás nem végrehajtható:

string s = "300";
int x = s; // nem vegrehajthato
int z = (int)s; // nem vegrehajthato
 

Nem létezik string->int irányban sem implicit, sem explicit típuskonverzió, ezért sem az x, sem a z változóra vonatkozó értékadás nem típushelyes.

Ha sem implicit, sem explicit típuskonverzió nem létezik két típus között, akkor még nem kell feladni végképp. Elképzelhető hogy létezik olyan függvény, amely elvégzi ezt az átalakítási lépést, de ekkor már nem típuskonverzióról, hanem függvényhívásról beszélünk. Az elemi típusok közötti átalakítások nagy része a Convert osztályban van leprogramozva.
Hernyák Zoltán
2013-01-24 10:06:42