Objektum-orientált C++ VII.

A sorozat (igazi) záró epizódjában a bejárókról lesz szó.

Annak idején, amikor a sorozat hatodik részét megírtam, még azt hittem, hogy vége a sorozatnak. De hatalmas hibát követtem el. Megfeledkeztem a bejárókról. Ezt a hatalmas hiányosságot igyekszem most hirtelen pótolni ennek a bejegyzésnek a keretein belül. Kezdjük is talán a legelején, hogy mi is az a bejáró (iterator)? A Nagy Könyv keveset mondó ám annál hangzatosabb definíciója szerint minden bejáró, ami úgy viselkedik, mint egy bejáró. Viselkedését - és operátorait - tekintve a bejáró viszont a mutatókhoz hasonlatos. Hasonlóan lehet növelni illetve csökkenteni (a ++ és a -- operátorokkal) vagy az éppen aktuális elem értékét megkapni a * operátor segítségével. Ellenben amíg van nullpointer addig "semmire" mutató bejáró nincsen.

A bejárók lényegében egy egységes felületet kívánnak nyújtani a különböző adatszerkezetekhez, hogy egyes algoritmusoknak ne kelljen tudnia, hogy például egy zsák adatszerkezet az most tömbösen vagy láncolt listásan lett-e vajon megvalósítva. Ugyebár miért is kéne tudnia? Bizonyos algoritmusoknál ez mindegy is.

A bejárókat meghatározott műveleteik alapján csoportosítani lehet (azt pedig, hogy milyen operátorok vannak definiálva, azt az határozza meg, hogy az egyes operátorok mennyire műveletigényesek - általában csak az alacsony műveletigényű funkciókhoz szokás operátort rendelni, a nagyobbakhoz pedig függvényt). Ez a standard template library-ban szépen meg is van téve, ám én erre a részre nem kanyarodnék rá, mivel egyrészt maga megérne jópár bejegyzést (az STL és az STL bejárókkal kapcsolatos része is), másrészt pedig nem szeretném ennyire elbonyolítani a helyzetet. Ezért a korábban már megírt láncolt lista adatszerkezetünkhöz fogok egy bejárót írni. Természetesen amennyire lehetséges tartom magam az STL-ben használt elnevezésekhez és ugyanazok az operátorok itt is ugyanazt a funkciót fogják betölteni (tehát visszakanyarodva az első "bejáró" meghatározásunkhoz, ez egy bejáró lesz, mert bejáróként fog viselkedni :) ).

Szerencsére az eredeti osztályunkat csak két függvénnyel kell kiegészíteni. Az egyik a begin(), ami egy, a lista elején álló bejáróval fog visszatérni. Az end() pedig a lista végén áll.

typedef cList_const_iterator<T> const_iterator;
typedef cList_iterator<T> iterator;

iterator begin(void)
{
    return *(new iterator(*(list->next)));
}
iterator end(void)
{
    return *(new iterator(*list));
}

A mutatók mintájára bejáróból is két fajtánk lesz, egy sima és egy konstans, ahogy az a fenti kódból is látszik. A typedef rész azért egy lényeges dolog (azon kívül, hogy a két függvényt egy csöppnyit szebbé és rövidebbé teszi), mert így cList<T>::iterator it; formában létre tudunk hozni egy cList<T>-beli bejárót (az STL is így viselkedik, ezért tartom magam ehhez). Na, jöhet az izgalmasabb része a dolognak, első körben a cList_iterator osztály:

template<class T> class cList_const_iterator;

template<class T> class cList_iterator
{
    private:
        listNode<T>* current_item;

        friend class cList_const_iterator<T>;
    public:
        cList_iterator(listNode<T>& value)
        {
            current_item = &value;
        }
        cList_iterator(const cList_iterator<T>& other)
        {
            current_item = other.current_item;
        }
        cList_iterator<T>& operator++()
        {
            current_item = current_item->next;
            return *this;
        }
        cList_iterator<T>& operator=(cList_iterator<T>& other)
        {
            this->current_item = other.current_item;
            return *this;
        }
        T& operator*()
        {
            return current_item->data;
        }
        bool operator==(const cList_iterator<T>& other)
        {
            return (current_item == other.current_item);
        }
        bool operator!=(const cList_iterator<T>& other)
        {
            return (current_item != other.current_item);
        }
};

Bár a cList_iterator-ral kezdünk, mégis előbb a cList_const_iterator-t kell egy sorban definiálni, az osztályban szereplő friend sor miatt (ami azért kell, hogy majd a const_iterator el tudja érni az iterator private adattagját (ami kell majd a copy konstruktorához, hogy lehessen cList_iterator-ból cList_const_iterator-t csinálni)). A dolog teljesen egyszerű egyébként, a current_item tárolja, hogy épp hogy vagyunk, lépdesni ugyanúgy lépdesünk vele, mint ahogy a láconlt lista kódjában is tettük. Akár a -- operátort is meg lehetne írni, mivel a listánk kétirányú volt. Érdemes még odafigyelni arra, hogy a * operátor referenciát ad vissza, tehát (ha minden igaz :D ) szerepelhet egyenlőség bal oldalán (a bejárón keresztül megváltoztatható az aktuális elem értéke). Második kör, a cList_const_iterator osztály. Csak azokat a funkciókat írom le, amivel bővült, illetve amik változtak (azt nem tekintem változásnak, hogy cList_iterator helyett csak cList_const_iterator-t kell írni):

template<class T> class cList_const_iterator
{
    private:
        const listNode<T>* current_item;
    public:
        cList_const_iterator(const cList_iterator<T>& other)
        {
            current_item = (const listNode<T>*)other.current_item;
        }
        T operator*()
        {
            return current_item->data;
        }
};

Az utolsó ami még hiányzik, az egy kis példakód, hogy mit is tudunk kezdeni ezzel a bejáróval, amit épp megírtunk. A lent látható kód a listán való végigmenés rövid, fájdalommentes, átlátható, bejárókat használó kódja:

cList<int> int_list;

int_list << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9;

for (cList<int>::const_iterator it = int_list.begin(); it != int_list.end(); ++it) {
    std::cout << *it << std::endl;
}
std::cout << "Lista vége" << std::endl;

Ennyit a saját készítésű bejárókról (az STL-ről meg talán a későbbiekben még egy nagyobb lélegzetvételű dolog születhet, ha úgy alakul - addig is ajánlott olvasmány a dologgal kapcsolatban a fentebb említett Nagy Könyv 19. fejezete). További jó iterálást mindenkinek.

A sorozat további részei

Hozzáfűznél valamit?

Dobj egy emailt a blog kukac deadlime pont hu címre.

Feliratkoznál?

Az RSS feed-et ajánljuk, ha kedveled a régi jó dolgokat.