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