-
Následující program je náš první příklad, který používá ochranu dat. Jestliže
jsou datové složky a metody třídy předka volně dostupné i ve třídách potomků,
dává to programátorům větší možnosti při odvozování nových tříd. To zajistíme
vložením klíčového slova protected před deklaraci těchto složek.
Toto bylo provedeno u deklarace třídy vozidlo. U obou odvozených
tříd je před deklaraci složek přidáno klíčové slovo private (implicitně
jsou všechna data třídy soukromá a přidání tohoto klíčového slova tedy
nemá žádný význam). Souhrnně můžeme popsat význam těchto klíčových slov
takto:
-
private - datové složky a metody jsou dostupné pouze v metodách
třídy, kde jsou deklarovány a v přátelích třídy.
-
protected - stejné jako private, ale navíc i v odvozených
třídách.
-
public - datové složky a metody mohou být používány kdekoli.
Po provedení těchto změn jsou v metodách odvozených tříd dostupné i složky
z třídy předka. Vyzkoušejte.
class vozidlo {
protected:
int kola;
float vaha;
public:
void inicializace(int
nova_kola, float nova_vaha);
int ziskej_kola(void){return
kola;}
float ziskej_vahu(void){return
vaha;}
float vaha_na_kolo(void){return
vaha/kola;}
};
class automobil :
public vozidlo {
private:
int osob;
public:
void inicializace(int
nova_kola, float nova_vaha, int lidi = 4);
int pasazeru(void){return
osob;}
};
class nakladni :
public vozidlo {
private:
int osob;
float naklad;
public:
void inic_nakl(int
kolik = 2, float max_naklad = 12000.0);
float vykonost(void);
int pasazeru(void){return
osob;}
};
void vozidlo::inicializace(int
nova_kola, float nova_vaha){
kola = nova_kola;
vaha = nova_vaha;
}
void automobil::inicializace(int
nova_kola, float nova_vaha, int lidi){
osob = lidi;
vozidlo::inicializace(nova_kola,
nova_vaha);
}
void nakladni::inic_nakl(int
kolik, float max_naklad){
osob = kolik;
naklad = max_naklad;
}
float nakladni::vykonost(void){
return naklad
/ (naklad + ziskej_vahu());
}
int main(int argc,
char **argv)
{
vozidlo unicykl;
unicykl.inicializace(1,
7.5);
cout <<
"Unicykl má " << unicykl.ziskej_kola() << " kol.\n";
cout <<
"Zátěž na kolo unicyklu je " << unicykl.vaha_na_kolo()<< endl;
cout <<
"Váha unicyklu je " << unicykl.ziskej_vahu() << " kg.\n";
automobil
sedan;
sedan.inicializace(4,
1500.0, 5);
cout <<
"Sedan má " << sedan.pasazeru() << " osob.\n";
cout <<
"Sedan má zátěž " << sedan.vaha_na_kolo() << " kg na kolo.\n";
cout <<
"Sedan váží " << sedan.ziskej_vahu() << " kg.\n";
nakladni tatra;
tatra.inicializace(2,
7500.0);
tatra.inic_nakl(1,
7000.0);
cout <<
"Váha tatry je " << tatra.ziskej_vahu() << " kg.\n";
cout <<
"Výkonnost tatry je "<<100.0*tatra.vykonost()<<" procent.\n";
return 0;
}
Specifikátor přístupu uvedený před identifikátorem třídy předka určuje
přístup ke zděděným složkám třídy. Pokud třída deklaruje svého předka se
specifikací public, jsou přístupová práva ke zděděným veřejným a
chráněným složkám stejné jako ve třídě předka. Soukromé složky jsou nepřístupné.
Deklarujeme-li předka se specifikací protected, omezí se přístupová
práva k zděděným veřejným složkám. Složky, které byly v předkovi veřejné
nebo chráněné, budou v potomkovi vystupovat jako chráněné a složky, které
byly soukromé, zůstávají nepřístupné. Deklarujeme-li předka se specifikací
private,
budou všechny zděděné veřejné a chráněné složky považovány za soukromé
(soukromé složky předka jsou nepřístupné). Pokud u předka nedeklarujeme
specifikaci přístupu, platí implicitní specifikace, která je stejná jako
implicitní specifikace přístupu k jednotlivým složkám: u třídy typu struct
je to specifikace public a u tříd typu class specifikace
private.
-
V další verzi tohoto programu jsou použity konstruktory. Třída vozidlo
má konstruktor inicializující položky kola a vaha na implicitní
hodnoty. Ostatní dvě třídy mají také konstruktory, inicializující jejich
položky na počáteční hodnoty. Jako implicitní hodnoty zde byly zadány neobvyklé
hodnoty. V hlavním programu byly odstraněny příkazy provádějící inicializaci
(byly změněny na komentář). Program vyzkoušejte.
class vozidlo {
protected:
int kola;
float vaha;
public:
vozidlo(void){kola
= 7; vaha = -55.7;}
void inicializace(int
nova_kola, float nova_vaha);
int ziskej_kola(void){return
kola;}
float ziskej_vahu(void){return
vaha;}
float vaha_na_kolo(void){return
vaha/kola;}
};
class automobil :
public vozidlo {
private:
int osob;
public:
automobil(void)
{osob = 12;}
void inicializace(int
nova_kola, float nova_vaha, int lidi = 4);
int pasazeru(void){return
osob;}
};
class nakladni :
public vozidlo {
private:
int osob;
float naklad;
public:
nakladni(void)
{osob = 9; naklad = 12.3;}
void inic_nakl(int
kolik = 2, float max_naklad = 12000.0);
float vykonost(void);
int pasazeru(void){return
osob;}
};
void vozidlo::inicializace(int
nova_kola, float nova_vaha){
kola = nova_kola;
vaha = nova_vaha;
}
void automobil::inicializace(int
nova_kola, float nova_vaha, int lidi){
osob = lidi;
vozidlo::inicializace(nova_kola,
nova_vaha);
}
void nakladni::inic_nakl(int
kolik, float max_naklad){
osob = kolik;
naklad = max_naklad;
}
float nakladni::vykonost(void){
return naklad
/ (naklad + ziskej_vahu());
}
int main(int argc,
char **argv)
{
vozidlo unicykl;
//
unicykl.inicializace(1, 7.5);
cout <<
"Unicykl má " << unicykl.ziskej_kola() << " kol.\n";
cout <<
"Zátěž na kolo unicyklu je "<< unicykl.vaha_na_kolo() << endl;
cout <<
"Váha unicyklu je " << unicykl.ziskej_vahu() << " kg.\n";
automobil
sedan;
//
sedan.inicializace(4, 1500.0, 5);
cout <<
"Sedan má " << sedan.pasazeru() << " osob.\n";
cout <<
"Sedan má zátěž " << sedan.vaha_na_kolo() << " kg na kolo.\n";
cout <<
"Sedan váží " << sedan.ziskej_vahu() << " kg.\n";
nakladni tatra;
//
tatra.inicializace(2, 7500.0);
//
tatra.inic_nakl(1, 7000.0);
cout <<
"Váha tatry je " << tatra.ziskej_vahu() << " kg.\n";
cout <<
"Výkonnost tatry je "<<100.0*tatra.vykonost()<<" procent.\n";
return 0;
}
K zjištění, kdy je který konstruktor automaticky volán přidejte do
všech konstruktorů v tomto programu příkazy k výpisu zprávy informující
o
volání konstruktoru. Prozkoumejte pořadí volání konstruktorů (i u odvozených
tříd).
Pokud neurčíme něco jiného, vyvolá C++ nejprve bezparametrický konstruktor
rodičovské třídy a poté začne provádět vlastní konstruktor. Pokud chceme
inicializovat zděděné složky jiným způsobem, musíme v hlavičce konstruktoru
uvést za dvojtečkou odpovídající volání rodičovského konstruktoru.
-
Ve skutečných aplikacích obvykle umisťujeme deklaraci a definici třídy
do jednoho hlavičkového a jednoho zdrojového souboru. Tyto soubory mají
jméno, které odpovídá jménu třídy. Např. pokud máme třídu mojeTrida,
pak umístíme její definici do zdrojového souboru MOJETRIDA.CPP a její deklaraci
do hlavičkového souboru MOJETRIDA.H (můžeme používat dlouhá jména souborů).
Ukážeme si to na následující konzolové aplikaci. Začneme vývoj nové
konzolové aplikace (uložíme ji do souboru LETISTE.CPP). Obsah tohoto souboru
je tento:
#include <condefs.h>
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
USEUNIT("letadlo.cpp");
#include "letadlo.h"
int ziskejVstup(int
max);
void ziskejPrvky(int&
rychlost, int& smer, int& vyska);
int main(int argc,
char **argv)
{
char vracenaZprava[100];
// nastavení
pole letadel a vytvoření tří objektů letadel
Letadlo* letadla[3];
letadla[0]
= new Letadlo("TWA 1040");
letadla[1]
= new Letadlo("United Express 749", DOPRAVNI);
letadla[2]
= new Letadlo("Cessna 3238T", SOUKROME);
// začátek
cyklu
do {
int letadlo, zprava, rychlost, vyska, smer;
rychlost = vyska = smer = -1;
cout << endl << "Kterému letadlu chcete zaslat zprávu?";
cout << endl << endl << "0. Konec" << endl;
for (int i = 0; i < 3; i++)
cout << (i + 1) << ". " << letadla[i]->jmeno <<
endl;
// Získání čísla letadla
letadlo = ziskejVstup(4);
// Při 0 konec cyklu
if (letadlo == -1) break;
cout << endl<<"Vybráno "<<letadla[letadlo]->jmeno<<endl<<endl;
cout << "Jakou zprávu chcete zaslat?" << endl;
cout << endl << "0. Konec" << endl;
cout << "1. Změna stavu" << endl;
cout << "2. Vzlet" << endl;
cout << "3. Přistání" << endl;
cout << "4. Zpráva o stavu" << endl;
zprava = ziskejVstup(5);
if (zprava == -1) break;
if (zprava == 0) ziskejPrvky(rychlost, smer, vyska);
bool dobraZprava = letadla[letadlo]-> PrijmiZpravu(
zprava, vracenaZprava, rychlost, smer, vyska);
if (!dobraZprava) cout << endl << "Nelze provést.";
cout << endl << vracenaZprava << endl;
} while (1);
for (int i
= 0; i < 3; i++) delete letadla[i];
return 0;
}
int ziskejVstup(int
max)
{
int volba;
do {
volba = getch();
volba -= 49;
} while (volba
< -1 || volba > max);
return volba;
}
void ziskejPrvky(int&
rychlost, int& smer, int& vyska)
{
cout <<
endl << "Zadej novou rychlost: ";
cin >> rychlost;
cout <<
"Zadej nový směr: ";
cin >> smer;
cout <<
"Zadej novou výšku: ";
cin >> vyska;
cout <<
endl;
}
Dále zvolíme File | New | Text a vytvoříme hlavičkový soubor
třídy LETADLO.H (uložíme jej do stejného adresáře jako LETISTE.CPP). Soubor
bude mít tento obsah:
#ifndef letadloH
#define letadloH
#define LINKOVE
0
#define DOPRAVNI
1
#define SOUKROME
2
#define VZLET
0
#define LET
1
#define PRISTANI
2
#define NADRAZE
3
#define ZPR_ZMENA
0
#define ZPR_VZLET
1
#define ZPR_PRIST
2
#define ZPR_ZPRAVA
3
class Letadlo {
public:
Letadlo(const char* _jmeno, int _typ = LINKOVE);
~Letadlo();
virtual int ziskejStav(char* stavovyRetezec);
int ziskejStav() {return stav; }
int Rychlost() { return rychlost; }
int Smer() { return smer; }
int Vyska() { return vyska; }
void ZpravaStav();
bool PrijmiZpravu(int zpr, char* odpoved, int rych = -1,
int sme = -1, int vys = -1);
char* jmeno;
protected:
virtual void Vzletni(int smer);
virtual void Pristan();
private:
int rychlost;
int vyska;
int smer;
int stav;
int typ;
int maxVyska;
};
#endif
Obdobně vytvoříme další soubor LETADLO.CPP (uložíme jej opět do stejného
adresáře) s obsahem:
#include <stdio.h>
#include <iostream.h>
#include "letadlo.h"
Letadlo::Letadlo(const
char* _jmeno, int _typ):
typ(_typ),
stav(NADRAZE), rychlost(0), vyska(0), smer(0)
{
switch (typ)
{
case LINKOVE : maxVyska = 10000; break;
case DOPRAVNI : maxVyska = 6000; break;
case SOUKROME : maxVyska = 3000; break;
}
jmeno = new
char[50];
strcpy(jmeno,
_jmeno);
}
Letadlo::~Letadlo()
{
delete[] jmeno;
}
bool Letadlo::PrijmiZpravu(int
zpr, char* odpoved, int rych,
int sme, int vys)
{
if (rych >
800) {
strcpy(odpoved, "Rychlost nemůže být větší než 800.");
return false;
}
if (sme >
360) {
strcpy(odpoved, "Směr nemůže být větší než 360 stupnů.");
return false;
}
if (vys <
100 && vys != -1) {
strcpy(odpoved, "Jsme příliš nízko.");
return false;
}
if (vys >
maxVyska) {
strcpy(odpoved, "To je příliš vysoko.");
return false;
}
switch (zpr)
{
case ZPR_VZLET : {
if (stav != NADRAZE) {
strcpy(odpoved, "Jsem již ve vzduchu.");
return false;
}
Vzletni(sme);
break;
}
case ZPR_ZMENA : {
if (stav == NADRAZE) {
strcpy(odpoved, "Jsem na zemi.");
return false;
}
if (rych != -1) rychlost = rych;
if (sme != -1) smer = sme;
if (vys != -1) vyska = vys;
stav = LET;
break;
}
case ZPR_PRIST : {
if (stav == NADRAZE) {
strcpy(odpoved, "Jsme na zemi.");
return false;
}
Pristan();
break;
}
case ZPR_ZPRAVA : ZpravaStav();
}
strcpy(odpoved,
"Provedeno.");
return true;
}
void Letadlo::Vzletni(int
sme)
{
smer = sme;
stav = VZLET;
}
void Letadlo::Pristan()
{
rychlost =
smer = vyska = 0;
stav = NADRAZE;
}
int Letadlo:: ziskejStav(char*
stavovyRetezec)
{
sprintf(stavovyRetezec,
"%s, Výška: %d, Směr: %d, Rychlost: %d\n",
jmeno, vyska, smer, rychlost);
return stav;
}
void Letadlo::ZpravaStav()
{
char buff[100];
ziskejStav(buff);
cout <<
endl << buff << endl;
}
Když se podíváme na hlavičkový soubor třídy Letadlo, pak na
jeho začátku vidíme řadu direktiv #define. Definujeme zde makra,
která nahradí textové řetězce číselnými hodnotami (řetězce si zapamatujeme
snadněji než čísla). Posuďte sami, který z následujících příkazů je srozumitelnější?
if (typ == LINKOVY)
...
// nebo
if (typ == 0) ...
Jména těchto konstant obvykle zapisujeme velkými písmeny (pro snadné
odlišení od proměnných). Jiným způsobem deklarace konstant je deklarace
proměnné s modifikátorem const. Např.
const int LINKOVY
= 0;
Použití konstantní proměnné je modernější metoda než definice konstant
pomocí maker.
Další řádky hlavičkového souboru obsahují deklaraci třídy. Krátké metody
jsou zde deklarovány jako vložené funkce. Je zde také překrytá funkce ziskejStav.
Povšimněte si, že ve třídě je pouze jedna veřejná datová složka. Ostatní
jsou soukromé a jsou tedy dostupné pouze pomocí metod. Tzn. pokud požadujeme
změnu rychlosti, výšky nebo směru, pak musíme instanci Letadlo zaslat
zprávu. To odpovídá skutečnosti. Řídící letového provozu také nemůže fyzicky
změnit směr letadla. Může pouze zaslat zprávu pilotovi a ten požadovanou
změnu provede.
Nyní přejdeme k definičnímu souboru třídy. Konstruktor provádí inicializaci,
včetně dynamické alokace místa pro pole znaků k uložení jména letadla.
Tato paměť je uvolňována v destruktoru. Většinu práce provádí metoda PrijmiZpravu.
Příkaz switch určuje, která zpráva byla přijata a je provedena příslušná
akce. Povšimněte si, že metody Vzletni a Pristan nemohou
být volány přímo (jsou chráněné), ale prostřednictvím PrijmiZpravu.
Nelze tedy říci letadlu aby vzlétlo nebo přistálo, ale lze mu zaslat zprávu,
aby to provedlo. Metoda ZpravaStav volá ZiskejStav k získání
stavového řetězce, který potom metoda vypíše.
Hlavní program deklaruje pole ukazatelů na Letadlo a vytváří
tři instance této třídy. Dále začíná cyklus ve kterém zasíláme zprávy objektům
Letadlo
voláním funkce PrijmiZpravu. Po odeslání zprávy, čekáme na odpověď
od letadla. Tento cyklus poběží stále, dokud není ukončen příkazem
break.
Rozdělovat program do několika souborů je vhodné u složitějších aplikací.
U našich (zatím jednoduchých) aplikací zůstaneme u jednoho zdrojového souboru.
Kontrolní otázky:
-
Jak můžeme dosáhnou toho, že metody budou nedotupné z vnějšku třídy a budeme
je moci volat v odvozených třídách?
-
Co je to objekt?
-
Může mít třída více než jeden konstruktor?
-
Jak se liší třída od struktury v C++?
-
Jaký význam mají soukromé datové složky?
-
Jak můžeme u soukromých datových složek umožmit uživateli číst a nastavovat
jejich hodnoty?
-
Jak a kdy je volán destruktor třídy?
-
K čemu slouží inicializační seznam třídy?
-
Může třída obsahovat instance jiné třídy jako datové složky?
Řešení