Kurz C++ (10.)
Z p°edchozφho
Φlßnku mßme hrub² nßstin objektovΘho programovßnφ. V dneÜnφm dφle se budeme
v∞novat specißlnφm metodßm - konstruktoru a destruktoru a podφvßme se podrobn∞ji
na ΦlenskΘ funkce. Probereme
takΘ vytvß°enφ objekt∙ a to jak statickΘ, tak dynamickΘ.
10.1. Zßklady prßce s objekty
V uvedenΘm k≤du jsou
vynechßny direktivy #include. VětÜina přφkladů vy₧aduje
stdio.h kvůli
konstantě NULL a jin²m, iostream.h kvůli
cout a string.h kvůli
manipulaci s řetězci. Pro pokusy s funkcφ malloc() je nutnΘ pou₧φt
hlaviΦkov² soubor malloc.h.
Nejprve se
zaměřφme na zmφněn² konstruktor. Konstruktor je členskß metoda třφdy, kterß je
volßna jako prvnφ při vytvßřenφ objektu, přičem₧ nezßle₧φ na tom, zdali
vytvßřφme objekt staticky nebo dynamicky (viz. nφ₧e). Mß tedy na starost inicializaci
danΘho objektu (např. alokaci paměti, nastavenφ určit²ch proměnn²ch atd.). Aby
se metoda stala konstruktorem stačφ jedinΘ a to nazvat ji stejn²m jmΘnem jako
třφdu a vynechat nßvratovou hodnotu, proto₧e konstruktory nikdy nic nevracφ.
Tedy pro třφdu Clovek bude metoda s nßzvem
Clovek() konstruktorem. Pokud se
podφvßte na přφklady z minulΘ lekce, tak si jistě vÜimnete, ₧e ve třφdßch
konstruktor chybφ. To je ale v pořßdku, jestli₧e toti₧ konstruktor explicitně
nedeklarujete, dosadφ si překladač takzvan² implicitnφ konstruktor, co₧ je
vlastně prßzdnß funkce. V přφpadě, ₧e je to vhodnΘ lze samozřejmě napsat vφce
konstruktorů, kterΘ budou mφt rozdφlnΘ parametry nebo různ² počet parametrů,
překladač si pak vybere ten sprßvn² prßvě podle parametrů, kterΘ mu předßte.
Teď, kdy₧ vφte zßkladnφ informace o
konstruktoru, je asi jasnΘ k čemu bude dobr² destruktor. Je to tedy opět
metoda třφdy, kterß je zodpovědnß za zruÜenφ objektu (tedy např. uvolněnφ
paměti alokovanΘ v konstruktoru). Destruktor se vyznačuje opět stejn²m jmΘnem
jako třφda, ale navφc mß předsazen znak "~". Opět nevracφ ₧ßdnou hodnotu a
navφc musφ b²t bezparametrick². Ka₧dß třφda mů₧e mφt pouze jeden destruktor.
Nynφ si uvedeme krßtkou ukßzku
konstruktoru a destruktoru. Pro nßsledujφcφ jednoduchΘ a krßtkΘ třφdy pou₧iji
zßpis metod přφmo v definici třφdy. Pro rozsßhlejÜφ třφdy je vÜak vhodnějÜφ
zapsat delÜφ členskΘ funkce vně tuto definici, jak bylo uvedeno v minulΘ
lekci. Ale zpět k naÜφ ukßzkovΘ třφdě :
class
Buffer {
private :
BYTE *m_Buffer;
// BYTE je definovan jako typedef unsigned char BYTE;
public :
Buffer(int _velikost) { m_Buffer =
new BYTE[_velikost]; }
~Buffer() { delete[] Buffer; }
void ZapisNa(int _pozice, BYTE _hodnota) { m_Buffer[_pozice] = _hodnota; }
};
Tento k≤d vypadß na prvnφ pohled celkem
rozumně, ale co by se stalo, kdyby nßhodou nebylo mo₧nΘ přidělit paměť o
velikosti _velikost? Z konstruktoru nelze vrßtit nßvratovou hodnotu
indikujφcφ chybu. Proto bychom měli nejprve nastavit proměnnou
m_Buffer
(členskΘ proměnnΘ je lepÜφ pro přehlednost značit předponou
m_, kterß nßm
zkracuje member variable. Pro zßpis parametrů pak pou₧φvßm
_ a za nφm jmΘno
parametru. V těle procedury pak jasně vidφm, s kterou proměnnou pracuji) na předem dohodnutou hodnotu, v tomto přφpadě tedy
nejspφÜe NULL a v destruktoru a člensk²ch metodßch pak kontrolovat, zdali je
m_Buffer platn² (tedy ne-NULLov²). Jinak bychom volßnφm funkce
ZapisNa()
způsobili chybn² přφstup do paměti. Přepsßno do k≤du tedy :
class
Buffer {
private :
BYTE *m_Buffer;
public :
Buffer(int
_velikost) { m_Buffer =
new BYTE[_velikost]; }
~Buffer() { if(m_Buffer) { delete[] m_Buffer; m_Buffer = NULL; } }
void ZapisNa(int _pozice, BYTE _hodnota) { if (m_Buffer) { m_Buffer[_pozice] =
_hodnota; } }
};
Jinou mo₧nostφ by pak bylo vyu₧φvat
konstruktor jen k nastavenφ proměnn²ch a vytvořit členskou funkci
Init(int
_velikost), kterß by nßm vracela např. TRUE v přφpadě ·spěchu a FALSE v
přφpadě ne·spěchu. Dßle bychom vytvořili jednu soukromou (private) proměnnou
m_JeInit typu boolean. Tuto proměnnou bychom v přφpadě ·spěÜnΘho provedenφ
funkce Init() nastavili na TRUE, jinak na FALSE. Pak bychom ji testovali v ka₧dΘ
metodě, jestli₧e bude TRUE, pak vφme, ₧e pole BYTEů mß přidělenou paměť a
přφstup do něj tedy nezpůsobφ chybn² přφstup do paměti. Opět si ukß₧eme k≤d :
class
Buffer {
private :
BYTE *m_Buffer;
BOOL m_JeInit;
public :
Buffer() { m_Buffer = NULL;
m_JeInit = FALSE; }
~Buffer() { if(m_Buffer) { if(m_JeInit) { delete[] m_Buffer; m_Buffer = NULL;
} }
BOOL Init(int _velikost) { m_Buffer = new BYTE[_velikost]; if(m_Buffer) {
m_JeInit = TRUE; } return m_JeInit; }
void ZapisNa(int _pozice, BYTE _hodnota) { if (m_JeInit) { m_Buffer[_pozice] =
_hodnota; } }
};
Mně osobně se zamlouvß vφce prvnφ způsob,
ale zßle₧φ jen na vßs, kter² si vyberete. V dalÜφm k≤du budu pou₧φvat způsob
prvnφ. V v²Üe uvedenΘm přφkladu se skr²vß jeÜtě jedna vada na krßse a to, ₧e neověřujeme
meze v členskΘ funkci ZapisNa(), je tedy mo₧nΘ zapsat mimo alokovanΘ pole, co₧
určitě nenφ to nejlepÜφ. Tento problΘm lze ale jednoduÜe vyřeÜit a
to přidßnφm dalÜφ členskΘ soukromΘ proměnnΘ
m_Velikost, kterou nastavφme po
·spěÜnΘm přidělenφ paměti a pak ve funkci
ZßpisNa() ověřφme, jestli jsme nßhodou
nepřekročili mez.
JeÜtě se podφvßme na takzvanou
inicializačnφ čßst, kterou je mo₧no pou₧φt u konstruktorů, zavedeme si novou
třφdu Clovek, kterß bude mφt jako datovΘ prvky proměnnΘ
m_Vek a m_Vyska:
class Clovek {
private :
BYTE m_Vek; int m_Vyska;
//
m_Vek jsem dal jako BYTE z duvodu usetreni pameti,
// protoze cloveka starsiho
nez 255 let asi tezko potkate ;))
public :
Clovek(BYTE _Vek, int _Vyska) : m_Vek(_Vek),
m_Vyska(_Vyska) { ; }
};
Vidφme, ₧e konstruktor
Clovek() nemß ₧ßdnΘ
tělo. ČlenskΘ proměnnΘ nastavφme pomocφ inicializačnφ čßsti, kterß se pφÜe
hned za uzavφrajφcφ zßvorku argumentů, oddělφme ji ":" a pak napφÜeme
proměnnou, do kterΘ chceme přiřadit a za ni do zßvorky hodnotu, kterou jφ
chceme přiřadit. V²sledn² efekt bude stejn², jako kdybychom do těla
konstruktoru napsali nßsledujφcφ přφkazy:
m_Vek = _Vek; m_Vyska = _Vyska;
Je pěknΘ, ₧e sice vφte k čemu je
konstruktor a destruktor, ale abychom mohli třφdu pou₧φt a vytvořit jejφ
instanci, musφme vědět jak toho v programu dosßhnout. Napřφklad
celočφselnou hodnotu (int) mů₧eme alokovat buď staticky nebo dynamicky.
Nejprve se podφvejme na statickou, kterou jsme pou₧φvali od zaΦßtku, napφÜeme
jmΘno prom∞nnΘ (int, real a jmΘno prom∞nnΘ). P°ekladaΦ tak p°esn∞ vφ, kolik
pam∞ti po n∞m chceme a tak ji pro nßs alokuje. Dynamickou alokaci musφme
pou₧φt tam, kde p°edem nevφme, jak velkß data budou. T°φdu Buffer
m∙₧eme chtφt pou₧φt t°eba jen pro 5 byt∙, ale takΘ t°eba pro n∞kolik desφtek
megabyte. Tato
pam∞¥ se alokuje za b∞hu programu pomocφ funkce malloc() (v klasickΘm C) nebo
pomocφ operßtoru new (v C++). To samΘ
lze provΘst s třφdami. Nejprve ukß₧i nějak² k≤d, např. funkci
main() ve kterΘ
budeme chtφt vytvořit instance třφdy Buffer:
void main(void)
{
Buffer mujStatickyBuffer(10);
// Tedy statickß alokace
Buffer *mujDynamickyBuffer = new
Buffer(15); // Dynamickß alokace
// Ted provedeme nejake operace s
bufferem
mujStatickyBuffer.ZapisNa(5, 5);
mujDynamickyBuffer->ZapisNa(10,8);
// Pred skoncenim musime dynamicke
promenne zrusit
delete(mujDynamickyBuffer);
}
Uveden² k≤d nßm tedy vytvořφ jeden
statick² a jeden dynamick² objekt. Je mezi nimi několik rozdφlů a to v
přφstupu k člensk²m funkcφm, u statickΘho pou₧φvßme tečkovΘ notace, kde₧to u
dynamickΘho musφme pou₧φt symbolu ->, proto₧e je to vlastně ukazatel. DalÜφ
rozdφl je v tom, ₧e staticky vytvořenΘ proměnnΘ se po v²stupu z bloku nebo
funkce samy zruÜφ. Naopak proměnnΘ, kterΘ si vytvořφme dynamicky musφme sami
explicitně uvolnit, jinak dojde ke ztrßtě paměti (tzv. MEMORY
LEAK). LiÜφ se takΘ umφst∞nφ t∞chto pam∞¥ov²ch blok∙ v pam∞ti. Pokud vytvo°φme
globßlnφ prom∞nnou, pak je ulo₧ena v tzv. datovΘm segmentu programu, kde se
nachßzφ po celou dobu chodu programu. Lokßlnφ prom∞nnΘ, vytvo°enΘ v t∞le
n∞jakΘ funkce nebo bloku (uzav°enΘho mezi slo₧enΘ zßvorky) jsou pak ulo₧eny na
zßsobnφku (STACK) a jsou zruÜeny p°i v²stupu z tΘto funkce nebo bloku. A
koneΦn∞ pokud si za₧ßdßme o pam∞¥ pomocφ malloc() nebo new je
vymezena pam∞¥ na hromad∞ (HEAP), doba jejφ existence zßvisφ jen na tom, kdy
se rozhodneme vrßtit ji systΘmu. Uve∩me si tedy jeÜt∞ kratiΦk² p°φklad na
obecnΘ alokace:
void main(void)
{
// Nejdrive se
na to podivame v C
int
i;
// Tedy statickß alokace, prom∞nnß
bude na stacku
int *p_i
= NULL; //
Pointer na int a nastavφme na NULL, abychom mohli otestovat
jestli byla alokace ·sp∞Ünß
p_i
= (int *)malloc(1*sizeof(int)); // Alokujeme 1 velikost integeru. Musφme
explicitn∞ p°etypovat, jinak si p°ekladaΦ bude st∞₧ovat
if(p_i)
{ // V₧dy rad∞ji ov∞°ujte, jestli je prom∞nnß platnß
*p_i = 5; // dßme na mφsto kam ukazuje p_i Φφslo 5
// N∞jakΘ operace s integerem na adrese p_i
// ....
// ....
// Uvoln∞nφ pam∞ti funkce free()
free(p_i);
}
// Ted
to same v C++
//
To same lze zapsat pomoci new
p_i
= NULL;
p_i
= new int; // Zde nemusφte
ukazatel p°etypovßvat
if(p_i) {
*p_i = 6;
// N∞jakΘ operace s integerem na adrese p_i
// ....
// ....
// Uvoln∞nφ pam∞ti operßtoru delete
delete p_i;
}
}
Nynφ opustφme tΘma konstruktorů a
destruktorů a podφvßme se na členskΘ metody, o nich₧ toho zatφm mnoho nevφme.
Metod je vφce druhů. Nejprve se zmφnφm o tzv. metodßch nastav/vrať (set/get).
Ji₧ vφme, ₧e v objektovΘm programovßnφ vyu₧φvßme v²hod zapouzdřenφ, tedy
neviditelnosti datov²ch členů z vnějÜφho prostředφ. Proto pro větÜinu
datov²ch členů naprogramujeme jednoduchΘ členskΘ funkce, kterΘ
nastavujφ/vracφ jejich hodnotu. Tyto funkce jsou větÜinou tak kratičkΘ, ₧e je
lze umφstit přφmo do deklarace třφdy. Vytvořφme si novou ukßzkovou třφdu
Clovek:
class Clovek {
private : BYTE m_Vek;
int m_Vyska;
public : Clovek(BYTE _Vek, int _Vyska) : m_Vek(_Vek),
m_Vyska(_Vyska) { ; }
void nastavVysku(int _NovaVyska) { m_Vyska = _NovaVyska; }
int vratVysku() { return m_Vyska; }
void nastavVek(BYTE _NovyVek) { m_Vek = _NovyVek; }
BYTE vratVek() { return m_Vek; }
};
Pro kratičkΘ funkce je vhodnΘ uvΘst
modifikßtor inline, kter² překladači řφkß, ₧e bychom byli rßdi, kdyby mφsto
volßnφ tΘto funkce nahradil jejφ v²skyt přφmo tělem tΘto funkce. Překladač nßs
mů₧e, ale nemusφ poslechnout. Důvodem tΘto maličkosti je jistΘ uspořenφ času,
kterΘ zabere volßnφ funkce, musφ se toti₧ ulo₧it vzhledem k velikosti tΘto
funkce hodně informacφ (kam se vrßtit, stav registrů, atp.). Inline funkce je
vlastně makro, kterΘ nßm ale navφc nabφzφ typovou kontrolu. DalÜφ modifikßtor,
kter² mů₧eme pou₧φt je modifikßtor const, kter² uvedeme za ukončujφcφ zßvorku
argumentů. Tento modifikßtor řφkß překladači, ₧e danß funkce nemodifikuje
objekt, pro kter² byla volßna. Tedy const je vhodnΘ uvΘst u vÜech metod, kterΘ
jen vracφ hodnoty, ale pro metody kterΘ nastavujφ hodnoty jej nelze pou₧φt.
Tedy např. funkce vratVysku() by mohla vypadat takto :
inline vratVysku() const { return m_Vyska; }
DalÜφ metody se kter²mi se setkßme jsou
operace nad objektem, mů₧e to b²t napřφklad procedura, kterß vypφÜe pro nßs
v²znamnΘ datovΘ členy. DneÜnφ lekci zakončφme troÜku ucelenějÜφm programem.
Ukß₧eme si takΘ zßpis metody mimo deklaraci třφdy a rozÜφřφme naÜeho člověka o
jmΘno:
#include <stdio.h>
#include <iostream.h>
#include <string.h>
typedef unsigned char BYTE;
class Clovek {
private :
BYTE m_Vek;
int m_Vyska;
char *m_Jmeno;
public :
Clovek(char *Jmeno, BYTE _Vek, int _Vyska);
~Clovek() { if(m_Jmeno) { delete[] m_Jmeno; } }
void Starni(BYTE _OKolik) { m_Vek += _OKolik; }
void nastavVysku(int _NovaVyska) { m_Vyska = _NovaVyska; }
int vratVysku() const { return m_Vyska; }
void nastavVek(BYTE _NovyVek) { m_Vek = _NovyVek; }
BYTE vratVek() const { return m_Vek; }
void Vypis();
};
Clovek::Clovek(char *Jmeno, BYTE _Vek, int _Vyska) :
m_Vek(_Vek), m_Vyska(_Vyska)
{
m_Jmeno = NULL;
if(Jmeno) {
int delka =
strlen(Jmeno) + 1; // Kvuli poslednimu '\0'
m_Jmeno = new
char[delka];
if(m_Jmeno) {
strncpy(m_Jmeno, Jmeno, delka);
}
}
}
void Clovek::Vypis()
{
if(m_Jmeno) { cout << "Jmeno : " <<
m_Jmeno << '\n'; }
else { cout << "Bezejmenny" << '\n'; }
cout << "Vek : " << (int)m_Vek << '\n';
cout << "Vyska : " << m_Vyska << '\n';
}
int main(int argc, char* argv[])
{
Clovek muj("Pepik Frantik",10,158);
muj.Vypis();
muj.Starni(5);
muj.Vypis();
char c;
cin >> c; // Cekame na stisk klavesy
return 0;
}
Budete-li mφt chuť, mů₧ete si
doprogramovat členskΘ funkce nastavJmeno(),
vratJmeno() a třeba jeÜtě funkci
Povyrost(). V přφÜtφm dφle si povφme o kopφrovacφm konstruktoru,
přetě₧ovßnφ člensk²ch funkcφ, o statick²ch člensk²ch proměnn²ch a
metodßch,
jak vytvßřet přehlednΘ moduly (jak dělit třφdy do souborů) a
pravděpodobně začneme s
dědičnostφ.
PřφÜtě nashledanou.
|