Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Osztálysablonok


      Az osztálysablont paraméteres típusnak is lehet nevezni, ugyanis
paraméterekre van szükség, hogy egy osztálysablont általános osztállyá
alakítsunk. A példákban a verem adatstruktúra lesz a központban, amelynek LIFO
(Last In First Out) alapú működése érvényes lesz bármilyen adattípusra. Az
osztálysablonok lehetővé teszik egy olyan általános jellegű verem létrehozását,
amelyet egy bizonyos adattípusra lehet szabni. Mindez elősegíti a
kódújrahasznosítást, hiszen nem kell a hasonló függvényeket mindenik adattípusra
külön-külön megírni. Amikor a programozó egy adattípust szeretne a sablonjába
illeszteni, akkor egyszerűen csak azzal használja, a többit a fordító megoldja.
Ilyen módon a
Stack osztálysablon
használható
double, int, char vagy akár Employee osztálytípusra is.





tstack.h


#ifndef TSTACK_H


#define TSTACK_H





template class T>


class Stack


{


     public:


           Stack(int = 10);                //konstruktor: 10
elem


           ~Stack()
{ delete[] stackPtr; } //destruktor


           bool push(const
T&);            //beszúrás


           bool pop(T&);                   //kivétel


     private:


           int size;                       //max elemek száma


           int top;                        //jelenlegi elemek
száma


           T*
stackPtr;                    //T osztálysablon
típusú mutató


           bool isEmpty() const
{return top == -1;}    //Üres-e


           bool isFull() const {return top == size-1;} //Megtelt-e


};





templateclass
T>


Stack::Stack(int
s)


{


     size
= s > 0 ? s : 10;


     top =
-1;               //kezdetben üres


     stackPtr
= new T[size]; //memóriahely
a verem számára


}





templateclass
T>


bool Stack::push(const
T& pushValue)


{


     if(!isFull())                      //ha nincs teli,
akkor


     {


           stackPtr[++top]
= pushValue; //berak egy elemet és növeli az
elemszámot


           return true;                 //sikeres
beszúrás


     }


     return false;                      //ha ideér,
akkor a beszúrás sikertelen


}





templateclass
T>


bool Stack::pop(T& popValue)


{


     if(!isEmpty())                    //ha nem üres,
akkor


     {


           popValue
= stackPtr[top--]; //kivesz egy elemet és csökkenti
az elemszámot


           return true;                //sikeres kivétel


     }


     return false;                     //ha ideér, akkor
a kivétel sikertelen


}


#endif





test_stack.cpp


#include


using std::cout;


using std::cin;


using std::endl;


#include "tstack.h"





int main()


{


     Stackdouble> doubleStack(5);


     double f = 1.1;





     cout
"Elemek beszúrása a doubleStack
verembe:\n"
;


     while(doubleStack.push(f))


     {


           cout
' '
;


           f
+= 1.1;


     }


     cout
"\nA verem megtelt. Nem szúrható be a
"
"
elem.";


      


     cout
"\n\nElemek kivétele a doubleStack
veremből:\n"
;


     while(doubleStack.pop(f)) cout ' ';


     cout
"\nA verem kiürült. Nem lehet több
elemet kivenni.\n"
;





     Stackint> intStack;


     int i = 1;





     cout
"\nElemek beszúrása az intStack
verembe:\n"
;


     while(intStack.push(i))


     {


           cout
' '
;


           ++i;


     }


     cout
"\nA verem megtelt. Nem szúrható be a
"
"
elem.";


    


     cout
"\n\nElemek kivétele az intStack
veremből:\n"
;


     while(intStack.pop(i)) cout ' ';


     cout
"\nA verem kiürült. Nem lehet több
elemet kivenni.\n"
;





     return 0;


}








A kimenet:


Elemek beszúrása
a doubleStack verembe:


1.1 2.2 3.3 4.4
5.5


A verem megtelt.
Nem szúrható be a 6.6 elem.





Elemek kivétele
a doubleStack veremből:


5.5 4.4 3.3 2.2
1.1


A verem kiürült.
Nem lehet több elemet kivenni.





Elemek beszúrása
az intStack verembe:


1 2 3 4 5 6 7 8
9 10


A verem megtelt.
Nem szúrható be a 11 elem.





Elemek kivétele
az intStack veremből:


10 9 8 7 6 5 4 3
2 1


A verem kiürült.
Nem lehet több elemet kivenni.





A Stack osztály definíciója hasonló az
általános osztálydefinícióhoz, csakhogy előtte van a
templateclass
T> fejléc. Ez azt jelenti,
hogy az osztály egy osztálysablont használ, a T paraméterrel. Amikor
létrehozunk egy
Stack
típusú objektumot, akkor a
T paramétert
az adott adattípus fogja helyettesíteni. Bármilyen adattípussal alkalmazható a
T paraméter. A T szerepében alkalmazott adattípusok esetén két dolgnak kell
teljesülnie:


- a konstruktor legyen alapértelmezve (int
= 10)


- támogassa a hozzárendelő operátort (=)


Ha a Stack objektum dinamikusan lefoglalt
memóriahelyekre hivatkozik, akkor a hozzárendelő operátor túlterhelt kell
legyen (át kell legyen definiálva) az adott adattípusnak.


     
A main függvény első utasítása egy 5 elemű, double típusú vermet hoz
létre. A fordító a double típust rendeli hozzá az osztálysablonT paraméteréhez,
így az Stack osztály double verziójú forráskódja fog legenerálódni és működni.
Bár a programozó nem látja ezt a forráskódot, az automatikusan része lesz a
programnak és lefordítódik. A program ezt követően double típusú adatokkal kezdi
feltölteni a vermet, addig amíg a
push
függvény hamisat nem térít vissza (ekkor a verem megtelt). Ezek után az
adatokat a
pop függvény elkezdi
kivenni, amíg a visszatérített érték hamis nem lesz (ekkor a verem kiürült).
Ugyanígy történik a beszúrás és a kivétel az int típusú verem esetén is. Ennek
deklarálásásnál nincs megadva a verem mérete, így az alapértelmezett
konstruktor 10 elemű vermet hoz létre.


     
A
templateclass T>  fejlécet a
tagfüggvények definícióinál is alkalmazni kell, sőt a
:: bináris operátor is a paraméterrel van alkalmazva, hogy hozzárendelje
a tagfüggvény nevét az osztálysablon tartományához. Például, amikor a
doubleStack objektum Stack-ként van értelmezve, akkor a konstruktor
a
new operátort használja a double típusú verem
létrehozására:
stackPtr
= new T[size]
átalakul stackPtr = new double[size]  paranccsá.


     
A következő példában a testStack függvény ugyanazt hajtja végre mint az
előző példa. A T paramétert használja a veremben lévő adattípusok
képviselésére.





test1_stack.cpp


#include


using std::cout;


using std::cin;


using std::endl;


#include "tstack.h"





templateclass
T>


void testStack(Stack &theStack, T
value, T increment, const char* stackName)


{


     cout
"\n\nElemek beszúrása a "

"verembe:\n";


     while(theStack.push(value))


     {


           cout
' '
;


           value
+= increment;


     }


     cout
"\nA verem megtelt. Nem szúrható be a
"
"
elem.";


    


     cout
"\n\nElemek kivétele a "

"veremből:\n";


     while(theStack.pop(value)) cout ' ';


     cout
"\nA verem kiürült. Nem lehet több
elemet kivenni.\n"
;


}





int main()


{


     Stackdouble> doubleStack(5);


     Stackint> intStack;





     testStack(doubleStack,
1.1, 1.1, "doubleStack");


     testStack(intStack,
1, 1, "intStack");





     return 0;


}


Ebben az esetben is a főprogram létrehoz
két Stack típusú objektumot. Az egyik double, a másik int típusú elemekkel
kompatibilis.





Értékparaméterek


     
A
Stack osztálysablon
csupán a T típusparamétert használta a sablon fejlécében. Emellett lehet
használni még értékparamétereket is, például:
templateclass
T, int elements>. Az objektum deklarálása is megváltozik: Stackdouble, 100> doubleStack. Ez az utasítás egy 100 elemű double vermet deklarál.
Az osztály ebben az esetben tartalmazhatna egy tömböt, mint privát tagváltozó:
T
stackHolder[elements];





Statikus tagok


     
A hagyományos osztályok esetén
static típusú adattagok az összes objektum számára
globálisak, ugyanis osztály (és nem objektum) szinten vannak tárolva. Az
osztálysablonokból generált osztályoknak külön-külön saját statikus adattagjai
lesznek, de továbbra is osztályszinten lesznek tárolva.





Dinamikus memórialefoglalás kezelése


      Előfordulhat, hogy például a stackPtr = new T[size]  művelet sikertelen lesz.
A konstruktor nem téríthet vissza értéket, de valahogy mégis el kellene küldeni
a hibaüzenetet a programnak anélkül, hogy az megálljon (amit például az
assert okoz). Egyik megoldás, hogy
visszatérítjük az objektumot akkor is, ha nem volt megfelelően felépítve annak
reményében, hogy az osztály felhasználója majd leellenőrzi azt mielőtt
használná. Egy másik megoldás, hogy beállítunk egy tagváltozót a konstruktoron
kívül, amelyet ellenőrizve jelezhető a hiba. A harmadik megoldás egy
kivételkezelés (try-catch) megvalósítása, amellyel még a konstruktorban jelezni
tudjuk a hibát. A konstruktorban keletkezett kivételek (try) automatikusan
meghívják az objektum destruktorát még mielőtt elindulna a kezelés (catch). Ha
az objektumnak a tagjai is objektumok, és kivétel keletkezik a gazda objektum
létrehozása során, akkor a tag-objektumok destruktorai is lefutnak még a hiba
megjelenése előtt. A destruktorban fellépő kivételek is kezelhetőek, ha a
destruktort meghívó függvényt egy
try
blokkba helyezzük, melyet a kivétel
catch-je kezel.


     
A standard C++ fordító egy
bad_alloc kivételt generál, amikor hiba lép fel a new operátor alkalmazásánál. Ez a fejléc-állományában van definiálva. Egyes
fordítók viszont nem kompatibilisek a C++ standard könyvtáraival, és 0 értéket
térítenek vissza a kivétel esetén:





test_new.cpp


#include


using std::cout;





int main()


{


     double *ptr[50];


    


     for(int i = 0; i 50; i++)


     {


           ptr[i]
= new double[300000000];


           if(ptr[i] == 0)


           {


                cout
"Sikertelen memóriafoglalás:
ptr["
"]\n";


                break;


           }


           else


                cout
"Sikeres memóriafoglalás: ptr["

"]\n";


     }





     return 0;


}





A fenti program egy 50 elemű double
mutatókból álló tömböt deklarál. A tömb minden elemének elkezd lefoglalni 300
ezer double méretű memóriahelyet, és leellenőrzi, hogy a tömb adott eleme nulla
értéket kapott-e vissza a lefoglalás során. Annak függvényében, hogy mennyi
memória áll rendelkezésre (ebben az esetben 8GB) és mekkora a double mérete
(ebben az esetben 8 byte), a program előbb utóbb meg fog állni. A tömb első
eleme rögtön lefoglal
8x300000000 Byte = 2400000000 Byte = 2.4GB memóriahelyet. Belátható, hogy a harmadik
lefoglalás után be fog telni a memória és a
new hibát generál:





Sikeres
memóriafoglalás: ptr[0]


Sikeres
memóriafoglalás: ptr[1]


Sikeres
memóriafoglalás: ptr[2]


Sikeres
memóriafoglalás: ptr[3]


Sikertelen
memóriafoglalás: ptr[4]





A következő példában olyan fordító
dolgozik, amely kezelni tudja a
bad_alloc kivételt:





test1_new.cpp


#include


using std::cout;


using std::endl;


#include


using std::bad_alloc;





int main()


{


     double *ptr[50];


    


     try


     {


           for(int i = 0; i 50; i++)


           {


                ptr[i]
= new double[300000000];


                cout
"Sikeres memóriafoglalás: ptr["

"]\n";


           }


     }


     catch(bad_alloc e)


     {


           cout
"A következő kivétel keletkezett:
"


     }


    


     return 0;


}





A kimenet:


Sikeres
memóriafoglalás: ptr[0]


Sikeres
memóriafoglalás: ptr[1]


Sikeres
memóriafoglalás: ptr[2]


Sikeres
memóriafoglalás: ptr[3]


A következő kivétel keletkezett: std::bad_alloc





A következő program azt igazolja, hogy az
objektumok destruktorai még azelőtt lebontják az objektumot, mielőtt a hiba
bekövetkezne:





test.h


#ifndef TEST_H


#define TEST_H


#include


using std::cout;





class Test


{


     public:


           Test(double = 0);                   


           ~Test()
{ cout "\nDestruktor: "



     private:


           double value;


           double *TestPtr;


};





Test::Test(double
s)


{


     value
= s > 0 ? s : 0;




This post first appeared on Altair Gate - News, please read the originial post: here

Share the post

Osztálysablonok

×

Subscribe to Altair Gate - News

Get updates delivered right to your inbox!

Thank you for your subscription

×