Vφtejte u dalÜφho dφlu, kter² bude v∞novßn prostor∙m jmen a specißlnφm operßtor∙m, kterΘ nßm mohou pomoci p°i p°etypovßnφ.
Minule jsme si psali o v²jimkßch, kde jsme se seznßmili s prostory jmen. KonkrΘtn∞ s prostorem jmen std, do n∞ho₧ spadajφ funkce ze standardnφ knihovny jazyka C++. Prostory jmen umo₧≥ujφ rozd∞lit identifikßtory, tedy nap°. prom∞nnΘ nebo funkce, do velk²ch skupin. Tyto skupiny jsou "odstφn∞nΘ", tedy v jednom prostoru jmen m∙₧e b²t identifikßtor a, kter² p°edstavuje nap°. prom∞nnou, v jinΘm pak identifikßtor a, kter² m∙₧e p°edstavovat funkci. Za chvφli uvidφte, ₧e k identifikßtor∙m definovan²m v prostoru jmen p°istupujeme podobn∞, jako jsme p°istupovali k statick²m prom∞nn²m definovan²m uvnit° t°φdy.
Prostor jmen deklarujeme nßsledujφcφm zp∙sobem:
namespace JmenoProstoru { Deklarace_prvku_prostoru }
JmenoProstoru nahradφme zvolen²m nßzvem. M∙₧eme se vÜak rozhodnout tento identifikßtor vynechat, pak dostaneme tzv. anonymnφ prostor jmen. Deklarace_prvku_prostoru jsou pak klasickΘ deklarace funkcφ a prom∞nn²ch po vzoru globßlnφch prom∞nn²ch. Deklarace prostoru jmen m∙₧e b²t rozd∞lena i do vφce Φßstφ (Φßsti dokonce nemusφ le₧et pouze v jednom souboru):
namespace Test { int i; }
namespace Test { float f; }
Nynφ ob∞ prom∞nnΘ le₧φ ve stejnΘm prostoru jmen s nßzvem Test. Pokud definujeme dva prostory jmen, kterΘ jsou vzßjemn∞ provßzßny, pak musφme dodr₧et pravidla, se kter²mi jsme se setkali u₧ u t°φd. M∞jme prostory jmen Test1 a Test2 podle nßsledujφcφho k≤du:
namespace Test1
{
int i;
Test1() { Test2::f = 5.0f; }
}
namespace Test2 { float f; }
P°i pokusu o p°eklad nßm p°ekladaΦ ohlßsφ chybu, proto₧e neznß ani prostor jmen Test2 a u₧ v∙bec ne prom∞nnou f. Tento problΘm se dß vy°eÜit prohozenφm obou deklaracφ. Na tomto ·seku k≤du takΘ vidφme, jak se p°istupuje k prvk∙m uvnit° prostoru jmen - pomocφ operßtoru Φty°teΦky ::. Za chvφli si ukß₧eme jeÜt∞ jin² zp∙sob, kter² nßm uÜet°φ velkΘ mno₧stvφ napsanΘho k≤du.
Pokud bychom zapisovali t∞la funkcφ p°φmo do deklarace prostoru jmen, deklarace by se velmi brzo stala nep°ehlednou. Proto podobn∞ jako u t°φd lze t∞la funkcφ umφstit mimo:
namespace Matika
{
int chyba; // Nastala naposledy chyba ?
int NaDruhou(int a);
}
int Matika::NaDruhou(int a)
{
chyba = 0;
// Nenastala chyba
return a*a;
}
Prom∞nnß chyba ve funkci Odmocni() odpovφdß prom∞nnΘ chyba z prostoru jmen Matika. Pokud by existovala globßlnφ prom∞nnß s nßzvem chyba, pak je zastφn∞na v t∞le funkcφ nßle₧ejφcφch do tohoto prostoru prom∞nnou z prostoru jmen Matika. Pokud bychom cht∞li pou₧φt z n∞jakΘ funkce takovouto globßlnφ prom∞nnou, pak musφme pou₧φt operßtoru Φty°teΦka ::, tedy nap°. ::chyba.
Nynφ si povφme o druhΘm zp∙sobu, jak si zp°φstupnit identifikßtory n∞jakΘho prostoru jmen. Ukß₧eme si to na rozsßhlΘm prostoru std. Zkusφme si nßsledujφcφ p°φklad:
#include <iostream>
// tady opravdu neni .h
int main(int argc, char* argv[])
{
std::cout << "Nazdar!!!" << std::endl;
return 0;
}
Podle standardu jazyka C++ bychom m∞li hlaviΦkovΘ soubory C++ psßt bez p°φpony .h (stdio.h, conio.h a dalÜφ soubory jazyka C z∙stßvajφ s p°φponou). Potom vÜak musφme vÜechny identifikßtory uvßd∞t i s nßzvem prostoru jmen, do kterΘho nßle₧φ, jak si m∙₧ete vÜimnout u cout a endl. Jist∞ by bylo velice namßhavΘ pou₧φvat vÜude mφsto jednoduchΘho zßpisu cout std::cout. Proto existuje direktiva using, kterou si zp°φstupnφme cel² prostor jmen. StaΦφ ji uvΘst za °ßdek s direktivou #include v nßsledujφcφ podob∞:
using namespace std;
Krom∞ direktivy using existuje i deklarace stejnΘho jmΘna, ale ta zp°φstupnφ pouze jeden zvolen² identifikßtor z n∞jakΘho prostoru jmen. V p°φpad∞, kdy vφme, ₧e nebudeme pot°ebovat veÜkerΘ identifikßtory z prostoru jmen (obzvlßÜt∞ rozsßhlΘho prostoru), je pou₧itφ deklarace using vhodn∞jÜφ. Nßsleduje v²Üe uveden² program, ale jsou zp°φstupn∞ny jen dva identifikßtory:
#include <iostream>
// tady opravdu neni .h
using std::cout;
using std::endl;
int main(int argc, char* argv[])
{
cout << "Nazdar!!!" << endl;
return 0;
}
Vidφme, ₧e pro ka₧d² identifikßtor, kter² chceme zp°φstupnit, musφme napsat jednu deklaraci using. U zp°φstup≥ovanΘho identifikßtoru se neuvßdφ typ prom∞nnΘ, nßvratov² typ funkce a neuvßd∞jφ se ani zßvorky po funkci. Pokud bychom tedy cht∞li zp°φstupnit funkci NaDruhou() z prostoru Matika, uvedli bychom:
using Matika::NaDruhou; // ne NaDruhou()
Doposud jsme se setkßvali nap°. s proudy pro vstup cin a v²stup cout, kterΘ takΘ pat°φ do prostoru std. Ale my jsme je volali p°φmo, nemuseli jsme p°ed n∞ vklßdat std::. To bylo zp∙sobeno tφm, ₧e pokud p°ekladaΦ zjistφ, ₧e pou₧φvßme soubory s p°φponou .h, pak direktivu using doplnφ sßm. Mo₧nß se ptßte, proΦ byste tedy m∞li pou₧φvat v direktiv∞ #include jmΘno bez p°φpony. Odpov∞∩ najdete nap°. v nejnov∞jÜφ verzi v²vojovΘho prost°edφ Microsoft Visual Studio .NET. Pokud vlo₧φte soubor iostream.h, p°ekladaΦ k≤d sice p°elo₧φ, ale s upozorn∞nφm, ₧e tento soubor ji₧ nebude v dalÜφ verzi v²vojovΘho prost°edφ k dispozici a mßte tedy pou₧φvat soubor iostream.
Je t°eba dßt pozor, ₧e zp°φstupn∞nφm n∞jakΘho prostoru mohou p°ekladaΦi vzniknout nejasnosti jako v nßsledujφcφ ukßzce:
namespace Prostor
{
int test;
int Test() { return test; }
}
class Test {
int a;
};
using namespace Prostor;
int main(int argc, char* argv[])
{
Test test; //
problem dela funkce Test() a trida Test
return 0;
}
Krom∞ prom∞nn²ch a funkcφ se m∙₧e v deklaraci prostoru jmen objevit dalÜφ prostor jmen. Potom se jednß o tzv. vno°en² prostor jmen. Pokud chceme p°istupovat k prvk∙m z vno°enΘho prostoru, musφme je uvßd∞t pln²m jmΘnem, tedy vΦetn∞ prostoru nad°azenΘho. P°φklad:
namespace Prostor
{
namespace PodProstor {
int test;
int Test() { return test; }
}
int test;
int NaDruhou(int a) { return a*a; }
}
int main(int argc, char* argv[])
{
Prostor::PodProstor::Test();
return 0;
}
Pokud bychom pot°ebovali do prostoru PodProstor vlo₧it dalÜφ vno°en² prostor, pak p°φstup k jeho prvk∙m by byl velice nep°φjemn² pro zßpis. Jazyk C++ nabφzφ tedy "p°ezdφvky" (aliasy), kterΘ pak umo₧≥ujφ kratÜφ zßpis:
namespace PP = Prostor::PodProstor;
Po tomto °ßdku m∙₧eme p°istupovat k funkci Test nßle₧ejφcφ do prostoru jmen PodProstor pomocφ nßsledujφcφho zßpisu:
PP::Test();
Samoz°ejm∞, ₧e nßm nic nebrßnφ v pou₧itφ direktivy using, kterou si m∙₧eme zp°φstupnit cel² PodProstor:
using namespace Prostor::PodProstor;
V objektov²ch typech (t°φd∞ a struktu°e) nelze pou₧φt direktivu using ke zp°φstupn∞nφ celΘho prostoru jmen, ale lze tam pou₧φt deklaraci using, pomocφ kterΘ jsme upravovali v dφlu 14 p°φstupovß prßva v objektech (vlastn∞ jsme zp°φstupnili prvek z p°edka).
Doposud jsme mluvili o prostorech, kter²m jsme p°id∞lili jmΘno. Pokud se rozhodneme jmΘno nep°id∞lit, vznikß tzv. anonymnφ prostor. Chceme-li zavolat funkci definovanou v tomto prostoru, pou₧ijeme pouze jejφ jmΘno podobn∞ jako by to byla globßlnφ funkce. Stejn² postup pak platφ i pro ostatnφ identifikßtory pat°φcφ do tohoto prostoru.
P°i p°ekladu jsou vÜechny anonymnφ prostory spojeny v jeden, kterΘmu je navφc p°id∞leno unikßtnφ jmΘno (liÜφ se mezi r∙zn²mi soubory). Tφm pßdem prom∞nnΘ a funkce z prostoru jednoho souboru nelze pou₧φt v souboru jinΘm. Je to jako bychom je prohlßsili za statickΘ (p°φstupnΘ jen v modulu, kde byly definovßny).
V minul²ch dφlech jsme se setkali s p°etypovßnφm v nßsledujφcφ podob∞:
(typ)JmenoPromenne
Jazyk C++ nßm vÜak nabφzφ jeÜt∞ Φty°i specißlnφ operßtory - static_cast, dynamic_cast, const_cast a reinterpret_cast. Tyto operßtory byly zavedeny, aby odstranili n∞kterΘ nejednoznaΦnosti p°i typovΘ konverzi. Nßsledujφcφ k≤d je platn²:
class A {
int m_iTmp;
};
class B {
private:
int m_b;
public:
B() { m_b = 55; }
void Fce() { return m_b*m_b; }
};
int main(int argc, char* argv[])
{
A a;
B *b;
b = (B*) &a;
// legalni pretypovani
b->Fce();
return 0;
}
AΦkoliv se tento k≤d na v∞tÜin∞ p°ekladaΦ∙ bez problΘm∙ zkompiluje, je nesprßvn². VÜimn∞te si, ₧e volßme metodu Fce(), ale v programu neexistuje ₧ßdn² objekt typu B. Prom∞nnß b je pouze ukazatelem na b, neexistuje tedy ani Φlenskß prom∞nnß B::m_b. V²sledkem takovΘto operace m∙₧e b²t v lepÜφm p°φpad∞ nesprßvn² v²sledek, v horÜφm pak chyba p°i b∞hu programu.
Operßtory se zapisujφ v nßsledujφcφ podob∞:
operator_pretypovani<typ>(vyraz);
Jako operator_pretypovani uvedeme jeden z v²Üe uveden²ch. Vyraz je to, co se mß p°evΘst a typ urΦuje, co bychom cht∞li dostat jako v²sledek konverze.
Tento operßtor pouze p°evede vyraz na typ a to pouze na zßklad∞ typ∙ p°φtomn²ch ve v²razu vyraz. Pokud tedy mßme ukazatel na t°φdu, pak static_cast m∙₧e provΘst p°etypovßnφ odvozenΘ t°φdy na jejφ rodiΦovskou t°φdu, ale takΘ nßm umo₧≥uje p°etypovat ukazatel na rodiΦovskou t°φdu na ukazatel na odvozenou t°φdu. Tento operßtor neprovßdφ ₧ßdnΘ ov∞°enφ za b∞hu programu, narozdφl od nφ₧e uvedenΘho dynamic_cast. V prvnφm p°φpad∞ to je platnΘ p°etypovßnφ, kterΘho bychom mohli docφlit i pou₧itφm klasickΘho zßpisu (typ), ale druh² p°φpad zavßnφ problΘmy, pokud nevφme co opravdu d∞lßme. Operßtor se pou₧φvß ve spojenφ s typy, kterΘ nejsou polymorfnφ, tedy neobsahujφ virtußlnφ funkce.
Tento operßtor lze takΘ pou₧φt ke konverzφm mezi zßkladnφmi datov²mi typy:
double pi=3.14159265;
int celacast = static_cast<int>(pi);
Tento operßtor pouze p°evede vyraz na typ a to pouze na zßklad∞ typ∙ p°φtomn²ch ve v²razu vyraz. Typ m∙₧e b²t pouze ukazatel nebo reference na d°φve deklarovanou t°φdu nebo ukazatel na typ void (tedy void *). Typ v²razu vyraz zßvisφ na typu typ. Pokud je typ ukazatelem, pak musφ i vyraz b²t ukazatelem. Pokud je to reference, pak to musφ b²t l-hodnota, tedy v²raz kter² m∙₧e stßt i na levΘ stran∞ rovnφtka p°i p°i°azenφ. Operßtor se narozdφl od static_cast pou₧φvß ke konverzφm polymorfnφch typ∙.
Jak jsme si uvedli v²Üe, tak dynamic_cast provßdφ kontrolu za b∞hu programu, jestli konverze mß smysl. Mohou nastat dva problΘmy:
ukazatel, kter² se sna₧φme p°etypovat nenφ ukazatelem na platn² kompletnφ objekt. Operßtor dynamic_cast pak vrßtφ hodnotu NULL. Pokud mßme odvozen² objekt B od t°φdy A a objekt C, kter² je potomkem t°φdy B, pak m∙₧eme objekt C brßt, jakoby obsahoval podobjekty A a B. Objekt C, obsahujφcφ podobjekty A a B naz²vßme kompletnφm objektem,
jestli₧e p°etypovßvßme referenci na objekt a tato konverze nenφ mo₧nß, operßtor dynamic_cast vyvolß v²jimku typu bad_cast, o kterΘ jsme se zmφnili v minulΘm kurzu.
Ukß₧eme si p°φklady na oba p°φpady:
#include <iostream>
using namespace std;
class Rodic { virtual Fce() { ; } };
class Potomek : public Rodic { };
int main(int argc, char* argv[])
{
Rodic* r1 = new Potomek;
Rodic* r2 = new Rodic;
Potomek* p1 = dynamic_cast<Potomek*>(r1);
// to je v poradku
Potomek* p2 = dynamic_cast<Potomek*>(r2);
// tady se vrati NULL
if(p1 == NULL)
{
cout << "Chyba!" << endl;
}
else
{
cout << "OK!" << endl;
}
if(p2 == NULL)
{
cout << "Failed!" << endl;
}
char c;
cin >> c;
return 0;
}
K tomuto p°φkladu je nutnΘ uvΘst, ₧e ne vÜechny p°ekladaΦe majφ implicitn∞ nastavenou podporu pro dynamickou identifikaci typ∙. To je i p°φpad Microsoft Visual C++ 6.0, po p°ekladu nßs p°ekladaΦ varuje, ₧e se sna₧φme o konverzi pomocφ dynamic_cast bez tohoto nastavenφ a po spuÜt∞nφ programu dojde k vyvolßnφ v²jimky. Pro nastavenφ RTTI (Run Time Type Information - Informace o typu za b∞hu programu) otev°ete menu Project, zvolte Settings. V okn∞ Settings pak vyberte kartu C/C++ a v listboxu Category vyberte C++ Language. ZaÜkrtn∞te volbu Enable RTTI. Program by pak m∞l fungovat sprßvn∞.
Jedinou funkcφ tohoto operßtoru je, ₧e modifikuje atributy const, volatile a _unaligned, co₧ ₧ßdn² jin² p°etypovßvacφ operßtor nedokß₧e. M∙₧e tyto atributy bu∩ p°idßvat nebo ubφrat:
class A {
/* deklarace */ };
const A *a = new A;
A *b = const_cast<A*> (a);
Tento operßtor p°evede ukazatel jakΘhokoliv typu na jak²koliv jin² typ. Navφc lze pomocφ n∞j p°evΘst libovolnou celoΦφselnou hodnotu na ukazatel, pop°. obrßcen∞. Je jasnΘ, ₧e takovΘto operace jsou velice nebezpeΦnΘ a Φasto nep°enositelnΘ. Rozumn²m pou₧itφm je p°evod ukazatele na jin² a pozd∞jÜφ konverze zp∞t na p∙vodnφ typ. Ostatnφ konverze jsou p°inejmenÜφm nep°enositelnΘ, v tom horÜφm pak nebezpeΦnΘ, jak jsme si uvedli.
class A {};
class B {};
A *a = new A;
B *b = reinterpret_cast<B*>(a); // ackoliv spolu tridy
vubec nesouvisi, lze to pouzit
Operßtory reinterpret_cast a
const_cast by se m∞ly pou₧φvat pouze, pokud jinΘ
°eÜenφ opravdu nenφ mo₧nΘ. Umo₧≥ujφ toti₧ konverze, kterΘ p°edstavujφ stejnΘ
nebezpeΦφ jako p∙vodnφ operßtor p°etypovßnφ (p°φklad byl na zaΦßtku odstavce o
operßtorech p°etypovßnφ).
P°φÜt∞ si povφme n∞co o urΦenφ typu za b∞hu programu a pak se budeme v∞novat vstupnφm a v²stupnφm proud∙m. Tφm bude prozatφm p°eruÜen kurz o C++, proto₧e si ud∞lßme v²let do sv∞ta datov²ch struktur, bez nich₧ ₧ßdn² smyslupln² program nem∙₧e existovat. Potom se vrßtφme jeÜt∞ na chvφli ke kurzu C++, konkrΘtn∞ k STL (Standard Template Library), co₧ je knihovna Üablon jazyka C++ usnad≥ujφcφ prßci s n∞kter²mi datov²mi strukturami.
PřφÜtě nashledanou.