-
Následují program zavádí použití konstruktorů a destruktorů.
class obdelnik {
// jednoduchá třída
int vyska;
int sirka;
public:
obdelnik(void);
// s konstruktorem,
int plocha(void);
// s dvěmi metodami
void inicializace(int,
int);
~obdelnik(void);
// a s destruktorem
};
obdelnik::obdelnik(void){
// konstruktor
vyska = 6;
sirka = 6;
}
int obdelnik::plocha(void){
// plocha obdélníku
return vyska
* sirka;
}
void obdelnik::inicializace(int
nova_vyska, int nova_sirka){
vyska = nova_vyska;
sirka = nova_sirka;
}
obdelnik::~obdelnik(void){
// destruktor
vyska = 0;
sirka = 0;
}
int main(int argc,
char **argv)
{
obdelnik okno,
ctverec;
cout <<
"Plocha okna je " << okno.plocha() << endl;
cout <<
"Plocha ctverce je " << ctverec.plocha() << endl;
okno.inicializace(12,10);
ctverec.inicializace(8,8);
cout <<
"Plocha okna je " << okno.plocha() << endl;
cout <<
"Plocha ctverce je " << ctverec.plocha() << endl;
return 0;
}
Tento program je identický s programem uvedeným na konci předchozí
kapitoly (do třídy je přidán konstruktor a destruktor a je vynechána struktura).
Konstruktor
je v C++ volán automaticky při deklaraci objektu a je tedy velkou pomocí
k zabránění použití neinicializovaných proměnných. Když deklarujeme objekt
okno,
konstruktor je volán automaticky systémem a nastaví hodnoty jeho položek
(v našem případě na hodnoty 6). Obdobně to platí i pro objekt
ctverec.
Konstruktor má stejné jméno jako třída (oboje se v našem případě jmenuje
obdelnik).
U konstruktoru nedefinujeme návratový typ. I když položky objektu jsou
již inicializovány konstruktorem, můžeme jim přiřadit jiné hodnoty. Destruktor
je velmi podobný konstruktoru, s tím rozdílem, že je volán automaticky,
když se objekt dostane mimo rozsah. Destruktor má stejné jméno jako třída,
předchází mu ale znak ~. Destruktor nemá žádný návratový typ. V našem případě
destruktor provádí pouze vynulování obou položek. Destruktor je zde pouze
použit pro ilustraci jeho použití. Jestliže objekt alokoval nějakou paměť,
pak destruktor by ji měl uvolnit.
-
Do konstruktoru a destruktoru přidejte příkaz vypisující informaci o volání,
aby jste se přesvědčili, že jsou opravdu volány systémem automaticky.
-
Podívejte se na následující program. Tento program se velmi podobá předchozímu.
Třída se nyní jmenuje okno. Program by vám měl být srozumitelný
až na jednu věc. Metoda označená červeně obsahuje implementaci metody jako
část deklarace. Je to obdoba vnořené funkce. Program vyzkoušejte.
class okno {
// jednoduchá třída
int vyska;
int sirka;
public:
okno(void);
// s konstruktorem,
int ziskej_plochu(void){
return vyska * sirka; }; // s dvěmi metodami
void nastav(int,
int);
~okno(void);
// a s destruktorem
};
okno::okno(void){
// konstruktor
vyska = 6;
sirka = 6;
}
void okno::nastav(int
nova_vyska, int nova_sirka){
vyska = nova_vyska;
sirka = nova_sirka;
}
okno::~okno(void){
// destruktor
vyska = 0;
sirka = 0;
}
int main(int argc,
char **argv)
{
okno male,
stredni, velke;
male.nastav(5,
7);
// stredni okno použije hodnoty nastavené konstruktorem
velke.nastav(15,
20);
cout <<
"Plocha malého okna je " << male.ziskej_plochu() << endl;
cout <<
"Plocha středního okna je " << stredni.ziskej_plochu() << endl;
cout <<
"Plocha velkého okna je " << velke.ziskej_plochu() << endl;
return 0;
}
Namísto termínu ?volání funkce používáme ?zasílání zpráv. V těchto
dvou operacích je rozdíl. Jelikož data objektu jsou pevně v objektu obsažena,
neexistuje mimo metod způsob jak tato data získat. Zašleme tedy objektu zprávu,
aby provedl nějakou operaci na svých vnitřních datech. Při volání funkcí
používáme pro přístup k datůe? používáme
-
Následující program ukazuje použití pole objektů.
class okno {
int delka;
int sirka;
public:
okno(void);
// konstruktor
void nastav(int
nova_delka, int nova_sirka);
int ziskej_plochu(void)
{return(delka * sirka);}
};
okno::okno(void){
// konstruktor
delka = 8;
sirka = 8;
}
void okno::nastav(int
nova_delka, int nova_sirka){
delka = nova_delka;
sirka = nova_sirka;
}
int main(int argc,
char **argv)
{
okno male,
stredni, velke, skupina[4];
// sedm oken
male.nastav(5,
7);
for (int index
= 1; index < 4; index++)
// skupina[0] je implicitní
skupina[index].nastav(index + 10, 10);
cout <<
"Plocha malého okna je " << male.ziskej_plochu() << endl;
cout <<
"Plocha středního okna je " << stredni.ziskej_plochu() << endl;
for (int index
= 0; index < 4; index++)
cout << "Plocha pole okna je " <<
skupina[index].ziskej_plochu() << endl;
return 0;
}
V tomto programu je přidána deklarace pole 4 objektů třídy okno.
Každý z těchto objektů je inicializován na hodnoty uvedené v konstruktoru,
neboť konstruktor je proveden pro všechny deklarované objekty. Pole objektů
je v programu dále používáno. Povšimněte si jak se zasílají zprávy jednomu
objektu v poli objektů. Pokuste se pochopit tento program a vyzkoušejte
jej.
-
Další program používá jako složku třídy řetězec znaků. Ve skutečnosti v
objektu není vložený řetězec znaků, ale je zde ukazatel na řetězec znaků.
class okno {
int delka;
int sirka;
char *radek_textu;
public:
okno(char
*radek);
void nastav(int
nova_delka, int nova_sirka);
int ziskej_plochu(void);
};
okno::okno(char *radek){
delka = 8;
sirka = 8;
radek_textu
= radek;
}
void okno::nastav(int
nova_delka, int nova_sirka){
delka = nova_delka;
sirka = nova_sirka;
}
okno::ziskej_plochu(void){
cout <<
radek_textu << " = ";
return (delka
* sirka);
}
int main(int argc,
char **argv)
{
okno male("malé
okno "), stredni("střední okno "), velke("velké okno ");
male.nastav(5,
7);
// stredni okno použije hodnoty nastavené konstruktorem
velke.nastav(15,
20);
cout <<
"Plocha ";
cout <<
male.ziskej_plochu() << endl;
cout <<
"Plocha ";
cout <<
stredni.ziskej_plochu() << endl;
cout <<
"Plocha ";
cout <<
velke.ziskej_plochu() << endl;
return 0;
}
V deklaraci třídy je nyní použit ukazatel na char pojmenovaný
radek_textu.
Konstruktor obsahuje vstupní parametr, kterým je ukazatel na řetězec a
tento ukazatel bude v konstruktoru přiřazen složce radek_textu.
Mohli bychom definovat proměnnou radek_textu jako pole znaků ve
třídě a potom použít funkci strcpy k překopírování řetězce do objektu.
Tím se budeme zabývat v následujícím zadání.
Náš konstruktor má nyní jeden parametr, který slouží k předání informací
do objektu. Mohli bychom jich použít i více. Když nyní deklarujeme objekty,
předáváme každé instanci jako aktuální parametr řetězcovou konstantu, která
je potom použita konstruktorem k přiřazení ukazatele na ní, složce radek_textu.
V metodě ziskej_plochu tento text vypisujeme. Výpis textu sice s
výpočtem plochy nesouvisí, ale zde je to použito pro ilustraci toho, že
objekt obsahuje skutečně ukazatel na text použitý při deklaraci. Vhodnější
by bylo vytvořit další metodu pro zjištění tohoto řetězce (a případně další
pro jeho nastavení).
-
Změňte předchozí program tak, aby třída používala pole znaků namísto ukazatele
na znak a do pole překopírujte řetězec znaků parametru konstruktoru. Přidejte
další metodu umožňující změnit uloženou hodnotu řetězce zasláním zprávy
z hlavního programu a použijte novou metodu ke změně hodnot uložených ve
všech třech objektech. Výpisem se přesvědčte o provedené změně.
-
Ve třídě může být také obsažen ukazatel na nějaká dynamicky alokovaná data.
To ukazuje následující program.
class okno {
int delka;
int sirka;
int *ukazatel;
public:
okno(void);
void nastav(int
nova_delka, int nova_sirka, int ulozena_hodnota);
int ziskej_plochu(void)
{return(delka * sirka);}
int ziskej_hodnotu(void)
{return *ukazatel;}
~okno(void);
};
okno::okno(void){
delka = 8;
sirka = 8;
ukazatel =
new int;
*ukazatel
= 122;
}
void okno::nastav(int
nova_delka, int nova_sirka, int ulozena_hodnota){
delka = nova_delka;
sirka = nova_sirka;
*ukazatel
= ulozena_hodnota;
}
okno::~okno(void){
delka = 0;
sirka = 0;
delete ukazatel;
}
int main(int argc,
char **argv)
{
okno male,
stredni, velke;
male.nastav(5,
7, 177);
velke.nastav(15,
20, 999);
cout <<
"Plocha malého okna je " << male.ziskej_plochu() << endl;
cout <<
"Plocha středního okna je " << stredni.ziskej_plochu() << endl;
cout <<
"Plocha velkého okna je " << velke.ziskej_plochu() << endl;
cout <<
"Uložená hodnota malého okna je "<<male.ziskej_hodnotu()<<endl;
cout <<
"Uložená hodnota středního okna je "<<stredni.ziskej_hodnotu()<<endl;
cout <<
"Uložená hodnota velkého okna je "<<velke.ziskej_hodnotu()<<endl;
return 0;
}
V našem případě jsme do třídy přidali ukazatel na int (je to
pouze ukazatel a alokaci místa v hromadě musíme provést v konstruktoru).
V našem programu jsou deklarovány tři objekty a každý z nich obsahuje ukazatel,
který ukazuje do hromady na tři různá místa. Každý objekt má svou vlastní
dynamicky alokovanou datovou složku pro své vlastní soukromé použití. V
tomto malém programu se nedostaneme do situace, kdy bude nedostatek paměti
v hromadě. Ve skutečných programech, je ale vhodné testovat hodnotu vráceného
ukazatele na NULL a zjistit tak, zda proměnná byla skutečně alokována.
Metoda nastav má nyní tři parametry a poslední z nich se používá
k nastavení hodnoty nové dynamicky alokované proměnné. V programu dále
zasíláme dvě zprávy, jednu malému oknu a druhou velkému oknu k nastavení
jejich položek. Střední okno má stále implicitní hodnoty. Alokovanou dynamickou
paměť musíme v destruktoru třídy opět uvolnit. K zjištění hodnoty dynamické
proměnné je použita metoda ziskej_hodnotu. Uložené hodnoty v dynamických
proměnných jsou vypsány na konci programu. Prostudujte si tento program
a vyzkoušejte jak pracuje.
-
V následujícím programu je použit dynamicky alokovaný objekt. Vidíme, že
používání dynamických objektů se neliší od jiných dynamických proměnných.
Program si prostudujte.
class okno {
int delka;
int sirka;
public:
okno(void);
void nastav(int
nova_delka, int nova_sirka);
int ziskej_plochu(void);
};
okno::okno(void){
delka = 8;
sirka = 8;
}
void okno::nastav(int
nova_delka, int nova_sirka){
delka = nova_delka;
sirka = nova_sirka;
}
okno::ziskej_plochu(void){
return (delka
* sirka);
}
int main(int argc,
char **argv)
{
okno male,
stredni, velke;
okno *ukazatel;
male.nastav(5,
7);
velke.nastav(15,
20);
ukazatel =
new okno;
// použití imlicitních hodnot
cout <<
"Plocha malého okna je " << male.ziskej_plochu() << endl;
cout <<
"Plocha středního okna je " << stredni.ziskej_plochu() << endl;
cout <<
"Plocha velkého okna je " << velke.ziskej_plochu() << endl;
cout <<
"Plocha nového okna je " << ukazatel->ziskej_plochu() << endl;
ukazatel->nastav(12,12);
cout <<
"Plocha nového okna je " << ukazatel->ziskej_plochu() << endl;
delete ukazatel;
return 0;
}
Na konci programu je použit příkaz ukazatel->nastav(12,12).
Zdůvodněte, zda tento příkaz by bylo možno nahradit příkazem (*ukazatel).nastav(12,
12). Posledním příkazem programu je dynamicky alokovaný objekt
zrušen. Změňte tento program tak, aby také objekty male a stredni
byly alokovány dynamicky.
-
Následující program používá objekt s vnitřním odkazem na jiný objekt své
vlastní třídy. Je to standardní struktura používaná pro jednosměrně zřetězený
seznam. V našem programu je použita velmi jednoduchým způsobem.
class okno {
int delka;
int sirka;
okno *dalsi_okno;
public:
okno(void);
void nastav(int
nova_delka, int nova_sirka);
int ziskej_plochu(void);
void nastav_ukazatel(okno
*ktere_okno);
okno *ziskej_dalsi(void);
};
okno::okno(void){
delka = 8;
sirka = 8;
dalsi_okno
= NULL;
}
void okno::nastav(int
nova_delka, int nova_sirka){
delka = nova_delka;
sirka = nova_sirka;
}
int okno::ziskej_plochu(void){
return (delka
* sirka);
}
void okno::nastav_ukazatel(okno
*ktere_okno){
dalsi_okno
= ktere_okno;
}
okno *okno::ziskej_dalsi(void){
return dalsi_okno;
}
int main(int argc,
char **argv)
{
okno male,
stredni, velke;
okno *ukazatel;
male.nastav(5,
7);
velke.nastav(15,
20);
cout <<
"Plocha malého okna je " << male.ziskej_plochu() << endl;
cout <<
"Plocha středního okna je " << stredni.ziskej_plochu() << endl;
cout <<
"Plocha velkého okna je " << velke.ziskej_plochu() << endl;
male.nastav_ukazatel(&stredni);
stredni.nastav_ukazatel(&velke);
ukazatel =
♂
ukazatel =
ukazatel->ziskej_dalsi();
cout <<
"Plocha okna je " << ukazatel->ziskej_plochu() << endl;
return 0;
}
V konstruktoru je inicializován ukazatel na NULL. Takto je vhodné inicializovat
všechny ukazatele, které zatím na nic neukazují. Přiřazení ukazatele v
konstruktoru zajistí, že všechny objekty této třídy budou mít svůj ukazatel
inicializovaný. Do třídy byly přidány další dvě metody. Pokuste se zjistit,
co dělají a co dělá celý program.
-
V C++ je další klíčové slovo a to this. Toto this je definováno
v každém objektu a označuje ukazatel na objekt, ve kterém je obsaženo.
Je implicitně deklarováno jako
jmeno_třidy *this;
a je inicializováno k ukazování na objekt jehož metoda je volána. Ukazatel
this
nesmíme nikdy modifikovat. Tento ukazatel je velmi užitečný pro práci s
ukazateli a obzvláště se zřetězenými seznamy, které potřebují ukazatel
na objekt při vkládání do seznamu. Ukázka použití bude uvedena později.
-
Ukazatel this se také často používá v aplikacích GUI. Když v aplikaci
GUI vkládáme komponenty na formulář, pak je většina práce udělána za nás.
Komponentu lze ale také vytvořit za běhu aplikace. Např. při stisku tlačítka
můžeme chtít vytvořit na formuláři jiné tlačítko. Obsluha stisku tlačítka
v tomto případě bude:
void __fastcall TForm1::Button1Click(TObject
*Sender)
{
TButton *tlacitko
= new TButton(this);
tlacitko->Parent
= this;
tlacitko->Caption
= "Nové tlačítko";
tlacitko->Left
= 100;
tlacitko->Top
= 50;
tlacitko->Show();
// další kód
}
V tomto kódu vidíme použití ukazatele this (parametr konstruktoru
tlačítka a vlastnost Parent vytvořeného tlačítka). Vyzkoušejte.
Nové pojmy:
-
Konstruktor je funkce, která je automaticky volána při vytváření
instance třídy.
-
Destruktor je speciální funkce, která je automaticky volána při
rušení objektu.