Kurz C++ (20.)

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++.

19.1. Oprava k minulΘmu dφlu

    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;

20.1. Identifikace typu instance za b∞hu programu

    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.

20.2. Vstup a v²stup pomocφ proud∙

    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.

20.2.1. Struktura objektov²ch proud∙

    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.

20.2.2. Manipulßtory standardnφ a vlastnφ

    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.

20.2.3. Vstup a v²stup do souboru

    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.

20.2.4. ┌prava t°φdy pro vstupn∞-v²stupnφ operace

    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.

20.3. Co bude p°φÜt∞

    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

Ond°ej BuriÜin