Kurz C++ (12.)

 V tomto pokraΦovßnφ kurzu C++ se budeme v∞novat nßsledujφcφm tΘmat∙m: Nejprve si °ekneme, jak rozd∞lit zdrojov² k≤d do vφce soubor∙, co₧ znaΦn∞ zv²Üφ  p°ehlednost.  Dßle bude nßsledovat p°et∞₧ovßnφ funkcφ, metod a operßtor∙.

12.1.    Organizace projektu

    P°edstavme si situaci, kdy mßme t°φdu Buffer v jednom zdrojovΘm souboru spoleΦn∞ s hlavnφm programem, tedy funkcφ main(). Dßle m∙₧eme mφt t°eba t°φdu BufferManager, kterß bude zajiÜ¥ovat sprßvu veÜker²ch instancφ t°φdy Buffer. Prozatφm si ukß₧eme jen deklaraci tΘto t°φdy a vlastnφ implementaci tΘto t°φdy se budeme v∞novat a₧ v dalÜφ kapitole. T°φda BufferManager bude obsahovat metody pot°ebnΘ k obsluze pole instancφ t°φdy Buffer - VytvorBuffer(), SmazBuffer(), VratBuffer() a dalo by se vymyslet mnoho dalÜφch funkcφ, kterΘ by byly u₧iteΦnΘ pro sprßvu pam∞¥ov²ch buffer∙ (nap°. VytvorBufferZeSouboru()). V nßsledujφcφ ukßzce je deklarace t°φdy BufferManager:

class BufferManager {
private :
    Buffer* PoleBuffer[MAX_BUFFERS];
// alokace bude dynamicka
    int PocetBufferu;
// Kolik je prave alokovano
public :
    BufferManager();
    ~BufferManager();

    int VytvorBuffer(int _velikost); // Vytvori Buffer a vrati cislo, pres ktere muzeme
                                     // k Bufferu pristupovat pres metodu VratBuffer
                                     // Hodnota 0xFFFFFFFF bude definovana jako chyba
    int SmazBuffer(int _poradovecislo); // Maze buffer, v pripade uspechu vrati poradove cislo
                                        // tohoto bufferu, jinak opet 0xFFFFFFFF
    Buffer* VratBuffer(int _poradovecislo);
};

    Z rozsßhlosti se dß p°edpoklßdat, ₧e pokud bychom tuto t°φdu jeÜt∞ p°idali do naÜeho jedinΘho zdrojovΘho souboru, vznikne obrovsk², t°eba n∞kolika tisφc °ßdkov², soubor. P°idßvßnφm dalÜφch t°φd by nßm p∞kn∞ rostl a za chvφli bychom se v n∞m p°estali orientovat.

    Podφvejme se na dalÜφ p°φstup, takzvanΘ modulßrnφ uspo°ßdßnφ programu, co₧ nenφ nic jinΘho ne₧ n∞kolik mezi sebou vzßjemn∞ spojen²ch zdrojov²ch soubor∙. Jak bychom tedy vhodn∞ rozd∞lili v²Üe uveden² p°φklad do t∞chto soubor∙? V p°φpad∞, ₧e pou₧φvßte Microsoft Visual C++ je situace velice jednoduchß, u ostatnφch p°ekladaΦ∙ ale nenφ o nic slo₧it∞jÜφ. Nejprve vytvo°φme projekt pomocφ menu danΘho v²vojovΘho prost°edφ. Potom do tohoto projektu p°idßme soubory Buffer.h a Buffer.cpp (op∞t volby menu). Do souboru Buffer.h vlo₧φme jen deklaraci t°φdy vΦetn∞ vÜech inline metod. Do souboru Buffer.cpp pak vlo₧φme °ßdek #include "Buffer.h", dßle pak veÜkerΘ t∞la metod, pop°φpad∞ p°etφ₧en²ch operßtor∙ a p°φpadnΘ inicializace statick²ch prom∞nn²ch t°φdy Buffer. Dvojici soubor∙ Buffer.cpp a Buffer.h naz²vßme modulem a m∞la by to b²t samostatn∞ p°elo₧itelnß Φßst programu. Pro t°φdu BufferManager bychom ud∞lali to samΘ, vzniknou tedy soubory BufferManager.h a BufferManager.cpp. Potom p°idßme do projektu nßÜ hlavnφ modul, tedy ten, kter² bude obsahovat funkci main(), kterß bude obsahovat instanci t°φdy BufferManager. Tento modul m∙₧eme nap°φklad nazvat main.cpp, pop°φpad∞ jmΘnem aplikace, kterou vyvφjφme. Nynφ je nutnΘ si uv∞domit zßvislosti tohoto programovΘho celku. Je z°ejmΘ, ₧e BufferManager bude pracovat s instancemi t°φdy Buffer, nebo¥ t°φda BufferManager bude obsahovat prom∞nnou typu pole ukazatel∙ na t°φdu Buffer. Z toho vypl²vß, ₧e BufferManager pot°ebuje znßt rozhranφ t°φdy Buffer, aby znal rozhranφ (metody), kterΘ m∙₧e po t°φd∞ Buffer po₧adovat (volat). To zajistφme tφm, ₧e pou₧ijeme direktivy #include a do souboru BufferManager.cpp na zaΦßtek vlo₧φme °ßdek #include "Buffer.h". Podobn∞ je z°ejmΘ, ₧e hlavnφ programov² modul bude vyu₧φvat t°φdu BufferManager k manipulaci s instancemi t°φdy Buffer, nebo¥ bude volat jeho metody. Tedy do souboru main.cpp vlo₧φme °ßdek #include "BufferManager.h". HlaviΦkovΘ soubory by v nejlepÜφm p°φpad∞ nem∞ly obsahovat dalÜφ direktivy #include, mohly by toti₧ vzniknout problΘmy s vφcenßsobn²m vlo₧enφm jednoho hlaviΦkovΘho souboru. Toto doporuΦenφ se vÜak Φasto poruÜuje. ProblΘmu s vφcenßsobn²m vlo₧enφm souboru se brßnφme podmφn∞n²m p°ekladem, kde pou₧ijeme konstanty:

// Hlavicka.h - doporucena struktura hlavickoveho souboru
#ifndef HLAVICKA_H
  #define HLAVICKA_H
  // Zde bude vlozeno telo hlavickoveho souboru
#endif

V p°φpad∞, ₧e konstanta HLAVICKA_H je definovßna, t∞lo hlaviΦkovΘho souboru nebude vlo₧eno, v opaΦnΘm p°φpad∞ bude. Pro ka₧d² hlaviΦkov² soubor je samoz°ejm∞ nutnΘ pou₧φt jinΘ konstanty. V²Üe uveden² p°φklad ve form∞ projektu pro Microsoft Visual C++ naleznete v sekci Download (projekt Organizace).

    Tento postup mß n∞kolik v²hod. Prvnφ v²hodou je, ₧e pokud nynφ zm∞nφte n∞co nap°φklad v souboru BufferManager.cpp, p°ekladaΦ p°elo₧φ jen tento soubor. To mß za nßsledek zv²Üenφ rychlosti p°ekladu.  V p°φpad∞ jednoho velkΘho souboru by bylo nutnΘ p°elo₧it ho cel² ·pln∞ od zaΦßtku. Druhou v²hodou je, ₧e takto m∙₧e pracovat na jednom projektu vφce programßtor∙. Ka₧d² si vezme jeden modul na kterΘm bude pracovat a ostatnφm programßtor∙m staΦφ znßt jen rozhranφ modul∙ (tedy hlaviΦkovΘ soubory) ostatnφch modul∙.

12.2. P°et∞₧ovßnφ funkcφ a metod

    V C++ lze p°et∞₧ovat funkce, tedy vlastn∞ definovat vφce funkcφ se stejn²m jmΘnem. P°ekladaΦ mezi nimi ovÜem pot°ebuje rozliÜit, tak₧e musφ mφt bu∩ rozdφlnΘ typy parametr∙ nebo r∙zn² poΦet parametr∙, pop°φpad∞ oboje. K rozliÜenφ p°ekladaΦi nestaΦφ jen, aby tyto funkce m∞ly pouze r∙znΘ nßvratovΘ hodnoty. V C++ tedy lze mφt funkce:

    int Test(int i);
    float Test(float f);
    int Test(int i, float f);

    Proto₧e metody t°φd jsou takΘ funkce, lze p°et∞₧ovßnφ vyu₧φt i u nich. V ukßzce si p°etφ₧φme metodu VytvorBuffer(), tak abychom jako parametr mohli pou₧φt ji₧ existujφcφ instanci t°φdy Buffer. Z po₧adavku na funkΦnost je vid∞t, ₧e se v t∞le bude pou₧φvat kopφrovacφ konstruktor t°φdy Buffer, kter² mßme ji₧ hotov² z minulΘho dφlu. V ukßzce nßsleduje implementace (tedy vlastn∞ soubor BufferManager.cpp):

// Zdrojovy soubor BufferManager.cpp

#include "stdafx.h"    // Pro pouziti predkompilovane hlavicky v MSVC

#include <stdio.h> // Nejprve systemove hlavickove soubory, potom nase

#include "Buffer.h"
#include "BufferManager.h"

BufferManager::BufferManager()
{
    PocetBufferu = 0;
    for(int i = 0; i < MAX_BUFFERS; i++)
    {
        PoleBuffer[i] = NULL;
    }
}

BufferManager::~BufferManager()
{
    // Musime smazat vsechny naalokovane buffery
    for(int i = 0; i < MAX_BUFFERS; i++)
    {
        if(PoleBuffer[i]) { delete PoleBuffer[i]; }
    }
}

int BufferManager::VytvorBuffer(int _velikost)
{
    // Nejpve overime, zdali mame jeste vubec misto pomoci promenne PocetBufferu
    if(PocetBufferu <= MAX_BUFFERS)
    {
        // Najdeme misto v poli, kam novy buffer umistime
        int i = 0; // Index v poli ktery prave zkoumame
        while(i < MAX_BUFFERS)
        {
            if(!PoleBuffer[i]) { break; }
            else { i++; }
        }

        PoleBuffer[i] = new Buffer(_velikost);
        if(PoleBuffer[i])
        {
            return i; // V pripade uspechu vratime index do pole PoleBuffer
        }
    }

    return 0xFFFFFFFF; // jinak vratime chybovy stav
}

int BufferManager::SmazBuffer(int _poradovecislo)
{
    // Overime, jestli buffer, ktery chceme uvolnit je alokovan
    if(PoleBuffer[_poradovecislo])
    {
        delete PoleBuffer[_poradovecislo];
        PoleBuffer[_poradovecislo] = NULL; // nastavime priznak, ze je volne misto

        return _poradovecislo; // vratime cislo bufferu, ktery jsme zrusili v pripade uspechu
    }

    return 0xFFFFFFFF; // jinak opet vratime chybu
}

Buffer* BufferManager::VratBuffer(int _poradovecislo)
{
    // jestlize tento buffer existuje, pak vratime ukazatel na tento buffer
    if(PoleBuffer[_poradovecislo])
    {
        return PoleBuffer[_poradovecislo];
    }

    return NULL; // v pripade neuspechu vratime NULL
}

Konstanta MAX_BUFFERS je definovßna v hlaviΦkovΘm souboru BufferManager.h jako 10. Metody VytvorBuffer() a SmazBuffer() vracφ v p°φpad∞ ne·sp∞chu hodnotu 0xFFFFFFFF. Jinak vracφ Φφslo, kterΘ identifikuje prßv∞ vytvo°en² buffer. Nynφ u₧ tedy dopφÜeme jen p°etφ₧enou metodu BufferManager::VytvorBuffer(), kterß bude jako parametr mφt ukazatel na t°φdu Buffer. Do hlaviΦkovΘho souboru BufferManager.h p°idßme °ßdek:

int VytvorBuffer(Buffer* _zdroj);

Do souboru BufferManager.cpp pak:

int BufferManager::VytvorBuffer(Buffer *_zdroj)
{
    // Nejprve overime, zdali mame jeste vubec misto pomoci promenne PocetBufferu
    if(PocetBufferu <= MAX_BUFFERS)
    { // Najdeme misto v poli, kam novy buffer umistime
        int i = 0; // Index v poli ktery prave zkoumame
        while(i < MAX_BUFFERS)
        {
            if(!PoleBuffer[i]) { break; }
            else { i++; }
        }

        PoleBuffer[i] = new Buffer(*_zdroj); // Pouzije kopirovaci konstruktor
        if(PoleBuffer[i])
        {
            return i; // V pripade uspechu vratime index do pole PoleBuffer
        }
    }

    return 0xFFFFFFFF; // jinak vratime chybovy stav
}

12.3. P°et∞₧ovßnφ operßtor∙

   Jazyk C++ dßle umo₧≥uje p°et∞₧ovat operßtory, Φφm₧ lze rozÜφ°it jejich v²znam i pro v²ΦtovΘ a objektovΘ datovΘ typy. Pokud si vzpomenete na minulou lekci o kopφrovacφm konstruktoru, probφrali jsme problΘm s m∞lkou kopiφ, kdy se pouze p°enesly ΦlenskΘ prom∞nnΘ. S tφmto problΘmem se setkßme i v p°φpad∞, kdy pou₧ijeme operßtor = mezi dv∞ma instancemi stejnΘ t°φdy. ╪eÜenφm je prßv∞ p°etφ₧enφ operßtoru =. P°etφ₧enφ operßtoru se provßdφ definovßnφm operßtorovΘ funkce, jejφ₧ jmΘno sestßvß ze slova operator a za nφm nßsleduje symbol operßtoru, kter² chceme p°etφ₧it. Tedy pro operßtor = napφÜeme jmΘno funkce operator =(). Tento p°etφ₧en² operßtor se pou₧ije stejn²m zp∙sobem jako p∙vodnφ operßtor. Alternativn∞ ho lze takΘ zavolat pomocφ operßtorovΘ funkce. P°et∞₧ovßnφm operßtor∙ nelze:

    Existujφ operßtory, kterΘ p°et∞₧ovat nejdou v∙bec, jsou to: ., .* , ::, ?:, typeid, const_cast, reinterpret_cast, dynamic_cast, static_cast a sizeof. Operßtory preprocesoru # a ## tΘ₧ nelze p°et∞₧ovat. Nßsledujφcφ operßtory je mo₧nΘ p°et∞₧ovat jen jako nestatickΘ metody objektov²ch typ∙: =, (), [], -> a (typ). Poslednφ je operßtor p°etypovßnφ. Ostatnφ operßtory, vyjma new a delete, lze p°et∞₧ovat bu∩ jako nestatickΘ metody objektov²ch typ∙ nebo jako funkce s alespo≥ jednφm parametrem objektovΘho nebo v²ΦtovΘho typu. Operßtory new a delete krom∞ p°etφ₧enφ i p°edefinovat a lze je p°et∞₧ovat jako statickΘ metody objektov²ch typ∙ nebo jako samostatnou funkci bez souvislosti s objektov²mi nebo v²Φtov²mi typy.

    Nejprve se budeme zab²vat p°et∞₧ovßnφm unßrnφch voln∞ p°etφ₧iteln²ch operßtor∙. Jak bylo uvedeno v p°ehledu, lze je p°et∞₧ovat jako nestatickΘ metody nebo jako funkce s alespo≥ jednφm parametrem objektovΘho nebo v²ΦtovΘho typu.

12.3.1. Unßrnφ operßtory

    Zde je nutnΘ se zmφnit o operßtorech ++ a --, kterΘ oba existujφ v prefixovΘ a postfixovΘ verzi. Abychom je oba mohli p°etφ₧it, musφ b²t n∞jak rozliÜitelnΘ. To je zajiÜt∞no nßsledovn∞, chceme-li p°etφ₧it prefixov² operßtor deklarujeme operßtorovou funkci jako obyΦejnou funkci s jednφm parametrem nebo jako metodu bez parametr∙. Pro p°etφ₧enφ postfixovΘho operßtoru definujeme tuto funkci jako obyΦejnou funkci se dv∞ma parametry, z nich₧ druh² je typu int, nebo jako metodu s jednφm parametrem typu int. Pokud navφc po postfixov²ch parametrech chceme aby vracely p∙vodnφ hodnotu, jak je to u standardnφch verzφ t∞chto operßtor∙, je nutnΘ si je tak naprogramovat. P°etφ₧enφ t∞chto dvou operßtor∙ nad v²Φtov²m typem m∞sφce si p°edvedeme v nßsledujφcφ ukßzce:

enum mesice { leden, unor, brezen, duben, kveten, cerven, cervenec, srpen, zari, rijen, listopad, prosinec };
mesice operator++(mesice& mes)
{   
   int i = mes;
   i++;    // Vyuzijeme operatoru pro cela cisla
   if(i > prosinec) { i = leden; }
   mes = mesice(i);    // Posuneme se na dalsi mesic
   return mes;    // vratime upravenou hodnotu
}

    Pokud bychom p°et∞₧ovali postfixov² operßtor, jeho funkΦnφ prototyp by vypadal mesice operator++(mesice &mes, int). Aby fungoval tak, jak postfixov² operßtor ++ fungovat mß, tedy aby vracel p∙vodnφ hodnotu je nutnΘ si v t∞le na zaΦßtku uchovat hodnotu parametru a tu nakonec vrßtit. To samΘ je nutnΘ ud∞lat i pro prefixovou verzi. P°φklad s ob∞ma p°etφ₧en²mi operßtory naleznete v p°φsluÜnΘm souboru v sekci Download (projekt PretezOperatory).

    Druh² zp∙sob jak definovat operßtorovou funkci je vytvo°it nestatickou metodu. To si p°edvedeme na p°φkladu implementace t°φdy pro prßci s komplexnφmi Φφsly:

class complex {
private: double re, im;

public:
    complex(double _re = 0, double _im = 0):re(_re), im(_im) { }
    double Re() { return re; }
    double Im() { return im; }
    cplx operator-() { return cplx(-re, -im); }
};

    Nynφ mßme-li dv∞ instance prvni, druha m∙₧eme napsat p°i°azenφ:

cplx prvni(5.0, 5.0), druha;
druha = -prvni;    // V druha bude zaporne komplexni cislo prvni, tedy (-5.0, -5.0);
druha = prvni.operator-();    // Alternativni zapis

12.4. Co bude p°φÜt∞?

    V dalÜφm dφle dokonΦφme p°et∞₧ovßnφ operßtor∙. Zmφnφm se o ukazateli this. Povφme si n∞co o klφΦovΘm slov∞ friend a koneΦn∞ naΦneme tΘma d∞diΦnosti.

PřφÜtě nashledanou.

Ond°ej BuriÜin