STL:n iteraattorit

Iteraattorit ovat tietotyyppejä (tai muuttujia, kontekstista riippuen), joiden avulla voidaan tutkia ja muokata säiliöön talletettuja alkioita. Iteraattoria voi ajatella kirjanmerkkinä, joka muistaa yhden säiliössä olevan alkion sijainnin.

Iteraattoreiden ideana on tarjota yhdenmukainen rajapinta, jonka avulla pystytään samoilla operaatioilla käsittemään säiliön alkioita, riippumatta säiliön täsmällisestä tyypistä. Operaatiot on siis toteutettu siten, että ne käsittelevät iteraattori(e)n osoittamia alkioita riippumatta siitä, minkälaisessa säiliössä kyseiset alkiot ovat talletettuina. (Jos ollaan tarkkoja, niin iteraattori ei osoita alkiota vaan kahden alkion väliä.)

Esimerkiksi usein vastaan tuleva ongelma on säiliön kaikkien alkioiden läpikäynti. Vektorin tapauksessa läpikäynti ei ole vaikeaa, koska vektorin alkiot voidaan indeksoida läpi silmukassa. Tämä ei kuitenkaan pidä yleisesti paikkaansa säiliöiden keskuudessa, sillä on olemassa säiliöitä, joiden alkioilla ei ole järjestysnumeroa.

Jos yhden iteraattorin avulla voidaan pitää kirjaa yhden alkion sijainnista, kahdella iteraattorilla voidaan esittää väli säiliön alkioita: kaikki kahden alkion välissä sijaitsevat alkiot. Tätä ominaisuutta hyödynnetään myöhemmin STL-algoritmien kanssa.

Kaikki C++:n säiliötyypit tarjoavat ohjelmoijalle joukon iteraattorityyppejä, joilla säiliöitä voi käsitellä. Hyödyllisimmät näistä ovat

säiliötyyppi<alkiotyyppi>::iterator
säiliötyyppi<alkiotyyppi>::const_iterator

Tyypin const_iterator avulla säiliön alkioita voidaan tutkia mutta ei muuttaa. Tällaisia iteraattoreita tarvitaan, jos säiliömuuttuja on määritelty vakioksi const-avainsanaa käyttäen. Esimerkiksi

const vector<string> KUUKAUDET = { "tammikuu", "helmikuu", ... };

Toteutetaan hyvin yksinkertainen ohjelma, joka käy vektorin alkiot läpi iteraattorin avulla ja tulostaa niiden arvot näytölle:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<double> luvut = {1.0, 1.5, 2.0, 2.75};
    vector<double>::iterator veciter;
    veciter = luvut.begin();
    while ( veciter != luvut.end() ) {
        cout << *veciter << " ";
        ++veciter;
    }
    cout << endl;
}

Metodin begin paluuarvo osoittaa säiliön ensimmäisen alkion sijainnin. Metodi end palauttaa iteraattorin, joka osoittaa säiliön loppuun: end ei osoita mihinkään varsinaiseen säiliössä olevaan arvoon, vaan kyseessä on eräänlainen loppumerkki.

Usein end-iteraattoria käytetään myös virhettä tai epäonnistumista kuvaavana paluuarvona iteraattorin palauttavasta funktiosta.

Seuraava kuva havainnollistaa begin- ja end-iteraattoreita:

../../_images/iteraattorit.png

Iteraattorin osoittamaa säiliön alkiota voidaan käsitellä kohdistamalla siihen *-operaattori. Esimerkiksi:

cout << *veciter << endl;
*veciter = 0.0;
*veciter += 2.5;
funktio(*veciter);

Jos halutaan kohdistaa metodeja olioon, johon iteraattori osoittaa, käytetään operaattoria ->.

Iteraattori saadaan osoittamaan seuraavaan vuorossa olevaan alkioon operaattorilla ++ ja edelliseen alkioon operaattorilla --. Operaattoreilla == ja != voidaan testata, osoittavatko kaksi iteraattoria samaan vai eri alkioon.

Jos ainoa tarve on käydä säiliön (toimii muillakin säiliöillä kuin vektorilla) kaikki alkiot läpi, voi käyttää for-rakennetta:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<double> luvut = {1.0, 1.5, 2.0, 2.75};

    for ( auto alkio: luvut ) {
        cout << alkio << " ";
    }
    cout << endl;
}

joka on toiminnaltaan analoginen verrattuna Pythonin rakenteeseen

luvut = [ 1.0 1.5 2.0 2.75 ]
for alkio in luvut:
    print(alkio, end=" ")
print()

Kulissien takana C++:n säiliön alkiot läpi käyvä versio for-rakenteesta käyttää iteraattoreita, mutta se ei näy ohjelmoijalle.

Edellä esitellyn for-rakenteen iterointimuuttuja (esimerkissä alkio) on oletusarvoisesti kopio käsiteltävän säiliön vuorossa olevasta alkiosta (kopiosemantiikka).

Jos kuitenkin halutaan, että for-rakenteen rungossa voidaan muuttaa säiliön alkioita, määritellään iterointimuuttuja viitteeksi:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<double> luvut = {1.0, 1.5, 2.0, 2.75};

    for ( auto& alkio: luvut ) {
        alkio *= 2;
    }

    // Tässä kaikki luvut-vektorin alkiot ovat
    // kaksinkertaistuneet: 2.0, 3.0, 4.0 5.5
}

Attention

Tärkeä yksityiskohta, joka on syytä pitää mielessä: jos säiliöön lisätään tai siitä poistetaan alkioita, iteraattorien arvot, jotka on asetettu osoittamaan kyseisen säiliön alkioihin ennen muutosta, eivät ole enää käyttökelpoisia.

Esimerkeissä käytetty sana auto on C++:n varattu sana, jota voidaan käyttää määriteltävänä olevan muuttujan tyyppinä tilanteissa, joissa kääntäjä osaa päätellä tyypin alustusarvosta:

auto lukumaara = 0;         // int
auto lampotila = 12.1;      // double
auto iter = luvut.begin();  // vector<double>::iterator

Materiaalin esimerkeissä auto-sanaa ei kuitenkaan käytetä läheskään kaikissa tilanteissa, joissa se olisi mahdollista, koska on hyvä oppia ymmärtämään staattisesti tyypitetyn kielen tyyppimäärittelymekanismia hiukan itsekin.