670 likes | 841 Views
Olio-ohjelmoinnin perusteet luento 7: C++ mallit, Standard Template Library (STL). Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista. Sisältö. C++ mallit Yleiskäyttöisyydestä C++ mallit Funktiomallit Luokkamallit
E N D
Olio-ohjelmoinnin perusteetluento 7: C++ mallit, Standard Template Library (STL) Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista
Sisältö • C++ mallit • Yleiskäyttöisyydestä • C++ mallit • Funktiomallit • Luokkamallit • Standard Template Library (STL) • Taustaa • Säiliöt • Iteraattorit • Algoritmit • 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 • Periytyminen on tyypillinen yleiskäyttöisyyteen liittyvä mekanismi • Luokkien yhteiset ominaisuudet voidaan toteuttaa kertalleen kantaluokassa. • Vaihtuvat asiat toteutetaan lapsiluokissa • Periytymisen käytöllä on kuitenkin rajansa!
Periytymisen rajoituksia yleiskäytettävyydessä • Aliluokat perivät kantaluokkansa rajapinnan ja toteutuksen kokonaisuudessaan ja täsmälleen samanlaisena! • Pysyvään osuuteen 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 periytymisen käyttö ei kovin kätevää! • Useat rajapinnoista ovat 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 periytymisen 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 käyttötilanteessa Deque
C++ mallin syntaksi template<typename tyyppiparam1, typename tyyppiparam2, ...> //Tähän normaali funktion tai luokan määrittely TAI VAIHTOEHTOISESTI 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) • Jäsenfunktiomalli on funktiomalli, joka on määritelty luokan jäsenfunktioksi • 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 mEka; • T2 mToka; • }; • Toimii mallina luokille, jotka ovat muuten samanlaisia, mutta joissa jotkin tyypit voivat 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) :mEka(eka), mToka(toka) //muodostaja • { • } • template <typename T1, typename T2> • T1 Pari<T1, T2>::annaEka() const • { • return mEka; • }
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
Jäsenfunktiomalli malliluokan jäsenenä • Luokkamallissa ja sen sisällä jäsenfunktiomallissa on nyt molemmissa aukijätettyjä parametreja: • Malli mallin sisällä • Mihin tahansa pariin voi nyt summata minkä tahansa tyyppisen toisen parin, kunhan parien alkiot voi laskea yhteen keskenään • template <typename T1, typename T2> • class Pari • { • . • . • . • template <typename T3, T4> • void summaa(const pari<T3, T4>& toinenPari) • . • . • . • }; template <typename T1, typename T2> template <typename T3, typename T4> Void Pari<T1, T2>::summaa(const Pari<T3,T2>& toinenPari) { mEka += toinenPari.annaEka(); mToka += toinenPari.annaToka(); }
Tyyppiparametreille asetetut vaatimukset • C++ mallin koodin on käännyttävä, kun tyyppiparametrien tilalle sijoitetaan todelliset tyypit • Esimerkiksi aiemmin määrittelemämme min –funktiomallin parametrien 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
Tyyppiparametreille asetetut vaatimukset • Koska tyyppiparametrien vaatimukset on siroteltu mallin koodin sekaan, tyyppiparametreihin kohdistuvat vaatimukset olisi hyvä dokumentoida ja kerätä yhteen paikkaan, josta mallin käyttäjä voi helposti ne tarkistaa • Jotta malli pysyisi mahdollisimman yleiskäyttöisenä, tulisi se suunnitella vaatimaan tyyppiparametreiltaan mahdollisimman vähän
Esimerkki • template <typename T> • T min(T p1, T p2) • { • T tulos; • if (p1 < p2) • { • tulos = p1; • } else { • tulos = p2; • } • return tulos • } • Aiemmin esitelty min-malli vaatii kopiomuodostimen ja sijoitusoperaattorin olemassaolon tyyppiparametreiltaan • Rajoittaa mallin käyttöä, koska kaikilla luokilla ei näitä ole • Alla on parempi versio min-mallista • Turhat sijoitukset on jätetty pois ja parametrit välitetään viitteinä, joten vaaditaan vain vertailuoperaattori • template <typename T> • const T& min(const T& p1, const T& p2) • { • if (p1 < p2) • { • return p1; • } else { • return p2; • } • }
Mallien oletusparametrit • Malleilla voi olla oletustyyppiparametreja, samaan tapaan kuin normaaleilla funktiolla oletusarvoja, esimerkiksi:template<typename T1=int, typename T2=T1> class Pari2 {…}; • Liukuluku-merkkijono yhdistelmä: Pari2<double,string> • Molemmat arvot liukulukuja: Pari2<double> • Molemmat arvot kokonaislukuja: Pari2<>
Mallien vakioparametrit • Malleissa voi jättää auki tiettyjä käännösaikaisia vakioita, jotka jäävät mallin parametreiksi, kelvollisia ovat: • Kokonaislukuvakiot ja enum tyyppiset arvot • Osoitin tai viite globaaliin olioon tai funktioon • Osoitin jäsenfunktioon tai muuttujaan • Jokainen eri vakioparametrin arvolla luotu luokka on oma erillinen luokkansa • Esimerkin eripituiset merkkijono-oliot (s1 ja s2) eivät siis kuulu samaan luokkaan, eikä niitä voi esimerkiksi sijoittaa toisiinsa • 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”); • Mjono<7> s2(”Lyhempi”);
Malliparametrit • Mallissa voi jättää auki myös toisen mallin • Esimerkissä on funktiomalli, jossa on jätetty auki yksi kaksi tyyppiparametria saava malli ja yksi tavallinen tyyppiparametri • Mallin summaa avulla voi nyt summata mistä tahansa kaksiparametrisesta luokkamallista instantuoituja olioita, kunhan tyyppiparametrit ovat samat ja se tarjoaa metodit annaEka ja annaToka • template<template<typename T1, • typename T2> class X, typename S> • S summaa(const X<S, S>& x) • { • return x.annaEka() + x.annaToka(); • } • void kayta() • { • Pari<int, int> p(1,2); • int tulos=summaa(p); • }
Mallien erikoistus Tyhjät kulmasulkeet merkiksi siitä, että erikoistumisessa ei ole enää auki jääneitä tyyppejä • On mahdollista määrittää erityinen versio (juuri tietyille tyypeille) mallista • Käyttäjän kannalta kaikki säilyy ennallaan, kääntäjä valitsee erikoistus koodin tarvittaessa • Esimerkissä on ideana vähentää muistinkulutusta tallentamalla bool tyyliset arvot yhteen char muuttujaan ekaJaToka, jota sitten käsitellään bittioperaattoreilla • Luokkamalleille on mahdollista tehdä myös osittaiserikoistus, jolloin vain osa tyypeistä sidotaan • 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
Mallin koodin sijoittelu • Koska mallin koodin on oltava kääntäjän tiedossa mallia käytettäessä, on mallin koodi kirjoitettava kokonaisuudessaan otsikkotiedostoon • Lisäämällä avainsana export ennen template sanaa sekä esittelyn että toteutuksen yhteyteen on periaatteessa mahdollista siirtää mallin koodi erilliseen .cpp tiedostoon • Valitettavasti suurin osa kääntäjistä ei tue tätä ominaisuutta ja siksi sitä ei tulisi käyttää
Missä mennään? • C++ mallit • Yleiskäyttöisyydestä • C++ mallit • Funktiomallit • Luokkamallit • Standard Template Library (STL) • Taustaa • Säiliöt • Iteraattorit • Algoritmit • Yhteenveto
STL (Standard Template Library) • Hyvä esimerkki C++ mallien käytöstä ja geneerisestä ohjelmoinnista • Määrittelee joukon tavallisimpia tietorakenteita ja niiden käyttöön tarkoitettuja algoritmeja • Tarkoituksena on uudelleenkä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) ovat olioita säiliöiden 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)
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 kohtaa 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 • Syntaksi vastaa osoitinaritmetiikkaa
Iteraattorikategoriat • 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.
Iteraattorit ja säiliöt • Iteraattorit liittyvät kiinteästi säiliöihin, joten STL:ssä iteraattoriluokat ja iteraattoreiden luominen on siirretty säiliöluokkien sisälle • Jokainen STL säiliöluokka määrittelee kaksi luokan sisäistä tyyppiä: iterator ja const_iterator • deque ja vector käyttävät hajasaanti iteraattoria • list, set, multiset, map ja multimap käyttävät kaksisuuntaista iteraattoria
Esimerkki, säiliön läpikäynti iteraattoreilla void nollaaAlkiot(vector<int>& vektori) { vector<int>::iterator i=vektori.begin(); //alkuun while (i!=vektori.end()) //toistetaan, kunnes ollaan lopussa { *i=0; //alkion arvoksi nolla ++i; //siirrytään seuraavaan alkioon } } void tulostaAlkiot(const vector<int>& vektori) { for(vector<int>::const_iterator i=vektori.begin(); i!=vektori.end(); ++i) //const_iterator, koska ei muuteta arvoa { cout << *i << ” ”; } cout << endl; }
Iteraattoreiden kelvollisuus • Jos säiliöön lisätään tai siitä poistetaan alkioita, osa olemassa olevista iteraattoreista (tai osoittimista/viitteistä) voi muuttua kelvottomiksi • Kelvotomalle iteraattorille ainoat sallitut operaatiot ovat tuhoaminen ja uuden arvon sijoitus • Vector: • Lisäys voi mitätöidä kaikki iteraattorit, osoittimet ja viitteet, jos joudutaan tekemään uusi muistinvaraus, muuten lisäyspaikan jälkeiset (jos vektorille on varattu riittävästi tilaa ennakkoon reserve jäsenfunktiolla) • Poisto mitätöi kaikki poistopaikan jälkeiset • Deque: • Lisäys alkuun tai loppuun mitätöi kaikki iteraattorit. Osoittimet ja viittaukset säilyvät. Lisäys keskelle mitätöi kaikki. • Poisto alusta tai lopusta mitätöi alkioon itseensä osoittavat osoittimet ja viitteet ja poistetun vasemmalla puolella olevan iteraattorin. Poisto keskeltä mitätöi kaikki. • List ja assosiatiiviset säiliöt: • Lisääminen ei mitätöi mitään • Poisto mitätöi alkioon itseensä osoittavat osoittimet ja viitteet ja poistetun vasemmalla puolella olevan iteraattorin.
Iteraattorisovittimet • Iteraattorisovittimet ovat erikoisiteraattoreita, jotka toimivat hieman eri tavalla kuin perusiteraattorit: • Käänteisiteraattorit (reverse iterator) • Tavallisen iteraattorin peilikuva, kaikki operaatiot toimivat nurinkurisesti, esim. ++ menee taaksepäin ja -- eteenpäin. • Jäsenfunktio rbegin palauttaa käänteisiteraattorin säiliön loppuun ja rend säiliön alkuun • Lisäysiteraattorit (insert iterator/inserter) • Voidaan käyttää lisäämään alkioita säiliöön • Virtaiteraattorit (stream iterator) • Lukevat ja kirjoittavat tiedostovirtoihin säiliöiden sijasta
STL:n säiliöt • STL:n säiliöt muistuttavat suuresti toisiaan • Rajapinnat ovat yhtenäisiä, eli suurin osa yhden säiliön jäsenfunktioista löytyy myös muista STL säiliöistä • Kun oppii käyttämään yhtä muiden käytön oppiminen on helppoa • 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: • Kopiomuodostin, 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 • Kaikissa sarjoissa annetaan sarjan alkioiden tyyppi mallin tyyppiparametrina. Esim: • vector<float>, deque<int> ja list<string>
Sarjat-”peräkkäissäiliöt” • STL:n sarjasäiliöt: • vector • STL vastine taulukoille. Tehokas jos tietorakennetta indeksoidaan paljon ja alkioita poistetaan tai lisätään vain loppuun. • deque • Kaksipäinen taulukko (pakka). Tehokkaampi kuin vektori, jos alkioiden lisäyksiä tai poistoja halutaan tehdä myös alkuun. Indeksointi hieman vektoria hitaampaa. • list • Kaksisuuntainen linkitetty lista. Nopea poistaa tai lisätä alkioita minne tahansa, indeksointi hidasta.
Vector esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // // front.cpp: Illustrates the vector::front and vector::back methods. // // Functions: // // front - Returns reference to first element. // // back - Returns reference to last element. // // push_back - Appends (inserts) an element to the end of a // container, allocating memory for it if necessary. // pop_back - Deletes the last element. // // size - Returns number of elements. // // begin - Returns an iterator to start traversal. // // end - Returns an iterator for the last element. // // erase - Deletes elements (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); //Erase the last element with pop_back() theVector.pop_back(); cout << endl << "After erasing 2 last elements, 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; } #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 2 last element, new last element is: 200 After erasing first element, new first element is: 200 Elements in vector: 1
Deque esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // deque.cpp : // // Functions: // // operator[] - Indexing // at – Same as above. // empty – Is the container empty // push_front – Inserts a new element at the beginning // pop_front – Removes the first element //////////////////////////////////////////////////////////////////////
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; //remove the first element a.pop_front(); //print out the contents print_contents (a,"a"); } //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; } #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(‘C’); a.push_front(‘B’); a.push_front(‘A’); 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 The contents of a : B C D
list esimerkki (1/3) Sample Code: ////////////////////////////////////////////////////////////////////// // // assign.cpp : Shows the various ways to assign and erase elements // from a list<T>. // // Functions: // // assign(p1,p2) – Replaces the contents of container with // p1 elements of p2 / or from p1 to p2 // clear – Empties the container // insert - Inserts an element // //////////////////////////////////////////////////////////////////////
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; //1 2 1 1 1 i=listAnother.begin(); i++; listAnother.insert(i,2); for (i = listAnother.begin(); i != listAnother.end(); ++i) cout << *i << " "; cout << endl; //empty the whole list listAnother.clear(); if (listAnother.empty()) cout << "All gone” << endl; } #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); //replace contents of listAnother with contents of listOne listAnother.assign(listOne.begin(), listOne.end());