-
Ve třídě může být definováno několik konstruktorů (liší se počtem a typy
parametrů). Výjimečné postavení mají bezparametrický konstruktor a kopírovací
konstruktor. Bezparametrický konstruktor je konstruktor bez parametrů
(nebo konstruktor jehož všechny parametry mají definované implicitní hodnoty).
Implicitní verze bezparametrického konstruktoru (konstruktoru vytvořeného
překladačem), pouze vyhradí místo pro definovanou instanci.
Kopírovací
konstruktor je konstruktor, jehož jediným parametrem je odkaz na proměnnou
téhož typu, jakého je tento konstruktor. Také kopírovací konstruktor umí
v případě potřeby vytvořit překladač. Tato implicitní verze kopírovacího
konstruktoru, vyhradí potřebnou paměť pro nový objekt a zkopíruje obsah
svého parametru do vytvářeného objektu.
Následují zásady používání konstruktorů:
-
Nechceme-li přiřazovat definovanému objektu žádné speciální počáteční hodnoty
ani s ním provádět nějaké parametrizované akce, můžeme jej definovat klasicky
(napíšeme identifikátor datového typu a za něj identifikátor definovaného
objektu). Překladač v tomto případě použije bezparametrický konstruktor.
Pokud ve třídě není žádný programátorem definovaný konstruktor, generuje
překladač implicitní verzi bezparametrického konstruktoru sám.
-
Pokud potřebujeme přiřadit definovanému objektu nějaké speciální počáteční
hodnoty nebo s ním provádět akce blíže specifikované hodnotami nějakých
parametrů, musíme zvolit jednu z následujících tří variant definic:
-
Pokud nejsou ve třídě explicitně definovány žádné konstruktory a pokud
chceme položkám definovaného objektu pouze přiřadit dané počáteční hodnoty,
můžeme použít způsob používaný v definicích strukturovaných datových typů
(seznam hodnot jednotlivých položek uvedený ve složených závorkách).
-
Pokud chceme definovanému objektu přiřadit hodnotu konstanty nebo proměnné
nějakého typu T a ve třídě je definován konstruktor, jehož jediný parametr
je typu T (nebo odkaz na typ T), pak je možno definovanou instanci inicializovat
tak, že za její identifikátor napíšeme v definici rovnítko a za něj onu
inicializační hodnotu.
-
Pokud je parametrizace potřebného konstruktoru složitější, napíšeme v definici
instance jednotlivé skutečné parametry konstruktoru do kulatých závorek
za identifikátor instance.
Konstruktory se volají ve chvíli, kdy jsou objekty vytvářeny. Definujeme-li
globální nebo statický objekt, provede se konstruktor ještě před inicializačními
funkcemi (před funkcemi uvedenými v direktivách #pragma
startup, a tím samozřejmě i před funkcí main; inicializační
funkce mohou počítat s tím, že všechny statické objekty jsou již připraveny
k použití). Automatické objekty (objekty, které nejsou globální ani statické)
jsou vytvářeny při každém vstupu do programu a stejně tak jsou volány i
jejich konstruktory.
Toto si nyní ukážeme na příkladě. Máme následující třídy:
class A {
public:
int a1;
int a2;
void metoda()
{};
};
class B {
public:
int bi;
A bA;
static int
pocet;
// počet vytvořených instancí
B(A& a,
int i = 0){
// definice konstruktoru
bA = a;
bi = i;
pocet++;
};// Je-li
definován libovolný konstruktor, pak překladač negeneruje bezparametrický
konstruktor
// (v případě potřeby jej musíme definovat sami, např. jako B() {}; nebo
jako následující konstruktor)
B(int = 0,
int = 0, int =0);
B(B& b);
// kopírovací konstruktor
};
int B::pocet = 0;
inline B::B(int i1,
int i2, int i3){
bi = i1;
bA.a1 = i2;
bA.a2 = i3;
pocet++;
}
B::B(B& b){
bi = b.bi;
bA.a1 = b.bA.a1;
bA.a2 = b.bA.a2;
pocet++;
}
Pro tyto třídy lze používat konstruktory takto:
A as1;
// použije se implicitní bezparametrický konstruktor
A as2 = {0, 2};//
způsob používaný při inicializaci strukturovaných datových typů
const A as3 = as2;
// použije se implicitní kopírovací konstruktor
B bs1;
// použije se B(0, 0, 0)
B bs2 = 22;
// použije se B(int, 0, 0)
const B bs3 = bs2.bi;
// použije se B(int, 0, 0)
B bs4 = bs2;
// použije se kopírovací konstruktor B(B&)
B bs5 = as2;
// použije se B(A&, 0)
B bs6(22);
// použije se B(int, 0, 0)
B bs7(bs2.bA.a1);
// použije se B(int, 0, 0)
B bs8(bs2);
// použije se kopírovací konstruktor B(B&)
const B bs9(as2);
// použije se B(A&, 0)
B bs10(as2, 3);
// použije se B(A&, 3)
B bs11(10, 20, 30);//
použije se B(int, int, int)
B bs12[3];
// použije se 3x B(0, 0, 0)
Pokuste se pochopit, jak tyto konstruktory pracují a kdy se který z
možných konstruktorů použije.
-
Třída typu union nesmí obsahovat složky, která mají konstruktory,
ale sama tato třída konstruktory mít může. Vyzkoušejte (pokuste se deklarovat
třídu typu union a jako položky v ní použijte třídy A a B z předchozího
zadání).
-
Dalším typem implicitně volaných konstruktorů jsou konverzní konstruktory.
Jsou to konstruktory, které lze volat pouze s jedním parametrem. Použijí
se, když potřebujeme inicializovat instanci daného objektového typu a máme
k dispozici instanci typu, pro nějž je definován potřebný konverzní konstruktor.
Typickým příkladem je např. předávání parametrů funkcím. Jedná se o ekvivalent
implicitního přetypování. Uveďme si příklad (předpokládáme platnost předchozích
definic):
class C{
public:
B b;
C(int i=0,
int j=0, int k=0){b = B(i, j, k);};
void c(B&
bb){b = bb;};
// toto není konstruktor
};
B Pokus(B b) {
// obyčejná funkce
return b;
// při předávání parametrů hodnotou a při předávání
}
// vrácené hodnoty se volá kopírovací konstruktor
void main(void){
C c1(10, 20,
30);
c1.c(B(as2));
// volání B::B(A&) pro přetypování parametru
for (int i=0;
++i <=3;) bs12[i] = B(i, i*i);
bs1 = Pokus(bs12[2]);//
protože se parametr předává hodnotou, musí se
// vytvořit
nová instance - automaticky se volá kopírovací konstruktor
bs2 = Pokus(123);//
automaticky se volá konverzní konstruktor B::B(int, 0, 0)
}
Pokuste se pochopit, kdy je volán který konstruktor.
-
Objektové složky složených tříd můžeme inicializovat přímo. V hlavičce
konstruktoru složené třídy lze určit konstruktor, který chceme použít pro
vytvoření dané složky. Provedeme to tak, že za seznamem parametrů konstruktoru
složené třídy napíšeme dvojtečku a za ní uvedeme seznam identifikátorů
složek, které chceme vytvářet nestandardně. Při vytváření instance se její
jednotlivé složky vytvářejí v tom pořadí, v jakém byly deklarovány v definici
třídy. Pořadí uvedení inicializátorů složek v hlavičce konstruktoru nemá
na pořadí jejich inicializace žádný vliv (v další ukázce jsou schválně
přeházeny). Složky, jejichž inicializátory nebudou uvedeny v hlavičce konstruktoru,
budou vytvořeny bezparametrickým konstruktorem. Složky, které již byly
inicializovány, je možné použít jako parametry v inicializátorech dalších
složek.
class D{
public:
int i;
A a;
B b1;
B b2;
C c;
D(B bb, A
aa, int id2, int id3) : i(842), c(i/2, id2, id3), a(aa), b1(bb) {};
};//složka b2 není
v seznamu a bude tedy inicializována bezparametrickým konstruktorem
D d(bs5, as2, 246,
987);
Pole objektových typů můžeme inicializovat stejně jako pole instancí
neobjektových typů. Jediný rozdíl je v tom, že když není typ inicializační
hodnoty mezi typy, pro něž je definován konverzní konstruktor nebo když
potřebujeme přiřadit danému prvku pole hodnotu, pro jejíž vytvoření je
potřebný konstruktor s více parametry, uvedeme místo dané hodnoty přímo
tento konstruktor (viz třetí až šestý prvek pole v následující ukázce).
Stejně jako u klasických polí platí, že inicializačních hodnot nesmí být
více než je prvků pole a stejně jako u klasických polí nemusíme inicializovat
všechny prvky daného pole (prvky na než se inicializační hodnoty nedostanou
budou inicializovány bezparametrickým konstruktorem).
B bpole[9] = {as2,
124, bs2, B(321, 765), B(as2, 654), B(bs7), B(1, 2, 3)};
Pokuste se pochopit jak inicializace objektových typů probíhá.
-
Můžeme mít také datovou složku třídy, která je odkazem. V tomto případě
odkaz může být inicializován pouze v inicializačním seznamu třídy a nikde
jinde:
class mojeTrida {
jinaTrida&
jina; // odkaz na jinou třídu
// ...
public:
mojeTrida();
// ...
};
mojeTrida::mojeTrida()
:
jina(*new
jinaTrida) // musí být umístěno zde
{
}
-
Složky, které jsou deklarovány se specifikátorem const, jsou konstantní
složky. Konstantní složky uchovávají hodnoty, které se nastaví při vzniku
instance a v průběhu jejího života se již nemění. Může to být např. čas
vzniku dané instance, její pořadové číslo nebo jiný údaj, který nebude
potřeba měnit. Vytvořte jednoduchou třídu s konstantní složkou por_cis
(pořadové číslo instance) a vyzkoušejte jak pracuje (v konstruktoru toto
pořadové číslo vypisujte, vytvořte několik objektů a u některého z nich
se pokuste tuto složku změnit).
-
Napište deklaraci a implementaci třídy datum se soukromými položkami
den,
mesic
a rok, konstruktorem s implicitními parametry (s nulovou hodnotou)
a metodami nastav (nastavení všech položek),
nastavDen,
nastavMesic,
nastavRok,
Den
(zjištění dne), Mesic a Rok. U konstruktoru a všech nastavovacích
metod zajistěte, v případě nevhodné hodnoty některého parametru, použití
hodnoty ze systémového datumu (např. pomocí standardní funkce getdate).
Toto lze využít v případě, kdy použijete bezparametrický konstruktor, pak
hodnoty budou nastaveny na systémový datum. Tuto třídu ještě doplňte přetížením
operátoru << (výstup datumu do datového proudu). V následující implementaci
tohoto operátoru je uvedeno mnoho komentářů, aby jste se naučili definovat
takovéto operátory i pro své vlastní datové typy.
ostream& operator<<
(ostream& o, datum& D){
//Zapamatuj
si původní nastavení formátovacích příznaků a nastav výpis v des.
//soustavě
zarovnávaný doprava. Hodnota ostatních příznaků není významná.
long flags
= o.flags(ios::right + ios::dec);
//zjisti rozdíl
mezi požadovanou a potřebnou velikostí výpisové oblasti
int width
= o.width() - 8;
//zapamatuj
si nastavený výplňový znak, my použijeme nulu, např. 01.03.99
char fill
= o.fill('0');
//pokud je
požadovaná oblast větší a pokud nebylo nastaveno zarovnávání vlevo
//zaplň úvodní
přebytek původními výplňovými znaky
if ((width
> 0) && !(flags & ios::left)){
//proměnná
i z následujícího cyklu je v tomto bloku lokální
for (int i = 0; i < width; i++, o << fill);
}
//výpis datumu
o <<
setw(2) << D.Den() << "." << setw(2) << D.Mesic()
<< "."
<< setw(2) << (D.Rok()%100);
//obnova původního
nastavení zaplnění závěrečného přebytku výplňovými znaky
o.fill(fill);
o.flags(flags);
if ((width
> 0) && (flags & ios::left)){
for (int i = 0; i < width; i++, o << fill);
}
return o;
}
Vytvořenou třídu vyzkoušejte v nějakém programu.
-
Stejně jako datové složky třídy mohou být se specifikátorem static,
mohou být s tímto specifikátorem i metody třídy. Definují se stejně jako
běžné metody, pouze musíme mít na paměti, že nejsou svázány s žádnou instancí
(jedná se vlastně o funkci) a nelze v nich tedy používat klíčové slovo
this.
Tyto metody můžeme kvalifikovat stejně jako složky třídy se specifikátorem
static,
buď prostřednictvím identifikátoru nějaké instance (ten oddělujeme tečkou)
nebo prostřednictvím identifikátoru třídy (oddělujeme jej dvěma dvojtečkami).
Metody se specifikátorem static se používají když potřebujeme pracovat
pouze se složkami static, když chceme omezit přístupová práva k
dané funkci na metody dané třídy nebo když potřebujeme nějakou funkci,
která nemůže být běžnou metodou, aby přistupovala k soukromým složkám třídy.
Pokuste se vytvořit nějakou třídu, ve které použijete metodu se specifikátorem
static
a tuto metodu vyzkoušejte.
-
Přáteli se mohou stávat jak jednotlivé funkce, tak i celé třídy. Pokud
je přítelem deklarované třídy celá třída, vztahuje se možnost přístupu
ke všem složkám deklarované třídy na všechny metody spřátelené třídy. Nositelem
sdělení o přátelství může být jedině třída o jejichž soukromých položkách
se jedná. Přátelé se označují pomocí klíčového slova friend, které
je následováno buď prototypem funkce nebo identifikátorem třídy, kterou
právě deklarovaná třída označuje za svého přítele. Pokud chceme zdůraznit,
že deklarovaným přítelem je třída, můžeme před identifikátor spřátelené
třídy uvést klíčové slovo class, struct nebo union.
Např.
class tajna_schranka{
char *obsah;
tajna_schranka(char
*o = "") {obsah = o;};
friend class
agent;
};
V tomto případě jsou datová položka a konstruktor soukromé a lze k
nim přistupovat pouze ze samotné třídy tajna_schranka a ze spřátelené
třídy agent. Vyzkoušejte.
-
Můžeme definovat i konstanty objektových datových typů. Má to ale jeden
háček: překladač neumí v obecném případě posoudit, zda nějaká metoda ovlivní
hodnotu své instance či nikoliv a tudíž nám pro jistotu nedovolí pro konstantu
použít žádnou metodu. Jediné, co s ní můžeme dělat, je předávat ji jako
parametr funkcím, které mají ve svém prototypu u příslušného parametru
specifikátor const. Aby bylo možno aplikovat na konstantní objekty
metody, můžeme označit metody, které nemění hodnoty instance, pro kterou
je voláme, takže je lze bezpečně použít i na konstanty. Tyto metody se
v deklaraci (v definici i v prototypu) označují klíčovým slovem const,
které se uvede za závorkou, ukončující seznam parametrů. Např.
class datum {
int den;
int mesic;
int rok;
public:
int Den()
const {return den;};
int Mesic()
const {return mesic;};
int Rok()
const;
};
int datum::Rok()
const{return rok;}
Vytvořte nějaké konstantní instance a vyzkoušejte.