• Nem Talált Eredményt

A változók tárolási osztályai

II. fejezet - Moduláris programozás C++ nyelven

3. Névterek és tárolási osztályok

3.1. A változók tárolási osztályai

static int puffermutato; // modul szintű definíció

// az n és elso blokk szintű definíciók

void IntToHex(register unsigned n, auto bool elso = true) { extern char puffer[]; // program szintű deklaráció auto int szamjegy; // blokk szintű definíció

// hex blokk szintű definíció

static const char hex[] = "0123456789ABCDEF";

extern char puffer[]; // program szintű deklaráció

extern char puffer [123] = {0}; // program szintű definíció

Az blokkok szintjén alapértelmezés szerinti auto tárolási osztályt gyakorlatilag sohasem adjuk meg, ezért a példában is áthúzva szerepeltetjük. A függvényeken kívül definiált változók alapértelmezés szerint külső (external) tárolási osztályúak, azonban az extern kulcsszót csak a külső változók deklarálása (nem definíció!!) kell megadnunk.

3.1.1. A változók elérhetősége (hatóköre) és kapcsolódása

A tárolási osztállyal szoros kapcsolatban álló fogalom a változók elérhetősége, hatóköre (scope). Minden azonosító csak a hatókörén belül látható. A hatókörök több fajtáját különböztetjük meg: blokk szintű (lokális), fájl szintű és program szintű (globális) (II.7. ábra - A változók hatókörei).

A változók elérhetőségét alapértelmezés szerint a definíció programban elfoglalt helye határozza meg. Így csak blokk szintű és program szintű elérhetőséggel rendelkező változókat hozhatunk létre. A tárolási osztályok közvetlen megadásával árnyalhatjuk a képet.

A hatókör fogalma hasonló a kapcsolódás (linkage) fogalmához, azonban nem teljesen egyezik meg azzal.

Azonos nevekkel különböző hatókörökben, különböző változókat jelölhetünk. Azonban a különböző hatókörben deklarált, illetve az azonos hatókörben egynél többször deklarált azonosítók a kapcsolódás felhasználásával ugyanarra a változóra hivatkozhatnak. A C++ nyelvben háromféle kapcsolódásról beszélhetünk: belső, külső, és amikor nincs kapcsolódás.

C++ programban a változók az alábbi hatókörök valamelyikével rendelkezhetnek:

blokk szintű A változó csak abban a blokkban (függvényblokkban)

látható, amelyben definiáltuk, így elérése helyhez kötött, vagyis lokális. Amikor a program eléri a blokkot záró kapcsos zárójelet, a blokk szintű azonosító többé nem lesz elérhető. A blokk szintjén extern és static tárolási osztályok nélkül definiált változók nem rendelkeznek kapcsolódással.

fájl szintű A változó csak abban a modulban látható, amely a deklarációját tartalmazza. Kizárólag a modul függvényeiből hivatkozhatunk az ilyen változókra, más modulokból nem. Azok az azonosítók rendelkeznek fájl szintű hatókörrel, amelyeket a modul függvényein kívül, belső kapcsolódással deklarálunk a static tárolási osztály segítségével. (Megjegyezzük, hogy a C++ szabvány a static kulcsszó ilyen módon történő felhasználását elavultnak nyilvánította, helyette a névtelen névterek alkalmazását javasolja a belső kapcsolódású azonosítók kijelölésére).

program szintű A változó a program minden moduljának (fordítási egységének) függvényeiből elérhető. Azok az ún.

globális azonosítók rendelkeznek program szintű hatókörrel, amelyeket a modulok függvényein kívül, külső kapcsolódással deklarálunk. Ehhez a globális változókat tárolási osztály nélkül vagy az extern tárolási osztállyal kell leírnunk.

A fájl és a program szintű hatóköröket a később ismertetett névterek (namespace) tovább bontják a minősített azonosítók bevezetésével.

II.7. ábra - A változók hatókörei

3.1.2. A változók élettartama

Az élettartam (lifetime) a program végrehajtásának azon időszaka, amelyben az adott változó létezik. Az élettartam szempontjából az azonosítókat három csoportra oszthatjuk: statikus, automatikus és dinamikus élettartamú nevek.

statikus élettartam Azok az azonosítók, amelyek a static vagy extern tárolási osztállyal rendelkeznek, statikus élettartamúak.

A statikus élettartamú változók számára kijelölt memóriaterület (és a benne tárolt adat) a program futásának teljes időtartama alatt megmarad. Az ilyen változók inicializálása egyetlen egyszer, a program indításakor megy végbe.

automatikus élettartam Blokkon belül a static tárolási osztály nélkül definiált változók és a függvények paraméterei automatikus (lokális) élettartammal rendelkeznek. Az automatikus élettartamú azonosítók memóriaterülete (és a benne tárolt adat) csak abban a blokkban létezik, amelyben az azonosítót definiáltuk. A lokális élettartamú változók a blokkba történő minden egyes belépéskor új területen jönnek létre, ami blokkból való kilépés során megszűnik (tartalma elvész). Amennyiben egy ilyen változót kezdőértékkel látunk el, akkor az inicializálás mindig újra megtörténik, amikor a változó létrejön.

dinamikus élettartam A tárolási osztályoktól függetlenül dinamikus élettartammal rendelkeznek azok a memóriaterületek, amelyeket a new operátorral foglalunk le, illetve a delete operátorral szabadítunk fel.

3.1.3. A blokk szintű változók tárolási osztályai

Azok a változók, amelyeket programblokkon/függvényblokkon belül, illetve a függvények paraméterlistájában definiálunk alapértelmezés szerint automatikus (auto) változók. Az auto kulcsszót általában nem használjuk, sőt a legújabb C++11 verzióban új értelmezéssel látták el a nyelv fejlesztői. A blokk szintű változókat gyakran lokális változóknak hívjuk.

3.1.3.1. Az automatikus változók

Az automatikus változók a blokkba való belépéskor jönnek létre, és blokkból való kilépés során megszűnnek.

A korábbi fejezetekben láttuk, hogy az összetett utasítások (a blokkok) egymásba is ágyazhatók. Mindegyik blokk tetszőleges sorrendben tartalmazhat definíciókat (deklarációkat) és utasításokat:

{

definíciók, deklarációk és utasítások

}

Valamely automatikus azonosító ideiglenesen elérhetetlenné válik, ha egy belső blokkban ugyanolyan névvel egy másik változót definiálunk. Ez az elfedés a belső blokk végéig terjed, és nincs mód arra, hogy az elfedett (létező) változóra hivatkozzunk:

int main() {

double hatar = 123.729;

// a double típusú hatar értéke 123.729 {

long hatar = 41002L;

// a long típusú hatar értéke 41002L }

// a double típusú hatar értéke 123.729 }

Mivel az automatikus tárolók a blokkból kilépve megszűnnek, ezért nem szabad olyan függvényt írni, amely egy lokális változó címével vagy referenciájával tér vissza. Ha egy függvényen belül kívánunk helyet foglalni olyan tároló számára, amelyet a függvényen kívül használunk, akkor a dinamikus memóriafoglalás eszközeit kell alkalmaznunk.

A C++ szabvány szerint ugyancsak automatikus változó a for ciklus fejében definiált ciklusváltozó:

for (int i=12; i<23; i+=2) cout << i << endl;

↯ cout << i << endl; // fordítási hiba, nem definiált az i

Az automatikus változók inicializálása minden esetben végbemegy, amikor a vezérlés a blokkhoz kerül.

Azonban csak azok a változók kapnak kezdőértéket, amelyek definíciójában szerepel kezdőértékadás. (A többi változó értéke határozatlan!).

3.1.3.2. A register tárolási osztály

A register tárolási osztály csak automatikus lokális változókhoz és függvényparaméterekhez használhatjuk. A register kulcsszó megadásával kérjük a fordítót, hogy az adott változót - lehetőség szerint - a processzor regiszterében hozza létre. Ily módon gyorsabb végrehajtható kódot fordíthatunk. Amennyiben nincs szabad felhasználható regiszter, a tároló automatikus változóként jön létre. Ugyancsak a memóriában jön létre a regiszter változó, ha a címe operátort (&) alkalmazzuk hozzá.

Fontos megjegyeznünk, hogy a regiszter tárolási osztály alkalmazása ellen szól, hogy a C++ fordítóprogramok képesek a kód optimalizálására, ami általában hatékonyabb kódot eredményez, mint amit mi a register előírással elérhetünk.

Az alábbi Csere() függvény a korábban bemutatott megoldásnál potenciálisan gyorsabb működésű. (A potenciális szót azért használjuk, mivel nem ismert, hogy a register előírások közül hányat érvényesít a fordító.) void Csere(register int &a, register int &b) {

register int c = a;

a = b;

b = c;

}

A következő példa StrToLong() függvénye a sztringben tárolt decimális számot long típusú értékké alakítja:

long StrToLong(const string& str) {

register int index = 0;

register long elojel = 1, szam = 0;

// a bevezető szóközök átlépése for(index=0; index < str.size()

&& isspace(str[index]); ++index);

// előjel?

if( index < str.size()) {

if( str[index] == '+' ) { elojel = 1; ++index; } if( str[index] == '-' ) { elojel = -1; ++index; } }

// számjegyek

for(; index < str.size() && isdigit(str[index]); ++index) szam = szam * 10 + (str[index] - '0');

return elojel * szam;

}

A register változók élettartama, láthatósága és inicializálásának módja megegyezik az automatikus tárolási osztályú változókéval.

3.1.3.3. Statikus élettartamú lokális változók

A static tárolási osztályt az auto helyett alkalmazva, statikus élettartamú, blokk szintű változókat hozhatunk létre. Az ilyen változók láthatósága a definíciót tartalmazó blokkra korlátozódik, azonban a változó a program indításától, a programból való kilépésig létezik. Mivel a statikus lokális változók inicializálása egyetlen egyszer, a változó létrehozásakor megy végbe, a változók a blokkból való kilépés után (a függvényhívások között) is megőrzik értéküket.

Az alábbi példában a Fibo() függvény a Fibonacci-számok soron következő elemét szolgáltatja a 3. elemtől kezdve. A függvényen belül a statikus lokális a0 és a1 változókban a kilépés után is megőrizzük az előző két elem értékét.

#include <iostream>

3.1.4. A fájl szintű változók tárolási osztálya

A függvényeken kívül definiált változók automatikusan külső, vagyis program szintű hatókörrel rendelkeznek.

Amikor több modulból (forrásfájlból) álló programot fejlesztünk, a moduláris programozás elveinek megvalósításához nem elegendő, hogy vannak globális változóink. Szükségünk lehet olyan modul szinten definiált változókra is, amelyek elérését a forrásállományra korlátozhatjuk (adatrejtés). Ezt a static tárolási osztály külső változók előtti megadásával érhetjük el. A függvényeken kívüli konstans definíciók alapértelmezés szerint szintén belső kapcsolódásúak.

Az alábbi Fibonacci-számokat előállító programban az a0 és a1 változókat modulszinten definiáltuk annak érdekében, hogy egy másik függvénnyel (FiboInit()) is elérjük a tartalmukat.

#include <iostream>

using namespace std;

const unsigned elso = 0, masodik = 1;

static unsigned a0 = elso, a1 = masodik;

A statikus változók inicializálása a programba való belépés során egyszer megy végbe. Amennyiben nem adunk meg kezdőértéket a definícióban, úgy a fordító automatikusan 0-val inicializálja a változót (feltöltve annak területét nullás bájtokkal). C++-ban a statikus változók kezdőértékeként tetszőleges kifejezést használhatunk.

A C++ szabvány a statikus külső változók helyett a névtelen névterek használatát javasolja (lásd később).

3.1.5. A program szintű változók tárolási osztálya

A függvényeken kívül definiált változók, amelyeknél nem adunk meg tárolási osztályt, alapértelmezés szerint extern tárolási osztállyal rendelkeznek. (Természetesen az extern kulcsszó közvetlenül is megadható.)

Az extern (globális) változók élettartama a programba való belépéstől a program befejezésig terjed. Azonban a láthatósággal lehetnek problémák. Egy adott modulban definiált változó csak akkor érhető el egy másik modulból, ha abban szerepeltetjük a változó deklarációját (például deklarációs állomány beépítésével).

A program szintű globális változók használata esetén mindig tudnunk kell, hogy a változót definiáljuk, vagy deklaráljuk. Ha a függvényeken kívül tárolási osztály nélkül szerepeltetünk egy változót, akkor ez a változó definícióját jelenti (függetlenül attól, hogy adunk-e explicit kezdőértéket vagy sem). Ezzel szemben az extern explicit megadása kétféle eredményre vezethet: kezdőérték megadás nélkül a változót deklaráljuk, míg kezdőértékkel definiáljuk. Az elmondottakat jól szemléltetik az alábbi példák:

Azonos defíniciók (csak egyikük adható meg) Deklarációk double osszeg;

extern const int meret = 7; extern const int meret;

Felhívjuk a figyelmet arra, hogy a globális változók deklarációi a programon belül bárhova elhelyezhetők, azonban hatásuk a deklaráció helyétől az adott hatókör (blokk vagy modul) határáig terjed. Szokásos megoldás, hogy a deklarációkat modulonként fejállományokban tároljuk, és azt minden forrásfájl elején beépítjük a kódba.

A globális változók inicializálása a programba való belépés során egyszer megy végbe. Amennyiben nem adunk meg kezdőértéket, úgy a fordító automatikusan 0-val inicializálja a változót. C++-ban a globális változók kezdőértékeként tetszőleges kifejezést használhatunk.

Fontos megjegyeznünk, hogy a globális változókat csak indokolt esetben, átgondolt módon szabad használni.

Egy nagyobb program esetén is csak néhány, központi extern változó alkalmazása a megengedett.