530 likes | 729 Views
Olio-ohjelmoinnin perusteet luento 7: C++ mallit, Standard Template Library (STL). Sami Jantunen LTY/Tietotekniikan osasto. Sisältö. C++ mallit Yleiskäyttöisyydestä C++ mallit Funktiomallit Luokkamallit Standard Template Library (STL) Taustaa Säiliöt Iteraattorit Yhteenveto.
E N D
Olio-ohjelmoinnin perusteetluento 7: C++ mallit, Standard Template Library (STL) Sami Jantunen LTY/Tietotekniikan osasto
Sisältö • C++ mallit • Yleiskäyttöisyydestä • C++ mallit • Funktiomallit • Luokkamallit • Standard Template Library (STL) • Taustaa • Säiliöt • Iteraattorit • Yhteenveto
Yleiskäyttöisestä suunnittelusta • Oleellista on pystyä erottamaan pysyvät ja vaihtelevat asiat toisistaan! • Halutaan uudelleenkäyttää pysyvät asiat ja johtaa uusia luokkia toteuttamalla erikseen vaihtelevat asiat • Tavoitteelle löytyy oliopohjaisista kielista erilaisia mekanismeja • Perintä on tyypillinen yleiskäyttöisyyteen liittyvä mekanismi • Luokkien yhteiset ominaisuudet voidaan toteuttaa kertalleen kantaluokassa. • Vaihtuvat asiat toteutetaan lapsiluokissa • Perinnän käytöllä on kuitenkin rajansa!
Perinnän rajoituksia yleiskäytettävyydessä • Aliluokat perivät kantaluokkansa rajapinnan ja toteutuksen kokonaisuudessaan ja täsmälleen samanlaisena! • Pysyvään osuutteen kuuluu siis kokonaisia palveluita (jäsenfunktioita) parametreineen ja paluuvarvoineen. • Todellinen tarve ei kuitenkaan aina ole tällainen!
Muistatko vielä?(IntDeque) Otetaanpa Deque luokka hyötykäyttöön! • Luodaan Int-pohjainen lista • IntDeque • void goBeforeFirst(); • void goAfterLast(); • void forth(); • void back(); • int isBeforeFirst() const; • int isAfterLast() const; • IntDeque(); • void insert (int); • void remove(); • int current () const; • ~IntDeque(); class IntDeque: public Deque { public: IntDeque(); void insert (int); void remove(); int current () const; ~IntDeque(); }; Deque-luokasta peritty Uusi toiminnallisuus next prev data next prev data next prev data
Esimerkki: 2-suuntainen lista • Pysyvä osa: • Listan toteutus (alkioiden käsittely) • Vaihtuva osa: • Alkioiden tyyppi Perinnän käyttö ei kovin kätevää! • Useat rajapinnoista on kuitenkin riippuvaisia alkion tyypistä. Uudelleenkäytettävyyden taso heikkenee Deque IntDeque DoubleDeque Muistathan vielä 2-suuntaisen listaesimerkkimme?
Entäpä jos? • Mitä jos vaatisimme, että eri tyyppiset alkiot perittäisiin samasta kantaluokasta? • Sama 2-suuntainen listatoteutus toimisi riippumatta alkion todellisesta tyypistä Deque Alkio IntAlkio DoubleAlkio
Ratkaisun huonoja puolia • Tyyppitietoisuus häviää • Listaan on mahdollista tallettaa eri tyyppejä sekaisin • Kääntäjä ei pysty tekemään tyyppitarkistuksia • Taulukon käyttäjän tulee itse tietää mitä tyyppiä alkiot oikeasti on (ja tehdä tarvittaessa osoittimen muunnostyöt) Deque Alkio IntAlkio DoubleAlkio
Johtopäätös • Erityyppisillä taulukoilla on todella paljon yhteistä • Yhteiset asiat on kuitenkin sen luonteisia, että niitä on vaikea toteuttaa yleiskäyttöiseksi kantaluokkaan • Taulukon alkioiden tyyppi vaikuttaa taulukon rajapintaan ja toteutukseen kauttaaltaan • Alkioiden tyyppien erilaisuuden merkitystä ei voi eliminoida ilman, että tingitään tyyppiturvallisuudesta ja käyttömukavuudesta • Olisi kiva jos olisi muitakin mekanismeja pysyvyyden ja vaihtelevuuden hallintaan
C++ Mallit (Templates) • Käännösaikaisesti tyypitetyissä kielissä (kuten C++) täytyy kaikkien parametrien ja muuttujien tyyppien olla selvillä jo ohjelmaa käännettäessä. • Tyyppien geneerisyys ei onnistu ilman lisäkikkoja • Mallit (template) ovat C++:n tapa kirjoittaa yleiskäyttöisiä: • funktioita (funktiomallit, function templates) • luokkia (Luokkamallit, class templates) • Malleissa tyyppejä ja joitain muitakin asioita voidaan jättää määrämäättä • Auki jätetyt asiat “sidotaan” vasta myöhemmin käytön yhteydessä. • Näin saadaan säilytettyä C++:n vahva käännösaikainen tyypitys, mutta annetaan silti mahdollisuus kirjoittaa geneeristä koodia
Esimerkki C++ mallista • Otetaan esimerkiksi meidän 2-suuntainen lista • Muutetaan olemassa olevaa toteutusta ja käytetään perinnän sijasta luokkamallia • Toteutetaan Deque-malliin yleinen toiminnallisuus • Jätetään alkioiden tyyppi vielä määrittelemättä • Kun lista otetaan käyttöön, määritellään samalla alkioiden tyyppi tyyliin: • Deque <int> • Deque <double> • Auki jätetyt asiat siis selviävät jo käännösaikana, mutta kuitenkin vasta vektoreiden käyttötilanteessa Deque
C++ mallin syntaksi template<typename tyyppiparam1, typename tyyppiparam2, ...> //Tähän normaali funktion tai luokan määrittely TAI template<class tyyppiparam1, class tyyppiparam2, ...> //Tähän normaali funktion tai luokan määrittely • Malli alkaa avainsanalla template • Seuraavaksi esitellään kulmasulkeiden sisällä kaikki auki jätetyt tyyppiparametrit • Tämän jälkeen seuraa itse mallin koodi (normaalin funktion tai luokan tapaan) • Tyyppiparametreja voi käyttää koodissa aivan kuin normaaleja C++ tyyppejä.
Funktiomallit (function template) • Geneerisiä malleja, joista kääntäjä voi generoida eri tyypeillä toimivia funktioita • Kääntäjä osaa automaattisesti päätellä kutsusta tyyppiparametrien arvot • Voidaan kutsua aivan kuin mitä tahansa muuta funktiota: min(1,2); //int min(2.3, 5.7); //double • On myös mahdollista eksplisiittesesti määrätä tyyppiparametreille arvot:float f = min<float>(3.2,6) • template <typename T> • //Tai template <class T> • T min(T p1, T p2) • { • T tulos; • if (p1 < p2) • { • tulos = p1; • } • else • { • tulos = p2; • } • return tulos • }
Luokkamallit (class template) • template <typename T1, typename T2> • class Pari • { • public: • Pari(T1 eka, T2 toka); • T1 annaEka() const; • T2 annaToka() const; • . • . • . • private T1 eka_; • T2 toka_; • }; • Toimii mallina luokille, jotka ovat muuten samanlaisia, mutta joissa jotkin tyypit voit erota toisistaan • Esimerkkinä vieressä oleva malli Pari: • Ei oma luokkansa vaan malli kokonaiselle “perheelle” luokkia • Kaksi auki jätettyä tyyppiä T1 ja T2 • Luokat saadaan luotua määräämällä tyyppiparametreille arvot. Esimerkki:Pari<int,double> p(1, 3.2); • template <typename T1, typename T2> • Pari<T1, T2>::Pari(T1 eka, T2 toka) :eka_(eka), toka_(toka) • { • } • template <typename T1, typename T2> • T1 Pari<T1, T2>::annaEka() const • { • return eka_; • }
Ajattelumalli, joka toivottavasti helpottaa luokkamallin käyttöä: Pari<int,double> p(1, 3.2); • Yllä olevassa esimerkissä Pari<int,double> voidaan käsittää luokan tyyppinä (aivan kuin luokan tyypit myString tai myVeryOwnClass) void f(Pari<int,int> &i, Pari<float, int*> d); • ihan tavallinen funktio jolla on kaksi täysin eri tyyppistä luokkaa parametrina • int,int -pari • float, int* -pari luotava olio luokan tyyppi luokan tyyppi luokan tyyppi
Tyyppiparametreille asetetut vaatimukset • C++ mallin koodin on käännyttävä, kun tyyppiparametrien tilalle sijoitetaan todelliset tyypit • esimerkiksi aiemmin määrittelemämme min –funktiomallin parametrit pitää olla sellaisia, että niiden arvoja voi vertailla < operaattorilla • Jos mallin koodi kutsuu auki jätetyn tyypin kautta esim. olion jäsenfunktiota, voi mallia käyttää vain sellaisten tyyppien kanssa, mistä kyseinen jäsenfunktio löytyy
Mallien parametreista • Malleilla voi olla oletusparametrejatemplate<typename T1 = int, typename T2 = T1> • Malleilla voi olla vakioparametreja template<unsigned long SIZE> class Mjono { public: Mjono(const char *arvo); const char* annaArvo() const; private: char taulukko[SIZE+1]; }; Mjono<12> s1(“Tuli täyteen”);
Mallien erikoistus • On mahdollista määrittää erityinen versio (juuri tietyille tyypeille) mallista: Tyhjät kulmasulkeet merkiksi siitä, että erikoistumisessa ei ole enään auki jääneitä tyyppejä • template <> • class Pari<bool, bool> • { • public: • Pari(bool eka, bool toka); • bool annaEka() const; • bool annaToka() const; • . • . • . • private: • unsigned char ekaJaToka_; • }; • bool Pari<bool, bool>::annaEka() const • { • return (ekaJaToka_ & 1) != 0; • } Kirjoitettu auki ne tyypit mitä erikoistumiinen koskee
Missä mennään? • C++ mallit • Yleiskäyttöisyydestä • C++ mallit • Funktiomallit • Luokkamallit • Standard Template Library (STL) • Taustaa • Säiliöt • Iteraattorit • Yhteenveto
STL (Standard Template Library) • Hyvä esimerkki C++ mallien käytöstä ja geenrisestä ohjelmoinnista • Määrittelee joukon tavallisimpia tietorakenteita ja niiden käyttöön tarkoitettuja algoritmeja • Tarkoituksena on uudelleen käyttää STL:n optimoituja tietorakenteita sen sijaan, että “keksittäisiin pyörä uudelleen”
STL:n rakenne • Säiliöt (container) ovat STL:n tarjoamia tietorakenteita • Iteraattorit (iterator) ovat “kirjanmerkkejä” säiliöiden läpikäymiseen • Geneeriset algoritmit (generic algorithm) käsittelevät säiliöitä iteraattoreiden avulla • Säiliösovittimet (container adaptor) ovat säiliömalleja, jotka toteutetaan halutun toisen säilion avulla. Niillä voi muuntaa säiliön rajapinnan toisenlaiseksi • Funktio-oliot (function object) ovat olioita, jotka käyttäytyvät kuten funktiot ja joita voi käyttää muun muassa algoritmien toiminnan säätämiseen • Varaimet (allocator) oava olioita säiliöivien muistinhallinnan räätälöintiin. Lyhyesti selitettynä varaimet ovat olioita, jotka osaavat varata ja vapauttaa muistia. Normaalisti STL:n säiliöt varaavat muistinsa new’llä ja vapauttavat deletellä. Jos niille annetaan ylimääräisenä tyyppiparametrina varainluokka, ne käyttävät ko.luokan palveluita tarvitsemansa muistin varaamiseen ja vapauttamiseen
STLAlgoritmien geneerisyys • Olemme olio-ohjelmoijina tottuneet siihen, että halutut tiedot ja toiminnallisuudet kirjoitetaan luokan jäsenfunktioihin. • Tuntuisi siis loogiselta, että STL:n tarjoamat algoritmit olisi myös toteutettu säiliöiden jäsenfunktiona • STL:ssä lähes kaikki algoritmit on kuitenkin kirjoitettu irrallisina funktioina. Miksi? • algoritmien geneerisyys on laajempaa kuin pelkkä tyypin auki jättäminen • tyypin lisäksi STL:n algoritmit jättävät mm. auki sen minkä tietorakenteen kanssa ne toimivat. • näin samaa geneeristä find-algoritmia voidaan käyttää niin listan, vektorin kuin joukonkin kanssa. • algoritmiä ei myöskään tarvitse kirjoittaa erikseen jokaiselle säiliölle vaan yksi geneerinen funktiomalli toimii kaikkien säiliöiden kanssa • geneerisyydestä seuraa myös se, että STL:n algoritmeja voi käyttää myös omien tietorakenteiden kanssa (kunhan ne vain toteuttavat STL:n algoritmien asettamat vaatimukset)
STL:n säiliöt • STL:n säiliöt muistuttavat suuresti toisiaan • Säiliöt jakautuvat pääpiirteittään kahteen kategoriaan: • Sarjat (sequence) ovat säiliöitä, joiden alkioita pystyy hakemaan niiden järjestysnumeron perusteella. Samoin alkioita voi lisätä haluttuun paikkaan ja poistaa siitä. Esimerkki: vector • Assosiatiiviset säiliöt (associative container) puolestaan perustuvat siihen, että alkioita haetaan säiliöstä avaimen (key) perusteella. Esimerkiksi puhelinluettelo muistuttaa assosiatiivista säiliötä– siinä numeron pystyy etsimään nopeasti nimen perusteella
STL:n alkioista • Alkioiden tyypeillä tulee olla: • kopiorakentaja, joka luo alkuperäisen olion kanssa samanlaisen olion • sijoitusoperaattori, jonka tuloksena sijoituksen kohteena olevasta oliosta tulee samanlainen sijoitetun olion kanssa • STL:n assosiatiiviset säiliöt vaativat, että alkioiden avaimia voi myös kopioida ja sijoitaa ja lisäksi kahta avainta täytyy pystyä vertailemaan • Viitteet eivät kelpaa alkioiksi
Sarjat-”peräkkäissäiliöt” • Sarjat ovat säiliöitä, joissa alkiot sijaitsevat “peräkkäin” ja joissa jokaisella alkiolla on järjestysnumero • Alkioita voi selata järjestyksessä • Halutun alkion voi hakea järjestysnumeron perusteella • Uusia alkioita voi lisätä säiliössä haluttuun paikkaan ja vanhoja voi poistaa • STL:n sarjasäiliöt: • vector • deque • list • Kaikissasarjoissa annetaan sarjan alkioiden tyyppi mallin tyyppiparametrina. Esim: • vector<float>, deque<int> ja list<string>
Vector esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // // Compile options needed: /GX // // front.cpp: Illustrates the vector::front and vector::back methods. // // Functions: // // vector::front - Returns reference to first element of vector. // // vector::back - Returns reference to last element of vector. // // vector::push_back - Appends (inserts) an element to the end of a // vector, allocating memory for it if necessary. // // vector::size - Returns number of elements in the vector. // // vector::begin - Returns an iterator to start traversal of the vector. // // vector::end - Returns an iterator for the last element of the vector. // // vector::erase - Deletes elements from a vector (single & range). // //////////////////////////////////////////////////////////////////////
Vector esimerkki (2/3) cout << "First element: " << theVector.front() << endl; cout << "Last element: " << theVector.back() << endl; cout << "Elements in vector: " << theVector.size() << endl; // Delete the last element of the vector. Remember that the vector // is 0-based, so theVector.end() actually points 1 element beyond // the end. theVector.erase(theVector.end() - 1); cout << endl << "After erasing last element, new last element is: " << theVector.back() << endl; // Delete the first element of the vector. theVector.erase(theVector.begin()); cout << "After erasing first element, new first element is: " << theVector.front() << endl; cout << "Elements in vector: " << theVector.size() << endl; } // The debugger can't handle symbols more than 255 characters // long. STL often creates symbols longer than that. When //symbols are longer than 255 characters, the warning is issued. #pragma warning(disable:4786) #include <iostream> #include <vector> using namespace std ; typedef vector<int> INTVECTOR; const ARRAY_SIZE = 4; void main() { // Dynamically allocated vector begins with 0 elements. INTVECTOR theVector; // Intialize the array to contain the members [100, 200, 300, 400] for (int cEachItem = 0; cEachItem < ARRAY_SIZE; cEachItem++) theVector.push_back((cEachItem + 1) * 100);
Vector esimerkki (3/3) Program Output is: ===================== First element: 100 Last element: 400 Elements in vector: 4 After erasing last element, new last element is: 300 After erasing first element, new first element is: 200 Elements in vector: 2
Deque esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // // Compile options needed: -GX // // deque.cpp : // // Functions: // // operator[] // at // empty // push_back // begin // end //////////////////////////////////////////////////////////////////////
Deque esimerkki (2/3) //print out the contents print_contents (a,"a"); cout <<"The first element of a is " <<a[0] <<endl; cout <<"The first element of a is " <<a.at(0) <<endl; cout <<"The last element of a is " <<a[a.size()-1] <<endl; cout <<"The last element of a is " <<a.at(a.size()-1) <<endl; } //function to print the contents of deque void print_contents (CHARDEQUE deque, char *name) { CHARDEQUE::iterator pdeque; cout <<"The contents of "<< name <<" : "; for(pdeque = deque.begin(); pdeque != deque.end(); pdeque++) { cout << *pdeque <<" " ; } cout<<endl; } /* Compile options needed:-GX */ #include <iostream> #include <deque> using namespace std; typedef deque<char > CHARDEQUE; void print_contents (CHARDEQUE deque, char*); void main() { //create an empty deque a CHARDEQUE a; //check whether it is empty if(a.empty()) cout<<"a is empty"<<endl; else cout<<"a is not empty"<<endl; //inset A, B, C and D to a a.push_back('A'); a.push_back('B'); a.push_back('C'); a.push_back('D'); //check again whether a is empty if(a.empty()) cout<<"a is empty"<<endl; else cout<<"a is not empty"<<endl;
Deque esimerkki (3/3) Program Output is: =============== a is empty a is not empty The contents of a : A B C D The first element of a is A The first element of a is A The last element of a is D The last element of a is D
list esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // // Compile options needed: -GX // // assign.cpp : Shows the various ways to assign and erase elements // from a list<T>. // // Functions: // // list::assign // list::empty // list::erase //////////////////////////////////////////////////////////////////////
list esimerkki (2/3) // 1 2 3 for (i = listAnother.begin(); i != listAnother.end(); ++i) cout << *i << " "; cout << endl; listAnother.assign(4, 1); // 1 1 1 1 for (i = listAnother.begin(); i != listAnother.end(); ++i) cout << *i << " "; cout << endl; listAnother.erase(listAnother.begin()); // 1 1 1 for (i = listAnother.begin(); i != listAnother.end(); ++i) cout << *i << " "; cout << endl; listAnother.erase(listAnother.begin(), listAnother.end()); if (listAnother.empty()) cout << "All gone\n"; } #include <list> #include <iostream> using namespace std ; typedef list<int> LISTINT; void main() { LISTINT listOne; LISTINT listAnother; LISTINT::iterator i; // Add some data listOne.push_front (2); listOne.push_front (1); listOne.push_back (3); listAnother.push_front(4); listAnother.assign(listOne.begin(), listOne.end());
list esimerkki (3/3) Program Output is: 1 2 3 1 1 1 1 1 1 1 All gone
Assosiatiiviset säiliöt (associative container) • Eroavat sarjoista siinä, että alkioita ei lueta, lisätä tai poisteta niiden “sijainnin” tai järjestysnumeron perusteella • Jokaiseen alkioon liittyy avain (key), jonka perusteella alkion voi myöhemmin hakea
Assosiatiiviset säiliötyypit • set • Yksinkertaisin säiliötyyppi, missä avaimena on alkio itsessään • multiset • Eroaa setistä siten, että samanarvoisia alkioita voi olla useita • map • Tietorakenne, missä avain ja alkio ovat erillisiä ja jossa avaimen perusteella voidaan hakea haluttu alkio. Voidaan myös ajatella eräänlaisena taulukkona • multimap • Eroaa mapista siten, että yhtä avainta kohde voi olla useita alkioita
set esimerkki (1/3) ////////////////////////////////////////////////////////////////////// // // Compile options needed: -GX // // SetFind.cpp: // Illustrates how to use the find function to get an iterator // that points to the first element in the controlled sequence // that has a particular sort key. // // Functions: // // find Returns an iterator that points to the first element // in the controlled sequence that has the same sort key // as the value passed to the find function. If no such // element exists, the iterator equals end(). //////////////////////////////////////////////////////////////////////
set esimerkki (2/3) void main() { SET_INT s1; cout << "s1.insert(5)" << endl; s1.insert(5); cout << "s1.insert(8)" << endl; s1.insert(8); cout << "s1.insert(12)" << endl; s1.insert(12); SET_INT::iterator it; cout << "it=find(8)" << endl; it=s1.find(8); cout << "it!=s1.end() returned "; truefalse(it!=s1.end()); // True cout << "it=find(6)" << endl; it=s1.find(6); cout << "it!=s1.end() returned "; truefalse(it!=s1.end()); // False } #pragma warning(disable:4786) #include <set> #include <iostream> using namespace std ; typedef set<int> SET_INT; void truefalse(int x) { cout << (x?"True":"False") << endl; }
set esimerkki (3/3) Program Output is: ============= s1.insert(5) s1.insert(8) s1.insert(12) it=find(8) it!=s1.end() returned True it=find(6) it!=s1.end() returned False
map esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // // Compile options needed: None // // <filename> : main.cpp // // Functions: // // end // find // insert //////////////////////////////////////////////////////////////////////
map esimerkki (2/3) #pragma warning(disable:4786) #include <iostream> #include <string> #include <map> using namespace std; typedef map<int, string, less<int> > INT2STRING; void main() { // 1. Create a map of ints to strings INT2STRING theMap; INT2STRING::iterator theIterator; string theString = ""; int index; // Fill it with the digits 0 - 9, each mapped to its string counterpart // Note: value_type is a pair for maps... theMap.insert(INT2STRING::value_type(0,"Zero")); theMap.insert(INT2STRING::value_type(1,"One")); theMap.insert(INT2STRING::value_type(2,"Two")); theMap.insert(INT2STRING::value_type(3,"Three")); theMap.insert(INT2STRING::value_type(4,"Four")); theMap.insert(INT2STRING::value_type(5,"Five")); theMap.insert(INT2STRING::value_type(6,"Six")); theMap.insert(INT2STRING::value_type(7,"Seven")); theMap.insert(INT2STRING::value_type(8,"Eight")); theMap.insert(INT2STRING::value_type(9,"Nine")); // Read a Number from the user and print it back as words for( ; ; ) { cout << "Enter \"q\" to quit, or enter a Number: "; cin >> theString; if(theString == "q") break; // extract each digit from the string, find its corresponding // entry in the map (the word equivalent) and print it for(index = 0; index < theString.length(); index++){ theIterator = theMap.find(theString[index] - '0'); if(theIterator != theMap.end() ) // is 0 - 9 cout << (*theIterator).second << " "; else // some character other than 0 - 9 cout << "[err] "; } cout << endl; } }
map esimerkki (3/3) Program Output is: ============== Enter "q" to quit, or enter a Number: 22 Two Two Enter "q" to quit, or enter a Number: 33 Three Three Enter "q" to quit, or enter a Number: 456 Four Five Six Enter "q" to quit, or enter a Number: q
Iteraattorit • On hyvin tavallista, että tietorakenteen alkioita käydään läpi järjestyksessä yksi kerrallaan • Tyypillinen ratkaisuyritys ongelmaan on lisätä läpikäymiseen tarvittavat operaatiot itse tietorakenteeseen • annaEnsimmainen • annaSeuraava • onkoLoppu • … • Tällöin säiliö muistaisi itse, missä alkiossa läpikäyminen on sillä hetkellä menossa
Iteraattorit • säiliön tila koostuisi sekä listan alkioista että läpikäyntipaikasta. • operaatiot kuten annaEnsimmainen() ja annaSeuraavat() muuttaisivat väistämättä säiliön tilaa • kyseiset operaatiot eivät voisi olla vakiojäsenfunktioita • vakioviitteen päässä olevaa listaa ei voisi täten selata läpi • Usein on tarve lukea listaa kahdesta kohtaan yhtä aikaa. Edellä ehdotetun ratkaisumallin ongelmat ovat:
Iterator • Luodaan kaksi eri luokkaa • varsinainen säiliö, joka ei sisällä mitään paikkatietoa • “kirjanmerkki”, joka vaan muistaa, missä kohtaa säiliötä ollaan läpikäymässä • Kyseessä on tunnettu Design Pattern (Iterator)
Iteraattorit • Erittäin tärkeä käsite STL:ssä • Jokaista säiliötyyppiä kohden on olemassa myös iteraattorityyppi, jonka avulla säiliön alkiot voi käydä läpi • Iteraatorin voi käsittää kirjainmerkkinä, joka muistaa tietyn paikan tietyssä säiliössä. • iteraattoria voi siirrellä säiliön sisällä • iteraattorin läpi voi lukea ja muuttaa säiliön alkioita
Iteraattorikategorioita • syöttöiteraattori (input iterator) voi vain lukea alkioita • tulostusiteraattori (output iterator) voi vain muuttaa alkioita (ei lukea) • eteenpäin-iteraattori (forward iterator) voi lukea ja muuttaa alkioita ja lisäksi iteraattoria voi siirtää yhdellä eteenpäin. • kaksisuuntainen iteraattori (bidirectional iterator) voi lukea, muuttaa ja liikkua kumpaakin suuntaan • hajasaanti-iteraattori (random access iterator) on kuin kaksisuuntainen iteraattori, mutta se voi harpata mielivaltaisia askelia.
iterator & map esimerkki #pragma warning(disable:4786) #include <iostream> #include <string> #include <map> using namespace std ; typedef map<string, int> STRING2INT; void main() { STRING2INT MyMap; STRING2INT::iterator MyIterator; string MyBuffer; // Fill in MyMap with the months of the year, mapped to their number // January - 1, February - 2, etc. using operator[]. MyMap["January"] = 1; MyMap["February"] = 2; . . . // Ask the user for a month of the year and print the number // that corresponds to the month entered //MyIterator = MyMap.end(); while(MyIterator == MyMap.end()){ cout << "Enter a Month :"; cin >> MyBuffer; if((MyIterator = MyMap.find(MyBuffer)) != MyMap.end()) cout << (*MyIterator).first << " is Month Number " << (*MyIterator).second << endl; else cout << "Enter a Valid Month (example: March)" << endl; } }
iteraattoreiden kelvollisuus • jos säiliöön lisätään tai siitä poistetaan alkioita, olemassa olevat iteraattorit muuttuvat käkyttökelvottomaksi • tällöin iteraattori tulee määritellä uudestaan