Vφtejte u prozatφm poslednφ Φßsti kurzu C++. V tomto Φlßnku se budeme v∞novat identifikaci typu instance objektu za b∞hu programu, pak p°ejdeme k vstupnφm a v²stupnφm operacφm s pou₧itφm objektov²ch vstupn∞-v²stupnφch proud∙, kterΘ pat°φ do knihovny jazyka C++.
V minulΘ lekci jsem v p°φkladu k operßtoru dynamic_cast zapomn∞l uvolnit pam∞¥, tak₧e pro ·plnost je t°eba doplnit nßsledujφcφ:
char c;
cin >> c;
delete
r1;
delete r2;
return 0;
N∞kdy se nßm m∙₧e hodit znalost typu instance, se kterou prßv∞ pracujeme. ObzvlßÜ¥ pokud mßme abstraktnφ bßzovou t°φdu a od nφ odvozenΘ t°φdy, tyto t°φdy jsou tedy polymorfnφ. K tomuto ·Φelu slou₧φ operßtor typeid, definovan² v hlaviΦkovΘm souboru typeinfo, kter² musφme tedy do naÜeho programu p°idat. HlaviΦkov² soubor existuje i ve verzi s p°φponou - typeinfo.h. Rozhodl jsem se ale pou₧φt to, co jsme se minule nauΦili o prostorech jmen, program bude vypadat vφc "C++-ov∞". Budeme tedy muset pou₧φt direktivu using, proto₧e operßtor le₧φ v prostoru jmen std.
Operßtor se pou₧φvß jednφm z nßsledujφcφch zp∙sob∙:
typeid(jmeno)
typeid(vyraz)
Prvnφ zßpis vrßtφ hodnotu, kterou pak m∙₧eme pou₧φt pro porovnßnφ. Druh² zßpis pak zjistφ typ v²razu, v²raz se p°itom nevyhodnocuje. V²sledkem operace je konstantnφ objekt typu type_info, kter² je taktΘ₧ definovßn v souboru typeinfo (resp. typeinfo.h).
Nynφ si ukß₧eme p°φklad:
#include <iostream>
#include <typeinfo>
using namespace std;
class Rodic {
Tisk() { cout << "Rodic" << endl; }
};
class Potomek : public Rodic {
Tisk() { cout << "Potomek" << endl; }
};
int main(int argc, char* argv[])
{
Rodic r;
Potomek p;
Rodic *ptr_r;
ptr_r = &r;
cout << "Promenna r ma typ : " << typeid(r).name() << endl;
cout << "Promenna p ma typ : " << typeid(p).name() << endl;
cout << "Promenna ptr_r ma typ " << typeid(ptr_r).name() << " a ukazuje na : "
<< typeid(*ptr_r).name() << endl;
ptr_r = &p;
cout << "Promenna ptr_r ma typ " << typeid(ptr_r).name() << " a ukazuje na : "
<< typeid(*ptr_r).name() << endl;
char c;
cin >> c;
return 0;
}
V²stup v nßsledujφcφ podob∞ nßs asi moc nep°ekvapφ:
Promenna r ma typ : class Rodic
Promenna p ma typ : class Potomek
Promenna ptr_r ma typ class Rodic * a ukazuje na : class Rodic
Promenna ptr_r ma typ class Rodic * a ukazuje na : class Rodic
Ale pokud z t°φdy Rodic ud∞lßme abstraktnφ t°φdu, tedy bude obsahovat alespo≥ jednu metodu s klφΦov²m slovem virtual:
class Rodic {
virtual Tisk() { cout << "Rodic" << endl; }
};
Bude v²stup vypadat takto:
Promenna r ma typ : class Rodic
Promenna p ma typ : class Potomek
Promenna ptr_r ma typ class Rodic * a ukazuje na : class Rodic
Promenna ptr_r ma typ class Rodic * a ukazuje na : class Potomek
Pro ·sp∞ÜnΘ zkompilovßnφ p°φkladu s polymorfnφmi typy je nutnΘ v n∞kter²ch p°ekladaΦφch zapnout podporu pro dynamickou identifikaci typ∙ (RTTI). Pro v²vojovΘ prost°edφ Microsoft Visual C++ 6.0 je postup zapnutφ RTTI nßsledujφcφ: 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. Pozor na to, ₧e volbu je nutnΘ zapnout zvlßÜ¥ pro Debug i Release verzi.
Z p°φkladu vidφme, ₧e pokud pou₧ijeme nepolymorfnφ t°φdu (bez virtußlnφch metod), vypφÜe se deklarovanΘ jmΘno t°φdy, na kterou ukazuje tento ukazatel. K vyhodnocenφ toti₧ dojde ji₧ v dob∞ p°ekladu. Pokud bude t°φda polymorfnφ, pak se vyhodnocenφ provede a₧ za b∞hu programu. Jestli₧e jako parametr pro operßtor typeid chceme pou₧φt ukazatel, pak ho musφme dereferencovat pomocφ *, jinak bychom dostali informaci o typu ukazatele, tedy nap°. class Rodic *, jak vidφme ve v²stupu.
Pokud bychom pou₧ili typeid na ukazatel s hodnotou NULL, dojde k vyvolßnφ v²jimky typu bad_typeid. Ukß₧eme si p°φklad:
#include <iostream>
#include <typeinfo>
using namespace std;
class A {
virtual f() { ; }
};
int main(int argc, char* argv[])
{
A *ptr_a = new A;
cout << "Ukazatel ukazuje na typ: " << typeid(*ptr_a).name() << endl;
try {
delete(ptr_a);
ptr_a = NULL;
cout << "Ukazatel ukazuje na typ: " << typeid(*ptr_a).name() << endl;
}
catch(bad_typeid)
{
cout << "Identifikace ukazatele s hodnotou NULL" << endl;
}
catch(...)
{
cout << "Divne chovani!!!" << endl;
}
char c;
cin >> c;
return 0;
}
K tomuto nevinn∞ vypadajφcφmu progrßmku je nutnΘ uvΘst dv∞ poznßmky.
Podle toho, co jsme si °ekli bychom po spuÜt∞nφ m∞li dostat nßsledujφcφ v²pis:
Ukazatel ukazuje ma
typ: class A
Identifikace ukazatele s hodnotou NULL
Pokud vÜak program p°elo₧φme p°ekladaΦem Microsoft Visual C++ 6.0 (bez Service pack) dostaneme nßsledujφcφ:
Ukazatel ukazuje ma typ: class A
Divne chovani!!!
Vidφme tedy, ₧e v²jimka byla zachycena handlerem ..., kter² je urΦen pro ostatnφ v²jimky. Po p°ekladu pomocφ Microsoft Visual Studio .NET program funguje tak, jak bychom oΦekßvali. Poslednφ Service Pack 5 pro Microsoft Visual C++ 6.0 jsme bohu₧el nem∞li k dispozici. Pokud vÜak pou₧ijeme hlaviΦkovΘ soubory s p°φponami .h a vypustφme direktivu using, pak vÜe funguje i pod Microsoft Visual C++ 6.0. Zde je ·prava:
#include <iostream.h>
#include <typeinfo.h>
class A {
virtual f() { ; }
};
// a dale stejne
Pokud ve t°φd∞ A nebude virtußlnφ funkce a p∙jde tedy o nepolymorfnφ t°φdu, nedojde k vyvolßnφ v²jmky a operßtor typeid nßm vrßtφ typ na kter² ukazuje ptr_a, tedy class A. Typ byl vyhodnocen ji₧ v dob∞ p°ekladu.
Krom∞ v²jimky bad_typeid m∙₧e b²t vyvolßna jeÜt∞ v²jimka typu __non_rtti_object. Tato v²jimka znaΦφ chybu p°φstupu (m∙₧e to b²t nap°φklad p°i kompilaci k≤du bez zapnutΘ RTTI nebo pokud je ukazatel neplatn²). T°φda __non_rtti_object je zd∞d∞na od t°φdy bad_typeid a proto by v seznamu handler∙ v²jimek m∞la p°edchßzet t°φd∞ bad_typeid, jinak dojde k jejφmu zachycenφ handlerem pro bad_typeid.
Nynφ se podφvßme na t°φdu type_info. Prozatφm jsme z tΘto t°φdy pou₧ili jejφ metodu name(), kterß vracφ jmΘno identifikovanΘho typu (formßt °et∞zce zßvisφ na konkrΘtnφ implementaci). T°φda obsahuje jeÜt∞ metodu raw_name(), kterß vracφ neupravenΘ jmΘno, nap°. .?AVA@@. Jak vidφte, toto jmΘno je pro Φlov∞ka neΦitelnΘ, ale pokud ho pou₧ijeme p°i porovnßnφ dvou t°φd, pak dosßhneme zrychlenφ programu, proto₧e se program nemusφ zdr₧ovat s p°evodem jmΘna na Φitelnou verzi. DalÜφmi metodami jsou operßtory porovnßnφ == a !=. Za zmφnku jeÜt∞ stojφ metoda before(), kterß lexikograficky porovnßvß °et∞zce identifikujφcφ typ. D∙le₧itou informacφ je, ₧e instance tΘto t°φdy nelze vytvß°et p°φmo, majφ toti₧ soukrom² operßtor kopφrovßnφ a p°i°azenφ. DoΦasnou instanci tohoto typu lze tedy dostat jen pomocφ operßtoru typeid.
V Φlßncφch o programovßnφ v C++ jsme doposud pou₧φvali vstupn∞-v²stupnφch proud∙ jen jako nßhrady za funkce scanf() a printf(). Nynφ si ukß₧eme jak proudy pou₧φt i pro vstup nebo v²stup ze souboru, jak upravit t°φdu, abychom ji mohli jednoduÜe vypsat pomocφ v²stupnφch proud∙. TakΘ uvidφme, jak formßtovat v²stup a dalÜφ.
Pokud nßm prßce s objektov²mi proudy nevyhovuje, pak nic nebrßnφ v pou₧φvßnφ star²ch vstupn∞-v²stupnφch operacφ pomocφ knihovny jazyka C.
Doposud jsme se seznßmili s proudy cin a cout, standardn∞ vÜak v knihovn∞ jazyka C++ existujφ jeÜt∞ proudy cerr a clog. Proud cin je instancφ t°φdy istream, kterß je urΦena pro vstup ze standardnφho vstupnφho proudu (v jazyce C to byl stdin), tedy nejΦast∞ji vstup z klßvesnice (lze ovÜem p°esm∞rovat). Instance cout t°φdy ostream slou₧φ k v²stupu do standardnφho v²stupnφho proudu (v jazyce C stdout), nejΦast∞ji tedy konzole (ale op∞t lze p°esm∞rovat). Proudy clog a cerr jsou takΘ instancemi t°φdy ostream, ale jejich v²stup sm∞°uje do standardnφho chybovΘho proudu (stderr), rozdφl mezi nimi je pak v u₧itφ vyrovnßvacφ pam∞ti. Zatφmco cerr zapisuje rovnou, pro v²stup do clog se vyu₧φvß vyrovnßvacφ pam∞¥. Tyto t°φdy jsou instancializovßny v hlaviΦkovΘm souboru iostream, resp iostream.h. T°φdy istream a ostream jsou virtußln∞ zd∞d∞ny od spoleΦnΘho p°edka - t°φdy ios_base, ve starÜφch verzφch knihovny jazyka C++ to je jen ios. Virtußlnφ d∞d∞nφ je vyu₧ito, aby ve v²slednΘ t°φd∞ nebyly dvakrßt obsa₧eny stavovΘ prom∞nnΘ proudu (viz. lekce 15). Navφc existuje objekt iostream, kter² lze pou₧φt pro vstupnφ i v²stupnφ operace. RodiΦovsk²mi t°φdami tohoto objektu jsou t°φdy istream a ostream.
DalÜφ odvozenΘ t°φdy nßm umo₧≥ujφ prßci se soubory a jsou to nßsledujφcφ: ifstream (vstupnφ), ofstream (v²stupnφ) a fstream (vstupn∞-v²stupnφ). Tyto t°φdy jsou implementovßny v souboru fstream, resp. fstream.h. Proudy mohou takΘ pracovat s tzv. pam∞¥ov²mi proudy, jejich jmΘna jsou istringstream (vstupnφ), ostringstream (v²stupnφ) a stringstream (vstupn∞-v²stupnφ). Implementace je v souboru sstream. Tyto proudy ke svΘ funkci vyu₧φvajφ dalÜφ knihovnφ t°φdu - t°φdu string - kterß zajiÜ¥uje prßci s °et∞zci. Z d∙vodu kompatibility se starÜφmi verzemi jsou pak jeÜt∞ k dispozici t°φdy istrstream, ostrstream a strstream, kterΘ nevyu₧φvajφ v²Üe zmφn∞nou t°φdu string, ale pracujφ s poli znak∙.
Jak u₧ jsme vid∞li, ke vstupu pou₧φvßme operßtor >>, resp. operßtor << k v²stupu. Tyto p°etφ₧enΘ operßtory jsou, pro standardnφ datovΘ typy, umφst∞ny ve t°φdßch istream, resp. ostream. Proto₧e od t∞chto t°φd jsou zd∞d∞ny ostatnφ t°φdy, mßme tyto operßtory k dispozici i u nich.
Pro od°ßdkovßnφ jsme pou₧φvali manipulßtoru endl. Tyto manipulßtory upravujφ stav proudu a mohou takΘ nap°. m∞nit formßtovßnφ, jak uvidφme dßle.
Nejprve si ukß₧eme, jak pou₧φt manipulßtory pro formßtovßnφ v²stupu, podobn∞ jako u funkce printf(). Ukß₧eme si nejd°φve p°φklad:
#include <iostream>
#include <iomanip>
using namespace std;
int main(int argc, char* argv[])
{
float f = 1.0825f;
int a = 244;
cout << "Vypis integeru (5): " << setfill('0') << setw(5) << a << endl;
cout << "Presnost nastavena na 3: " << setprecision(3) << f << endl;
cout << "Vypis integeru : " << a << endl;
cout << "Hexadecimalne: " << setbase(16) << a << endl;
cout << "Hexadecimalne: " << a << endl;
cout << "Osmickova soustava: " << setbase(8) << a << endl;
char c;
cin >> c;
return 0;
}
Nßsleduje v²stup programu:
Vypis integeru (5):
00244
Presnost nastavena na 3: 1.08
Vypis integeru : 244
Hexadecimalne: f4
Hexadecimalne: f4
Osmickova soustava: 364
Pro pou₧itφ t∞chto manipulßtor∙ musφme vlo₧it soubor iomanip (nelze iomanip.h, alespo≥ v MSVC 6), kde jsou definovßny manipulßtory s parametry. Vidφme, ₧e n∞kterΘ manipulßtory ovlivnφ v²stupnφ proud trvale (dokud je znova nezm∞nφme), jinΘ platφ jen pro nßsledujφcφ vypisovanou prom∞nnou. Mezi trvale ovliv≥ujφcφ pat°φ nap°. zm∞na bßze ΦφselnΘ soustavy, p°esnost Φφsla v plovoucφ Φßrce. Mezi doΦasn∞ ovliv≥ujφcφ pak nastavenφ Üφ°ky nßsledovn∞ vypsanΘho Φφsla a s nφm spojen² vypl≥ujφcφ znak.
DalÜφmi manipulßtory jsou pak nap°. fixed, scientific nebo showpoint. Ukß₧eme si op∞t p°φklad:
#include <iostream>
#include <iomanip>
using namespace std;
int main(int argc, char* argv[])
{
float f = 2.000000f;
cout << "Bez nastaveni: " << f << endl;
cout << setprecision(8) << showpoint;
cout << "S showpoint: " << f << endl;
cout << "Nastaveni scientfic: " << scientific << f << endl;
cout << "Zpet na fixed: " << fixed << f << endl;
char c;
cin >> c;
return 0;
}
V²stup:
Bez nastaveni: 2
S showpoint: 2.0000000
Nastaveni scientfic: 2.00000000e+000
Zpet na fixed: 2.00000000
Manipulßtor fixed je nastaven standardn∞ a urΦuje zßpis Φφsla jen desetinnou Φßrkou, scientific pak umo₧≥uje v²pis ve tvaru s exponentem. Manipulßtor showpoint pak °φkß, ₧e chceme vid∞t Φφsla za desetinnou Φßrkou i v p°φpad∞, ₧e to je 0.
DalÜφ vlastnosti se dajφ nastavit pomocφ metody setiosflags(), mazat se pak dajφ pomocφ metody resetiosflags().
Nynφ si ukß₧eme, jak napsat vlastnφ manipulßtor:
#include <iostream>
using namespace std;
ostream& DveRadky(ostream& vystup)
{
return vystup << '\n' << '\n' << '\n';
}
int main(int argc, char* argv[])
{
cout << "Nasleduji dve prazdne radky: " << DveRadky << "Konec vystupu" << endl;
char c;
cin >> c;
return 0;
}
NßÜ upraven² manipulßtor musφ vracet referenci na objekt v²stupnφho proudu, aby se dal °et∞zit jako ostatnφ operßtory. Podobn∞ lze vytvo°it i operßtor pro vstupnφ proud, staΦφ mφsto ostream pou₧φt t°φdu istream.
Jak jsme si uvedli pro v²stup do souboru slou₧φ t°φda ofstream. My se vÜak rozhodneme pro jejφho dokonalejÜφho potomka, vstupn∞-v²stupnφ t°φdu fstream, kterou otev°eme pro zßpis, podobn∞ jako jsme to d∞lali v C pomocφ funkce fopen(). NejlepÜφ bude, ukßzat si vÜe na p°φkladu:
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
fstream soubor("data.dat", ios::out);
if(soubor.is_open())
{
soubor << "Nazdar svete diskoveho prostoru!!!" << endl;
soubor.close();
}
return 0;
}
Vidφme, ₧e postup je velice jednoduch². JmΘno souboru a zp∙sob p°φstupu (existuje jich samoz°ejm∞ celß °ada) k souboru zadßme v konstruktoru, pak jen ov∞°φme, zda-li otev°enφ prob∞hlo ·sp∞Ün∞ a m∙₧eme zapisovat. Po prßci samoz°ejm∞ soubor uzav°eme. Zde zmφnφme, ₧e pokud dojde k vyvolßnφ v²jimky v programu, pak se standardnφ proudy samy uzav°ou v destruktorech. V reßlnΘm k≤du by samoz°ejm∞ m∞la b²t dopln∞na vhodnß reakce na chybu p°i otev°enφ souboru.
Podφvejme se na Φtenφ ze souboru. M∙₧eme pou₧φt op∞t vstupn∞-v²stupnφ t°φdu fstream (pokud ji otev°eme pro Φtenφ), ale tentokrßt zkusφme t°φdu ifstream. Abychom m∞li co Φφst, pou₧ijeme i t°φdu ofstream pro zßpis n∞jak²ch dat. P°φklad:
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ofstream out("data.dat");
if(out.is_open())
{
for(unsigned char a = 27; a < 255; a++)
{
out << a; // Zapiseme ASCII tabulku (bez problemovych znaku)
}
out.close();
ifstream in("data.dat");
if(in.is_open())
{
char c;
while(in >> c)
{
cout << c;
}
in.close();
}
}
char c;
cin >> c;
return 0;
}
Vidφme, ₧e vstup nenφ o moc slo₧it∞jÜφ. Program by se samoz°ejm∞ dal upravit, aby pou₧φval jen jednu prom∞nnou typu fstream, staΦφ ho p°ed druh²m pou₧itφm uzav°φt a znovu otev°φt pomocφ metody open() . Op∞t chybφ reakce na chyby p°i otev°enφ soubor∙.
JeÜt∞ je nutnΘ uv∞domit si, s jak²mi daty pracujeme. Pokud jsou v souboru binßrnφ data, pak pou₧itφm operßtor∙ << a >> dojde ke ztrßt∞ n∞kter²ch BYTE. Pro otev°enφ proudu jako binßrnφ ho musφme otev°φt takto (implicitn∞ se otevφrß v textovΘm m≤du):
fstream soubor("data.dat", ios::out || ios::binary);
Pro vstup a v²stup je pak t°eba pou₧φt metody istream::read(), resp. ostream::write(). Tyto metody se pou₧φvajφ nßsledovn∞:
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ofstream out("data.dat", ios::binary);
if(out.is_open())
{
for(unsigned char a = 0; a < 255; a++)
{
out.write((char*)&a, sizeof(a)); // Zapiseme ASCII tabulku
}
out.close();
ifstream in("data.dat", ios::binary);
if(in.is_open())
{
char c;
while(in.read((char *)&c, sizeof(c)))
{
cout << c;
}
in.close();
}
}
char c;
cin >> c;
return 0;
}
Pro porovnßnφ zkuste v p°φkladu, kde se pou₧φvajφ klasickΘ operßtory pro vstup a v²stup, zm∞nit hodnotu prom∞nnΘ a na 0, program neprob∞hne v po°ßdku.
Pro standardnφ datovΘ typy jsou operßtory pro vstup a v²stup p°etφ₧eny uvnit° t°φdy istream (nebo ostream). Proto₧e to u₧ jsou hotovΘ t°φdy, nem∙₧eme naÜe metody do nφ p°idat, musφme tedy p°etφ₧it operßtorovΘ funkce pro naÜe typy a vyu₧φt klφΦovΘho slova friend:
#include <iostream.h>
// pokud tu je iostream + using namespace
std, tak msvc6 hlasi chyby
class Souradnice {
private:
int m_ix, m_iy;
public:
Souradnice(int _ix = 0, int _iy = 0) : m_ix(_ix), m_iy(_iy) { ; }
friend istream& operator>>(istream& p, Souradnice& s);
friend ostream& operator<<(ostream& p, Souradnice& s);
};
istream& operator>>(istream& p, Souradnice& s)
{
return p >> s.m_ix >> s.m_iy;
}
ostream& operator<<(ostream& p, Souradnice& s)
{
return p << "[" << s.m_ix << "," << s.m_iy << "]";
}
int main(int argc, char* argv[])
{
return 0;
}
Operßtory pro vstup a v²stup zase vracφ p°φsluÜnΘ proudy a jdou tedy °et∞zit. Pokud bychom m∞li celou hierarchii t°φd, pak je vhodn∞jÜφ definovat pro rodiΦovskou t°φdu operßtory pro vstup a v²stup a z jejich t∞la pak volat virtußlnφ metody pro vstup, resp. v²stup.
Jak jsme naznaΦili v ·vodu, tak tento Φlßnek o C++ byl prozatφm poslednφ. V p°φÜtφm Φlßnku se budeme v∞novat datov²m strukturßm - od jednoduch²ch prom∞nn²ch, p°es lineßrnφ spojovΘ seznamy a₧ po stromy. A₧ tohle dokonΦφme, tak se vrßtφme k standardnφ ÜablonovΘ knihovn∞ jazyka C++, kterß nßm usnad≥uje operace s n∞kter²mi datov²mi strukturami.
PřφÜtě nashledanou