STL-kirjasto
Iteraattorit
Iteraattorit ovat tietotyyppejä (tai kontekstista riippuen muuttujia), joilla tutkitaan ja käsitellään säiliöön tallennettuja alkioita. Iteraattoria voi ajatella kirjanmerkkinä, joka pitää kirjaa sijainnista säiliössä.
Iteraattorien tärkein hyöty on yhdenmukainen rajapinta: samoilla operaatioilla voi käsitellä eri säiliöiden alkioita, vaikka säiliöiden sisäinen toteutus olisi erilainen. Tarkemmin sanottuna iteraattori osoittaa yleensä alkioon tai alkioiden väliseen kohtaan säiliöstä riippuen.
Vektorin alkiot voi käydä läpi indekseillä, mutta kaikissa säiliöissä alkioilla ei ole indeksinumeroa. Iteraattorit tarjoavat yleisen tavan kulkea säiliöstä toiseen alkioon. Kahdella iteraattorilla voidaan ilmaista myös alkioiden väli, jota STL:n algoritmit käyttävät.
Iteraattorityypit
Säiliöt tarjoavat iteraattoreita, joiden tyyppi määräytyy säiliön ja alkioiden tyypin perusteella. Tavallisimmat ovat iterator ja const_iterator:
vector<double>::iterator iter;
vector<string>::const_iterator const_iter;
iterator-iteraattorin kautta alkiota voi lukea ja muuttaa. const_iterator sallii alkion tutkimisen, mutta ei muuttamista. Vakioksi määritellyn säiliön kautta saadaan vain vakioiteraattoreita.
const vector<string> KUUKAUDET = { "tammikuu", "helmikuu", "maaliskuu" }; vector<string>::const_iterator iter = KUUKAUDET.begin();
begin ja end
begin() palauttaa iteraattorin säiliön ensimmäiseen alkioon. end() palauttaa loppumerkin: se osoittaa heti viimeisen alkion jälkeiseen kohtaan, eikä sitä saa dereferoida. Tyhjässä säiliössä begin() ja end() ovat samat.
begin() osoittaa ensimmäiseen alkioon; end() on viimeisen alkion jälkeinen loppumerkki.Seuraava ohjelma käy vektorin läpi alusta loppumerkkiin asti:
#include <iostream> #include <vector> using namespace std; int main() { vector<double> luvut = { 1.0, 1.5, 2.0, 2.75 }; vector<double>::iterator iter = luvut.begin(); while (iter != luvut.end()) { cout << *iter << " "; ++iter; } cout << endl; }
Kun iteraattoria käytetään ehtona, vertaa sitä end()-iteraattoriin. Loppumerkkiä ei koskaan käsitellä alkiona.
Iteraattorin käyttäminen
*-operaattorilla pääsee käsiksi iteraattorin osoittamaan alkioon. Sillä voi lukea ja muuttaa alkiota sekä välittää alkion funktiolle:
cout << *iter << endl; *iter = 0.0; *iter += 2.5; funktio(*iter);
Jos iteraattori osoittaa olioon, jonka jäsenfunktiota halutaan kutsua, voidaan käyttää ->-operaattoria, esimerkiksi iter->jasenfunktio(). Operaattori ++ siirtää iteraattorin seuraavaan alkioon. Operaattorit == ja != vertaavat iteraattorien sijainteja. Kaikki iteraattorit eivät tue siirtymistä taaksepäin ---operaattorilla.
Säiliön läpikäynti for-silmukalla
Jos tarkoitus on vain käsitellä kaikki alkiot järjestyksessä, range-based for-silmukka on usein helpoin vaihtoehto:
for (auto alkio : luvut) { cout << alkio << " "; } cout << endl;
Tämä vastaa ajatukseltaan Pythonin rakennetta:
luvut = [1.0, 1.5, 2.0, 2.75] for alkio in luvut: print(alkio, end=" ") print()
C++:n range-based for käyttää iteraattoreita kulissien takana. Ohjelmoijan ei tarvitse kirjoittaa niitä näkyviin.
Oletuksena silmukkamuuttuja saa kopioksi kunkin alkion arvon. Jos alkiota halutaan muuttaa, käytä viitettä:
for (auto& alkio : luvut) { alkio *= 2; }
Tämän jälkeen vektorin arvot ovat 2.0, 3.0, 4.0 ja 5.5. Jos alkiot halutaan vain lukea ilman kopiointia, voi käyttää vakioviitettä const auto&.
Iteraattorien mitätöityminen
Säiliön muokkaaminen voi mitätöidä iteraattorin. Kun iteraattori on mitätöitynyt, sitä ei saa enää käyttää tai dereferoida. Tarkat säännöt riippuvat säiliöstä ja tehdystä muutoksesta. Esimerkiksi vektorin kasvatus voi varata uuden muistialueen ja mitätöidä kaikki vektoriin aiemmin asetetut iteraattorit. Siksi iteraattoria ei pidä säilyttää säiliön muokkauksen yli tarkistamatta kyseisen säiliön sääntöjä.
auto ja tyyppipäättely
auto on avainsana, jolla muuttujan tyyppi jätetään kääntäjän pääteltäväksi alustusarvosta:
auto lukumaara = 0; // int auto lampotila = 12.1; // double auto iter = luvut.begin(); // vector<double>::iterator
auto on erityisen hyödyllinen pitkien iteraattorityyppien kanssa. Tyyppiä ei kuitenkaan kannata jättää pääteltäväksi kaikkialla: eksplisiittinen tyyppi voi tehdä koodista lukijalle selvemmän ja auttaa ymmärtämään C++:n staattista tyypitystä.
Kokeile itse: etsi virhe
Mikä virhe seuraavassa läpikäynnissä pitää korjata?
auto iter = luvut.begin(); while (iter <= luvut.end()) { cout << *iter << endl; ++iter; }
iter saavuttaa end()-loppumerkin, sitä ei enää saa dereferoida. Käytä ehtoa iter != luvut.end(), jolloin silmukka päättyy ennen loppumerkkiä.