Standardi mallikirjasto

Koko: px
Aloita esitys sivulta:

Download "Standardi mallikirjasto"

Transkriptio

1 Standardi mallikirjasto 20 Standardi mallikirjasto Kuten jo tiedätkin C++ sisältää laajan standardikirjaston, joka yksinkertaistaa monia ohjelmointitehtäviä. Tähän saakka näkemäsi lisäksi tämä kirjasto sisältää joukon malleja, jotka tarjoavat vielä suuremmat mahdollisuudet. Voit käyttää näitä malleja luodessasi standardimuotoisia säiliöitä, algoritmeja ja funktioita omille tietotyypeille, jos ne tarvitsevat tällaisia ominaisuuksia ohjelmissasi. Nämä mallit muodostavat yhdessä standardin mallikirjaston (STL). Tässä luvussa esittelemme STL:n perussäiliöt ja algoritmit, jotka toimivat näiden säiliöiden yhteydessä. Luvun aiheita ovat: Standardin mallikirjaston perusrakenne Miten luodaan ja käytetään peräkkäissäiliöitä vector<> ja list<> sekä assosiatiivisia säiliöitä map<> ja multimap<> vector<>- ja list<>-säiliöiden erikoiset talletustavat Miten iteraattorit yhdistävät säiliöt yksinkertaisiin C++:n standarditaulukoihin Miten STL käyttää erikoistumista suorituskyvyn parantamiseksi STL:n arkkitehtuurin perusteet Yksi STL:n tärkeä ominaisuus on se, että se antaa käyttöösi yleisiä työkaluja. Koska STL:n kaikki ominaisuudet ovat mallipohjaisia, ne toimivat lähes kaikkien mahdollisten tietotyyppien kanssa. STL:n avulla voit luoda luokan, joka määrittelee linkitetyn listan mille tahansa tietotyypillesi. Jos tarvitset linkitetyn listan Laatikko-olioille ja JalkapallonPelaaja-olioille, se ei ole mikään ongelma STL:lle. Kun sinun tarvitsee käsitellä olioitasi tavalliseen tapaan, STL voi auttaa tässäkin. Jos sinun tarvitsee lajitella Laatikko-oliosi nousevaan järjestykseen, STL voi muodostaa lajittelufunktion mille tahansa tietotyypille, jolle on määritelty vertailuoperaattori järjestyksen vertailua varten. Käyttääpä ohjelmasi millaisia olioita tahansa, on mahdollista, että STL voi auttaa niiden standardimuotoisessa järjestelyssä tai analysoinnissa. 823

2 C++ Ohjelmoijan käsikirja STL sisältää kolmenlaisia työkaluja - säiliöitä, iteraattoreita ja algoritmeja. Säiliöiden avulla voit järjestää mielivaltaisen tyyppisiä olioita - linkitetty lista on yksi esimerkki säiliöistä. STL:n säiliöt jakaantuvat kahteen luokkaan: Peräkkäissäiliöt (vector, deque ja list), jotka sisältävät tietyn tyyppisiä olioita peräkkäisessä muodossa. Assosiatiiviset säiliöt (map, multimap, set ja multiset) mahdollistavat tietyn tyyppisten olioiden tallettamisen ja hakemisen erityyppisten avainten avulla. Yksinkertainen esimerkki voisi olla Tyontekija-olio, jonka avaimena on henkilönumero. Tyontekija-olio voidaan hakea henkilönumeron perusteella. Iteraattorit ovat liima, joka pitää STL:n kasassa. Riippumatta käytettävästä säiliöstä, sen alkioita käsitellään lähes yksinomaan iteraattoreiden avulla ja iteraattoreita käytetään algoritmien soveltamisessa säiliön olioille. Iteraattori on fiksu osoitin. Luvussa 14 määrittelimme yksinkertaisen iteraattoriluokan - LtkPtr-luokan - AutoKuorma-säiliöllemme. Lisäksi STL sisältää iteraattoreita, joita voidaan käyttää olioiden siirtämiseen virtaan tai virrasta. Algoritmit tarjoavat laskenta- tai analysointimekanismeja iteraattoreiden avulla käytettäville oliojoukoille. Algoritmien esimerkkinä voidaan mainita säiliön olioiden haku ja lajittelu. Kaikille STL:n algoritmeille välitetään parametrinä iteraattori. Emme mitenkään voi käsitellä koko STL:ää tässä luvussa. Koko STL:n käsittely vaatisi oman kirjansa, joten käsittelemme tässä vain pienen osan mahdollisuuksistamme. Käsittelemme joitakin perustyökaluja ja katsomme, miten voimme niitä käyttää yksinkertaisten ongelmien ratkaisuun. Näin pääset alkuun ja saat itseluottamusta kokeilla itse joitakin muita STL:n ominaisuuksia. STL:n kaikkein tärkeimmät työkalut ovat säiliöt - tulet varmastikin käyttämään niitä kaikkein useimmin. Et voi kuitenkaan käyttää säiliöitä ilman iteraattoreita, joten sinun tulee ymmärtää myöskin ne hyvin. Algoritmit ovat STL:n kaikkein suurin työkalukokoelma. Suuri osa niistä ei ole kuitenkaan käyttökelpoisia suurelle osalle ohjelmia ja joidenkin käyttö on varsin erikoistunutta. Emme käsittele STL:n algoritmeja yksityiskohtaisesti tässä luvussa, mutta käytämme joitakin niistä. Seuraava taulukko antaa yleiskuvan tarjolla olevista vaihtoehdoista. Algoritmiperhe Perustuu Esimerkkejä 824 kuuluminen operator= copy, remove, replace sijoittaminen operator=swap random_shuffle, rotate, reverse, permute järjestys operator< sort, partition, merge, find, min, max etsintä ja valinta operator== unique, match, count, search, copy_if, remove_if, replace_if muuntaminen funktio-olio for_each, transform, generate, fill numeeriset funktio-olio inner_product, partial_sum, accumulate, adjacent_difference

3 Standardi mallikirjasto Nämä ovat vapaamuotoisia perheitä ja taulukossa on lueteltu vain pieni osa kaikista käytettävissä olevista algoritmeista. Algoritmit toimivat yleisesti ottaen säiliöihin talletettujen olioiden kanssa, mutta niiden ei tarvitse tietää mitään varsinaisesta käytetystä säiliöstä, koska olioita käsitellään aina iteraattoreiden avulla. Kaksi ensimmäistä ryhmää - kuuluminen ja sijoittaminen - hoitavat alkioiden siirtelyt. Tärkeimmät tässä käytettävät operaattorit ovat sijoitusoperaattori ja swap-operaattori (vaihtooperaattori). Järjestys-ryhmän algoritmit ovat kaikkein tärkeimmät. Lajittelu on itsessään äärettömän suuri tieteenala ja oikean tekniikan käyttö on suorituskyvyn kannalta kriittinen. STL:n tarjoamat lajittelualgoritmit ovat parhaimpien tunnettujen joukossa. Monet STL:n algoritmit ovat suhteellisen helppoja tehdä itsekin uudelleen, mutta lajittelualgoritmit ovat suuri poikkeus. Jos voit opetella vain yhden algoritmiperheen, opettele järjestys-algoritmit. Etsintä ja valinta -algoritmit ovat yksinkertaisia, mutta erittäin käteviä. Näitä algoritmeja käytetään tilanteissa, joissa alkiot ovat samat. Muuntaminen-ryhmän algoritmeja näkyy ohjelmissa harvemmin. Ellei tilanne ole hyvin yksinkertainen, on usein helpompaa kirjoittaa itse for-silmukka. STL:n toteutus näille algoritmeille sallii niiden optimoinnin tavalla, joka ei ole mahdollista omalle koodillesi. Harkitse näiden algoritmien käyttöä, kun hyvä suorituskyky on äärimmäisen tärkeää. Funktio-oliot, joihin muuntaminen- ja numeeriset -ryhmät perustuvat, ovat luokkaolioita, jotka uudelleenmäärittelevät funktion kutsu -operaattorin operator(), ja ovat erityisesti suunnitellut funktion välittämiseen parametrinä tehokkaammin kuin pelkän funktion osoittimen välittäminen tarjoaa. Edellä oleva taulukko jakaa algoritmit tärkeimmän toiminnon mukaan, mutta algoritmit voidaan jakaa ryhmiin myös muilla tavoilla. Usein algoritmit jaetaan muuttaviin ja ei-muuttaviin algoritmeihin. Näistä ensimmäiset ovat algoritmeja, jotka muuttavat säiliön sisältöä ja jälkimmäiset ovat algoritmeja, jotka eivät muuta säiliön sisältöä. STL:n otsikkotiedostot STL:n ominaisuudet esitellään seuraavissa otsikkotiedostoissa: Otsikkotiedosto <vector> <deque> <list> <map> Tarkoitus taulukkosäiliö, jossa on yksi pää taulukkosäiliö, jossa on kaksi päätä molempiin suuntiin linkitetty listasäiliö assosiatiivinen taulukkosäiliö Taulukko jatkuu seuraavalle sivulle 825

4 C++ Ohjelmoijan käsikirja Otsikkotiedosto <set> <queue> <stack> <iterator> <algorithm> <numeric> <functional> <bitset> <valarray> <complex> <utility> Tarkoitus järjestetty joukkosäiliö kaksipäinen jono (säiliömuunnin) pino (säiliömuunnin) iteraattorit ja säiliötuki yleiskäyttöiset algoritmit numeeriset algoritmit algoritmien funktio-olio -tuki bittisarjoja kuvaavat oliot numeeristen arvojen taulukot kompleksilukujen tuki funktio-olioiden tuki Jotkin STL:n osat on esitelty myös virtojen otsikkotiedostoissa - <iostream>, <istream>, <ostream>, <sstream>. Nämä tukevat STL:n algoritmien pohjalta syöttöä ja tulostusta. vector-säiliön käyttö Paras paikka aloittaa STL:n käsittely on vector-säiliö, joka on lähinnä C++:n tavallista taulukkoa. Itse asiassa vector-säiliötä voidaan käyttää lähes kaikissa paikoissa, joissa voidaan käyttää C++:n taulukkoakin. vector-säiliön käyttö on paljon helpompaa kuin C++:n taulukon - taulukon kohdalla sinun tulee itse huolehtia taulukon koosta, mutta vector-säiliön kohdalla kaikki tällainen työ hoidetaan automaattisesti. Kun olet tottunut käyttämään vector-säiliötä, saatat päättääkin, että et enää koskaan käytä tavallista taulukkoa. Yksi tehtävä kuitenkin vaatii yhä C++:n taulukon käyttöä: staattisten alkuarvojen esittely: string nimet[] = Alf, Bjarne, Zaphod ; Suurissa ohjelmissa tällaisten määrittelyjen määrä kannattaa pitää mahdollisimman pienenä. On usein parempi hakea kiinteät alkuarvot ohjelman resurssitiedoista suoritusaikana. 826 Kokeile itse - vector<> vs. C++:n taulukko Katsotaan pikaisesti int-taulukkoa ja int-tyyppistä vector-säiliötä. Huomaamme, että ne toimivat suurelta osin samaan tapaan. // Esimerkki 20.1 Taulukon ja vector-säiliön pikavertailu #include <iostream> #include <vector> using namespace std;

5 Standardi mallikirjasto int main() int a[10]; vector<int> v(10); // C++:n taulukon esittely // vastaava STL:n vector-säiliön esittely cout << "10-alkioisen taulukon koko: " << sizeof a << endl; cout << "10-alkioisen vector-säiliön koko: " << sizeof v << endl; for (int i = 0; i < 10; ++i) a[i] = v[i] = i; int a_summa = 0, v_summa = 0; for (int i = 0; i < 10; ++i) a_summa += a[i]; v_summa += v[i]; cout << "taulukon 10 alkion summa on: " << a_summa << endl; cout << "vector-säiliön 10 alkion summa on: " << v_summa << endl; return 0; Kun suoritat tämän ohjelman, se tulostaa: 10-alkioisen taulukon koko: alkioisen vector-säiliön koko: 16 taulukon 10 alkion summa on: 45 vector-säiliön 10 alkion summa on: 45 Kuinka se toimii Kaikki STL:n oliot ovat osa standardikirjastoa, joten ne on määritelty nimiavaruudessa std. Tarvitsemiasi STL:n ominaisuuksia vastaavat otsikkotiedostot tulee tietysti sisällyttää lähdetekstiin ennen ominaisuuksien käyttöä. Jos haluat käyttää nimiä ilman std:n kirjoittamista aina nimen yhteyteen, voit ottaa std-nimiavaruuden käyttöön using-komennolla: #include <vector> using namespace std; Ainut ero taulukon ja vector-säiliön käytössä on niiden esittelyissä: int a[10]; vector<int> v(10); // C++:n taulukon esittely // vastaava STL:n vector-säiliön esittely C++:n taulukko on sisäänrakennettu tyyppi, joka esitellään []-syntaksilla. Toisaalta vectorsäiliö on mallina toteutettu standardikirjaston tyyppi. Malliparametri määrittelee vector-säiliön sisältämien olioiden tyypin. Tässä olemme esitelleet vector-säiliön, joka tallettaa int-tyyppisiä olioita. 827

6 C++ Ohjelmoijan käsikirja Tässä esimerkissä taulukko a ja vector-säiliö v ovat molemmat automaattisia muuttujia. Taulukon kohdalla tämä tarkoittaa sitä, että 10 kokonaisluvun tila varataan automaattisesta muistista. Minun tietokoneessani tyyppi int vie 4 tavua, joten taulukko a vie tilaa 40 tavua, kuten tulostuksesta huomaat. vector-säiliön olio v on myöskin automaattisessa näkyvyysalueessa, mutta (toisin kuin taulukko) se ei talleta sisältöään automaattiseen muistiin. Se varaa sisäisesti muistia alkioita varten vapaasta muistista. Aluksi se varaa talletettujen alkioiden verran muistia - mutta voit lisätä siihen niin monta alkiota kuin haluat. vector-säiliö huolehtii sisäisesti kaikista muistinvarauksen ongelmista (tietysti suhteessa vapaaseen muistiisi!). vector-olion ilmoitettu koko ei sisällä alkioita varten tarvittua muistia. Tästä syystä tällaista oliota kutsutaan usein kahvaksi. Se sisältää ainoastaan tarvittavan määrän muistia, jotta vectorsäiliö voi käsitellä alkioiden muistia. Kuten tulostuksesta huomaat, vector-olion sizeofoperaattorin palauttama koko on vain 16 tavua. v:n todellinen sisältö - muisti, joka tarvitaan sen tallettamien olioiden arvojen tallettamiseen - on varattu dynaamisesti, ja vie lisäksi 40 tavua vapaasta muistista. Taulukko vie siis selvästikin vähemmän muistia. vector-olion viemä lisätila on kuitenkin varsin pieni. Taulukon ongelma on siinä, että muisti on varattu ohjelmapinosta ja sen koko ja alkioiden lukumäärä on määrätty kiinteäksi jo käännösaikana. vector-olion viemän lisätilan vastapainona on kaksi korvaavaa hyötyä: ensiksikin, muistinhallinta on automaattinen ja toiseksi, voit lisätä alkioita vector-säiliöön aina tarvittaessa. 828 vector-säiliön perusoperaatiot vector-säiliön kanssa työskentely on huomattavasti joustavampaa kuin tavallisen taulukon kohdalla. Voit esimerkiksi lisätä alkioita vector-olion loppuun tai keskelle ja voit poistaa alkioita. Tarvittava muisti hallitaan aina automaattisesti. Saat aina selville vector-oliossa olevien alkioiden lukumäärän (kutsumalla sen size()-jäsenfunktiota) ja sen empty()-jäsenfunktio palauttaa arvon true, jos vector-oliossa ei ole lainkaan alkioita. Perusoperaatiot alkion lisäämiseksi tai poistamiseksi ovat: insert() Lisää yhden tai useamman alkion push_back() Lisää parametrinään saamansa oliot vector-olion perään erase() Poistaa yhden tai useamman alkion clear() Poistaa kaikki alkiot Jotta ymmärrät näiden alkioiden toiminnan, kokeillaan niitä esimerkin avulla. Meidän täytyy jo tässä esimerkissä käyttää iteraattoreita, mutta olet nähnyt iteraattoreita toiminnassa jo luvussa 14, joten tämän ei pitäisi olla kovinkaan pelottavaa. vector-olion begin()-jäsen palauttaa iteraattorin, joka osoittaa ensimmäiseen alkioon ja end()-jäsen palauttaa iteraattorin, joka osoittaa osoittimen, joka osoittaa yhden alkion verran viimeisen alkion ohi. Voit käyttää näitä iteraattoreita käydessäsi vector-olion alkioita läpi - kasvattamalla begin()-jäsenen palauttamaa iteraattoria, kunnes se on sama kuin end()-jäsenen palauttama iteraattori. Voit tietysti käyttää myös []-operaattoria; voit vapaasti päättää kumpaa tapaa käytät.

7 Standardi mallikirjasto Kokeile itse - Lyhyt johdatus vector-säiliöön! Tämä ohjelma käy läpi juuri käsittelemämme yksinkertaiset vector-säiliön operaatiot. Kokeilemme alkioiden lisäämistä ja poistamista vector-oliosta ja erilaisia tapoja tutkia vectorolion sisältöä. Huomaa, että sinun käännösympäristösi STL:n toteutus ei ehkä toimi tämän esimerkin kanssa täsmälleen kirjoitetulla tavalla, koska tällä hetkellä STL:n toteutukset vaihtelevat suurestikin. ANSI/ISO-standardin pitäisi muuttaa tämä tilanne, koska yhä enemmän ja enemmän C++:n toteutuksia päivitetään sen mukaiseksi. Jos esimerkki ei toimi, sinun tulee tarkistaa kirjaston dokumenteista, mikä ohjelman osa tulee muuttaa. Tässä esimerkissä on kirjoitettu funktio nayta_jarjestys(), joka havainnollistaa C++:n osoittimien ja vector-säiliön iteraattoreiden yhtäläisyyttä. tutki_vector()-funktio, joka tulostaa vector-säiliön sisällön ohjelman eri kohdissa, kutsuu nayta_jarjetys()-funktiota. // Esimerkki 20.2 vector<>-säiliön käsittely #include <iostream> #include <vector> #include <algorithm> // STL:n vector-säiliö // copy()-funktio using namespace std; // Näyttää alkiot järjestyksessä void nayta_jarjestys(const int* ensim, const int* viim) cout << " "; copy(ensim, viim, ostream_iterator<int>(cout, " ")); cout << "" << endl; // Näyttää vector-olion sisällön void tutki_vector(const vector<int>& v) cout << " vector-oliolla on " << v.size() << " alkiota: "; nayta_jarjestys(v.begin(), v.end()); int main() vector<int> v; cout << "uusi vektori luotu" << endl; tutki_vector(v); // Luodaan tyhjä vektori cout << "täytetään vektori taulukosta" << endl; int arvot[] = 1, 3, 7, 5; v.insert(v.end(), arvot+1, arvot+3); // Lisätään kaksi alkiota tutki_vector(v); cout << "lisätään arvo 5" << endl; v.push_back(5); tutki_vector(v); // Lisätään alkio loppuun 829

8 C++ Ohjelmoijan käsikirja cout << "poistetaan alkio siirtymässä 1" << endl; v.erase(&v[1]); // Poistetaan toinen alkio tutki_vector(v); cout << "lisätään alkio 4 siirtymään 1" << endl; v.insert (v.begin()+1, 4); // Lisätään alkio tutki_vector(v); cout << "poistetaan kaikki alkiot" << endl; v.clear(); tutki_vector(v); // Poistetaan kaikki alkiot return 0; Tulostus näyttää vector-olion sisällön jokaisen vector-operaation jälkeen: uusi vektori luotu vector-oliolla on 0 alkiota: täytetään vektori taulukosta vector-oliolla on 2 alkiota: 3 7 lisätään arvo 5 vector-oliolla on 3 alkiota: poistetaan alkio siirtymässä 1 vector-oliolla on 2 alkiota: 3 5 lisätään alkio 4 siirtymään 1 vector-oliolla on 3 alkiota: poistetaan kaikki alkiot vector-oliolla on 0 alkiota: Kuten huomaat, vector-säiliö on hyvä pitämään huolta sisällöstään. Tavallisen taulukon kohdalla kaikkien näiden operaatioiden toteuttaminen olisi ollut huomattavasti hankalampaa. Kuinka se toimii Koodi alkaa kahdella #include-komennolla, jotka sisällyttävät kaksi STL:n otsikkotiedostoa: #include <vector> // STL:n vector-säiliö #include <algorithm> // copy()-funktio 830 Otsikkotiedosto vector määrittelee vector-säiliön ja kaikki sen sisäänrakennetut operaatiot. STL mahdollistaa, että voit tehdä vector-oliolla paljon muutakin, kuin mikä kuuluu suoraan vectormallin ilmentymään. Otsikkotiedosto algorithm sisältää sitten loput tarvittavat herkut. Seuraavaksi määrittelemme nayta_jarjestys()-funktion, joka tulostaa alkiot järjestyksessä: void nayta_jarjestys(const int* ensim, const int* viim) cout << " "; copy(ensim, viim, ostream_iterator<int>(cout, " ")); cout << "" << endl;

9 Standardi mallikirjasto Tämä funktio käyttää STL:n copy()-algoritmia, joka tulostaa cout-virtaan kahden osoittimen osoittaman välin. Näet ohjelman tulostuksesta, että tämä myöskin toimii. Palaamme ostream_iterator<>-iteraattoriin myöhemmin, kun käsittelemme tulostusiteraattoreita tarkemmin. Funktio copy() on ainut funktio tässä esimerkissä, joka ei kuulu itse vector-malliin. Sen kaksi ensimmäistä parametriä määrittelevät lähteen välin. Kopiointi alkaa ensim-alkiosta ja jatkuu viim-alkioon saakka. viim-alkiota ei kuitenkaan kopioida. copy-funktio hyväksyy minkä tahansa iteraattorin kahden ensimmäisen parametrinsä arvoksi - tässä olemme käyttäneet tavallisia osoittimia. Kolmas parametri on kohdesijainti, joka määritellään myöskin iteraattorin avulla. Tässä esimerkissä käytämme tulostusiteraattoria, joka luodaan lausekkeella ostream_iterator<int>(cout, " "). STL sallii tällaisen joustavan määrittelyn. copy()-algoritmi sijoittaa jokaisen lähdealkion kohdeolioomme tietämättä tarkasti, minkälaista kohdetta se käsittelee - se vain sijoittaa kopioitavat oliot kolmantena parametrinä olevan iteraattorin mukaan ja olettaa, että iteraattori tietää, minne olio tulee sijoittaa. Tässä tapauksessa kohdeolio tulostaa siihen kopioidut arvot standardiin tulostusvirtaan cout. Voit välittää monia erilaisia iteraattoreita STL:n algoritmeille ja ne toimivat sen mukaisesti. Iteraattori voi olla kuten osoitin, joka lukee ja kirjoittaa arvoja muistiin, tai se voi olla monimutkaisempi olio, joka lukee tai kirjoittaa arvot virtaan. copy()- funktio toimii yhtä hyvin kopioitaessa olio vector-säiliöstä virtaan tai kopioitaessa tietynlainen olio säiliöstä toiseen. Funktio tutki_vector() kutsuu nayta_jarjetys()-funktiotamme: void tutki_vector(const vector<int>& v) cout << " vector-oliolla on " << v.size() << " alkiota: "; nayta_jarjestys(v.begin(), v.end());! Tämä funktio havainnollistaa yhtä vector-säiliön varsin hienoa ominaisuutta. Huomaa, että välitämme vector-olion vain yhtenä parametrinä. Meidän ei tarvitse välittää toista parametriä, joka kertoisi vector-säiliön sisältämien alkioiden lukumäärän - eikä kuinka monta alkiota vectorsäiliöön voi sijoittaa. Tulostamme vector-olion alkioiden lukumäärän kutsumalla sen size()- jäsentä. vector-olio tietää aina, montako alkiota siihen on talletettu. Huomaa, että saamme vector-olion ensimmäisen ja viimeisen alkion iteraattorin selville begin()- ja end()-jäsenillä ja välitämme ne nayta_jarjestys()-funktiolle. Nämä iteraattorit muunnetaan automaattisesti nayta_jarjestys()-funktion parametrien tyyppisiksi, eli int*-tyyppisiksi. Vector-iteraattorit ovat lähes identtiset tavallisiin C++:n osoittimiin. Itse asiassa, monet STL:n toteutukset määrittelevät vector<t>::iterator-tyypin tavalliseksi C++:n T*-tyyppiseksi osoittimeksi. Juuri tämä tekee vector-säiliöstä niin hyvän tavallisten taulukoiden korvaajan. Lähes kaikki, mitä olet tehnyt osoittimien kanssa voit tehdä myös vector-säiliön kanssa. 831

10 C++ Ohjelmoijan käsikirja Kuten olemme jo kertoneet, v.begin() osoittaa ensimmäiseen alkioon. Pääset käsiksi ensimmäisen alkion sisältöön kirjoittamalla lausekkeen *v.begin(), v[0] tai v.front(). Mitä näistä käytät, riippuu käyttötilanteesta. Myöhemmin tässä luvussa näemme tilanteita, joissa jokin näistä on luonnollisempi vaihtoehto kuin muut. Huomaa, että et voi käyttää osoittimen end() osoittamaa arvoa - se osoittaa aina yhden osoitteen säiliön viimeisen alkion yli. Osoitinta end() kutsutaan yleensä lopun yli -osoittimeksi. Eli *v.begin() on sallittu, mutta varsin samanlainen lauseke *v.end() on huono idea. Sen sijaan *(v.end()-1) on sallittu ja hyödyllinen tapa päästä käsiksi viimeiseen alkioon. Viimeiseen alkioon pääset käsiksi myös lausekkeilla *(v.begin()+v.size()-1), v[v.size()-1] tai yksinkertaisesti v.back(). Ennen kuin yrität käsitellä alkiota, sinun tulee varmistaa, että viimeinen alkio on olemassa. Nopea tapa tarkistaa tämä on kutsua jäsenfunktiota v.empty(), joka palauttaa arvon true, kun v ei sisällä ainuttakaan alkiota. Ohjelmamme käyttää vector-mallin ilmentymää vector<int>, joten esittelemme int-tyyppisiä arvoja tallettavan vector-säiliön seuraavasti: vector<int> v; // Luodaan tyhjä vektori Tässä esittelemme v:n mallin vector<int>-ilmentymänä kutsumalla oletusmuodostinfunktiota. Näemme tulostuksesta, että uusi säiliömme on aluksi tyhjä. Käytössäsi on tietysti myös muunlaisia muodostinfunktioita. Jos esimerkiksi haluaisit säiliön 50 double-tyyppiselle arvolle, voisit esitellä vector-säiliön data seuraavasti: vector<double> data(50); Uuteen säiliöömme voimme tallettaa arvoja monella tavalla. Yksi kaikkein tehokkaimmista on insert()-jäsenfunktio, joka kopioi kokonaisen sarjan arvoja: cout << "täytetään vektori taulukosta" << endl; int arvot[] = 1, 3, 7, 5; v.insert(v.end(), arvot+1, arvot+3); // Lisätään kaksi alkiota tutki_vector(v); 832 Tästä huomaa jälleen, että STL mielellään määrittelee sarjat kahden osoittimen väliin jäävällä välillä. Funktion insert() ensimmäinen parametri, v.end(), on osoitin sijaintikohtaan, johon haluamme alkiot sijoittaa. Huomaa, että käytämme tässä lopun yli -osoittavaa osoitinta - sen käyttö on sallittu, kun yhdistät arvoja. Tästä onkin helppo muistaa, mitä lopun yli -osoitin tarkoittaa: se on sijaintikohta, johon seuraava lisättävä arvo sijoitetaan. Eli voit käyttää end()-osoitinta uusien alkioiden lisäämiseen - mutta älä yritä käsitellä sen osoittamaa arvoa. insert()-funktion toinen ja kolmas parametri, eli lausekkeet arvot-1 ja arvot+3, määrittelevät yhdessä lisättävien alkioiden välin. Tässä lähteemme on taulukko arvot ja kopioimme tässä toisen ja kolmannen alkion - eli alkaen kohdasta arvot+1 ja päättyen alkiota arvot+3 edeltävään alkioon.

11 Standardi mallikirjasto! Välit määritellään tavallisesti tällä tavalla STL:ssä - väliä, joka sisältää ensimmäisen alkion, mutta ei viimeistä, kutsutaan puoliavoimeksi väliksi. Tällainen merkitään [alku, loppu), jossa sulkeet osoittavat, että väli on suljettu vasemmasta päästä, mutta avoin oikeasta päästä. Eli väli sisältää kohdan alku, mutta ei kohtaa loppu. Kun määrittelet [alku, loppu) välin, kuten esimerkiksi arvoilla arvot+1 ja arvot+3, varmista, että loppu on suurempi kuin alku - ja että molemmat osoittimet on liitetty samaan lähdeolioon. Nämä ovat varsin yleisiä - ja vaikeasti havaittavia - virheitä. Palataan esimerkin 20.2 käsittelyyn. Lisäämme vector-säiliöön alkion push_back()-jäsenellä: v.push_back(5); // Lisätään alkio loppuun Tämä lisää arvon 5 uudeksi alkioksi v-olion perään. Miksi tätä funktiota ei kutsuta yksinkertaisesti nimellä append()? Jos push saa sinut ajattelemaan pinoja, olet oikeilla jäljillä. Itse asiassa STL sisältää pinomuuntimen (stack adaptor), jolla voit muuntaa vector-säiliön pinoksi. Emme käsittele STL:n muuntimia tässä yhteydessä, saat lisätietoa STL:n dokumenteistasi. Monet STL:n nimet viittaavat siihen, että säiliöt voidaan muuntaa toimimaan toisella tavalla, kuten stack ja queue. Seuraavaksi testaamme erase()- ja insert()-funktioita: v.erase(&v[1]); // Poistetaan toinen alkio erase()- ja insert()-operaatioihin kuuluu alkioiden siirtelyä, jotta lisättäville alkioille saadaan tilaa tai poistettavan alkion aukko poistetaan. Sisäisesti vector-säiliö kopioi alkioita, jotta ne pysyvät oikeassa järjestyksessä. Tämä voi olla hyvin hidas operaatio, kuten näemme, kun käsittelemme vector-säiliön muistinhallintaa. Koska vector-säiliö on malli, se voi sisältää myös luokkaolioita, ja tällöin erase()-funktion tulee kutsua jokaisen poistettavan olion tuhoajafunktiota. Tämä lause poistaa toisen alkion vector-säiliöstä v. Se käyttää erase()-funktion yksinkertaisinta muotoa, jolle välitetään vain yksi iteraattori, joka määrittelee yhden poistettavan alkion. Osoitintyyppinen parametri muunnetaan automaattisesti iteraattoriksi. Sen sijaan, että olisimme käyttäneet begin()- ja end()-jäseniä iteraattoreiden muodostuksessa, olemme käyttäneet taulukkomuotoa. Tällä havainnollistamme sitä, että v teeskentelee olevansa taulukko. Lauseke &v[1] viittaa v:n toisen alkion osoitteeseen. Tämän lauseen tuloksena toinen alkio, jonka arvo on 7, poistetaan vector-säiliöstä v. Seuraavaksi lisäämme uuden alkion lauseella: v.insert (v.begin()+1, 4); // Lisätään alkio 833

12 C++ Ohjelmoijan käsikirja! Olemme jo kerran käyttäneet insert()-funktiota. Tällöin sillä oli kaksi iteraattoriparametriä. Tässä muodossa sillä on kaksi parametriä, joista ensimmäinen on iteraattori ja toinen kokonaisluku. Määrittelemme sijaintikohdan lausekkeella v.begin()+1 ja lisäämme uuden alkion - arvon 4 - tähän sijaintikohtaan. Tässä sijaintikohdassa nyt oleva alkio ja kaikki sitä seuraavat alkiot tulee ensin siirtää eteenpäin, jotta uusi arvo mahtuu väliin. Nytkin vector-olio varaa uuden tilan ja kopioi alkiot automaattisesti. Tämä automaattinen kopiointi on varsin miellyttävää. Ohjelmasi suoritus kuitenkin hidastuu merkittävästi, jos vector-olio on suuri ja lisäät tai poistat alkioita olion alkupäästä. STL sisältää toisen säiliön, listan list<>, joka tekee nämä kaikki samat asiat, mutta mitään kopiointia ei tarvitse tehdä. Näemme listan toiminnassa varsin pian. Lopuksi poistamme kaikki alkiot lauseella: v.clear(); // Poistetaan kaikki alkiot Voimme käyttää clear()-jäsentä, jos haluamme poistaa kaikki alkiot eksplisiittisesti. Tämä ei ole kuitenkaan tarpeen. Kun vector-olion v näkyvyysalue päättyy (lohkon lopussa), v ja sen sisältö poistetaan automaattisesti v:n tuhoajafunktion toimesta. v.clear() on itse asiassa lyhyempi muoto lausekkeesta v.erase(v.begin(), v.end()). vector-säiliön käyttö taulukko-operaatioissa Katsotaan seuraavaksi esimerkkiä, johon törmäsimme ensimmäisen kerran luvussa 8, kun käsittelimme funktioita. Seuraavassa on keskiarvo()-funktio esimerkistä 8.5, joka on kiinnostaja siitä syystä, että sille välitetään parametrinä taulukko[]: // Funktio, joka laskee keskiarvon double keskiarvo(double taulukko[], int lkm) double summa = 0.0; // Tähän lasketaan summa for(int i = 0 ; i < lkm ; i++) summa += taulukko[i]; // Summataan taulukon alkiot return summa / lkm; // Palautetaan keskiarvo 834 Ennen kuin STL oli olemassa, C- ja C++-ohjelmat kirjoitettiin tällä tavalla. Vaikka olemmekin käsitelleet runsaasti uusia asioita luvun 8 jälkeen, muistat varmasti vieläkin, että double taulukko[] ja double* taulukko tarkoittavat tässä yhteydessä aivan samaa - eli keskiarvo()-funktion ensimmäinen parametri on osoitin. Koska taulukko ei tiedä, montako alkiota siinä on, meidän tulee aina välittää myös toinen parametri, joka kertoo taulukon alkioiden lukumäärän (joskus meidän tarvitsee välittää myöskin taulukon kapasiteetti). Katsotaan uudelleen esimerkkimme 20.2 nayta_jarjestys()-funktiotamme. Tälle funktiolle välitetään ensimmäisenä parametrinä myöskin osoitin, mutta käytetään vain erilaista muotoa. Toinen parametri on myöskin osoitin:

13 Standardi mallikirjasto void nayta_jarjestys(const int* ensim, const int* viim) cout << " "; copy(ensim, viim, ostream_iterator<int>(cout, " ")); cout << "" << endl; Koska osoittimet vastaavat iteraattoreita, voimme ajatella parametrien olevan tässä iteraattoreita. Väli ensim - viim on jälleen puoliavoin väli - ensim otetaan mukaan, mutta viim ei oteta. Tämä on kuitenkin täysin eri kuin kahden indeksin käyttäminen, koska välin olioiden tyyppi on iteraattoreiden yhteydessä implisiittinen. Vaikka vector-säiliön ja välin käyttäminen on eri taulukkoon ja muuttujaan lkm verrattuna, saamme kuitenkin helposti selville käsiteltävien alkioiden lukumäärän: int lkm = viim - ensim; Eli miksi STL käyttää välejä, eikä taulukoita ja lukumääriä? Tähän on useita syitä. Ensinnäkin, jos taulukoita käytetään runsaasti, osoittimien käyttö on miellyttävämpää. Toiseksi, se on myös huomattavasti tehokkaampaa. Kun näet lauseen, jossa on moniulotteinen taulukko, kääntäjä joutuu tekemään runsaasti töitä lauseen kohdalla. Tutkitaan esimerkiksi seuraavaa koodia: // kun kirjoitat seuraavaa double ilmanpaine[200][200][200]; double paine_keskella = ilmanpaine[99][100][101]; Kääntäjä löytää alkion ilmanpaine[99][100][101] suorittamalla seuraavat lauseet: // kääntäjä tekee todellisuudessa seuraavaa double* apu = ilmanpaine + 99 * sizeof (ilmanpaine[0]) * sizeof (ilmanpaine[0][0]) + 101; double paine_keskella = *apu; Kuten huomaat, kääntäjä tekee kaiken tämän laskennan vain muuntaakseen kaikki takaisin osoitinlausekkeeksi. Hyvä kääntäjä tekee lisäksi paljon yksinkertaistaakseen tätä aina, kun se vain on mahdollista - mutta silti kaikki nämä laskennat ja lisäykset vaativat aikaa. Yksinkertainen totuus on, että osoittimet ovat yksinkertaisempia kuin taulukot ja lisäksi, kun muistamme, että iteraattori on olio, joka toimii osoittimen tapaan, niin tässä on syy siihen, miksi STL:ssä käytetään yleensä välejä, jotka on määritelty iteraattoriparin avulla. Keskiarvon laskeminen iteraattoreiden avulla Voimme määritellä mallin keskiarvo()-funktiolle, joka käyttää iteraattoreita taulukkojen sijaan. Mallin koodi olisi tässä tapauksessa seuraava: template <typename Iter> double keskiarvo(iter a, Iter loppu) double summa = 0.0; // Tähän lasketaan summa 835

14 C++ Ohjelmoijan käsikirja int lkm = 0 ; for( ; a!= loppu ; lkm++) summa += *a++; // Summataan taulukon alkiot // Palautetaan keskiarvo return summa / lkm; Huomaa, kuinka vähän suoritamme operaatioita parametrimuuttujalle a: a!= loppu tarkistaa viimeisen arvon *a haetaan nykyinen arvo a++ siirrytään seuraavaan arvoon Kaksi viimeistä operaatiota on yhdistetty yhdeksi lausekkeeksi *a++. Huomaat varmastikin jo nyt, että tällainen lauseke on yksi kaikkein useimmin C++:ssa käytetty lauseke. Olemme koodanneet mallin siten, että operaation * ja jälkiliitteellisen ++ täytyy toimia tyypille Iter ja näin on sekä osoittimien että iteraattoreiden kohdalla. Iteraattoreiden käyttö yksinkertaisti koodia varsin paljon. Kokeillaan sitä esimerkin avulla. Kokeile itse - Keskiarvon laskeminen iteraattoreiden avulla Seuraavassa on ohjelma, joka laskee keskiarvot mallimme avulla: // Esimerkki 20.3 Keskiarvon laskeminen iteraattoreiden avulla #include <iostream> #include <vector> using namespace std; template <typename Iter> double keskiarvo(iter a, Iter loppu) double summa = 0.0; int lkm = 0 ; for( ; a!= loppu ; lkm++) summa += *a++; // Tähän lasketaan summa // Summataan taulukon alkiot return summa / lkm; // Palautetaan keskiarvo int main() double lampotila[] = 10.5, 20.0, 8.5 ; cout << "taulukon keskiarvo = " << keskiarvo(lampotila, lampotila + (sizeof lampotila/sizeof lampotila[0])) << endl; vector<int> aurinkoinen; aurinkoinen.push_back(7); aurinkoinen.push_back(12); aurinkoinen.push_back(15); cout << aurinkoinen.size() << " kuukautta" << endl; cout << "aurinkoisten päivien lukumäärän keskiarvo: "; cout << keskiarvo(aurinkoinen.begin(), aurinkoinen.end()) << endl; return 0; 836

15 Standardi mallikirjasto Tämän ohjelman tulostus on seuraava: taulukon keskiarvo = 13 3 kuukautta aurinkoisten päivien lukumäärän keskiarvo: Kuinka se toimii Mallien hyvänä puolena on se, että saat niin monta versiota kuin tarvitset, mutta joudut määrittelemään vain yhden. Tämä esimerkki muodostaa kaksi ilmentymää mallifunktiosta keskiarvo. Ensimmäinen keskiarvo-mallin ilmentymä on tavalliseen C++:n taulukkoon sijoitetuille arvoille: double lampotila[] = 10.5, 20.0, 8.5 ; cout << "taulukon keskiarvo = " << keskiarvo(lampotila, lampotila + (sizeof lampotila/sizeof lampotila[0])) << endl; Mallin ilmentymän tyyppi määritellään automaattisesti funktion parametreistä. Toisen parametrin tulee olla osoitin mukaan haluamaamme viimeistä alkiota seuraavaan alkioon - laskemme tämän lisäämällä taulukon alkioiden lukumäärän osoittimeen lampotila. Toinen keskiarvo-mallin ilmentymä on vector-säiliöön talletetuille int-tyyppisille arvoille. Ensin esittelemme vector<int>-olion aurinkoinen: vector<int> aurinkoinen; Ilmentymä luodaan lauseessa: cout << keskiarvo(aurinkoinen.begin(), aurinkoinen.end()) << endl; aurinkoinen.begin() ja aurinkoinen.end() -funktioiden palauttamat paluuarvot ovat tyyppiä vector<int>::iterator - tätä tyyppiä käytetään mallin toisen version ilmentymän muodostamisessa. Eli mallifunktion keskiarvo() kahden ilmentymän allekirjoitukset näyttävät seuraavilta: double keskiarvo<double*>(double* a, double* loppu); double keskiarvo<vector<int>::iterator>(vector<int>::iterator a, vector<int>::iterator loppu); Kuten olemme maininneet, jotkin STL:n vector-säiliön toteutukset käyttävät osoittimia (kuten int*) iteraattoreina. Tällöin toisen ilmentymän allekirjoitus näyttää seuraavalta: double keskiarvo<int*>(int* a, int* loppu); Kaikki STL:n toteutukset eivät kuitenkaan näin tee eikä muutenkaan ole korrektia olettaa, että voimme käyttää lyhyempää muotoa. Jotta ymmärrät, miksi olemme kirjoittaneet ::iterator-osan tyyppiin, vilkaistaan, miltä vectormallin määrittely näyttäisi: template <typename Arvo> 837

16 C++ Ohjelmoijan käsikirja class vector public: typedef Arvo* iterator; // paljon lisää koodia... ; Kun ilmennämme vector-mallin ilmentymän vector<int>, mallista tulee jotain seuraavankaltaista: class vector<int> public: typedef int* iterator; ; Eli vector<int>::iterator on vain tapa viitata siihen tyyppiin, jota vector-mallin ilmentymässä on käytetty iteraattorin toteutuksessa. Nimi iterator kuvaa tätä tyyppiä. Kuten tulostuksesta huomaat, keskiarvon laskenta sujuu kuten odotimmekin. Funktiomme toimii niin vector-säiliön alkioiden kuin tavallisen taulukonkin yhteydessä. Mallitaulukko-vaihtoehto Olisimme voineet hyödyntää malleja myöskin taulukkomuotoisessa ratkaisussa. Kokeile itse - Keskiarvon laskeminen Mallitaulukon avulla Tässä on vaihtoehtoinen versio esimerkille 20.3: // Esimerkki 20.4 Keskiarvon laskeminen mallitaulukon avulla #include <iostream> #include <vector> using namespace std; template <typename Taulukko> double keskiarvo (Taulukko a, long lkm) // Taulukko voi olla osoitin tai iteraattori double summa = 0.0; for (long i = 0; i < lkm; ++i) summa += a[i]; return summa/lkm; // tapahtuu virhetilanne, jos lkm==0 int main() double lampotila[] = 10.5, 20.0, 8.5 ; // Toinen parametri on nyt lukumäärä cout << "taulukon keskiarvo = " << keskiarvo(lampotila, (sizeof lampotila/sizeof lampotila[0])) << endl; 838

17 Standardi mallikirjasto vector<int> aurinkoinen; aurinkoinen.push_back(7); aurinkoinen.push_back(12); aurinkoinen.push_back(15); cout << aurinkoinen.size() << " kuukautta" << endl; cout << "aurinkoisten päivien lukumäärän keskiarvo: "; cout << keskiarvo(aurinkoinen.begin(), aurinkoinen.end() - aurinkoinen.begin()) << endl; return 0; Tämän ohjelman tulostus on aivan sama kuin esimerkissä Koodi näyttää jopa selkeämmältä! Huomaa, kuinka nyt tarvitsemme ainoastaan yhden operaation alkiolle a, nimittäin a.operator[](long). Tästä huomaamme heti, että indeksointioperaattorin parametri on long-tyyppinen - se kuvaa indeksin arvoa. Tässä versiossa meidän täytyy esitellä oma silmukkamuuttuja i - edellisessä versiossa se oli tarpeeton. Iteraattori istream_iterator Seuraava esimerkki havainnollistaa, kuinka voit käyttää mallialgoritmia epätavallistenkin iteraattoreiden kanssa. Olemme jo nähneet ostream_iterator-iteraattorin toiminnassa - tällä kertaa käytämme sen vastakohtaa, istream_iterator, joka hakee tiedon syöttövirrasta. Kokeile itse - Maaginen istream_iterator<> Esimerkeissä 20.3 ja 20.4 käytimme keskiarvo()-funktiotamme arvoille, jotka olivat olemassa taulukossa tai säiliössä. Tällä kertaa keskiarvo()-funktio lukee arvot suoraan syöttövirrasta. Käytämme tässä jälleen esimerkin 20.3 mallifunktiota: // Esimerkki 20.5 Lasketaan virrasta luettujen arvojen keskiarvo #include <iostream> #include <iterator> // istream_iterator<>-malli using namespace std; template <typename Iter> double keskiarvo(iter a, Iter loppu) double summa = 0.0; // Tähän lasketaan summa int lkm = 0 ; for( ; a!= loppu ; lkm++) summa += *a++; // Summataan taulukon alkiot return summa / lkm; // Palautetaan keskiarvo int main() cout << "Syötä numeroita, eroteltuina tyhjillä merkeillä - välilyönneillä, " << endl << "sarkaimella tai rivinvaihdoilla. Lopuksi paina merkkiyhdistelmää, " << endl << "joka vastaa tiedoston loppua (Ctrl-Z PC:ssä)" << endl; double ka = keskiarvo(istream_iterator<double>(cin), istream_iterator<double>()); cout << "Arvojen keskiarvo on " << ka << endl; return 0; 839

18 C++ Ohjelmoijan käsikirja! Kun suoritat tämän ohjelman, varmista, että käytät oikeaa merkkiyhdistelmää syöttövirran sulkemiseksi. PC-maailmassa tämä merkkiyhdistelmä on Ctrl-Z. Tämän jälkeen voi olla tarpeen painaa Enter. Eräs suosittu ohjelmointiympäristö tulee yhtiöltä, jonka tulee kuulla kaikki kahteen kertaan, joten jos ensimmäinen Ctrl-Z ei sulje virtaa, yritä painaa Ctrl-Z useampaan kertaan eri riveille. Tässä on ohjelman tulostus: Syötä numeroita, eroteltuina tyhjillä merkeillä - välilyönneillä, sarkaimella tai rivinvaihdoilla. Lopuksi paina merkkiyhdistelmää, joka vastaa tiedoston loppua (Ctrl-Z PC:ssä) Arvojen keskiarvo on 4.6 Sinun järjestelmäsi saattaa tulostaa jotain hieman erilaista. Älä yritä suorittaa tätä ohjelmaa liian hartaasti, jos se tuottaa ongelmia. Myöhemmin meillä on toinen esimerkki, jossa vältetään konsolin käyttö. Palaamme istream_iterator-iteraattoriin vielä uudelleen. Kuinka se toimii Seuraava lause muodostaa mallimme ilmentymän: double ka = keskiarvo(istream_iterator<double>(cin), istream_iterator<double>()); Tässä välitämme keskiarvo()-funktiollemme varsin oudon näköisiä lausekkeita! Kirjoitetaan tämä toisella tavalla, jotta voimme tutkia sitä tarkemmin: istream_iterator<double> alku(cin); istream_iterator<double> loppu; double ka = keskiarvo(alku, loppu); Olio alku on mallin istream_iterator<double> ilmentymä. Virtaolio cin välitetään muodostinfunktiolle, jotta iteraattori lukee standardista syöttövirrasta. Olio loppu on myöskin mallin istream_iterator<double> ilmentymä. Luomme end-olion käyttämällä istream_iterator<double>-luokan oletusmuodostinfunktiota. Kun muuttuja end luodaan tällä tavalla, se toimii lopun yli -arvona, joka vastaa tiedoston loppua. Uuden versiomme alku ja loppu eivät todellakaan ole osoittimia - ne ovat luokkaolioita, joten miksi tämä toimii? Vastaus on se, että nämä oliot ovat iteraattoreita, mikä tarkoittaa, että ne voivat käyttäytyä osoittimien tapaan. Ne silti välittävät käyttöömme kolme operaattoria, joista olemme riippuvaisia keskiarvo()-funktiossamme: 840 bool Iter::operator!=(Iter end_value); double Iter::operator*(); Iter Iter::operator++(int); Vertailu Viittaus Lisäys

19 Standardi mallikirjasto Tällä kertaa olemme esittäneet nämä luokkafunktioina, koska ne todellakin ovat luokkafunktioita. Ne ovat luokan istream_iterator<double> uudelleenmääriteltyjä operaattorifunktioita. Palauta mieleen, että operator++(int) ei itse asiassa saa int-tyyppistä parametriä - tällä tavalla vain kerromme kääntäjälle että tarkoitamme lisäysoperaattorin jälkiliitteellistä muotoa. Voimme käyttää näitä operaatioita minkä tahansa istream_iterator<double>-tyyppisen olion kohdalla. Toiminnallisuutta sisältävät istream_iterator-iteraattorit Edellä olleet istream_iterator-oliot teeskentelevät olevansa osoittimia, kuten LtkPtr-luokkamme oliot esimerkissä Nämä oliot pystyvät kuitenkin paljon muuhunkin - ne voivat sisältää monimutkaistakin toiminnallisuutta, kuten virtojen I/O-operaatioita. Tämä on erittäin keskeinen ajatus, sillä sen avulla STL on niin tehokas kirjasto. Kun yhdistämme mallifunktiot ja iteraattorit, voimme hankkia välimme mistä tahansa lähteestä. Tätä et voi tehdä vanhan taulukkomuotoisen esitystavan avulla. Kokeile itse - Lisää istream_iterator-taikaa Korostaaksemme iteraattoreiden yleistä luonnetta, tässä on vielä yksi esimerkki. Tällä kertaa kokeilemme puoliavointa väliä arvoilla, jotka ovat merkkijonossa. Jos sinulla oli vaikeuksia esimerkin 20.5 suorituksessa, kokeile mieluummin tätä. // Esimerkki 20.6 Lasketaan merkkijonovirran arvojen keskiarvo #include <iostream> #include <iterator> // istream_iterator<>-malli #include <sstream> // istringstream-iteraattori using namespace std; template <typename Iter> double keskiarvo(iter a, Iter loppu) double summa = 0.0; // Tähän lasketaan summa int lkm = 0 ; for( ; a!= loppu ; lkm++) summa += *a++; // Summataan taulukon alkiot return summa / lkm; // Palautetaan keskiarvo int main() char* kurssi_lukemat = " "; istringstream kello (kurssi_lukemat); istream_iterator<double> alku(kello); istream_iterator<double> loppu; cout << "Lukemat: " << kurssi_lukemat << ". Päivän keskiarvo on "; cout << keskiarvo (alku, loppu) << endl; return 0; Esimerkin tulostus näyttää seuraavalta: Lukemat: Päivän keskiarvo on

20 C++ Ohjelmoijan käsikirja Kuinka se toimii Jos katsot tarkkaan, tämä esimerkki on lähes identtinen esimerkkiin 20.5 verrattuna. Erona on se, että olemme korvanneet cin-virran omalla kello-virralla käyttämällä luokkaa istringstream: char* kurssi_lukemat = " "; istringstream kello (kurssi_lukemat); Alustamme kello-syöttövirtamme siten, että se lukee arvot suoraan ohjelman merkkijonosta kurssi_lukemat. Tämän jälkeen välitämme virtaolion istream_iterator<doubel>muodostinfunktiolle: istream_iterator<double> alku(kello); Uuden kello-virtamme hienona piirteenä on se, että sitä voidaan käyttää useimmissa tapauksissa, joissa saattaisimme käyttää cin-virtaa - istream_iterator ei ole niin pikkutarkka. Se käyttää tyytyväisenä istingstream-oliotamme kello tai muuta merkkijonojen lähdettä virran cin sijaan. Kaikki muu säilyy aivan samana, mukaan lukien keskiarvo()-mallimme! Lisää iteraattoreista Esimerkeissä olemme päässeet varsin pitkälle käyttämällä mallipohjaista versiotamme keskiarvo()-funktiosta. Olemme kuitenkin muodostaneet vain kolme erilaista ilmentymää: double keskiarvo<double*>(double*, double*); double keskiarvo<int*>(int*, int*); double keskiarvo<istream_iterator<double> > (istream_iterator<double>, istream_iterator<double>); Näistä näet selvästi, että kaksi ensimmäistä versiota käsittelevät yksinkertaisia osoittimia, kun taas kolmas käsittelee istream_iterator-oliota. Esimerkiksi esimerkissä 20.3 välitämme alkiot vector<int>-säiliöstä keskiarvo<int*>-versiolle. Esimerkeissä 20.5 ja 20.6 käytämme istream_iterator-oliota, jolloin voimme lukea numeeriset arvot konsolilta tai suoraan tiedostosta. Osoitinversiot käsittävät sekä C++:n perustaulukot että vector-säiliöt, kun taas istream_iterator-versio käsittää tiedostot ja merkkijonot. Emmekä ole vielä kuin hieman raapaisseet pintaa, mitä iteraattoreihin tulee. Iteraattoriolio näyttää yhä varsin mysteeriseltä, joten pysähdytään hetkeksi ja mennään hieman syvemmälle iteraattoriolioiden toimintaan. Huomaat varsin pian, että omien iteraattoriluokkien luominen ei olekaan niin vaikeaa. 842 Iteraattorit Meillä on jo hieman kokemusta hyvin yksinkertaisesta iteraattorista, kun luvussa 14 loimme LtkPtr-luokan; mutta voimme tehdä huomattavasti enemmänkin. Ymmärrämme paremmin STL:n iteraattoreiden toimintaa, jos luomme oman iteraattorimme aloittamalla ensin aivan tyhjästä. Aloitamme yksinkertaisimmasta mahdollisesta iteraattorista ja laajennamme sitä pikku hiljaa, kunnes se on sellainen kuin haluammekin sen olevan - STL:n iteraattoreiden veljeskunnan täysi jäsen.

21 Standardi mallikirjasto Kokeile itse - Oman iteraattorin luonti Aloitamme yksinkertaisesta iteraattorista, joka käy läpi kokonaislukuja. Seuraavassa on esimerkkimme koodi: // Esimerkki 20.7 Yksinkertainen kokonaislukuiteraattoriluokka #include <iostream> using namespace std; class Kokonaisluku public: Kokonaisluku (int par = 0) : X(par) bool operator!=(const Kokonaisluku& par) const //!= -vertailu if (X == par.x) // Debuggaustulostus cout << endl // Näytetään, että olemme täällä << "operator!= palauttaa arvon false" << endl; return X!= par.x; int operator*() const return X; Kokonaisluku& operator++() ++X; return *this; //Osoitusoperaattori // Etuliitteellinen lisäysoperaattori private: int X; ; int main() Kokonaisluku alku(3); Kokonaisluku loppu(7); cout << "Tämän päivän kokonaisluvut ovat: "; for( ; alku!= loppu ; ++alku) cout << *alku << " "; cout << endl; return 0; Kun esimerkki suoritetaan, se tulostaa: Tämän päivän kokonaisluvut ovat: operator!= palauttaa arvon false Kuinka se toimii Ohjelma toimii aivan kuin olisimme esitelleet muuttujat alku ja loppu tavallisiksi kokonaisluvuiksi ja sijoittaneet niihin samat arvot. Kokonaisluku-luokkamme tulostaa lisäksi lisärivin, jos uudelleenmääritelty!=-operaattori palauttaa arvon false. Määrittelemme Kokonaisluku-luokan muodostinfunktion seuraavasti: 843

22 C++ Ohjelmoijan käsikirja Kokonaisluku (int par = 0) : X(par) Kokonaisluku-luokalla on jäsenmuuttuja X, joka tallettaa nykyisen arvon. Jos emme onnistu alustamaan Kokonaisluku-oliota esittelyn yhteydessä, jäsenmuuttujan X oletusarvo on 0. Luokalle määritellään!=-operaattorifunktio: bool operator!=(const Kokonaisluku& par) const //!= -vertailu if (X == par.x) // Debuggaustulostus cout << endl // Näytetään, että olemme täällä << "operator!= palauttaa arvon false" << endl; return X!= par.x; Tämän oikeanpuolisena parametrinä on Kokonaisluku-olio ja se palauttaa bool-tyyppisen paluuarvon. Ohjelman tulostuksesta huomaat, että heti kun alku on yhtä suuri kuin loppu,!=operaattori palauttaa arvon false ja silmukan suoritus päättyy. Osoitusoperaattori määritellään seuraavasti: int operator*() const return X; //Osoitusoperaattori Tämä palauttaa nykyisen Kokonaisluku-olion jäsenmuuttujan X. Tämä funktio, sekä myös operator!=()-funktio, määritellään const-tyyppisiksi, koska ne eivät muuta olion sisältöä. Etuliitteellinen lisäysoperaattori määritellään seuraavasti: Kokonaisluku& operator++() ++X; return *this; // Etuliitteellinen lisäysoperaattori Edellisten esimerkkien keskiarvo()-funktiossa käytimme lauseketta *a++ jälkiliitteellisen operator++(int)-funktion sijaan. Kokonaisluku-luokallamme on nyt yksinkertaiset funktiot. Tässä on itse asiassa kaikki mitä tarvitsemme, jotta saamme sen toimimaan osoittimen tapaan, jolloin voimme tehdä joitakin varsin mielikuvituksellisia asioita. main()-funktiossa meillä on lause: for( ; alku!= loppu ; ++alku) cout << *alku << " "; 844 Tällaisen lauseen pitäisi näyttää jo varsin tutulta. Käytimme kovin samanlaista lausetta keskiarvo()-algoritmissamme, mutta tällöin käytimme *a++-lauseketta. Itse asiassa tulostuslauseen *alku:n korvaaminen *alku++:lla ei toimi tässä, koska emme ole määritelleet operator++(int)- funktiota, joka tukee Kokonaisluku-luokan jälkiliitteellistä muotoa. Tästä syystä käytämme muotoa *alku tulostuslauseessa ja lauseketta ++alku silmukan kontrollilausekkeessa. Itse asiassa olisimme voineet kirjoittaa:

23 Standardi mallikirjasto for( ; alku!= loppu ; ) cout << *++alku << " "; Tässä silmukka tulostaisi arvot 4-7. Lausekkeen *++alku arvo on jäsenmuuttujan alku.x arvo lisäyksen jälkeen. Iteraattoreiden välitys algoritmille Eli mitä hyötyä Kokonaisluku-luokastamme on? Mitä sellaista Kokonaisluku voi tehdä, mihin vanha kunnon int ei pysty? Katsotaanpa. Kokeile itse - Kokonaisluku-olion välityskeskiarvo<>:lle // Esimerkki 20.8 Kokonaisluku-olion keskiarvojen laskenta #include <iostream> using namespace std; class Kokonaisluku public: Kokonaisluku (int par = 0) : X(par) bool operator!=(const Kokonaisluku& par) const return X!= par.x; //!= -vertailu int operator*() const return X; Kokonaisluku& operator++() ++X; return *this; //Osoitusoperaattori // Etuliitteellinen lisäysoperaattori const Kokonaisluku operator++(int) // Jälkiliitteellinen ++-operaattori Kokonaisluku temp(*this); // Talletetaan nykyinen arvo ++X; // Vaihdetaan uuteen arvoon return temp; // Palautetaan muuttumaton talletettu arvo private: int X; ; template <typename Iter> double keskiarvo(iter a, Iter loppu) double summa = 0.0; // Tähän lasketaan summa int lkm = 0 ; for( ; a!= loppu ; lkm++) summa += *a++; // Summataan taulukon alkiot 845

24 C++ Ohjelmoijan käsikirja return summa / lkm; // Palautetaan keskiarvo int main() Kokonaisluku ensim(1); Kokonaisluku viim(11); cout << "Kokonaislukujen keskiarvo väliltä " << *ensim << " - " << -1+*viim; cout << " on " << keskiarvo(ensim, viim) << endl; return 0; Ohjelman tulostus on seuraava: Kokonaislukujen keskiarvo väliltä 1-10 on 5.5 Kuinka se toimii Emme tehneet kovinkaan suuria muutoksia. Lisäsimme operator++()-operaattorin jälkiliitteellisen muodon: const Kokonaisluku operator++(int) // Jälkiliitteellinen ++-operaattori Kokonaisluku temp(*this); // Talletetaan nykyinen arvo ++X; // Vaihdetaan uuteen arvoon return temp; // Palautetaan muuttumaton talletettu arvo Tiedämme jo entuudestaan, että toteuttaaksemme jälkiliitteellisen lisäysoperaattorin, meidän täytyy luoda kopio nykyisestä oliosta - tätä meidän ei tarvitse tehdä etuliitteellisessä muodossa. Tämä luo tietysti lisätyötä, koska tällöin kutsutaan kopiomuodostinta. Tästä syystä suurin osa seuraavista esimerkeistä käyttää etuliitteellistä muotoa aina, kun se vain on mahdollista, koska se on tehokkaampaa. Yksinkertaisen Kokonaisluku-luokkamme kohdalla tällä ei ole juuri mitään merkitystä, mutta kun teemme mallialgoritmeja, emme voi olla varmoja, miten paljon suoritusaikaa olion kopioiminen vie, kun käytämme lauseketta Iter++. Käytämme Kokonaisluku-luokkaamme seuraavissa lauseissa: Kokonaisluku ensim(1); Kokonaisluku viim(11); cout << "Kokonaislukujen keskiarvo väliltä " << *ensim << " - " << -1+*viim; cout << " on " << keskiarvo(ensim, viim) << endl; Jälleen kerran, viim on lopun yli -arvo. Meidän tulee vähentää siitä 1, jotta saamme sallitun arvon. Lisäksi, jos ensim olisi pelkkä int iteraattorin sijaan, meidän ei tarvitsisi käyttää lauseketta *ensim, jotta saamme sen arvon selville. 846 Jos oikein pysähdyt ajattelemaan, olemme tehneet jotain hyvin erilaista kutsuessamme keskiarvo()-funktiota uudella Kokonaisluku-iteraattorillamme. Kaikissa aikaisemmissa esimerkeissä keskiarvoon lasketut arvot olivat jossain - taulukoissa, vector-säiliöissä tai virroissa. Tässä esimerkissä arvoja ei ole olemassa missään. Ne lasketaan Kokonaisluku-luokan sisällä, kun keskiarvo()-algoritmi kutsuu kolmikkoa operator!=(), operator*() ja operator++().

25 Standardi mallikirjasto istream_operator:n ja ostream_operator:n jäljittely Ei ole vaikeaa luoda olioita, jotka toimivat kuten istream_operator ja ostream_operator. Outoa kyllä, STL ei tarjoa yhtään oliota, joka toimii kuten yksinkertainen Kokonaisluku-iteraattorimme. Kokeile itse - Keskiarvon laskenta partial_sum- ja accumulate-funktioiden avulla Tämä ohjelma ratkaisee saman ongelman kuin esimerkki 20.8, mutta se käyttää vain STL:n komponentteja: // Esimerkki 20.9 Kokonaislukujen keskiarvon laskenta partial_sum- ja accumulatefunktioilla #include <iostream> #include <vector> #include <algorithm> #include <numeric> using namespace std; template <typename RndIter> double keskiarvo (RndIter ensim, RndIter viim) return accumulate(ensim, viim, 0.0)/(viim-ensim); int main() vector<int> v(10, 1); // 10 kopiota arvosta 1 partial_sum(v.begin(), v.end(), v.begin()); copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); cout << endl; cout << "Keskiarvo on " << keskiarvo (v.begin(), v.end()) << endl; return 0; Esimerkin tulostus näyttää seuraavalta: Keskiarvo on 5.5 Kuinka se toimii Voidaanpa asiat tehdä vaikeasti! Ohjelma kuitenkin toimii oikein ja se on itse asiassa tehokkaampi miltä se näyttää. partial_sum-algoritmi on yksi STL:n eksoottisimmista komponenteista. Se toimii samaan tapaan kuin osasumman laskeva laskuri, eli se korvaa kaikki arvot nykyisen arvon ja kaikkien sitä edeltävien arvojen summalla. Eli sarja 1, 2, 3, 4, 5 korvataan sarjalla 1, 3, 6, 10, 15. Tässä esimerkissä käytämme partial_sum-algoritmia 10-alkioisen vector-säiliön kanssa. Jokaisen alkion arvo on 1. Tuloksena olevat alkioiden arvot näkyvät tulostuksessa. Eli partial_sum-algoritmia voidaan käyttää tuottamaan kokonaisluvut 1 - N. Esimerkin sanoma oli kuitenkin, että STL ei aina ratkaise asioita yksinkertaisimmalla mahdollisella tavalla. STL ei sisällä suoraa vastinetta Kokonaisluku-iteraattorille. 847

26 C++ Ohjelmoijan käsikirja Iteraattoreiden vaatimukset Tähän saakka Kokonaisluku-esimerkkiluokkamme on toiminut riittävän hyvin iteraattorina yksinkertaisissa esimerkeissämme. STL määrää kuitenkin joitakin erityisvaatimuksia olioille, jotka väittävät olevansa iteraattoreita - tämä siitä syystä, että kaikki algoritmit, joille välitetään iteraattoreita, toimisivat oikein. Yksi ongelma mallien ohjelmoinnissa on se, että et aina tiedä kaikkia tarvitsemiasi tyyppejä, ennen kuin käytät niitä. Tarkastellaan seuraavaa esimerkkiä: template <typename Iter> void Swap(Iter& a, Iter& b) apu = *a; // virhe -- muuttujaa apu ei ole esitelty *a = *b; *b = apu; Minkä tyyppinen muuttujan apu tulisi olla? Emme voi mitenkään tietää - tiedämme vain, että se on iteraattorin osoittamaa tyyppiä, mutta emme tiedä, mikä se voisi olla. Miten voimme esitellä muuttuja, jonka tyyppiä emme tiedä? Yksinkertaisena ratkaisuna on varmistaa, että jokainen iteraattori sisältää julkisen tyypinmäärittelyn value_type palautettavalle määrälle: template <typename Iter> void Swap(Iter& a, Iter& b) typename Iter::value_type tmp = *a; *a = *b; *b = apu; // Parempi - mutta ei vielä riittävän hyvä 848 Tämä toimii oikein suurimman osan STL:n iteraattoreiden yhteydessä. Jos Iter on kuitenkin osoitintyyppinen, kuten int* - mikä onkin usein tosi - tämä ei toimi. Ongelma on siinä, että et voi kirjoittaa int*::value_type - osoittimet ovat kielen sisäänrakennettuja tyyppejä eivätkä sisällä sisäisiä tyypinmäärittelyjä. STL ratkaisee tämän ongelman, ja muut siihen liittyvät ongelmat, iterators_traits-mallilla, iteraattorin merkillä ja joukolla vaadittavia iteraattorityyppejä: template <typename Iter> void Swap(Iter& a, Iter& b) // Hankalan näköinen, mutta toimii aina typename iterators_traits<iter>::value_type tmp = *a; *a = *b; *b = apu; Malli iterators_traits määrittelee, onko parametri Iter osoitin. Jos se on osoitin, meillä on kutakuinkin iterators_traits<t*>::value_type, mikä on sama kuin T-tyyppinen olio. Jos Iter on luokkaolio, meillä on kutakuinkin iterators_traits<c>::value_type, joka on sama kuin C::value_type.

27 Standardi mallikirjasto Eli, jos haluamme, että Kokonaisluku-iteraattorimme toimii iterator_traits:n kanssa, sen tulee sisältää sisäinen julkinen tyypinmäärittely value_type:lle. Seuraavassa taulukossa on lueteltu kaikki tyypit, jotka STL vaatii oman iteraattorin määrittelevän. Jos sinulla on esimerkiksi tuntematon malliparametri Iter, voit kirjoittaa Iter::pointer, kun sinun täytyy esitellä osoitin tyyppiin, jonka mukaista iteraattori on, kun sitä käytetään yhdessä osoitusoperaattorin kanssa. Iteraattorityypit, kun Iter -> T Tarkoitus iterator_category katso alla oleva taulukko difference_type iteraattoreiden vähennyslasku value_type T reference T& pointer T* iterator_category:n arvot otetaan kiinteästä kategoriamerkeistä. Iteraattorin kategoria määrää, minkä tyyppisten algoritmien kanssa iteraattori voi toimia. Iteraattorityyppi Tarvittava kategoriamerkki! syöttö input_iterator_tag tulostus output_iterator_tag eteenpäin forward_iterator_tag molempiin suuntiin Bidirectional_iterator_tag hajasaanti random_access_iterator_tag C++:n osoitin toimii täydellisenä hajasaanti-iteraatorina. Muut iteraattorikategoriat ovat rajoittavampia. Käsittelemme esimerkiksi pian list-säiliötä, jolla on molempiin suuntiin - tyyppiset iteraattorit. Tämän tyyppinen iteraattori voi siirtyä eteen ja taaksepäin yksittäisin askelin, mutta sitä ei voida käyttää indeksointioperaattorin operator[] tapaan hajasaannissa. Kun nyt olemme tutustuneet tarkemmin Kokonaisluku-iteraattoriin ja sen kaikkiin uusiin vaatimuksiin, palataan taaksepäin ja tarkastellaan virtaiteraattoreita tarkemmin. Olet jo nähnyt, miten ostream_iterator toimii. Itse asiassa, ostream_iterator on esimerkki output_iterator-tyypistä. Käsittelemme tämän toiminnan yksityiskohtia hetken kuluttua, mutta siirrytään nyt eteenpäin Kokonaisluku-iteraattorimme kanssa. Parempi Kokonaisluku-iteraattori Esimerkissä 20.8 pystyimme välittämään Kokonaisluku-olion suoraan keskiarvo()- mallifunktiolle. Tätä et olisi voinut tehdä vanhan int-tyypin kanssa. Voit tehdä varsin vaikuttavia asioita omien iteraattoreiden ja STL:n kanssa, mutta sillä on kova hinta. Ennen kuin iteraattorisi toimii yleisesti STL:n ominaisuuksien kanssa, sinun tulee lisätä iteraattoriin useita esittelyjä ja jäsenfunktioita. Katsotaan, miten tämä tapahtuu käytännössä. 849

28 C++ Ohjelmoijan käsikirja Kokeile itse - Täydellinen iteraattori Katsotaan nyt, miltä Kokonaisluku-luokka näyttää, kun se sisältää kaikki STL:n hajasaantiiteraattorilta vaatimat asiat. // Esimerkki Täydellinen Kokonaisluku-hajasaanti-iteraattori #include <iostream> #include <iterator> #include <algorithm> using namespace std; #include <utility> using namespace std::rel_ops; class Kokonaisluku : public iterator<random_access_iterator_tag, int, int, int*, int> public: Kokonaisluku(int x=0) : X(x) // Oletusmuodostinfunktio Kokonaisluku(const Kokonaisluku& x) : X(x.X) ~Kokonaisluku() Kokonaisluku& operator=(const Kokonaisluku& x) X = x.x; return *this; // Kopiomuodostin // Tuhoajafunktio // Sijoitusoperaattori // Vertailuoperaattorit bool operator==(const Kokonaisluku& x) const return X == x.x; bool operator!=(const Kokonaisluku& x) const return!(*this == x); bool operator<(const Kokonaisluku& x) const return X < x.x; int operator*() const return X; int operator[](int n) const return X+n; // Molempiin suuntiin -operattorit Kokonaisluku& operator++() ++X; return *this; Kokonaisluku& operator--() --X; return *this; Kokonaisluku& operator++(int) Kokonaisluku temp(*this); ++X; return temp; 850

29 Standardi mallikirjasto Kokonaisluku& operator--(int) Kokonaisluku temp(*this); --X; return temp; // Hajasaantioperaattorit Kokonaisluku operator+(int n) const return Kokonaisluku (X+n); Kokonaisluku operator-(int n) const return Kokonaisluku (X-n); private: int X; ; int main() Kokonaisluku F1(-1); Kokonaisluku L1(10); cout << "Arvot [-1..10) oikeassa järjestyksessä: " << endl; copy (F1, L1, ostream_iterator<int>(cout, " ")); cout << endl; typedef reverse_iterator<kokonaisluku> AlasPain; AlasPain F2(10); AlasPain L2(-1); cout << "Arvot (10..-1] päinvastaisessa järjestyksessä: " << endl; copy (F2, L2, ostream_iterator<int>(cout, " ")); cout << endl; return 0; Pieni kokonaisluku-luokkamme ei olekaan enää niin kovin pieni. Se sisältää nyt kaiken, mitä täydellisen STL:n iteraattorin tuleekin sisältää. Mielenkiintoinen seikka on Kokonaislukuluokkamme käyttö STL:n mielenkiintoisen reverse_iterator-muuntimen kanssa. Kun suoritamme ohjelman, se tulostaa: Arvot [-1..10) oikeassa järjestyksessä: Arvot (10..-1] päinvastaisessa järjestyksessä: ! Ei pitäisi olla kovinkaan yllättävää, että saamme saman arvojoukon kumpaankin suuntaan tulostettaessa. Päinvastaisessa järjestyksessä saamme samat arvot, mutta päinvastaisessa järjestyksessä. Oikeassa järjestyksessä arvo 10 on lopun yli -arvo; päinvastaisessa järjestyksessä sillä on uusi merkitys alkua ennen -arvona. Tämän ohjelman kääntämisessä saattaa olla ongelmia vanhemmissa kääntäjissä. Koska C++:n standardiluonnos vaatii, että iterator-mallilla on (jota käytetään tässä Kokonaisluku-luokan kantaluokkana) 5 parametriä, mutta se saattaa olla toteutettu ainoastaan neljällä parametrillä. 851

30 C++ Ohjelmoijan käsikirja Kuinka se toimii Tällä kertaa aloitamme sieltä, missä toimintakin on ja jatkamme ylöspäin. Esimerkin taika on AlasPain-iteraattoriluokan luonnissa: typedef reverse_iterator<kokonaisluku> AlasPain; AlasPain F2(10); AlasPain L2(-1); cout << "Arvot (10..-1] päinvastaisessa järjestyksessä: " << endl; copy (F2, L2, ostream_iterator<int>(cout, " ")); Tämä on luokka siinä missä Kokonaisluku-luokkakin, mutta se on erilainen. reverse_iterator-tyyppi tunnetaan muunninluokkana. Se on malli, jolle välitetään parametrinä iteraattori ja se luo tuloksena uuden iteraattorin. Uusi iteraattori on täsmälleen samanlainen kuin alkuperäinenkin iteraattori, muta kaikki on käännetty. Kaikki on päinvastaisessa järjestyksessä. Palaamme esimerkin käsittelyyn hetken kuluttua. Katsotaan nyt kahta tapaa, joilla voimme kutsua vanhaa nayta_jarjestys()-funktiotamme, johon tutustuimme esimerkissä Seuraavassa on hyvin yksinkertainen ohjelma, joka kutsuu nayta_jarjestys()-funktiota kaksi kertaa. Jos poistat kommentoinnin toisesta kutsusta, saat virheilmoituksen: // Esimerkki rbegin Ongelma ei-mallityyppisen algoritmin kanssa #include <iostream> #include <algorithm> #include <vector> using namespace std; // Tulostetaan alkioiden järjestys void nayta_jarjestys(const int* ensim, const int* viim) cout << " "; copy(ensim, viim, ostream_iterator<int>(cout, " ")); cout << "" << endl; int main() int arvot[] = 11, 88, 99 ; vector<int> V(arvot, arvot+3); nayta_jarjestys(v.begin(), V.end()); // Saattaa toimia //show_sequence(v.rbegin(), V.rend()); // Poista kommentointi niin saat virheen copy(v.rbegin(), V.rend(), ostream_iterator<int>(cout, ".0 ")); 852 cout << endl; return 0; Esimerkin tulostus näyttää seuraavalta: Kuten huomaat, vector-säiliö sisältää taaksepäin-tyyppiset iteraattorit. Käytetyt jäsenfunktiot ovat rbegin() ja rend(). Tässä on kuitenkin avoimia kysymyksiä: Miksi nayta_jarjestys() toimii eteenpäin-tyyppisten iteraattoreiden kanssa, mutta ei taaksepäin-tyyppisten iteraattoreiden kanssa? Miksi copy() toimii kummankin tyyppisen iteraattoreiden kanssa?

31 Standardi mallikirjasto Ongelma juontaa juurensa siihen, että esittelimme nayta_jarjestys()-funktion sillä oletuksella, että sen parametrit ovat osoittimia - eli se ei ole oikein tehty yleinen funktio. Todellinen yleinen algoritmi kirjoitettaisiin mallifunktiona: template <typename FwdIter> void show_sequence(fwditer ensim, FwdIter viim) cout << " "; copy (ensim, viim, ostream_iterator<int>(cout, " ")); cout << "" << endl; Kuten olemme jo maininneet, jotkin STL:n versiot (mutta eivät kaikki) olettavat, että vector<int>::iterator on tyyppiä int*. Tämä yleinen algoritmi hallitsee kaikki mahdolliset tapaukset, eikä tee mitään tällaisia oletuksia. Minkä tyypin vector-säiliön rbegin() palauttaa? Se on mitä todennäköisimmin reverse_iterator-muuntimella tehty muunnos tavallisesta iteraattorista: reverse_iterator<vector<int>::iterator> Muunnin reverse_iterator on malli, joka muuntaa tavallisen iteraattorin elämän toisin päin. Muuntimet ovat STL:n kehittyneimpiä käsitteitä, joten emme murehdi niistä tässä tämän enempää. Tärkein kohta on yksinkertaisesti siinä, että käytössä on monia erilaisia iteraattorityyppejä, eikä meidän tulisi tehdä niistä liikaa olettamuksia. Palataan nyt esimerkkiin Kokonaisluku-luokan määrittelyn ensimmäinen rivi on tärkeä lause, joka mahdollistaa sen, että reverse_iterator<>-muunnin voi ottaa parametrinään Kokonaisluku-luokkamme: class Kokonaisluku : public iterator<random_access_iterator_tag, int, int, int*, int> Olemme nyt varsin syvällä syntaksin kiemuroissa. Kantaluokka iterator on ilmentymä mallista, joka on apuluokka, joka varmistaa, että iterator-luokka esittelee kaikki tarvittavat tyypit (kuten iterator_category, value_type ja pointer). Seuraavassa on tämän toiminta lyhyesti. Kokonaisluku-luokka on periytetty iteratorilmentymästä ja jokainen iterator-mallille välitetty parametri määrittelee yhden vaadituista iteraattorityypeistä. Koska periytämme luokan iterator-mallista julkisesti, iterator-mallin sisällä esitellyt tyypit tulevat osaksi Kokonaisluku-luokan rajapintaa. Ensimmäinen parametri on iteraattorikategoria. Kokonaisluku-luokalla on kaikki tarvittavat funktiot, jotta se voidaan sijoittaa kaikkein tehokkaimpaan kategoriaan random_access_iterator, joten käytämme tätä merkkiä. Muut neljä parametriä määrittelevät tyypit value_type, difference_type, pointer ja reference. Kokonaisluku-palauttaa tyypin int, joten välitämme int-tyyppin value_type-parametrille. Vastaavasti välitämme tyypin int* pointer-tyypille. On tärkeää, että Kokonaisluku-luokkamme sisältää kaikki tarvittavat tyyppien määrittelyt. Kokonaisluku-luokallamme on nyt monia uusia jäsenfunktioita, mutta ne kaikki ovat hyvin yksinkertaisia. Ne on helpompi käsitellä, jos kokoamme ne ryhmiin. 853

32 C++ Ohjelmoijan käsikirja Ensimmäinen ryhmä on muodostinfunktiot. Niihin kuuluvat muutamat tärkeät funktiot, jollaiset jokaisella monimutkaisella luokalla tulee olla: oletusmuodostinfunktio, kopiomuodostin ja sijoitusoperaattori. Sääntönä voidaan pitää, että jos määrittelet nämä funktiot iteraattorille, sinun tulee myöskin määritellä eksplisiittinen tuhoajafunktio. Tässä tapauksessa emme sellaista tarvitse; Kokonaisluku-luokan kohdalla se ei tee mitään. Näiden funktioiden prototyypit ovat seuraavat: Kokonaisluku(int x=0); // Oletusmuodostinfunktio Kokonaisluku(const Kokonaisluku& x); // Kopiomuodostin ~Kokonaisluku(); // Tuhoajafunktio Kokonaisluku& operator=(const Kokonaisluku& x); // Sijoitusoperaattori Olemme huijanneet hieman oletusmuodostinfunktiossamme, koska olemme määritelleet vain oletusarvon muodostinfunktion parametrille. STL käyttää oletusmuodostinfunktiota luodessaan uusia alkioita säiliöön, joten on tärkeää, että se on määritelty. Luokkamme yhtäsuuruus- ja vertailuoperaattorit ovat: bool operator==(const Kokonaisluku& x) const; bool operator!=(const Kokonaisluku& x) const; bool operator<(const Kokonaisluku& x) const; #include-komento otsikkotiedostoa utility varten on tärkeä tässä kohtaa: #include <utility> using namespace std::rel_ops; Jos määrittelet luokallesi operaattorit operator==() ja operator<(), rel_ops nimiavaruus sisältää mallifunktiot, jotka muodostavat automaattisesti funktiot myös operaattoreille!=, >, >= ja <=. Joten rel_ops-nimiavaruuden aktivoiminen säästää meidät näiden neljän operaattorin määrittelemiseltä käsin. Tässä esimerkissä määrittelimme oman!=-operaattorimme. Tällöin sitä käytetään rel_opsnimiavaruuden puolestamme muodostaman operator!=()-funktion sijaan. Operaattori operator<() on erikoinen. Sitä kutsutaan järjestysoperaattoriksi. Palaamme siihen, kun käsittelemme etsintä- ja vertailualgoritmejä. STL käyttää järjestysoperaattoreita hyvin runsaasti. Kun määrittelemme operaattorin operator<(), rel_ops määrittelee automaattisesti operator>()-funktion seuraavanlaisen määrittelyn: template<typename T> bool operator>(const T& x, const T& y) return y<x; 854 Tämä saadaan aikaan yksinkertaisesti vaihtamalla parametrien x ja y paikkoja keskenään. Operaattori operator==() on yhtäsuuruusoperaattori, jota käytetään testaamaan, onko kahdella oliolla sama sisältö. Tämän toiminnasta on eräs mielenkiintoinen seuraus. Saatat ajatella, että minkä tahansa x ja y -parin kohdalla lausekkeen (x<y y<x x==y) arvo on true - jonkin kolmesta lausekkeen osasta tulee olla tosi. Tämä ei kuitenkaan välttämättä toimi näin. On selvää, että jos (x==y) on true, (x<y) ja (y<x) eivät voi olla true. Yksi asia, josta voimme olla varmoja, on se, että samat arvot eivät voi olla eri arvoja.

33 Standardi mallikirjasto Jos kuitenkin (x!=y), emme voi olettaa, että (x<y) tai (y<x) on true - kun (!(x<y)) && (!(y<x)) on true, alkioiden x ja y sanotaan olevan erisuuria, mikä yksinkertaisesti tarkoittaa sitä, että emme voi asettaa kumpaakaan etusijalle lajittelussa. Tällainen tilanne esiintyy esimerkiksi silloin, kuin lajittelemme merkkijonoja ja emme ota merkkikokoa huomioon. Merkkikoon huomioivassa tapauksessa merkkijonot A123 ja a123 ovat erisuuret, mutta ne eivät ole samat eivätkä ne ole yhtä suuret. Palataan Kokonaisluku-luokkamme ominaisuuksiin. Seuraavat operaattorit ovat saantioperaattoreita: int operator*() const; int operator[](int n) const; Niitä käytetään arvon palauttamiseen sarjasta. Operaattori operator[] löytyy ainoastaan iteraattoreilta, jotka kuuluvat kaikkein tehokkaimpaan random_access-kategoriaan. Molempiin suuntiin -ryhmään kuuluvat seuraavat operaattorit: Kokonaisluku& operator++(); Kokonaisluku& operator--(); Kokonaisluku& operator++(int); Kokonaisluku& operator--(int); Näiden avulla voit siirtyä eteen- tai taaksepäin yksi alkio kerrallaan. Eteenpäin-iteraattorit eivät voi siirtyä taaksepäin, joten ne eivät sisällä kumpaakaan vähennysoperaattoria. Hajasaantia tukevat operaattorit ovat: Kokonaisluku operator+(int n) const; Kokonaisluku operator-(int n) const; Nämä löytyvät aina, kun säiliö sisältää operator[]-operaattorin. Niiden avulla pääsemme käsiksi sarjan mihin tahansa alkioon toisesta alkiosta yksinkertaisesti lisäämällä tai vähentämällä halutun siirtymän. Tutkitaan esimerkiksi seuraavaa koodia: vector<int> V; V.push_back(13); V.push_back(42); cout << V.end()[-1]; // operator[]()-funktion eksplisiittinen käyttö cout << *(V.end()-2); // operator-() Ensimmäinen tulostuslauseke sisältää operator[]-operaattorin laillisen käyttötavan. Voimme käyttää negatiivisia siirtymiä, jos aloitamme sijaintikohdasta, joka on alkukohdan yli. Tässä tapauksessa viittaamme alkioon, joka on juuri ennen lopun yli -sijaintia, joka on arvo 42 (sarjan viimeinen arvo). On meidän vastuullamme, että käytämme sallittua indeksiä. Toinen tulostuslauseke käyttää operator-()-operaattoria käsitellessään vector-säiliön ensimmäistä alkiota. Syöttö- ja tulostusiteraattorit Syöttöiteraattoreita voidaan käyttää ainoastaan arvojen lukemiseen. Vastaavasti tulostusiteraattoreita voidaan käyttää ainoastaan arvojen tulostukseen. Nämä iteraattorit eivät tue hajasaantia eikä taaksepäin siirtymistä. Itse asiassa, näiden iteraattoreiden kohdalla sinun tulee olla tarkkana, että luet tai kirjoitat tietyn sijaintikohdan vain kertaalleen. 855

34 C++ Ohjelmoijan käsikirja Näiden iteraattoreiden tärkein käyttötarkoitus on mahdollistaa STL:n algoritmien, kuten copy(), toimivan virtojen yhteydessä. Olemme jo käyttäneet esimerkeissämme sekä ostream_iteratoriteraattoria että istream_iterator-iteraattoria. Lisäksi käytössämme on toinenkin erittäin hyödyllinen output_iterator, jota voimme kutsua myös lisäysiteraattoriperheeksi. Voit käyttää niitä, kun haluat lisätä alkion säiliöön käyttämällä esimerkiksi copy()-algoritmia. Voit esimerkiksi käyttää back_inserter()-funktiota lisäämään alkioita säiliön loppuun: template <typename Sailio, class Iter> void append(sailio& S, Iter lahde, Iter lahde_loppu) copy(lahde, lahde_loppu, back_inserter(s)); Tämä määrittelee mallifunktion, joka lisää alkion ensimmäisenä parametrinä saamaansa säiliöön. Kopioitavat alkiot määritellään toisella ja kolmannella parametrillä, jotka ovat iteraattoreita. Funktiolle back_inserter() välitetään parametrinä säiliö ja se palauttaa output_iterator:n, joka lisää jokaisen arvon kohdesäiliöön. Jos kopioisimme S.end()-sijaintikohtaan suoraan, copy()- algoritmi toimisi korvaustilassa ja rikkoisimme säiliön rajoja kirjoittamalla vector-säiliön alueelle, johon ei saa kirjoittaa. Tulostusiteraattori toimii lisäystilassa, joten se suurentaa säiliötä tarvittaessa. Lisäysfunktio front_inserter() on toinen hyödyllinen funktio - sen avulla voit lisätä alkion säiliön alkuun. Huomaa, että et voi käyttää tätä vector-säiliön kohdalla, koska front_inserter() tarvitsee push_front()-jäsentä alkion lisäämiseksi säiliön alkuun. Tällaista funktiota ei ole kuitenkaan määritelty vector-säiliölle. Lisäysfunktio inserter() lisää alkion annettuun sijaintikohtaan: copy(lahde, lahde_loppu, inserter(s, mika_tahansa_sijainti_s_sailiossa)); Tämä lisäysfunktio lisää kaikki alkiot ja siirtyy seuraavaan sijaintikohtaan. Lisätyt alkiot alkavat sijaintikohdasta eteenpäin, joten säiliö laajenee lisättyjen alkioiden määrällä. Kokeile itse - Lisäysfunktion käyttö Voimme nyt kokeilla front_inserter()-funktiota. Koska emme voi käyttää sitä vector-säiliön kanssa, kokeilemme sitä list-säiliöllä. Käytämme muokattua versiota esimerkistä 20.11: // Esimerkki Lisäysfunktion käyttö #include <iostream> #include <iterator> #include <algorithm> #include <list> using namespace std; 856 template <typename Sailio, class Iter> void lisaa(sailio& S, Iter lahde, Iter lahde_loppu) copy(lahde, lahde_loppu, front_inserter(s));

35 Standardi mallikirjasto int main() int A[] = 1, 9, 7, 5, 15 ; list<int> L; lisaa (L, A, A+5); copy(l.begin(), L.end(), ostream_iterator<int>(cout," ")); cout << endl; return 0; Esimerkki tulostaa lisätyt arvot käänteisessä järjestyksessä: Kuinka se toimii Taulukon arvot ovat säiliössä käänteisessä järjestyksessä, koska käytimme front_inserter()- funktiota lisaa()-mallifunktiossamme. Arvot lisätään järjestyksessä list<int>-säiliön L alkuun, alkaen ensimmäisestä alkiosta ja päättyen viimeiseen alkioon - eli taulukon viimeinen alkio tulee säiliön ensimmäiseksi alkioksi. Kuten sanottua, et voi käyttää front_inserter()-funktiota vectorsäiliön kanssa, mutta back_inserter()-funktiota voit käyttää niin vector- kuin list-säiliönkin kanssa. Tähän saakka käsittelemämme aiheet Olemme tähän saakka käsitelleet vector-siliötä, mallialgoritmia keskiarvo<>, omaa Kokonaisluku-iteraattoriamme ja reverse_iterator-muunninta; olemme lisäksi käyttäneet input_iterator:a, output_iterator:a ja stirngstream:a, sekä muutamia standardialgoritmeja, kuten copy(). Nyt tunnet olosi varmastikin jo kotoisammaksi ja huomaat, miten STL:n osat toimivat yhdessä. STL:n selkäranka on säiliöiden, iteraattoreiden ja algoritmien yhteiskäyttö. Säiliöt huolehtivat talletuksesta ja antavat meille käyttöön iteraattoreita, joiden avulla voimme käsitellä säiliön sisältöä. Iteraattorit määrittelevät sijaintikohtia ja välejä. Algoritmit ovat mallifunktioita, jotka toimivat useiden erilaisten iteraattoreiden kanssa, käyttämällä ainoastaan varsin yksinkertaisia operaatioita. Kun käsittelimme Kokonaisluku-luokkaamme, näimme, kuinka suuri osa tästä toimii emulaation avulla. Kokonaisluku-luokalle määriteltiin kaikki operaatiot, jotka ovat tavallisellakin osoittimella, joten se voi toimia kuten osoitin. Tästä seuraa se, että voimme välittää Kokonaisluku-luokkamme mille tahansa mallifunktiolle, jolle normaalisti välitetään osoitin. Olemme nyt nähneet suurimman osan STL:n tärkeistä ominaisuuksista toiminnassa. Loput tästä luvusta rakentuu tälle pohjalle. Nyt on aika palata vector-säiliöön ja tarkastella tarkemmin, miten se käsittelee muistia. Muistinkäsittely On vaikea päästä STL:n sisälle katsomaan, mitä siellä todella tapahtuu. STL:n määrittely ottaa suorituskyvyn tarkasti huomioon (miten usein mikäkin operaatio suoritetaan, jotta algoritmi voidaan suorittaa loppuun), ja se uhraa paljon tilaa sellaisille tekniikoille, kuten milloin lisäykset ja poistot tekevät toisista, samaan olioon osoittavista, iteraattoreista huonoja. 857

36 C++ Ohjelmoijan käsikirja Joskus voi olla vaikea saada tuntumaa, mitä tämä kaikki abstrakti asia pitää sisällään. Paras tapa saada selville, mitä oikein tapahtuu, on tutkia asiaa esimerkin avulla. Määrittelemme kaksi yksinkertaista luokkaa - Laskuri ja Papu - joiden avulla voimme vakoilla alkioiden käsittelyä, joka on normaalisti piilossa. Voit käyttää tätä tekniikkaa aina, kun sinusta tuntuu siltä, että haluat tietää pohjimmaisen totuuden. Apuluokka Laskuri Laskuri-luokkaa käytetään tapahtumien laskentaan. Se on toteutettu STL:n map-säiliön avulla. Se on yksi assosiatiivisista säiliöistä, joita käsittelemme myöhemmin. Nyt meille riittää ymmärtää sen abstrakti liittymä - miten luokan halutaan toimivan. // Laskuri.h #ifndef LASKURI_H #define LASKURI_H #include <iostream> #include <iomanip> #include <string> #include <map> using namespace std; class Laskuri typedef map<string, int> Paivakirja; public: Laskuri(void) void clear(void) P.clear(); Laskuri& operator<<(const string& alkio) P[alkio]++; return *this; void tarkastus(void) const Paivakirja::const_iterator iter; for(iter = P.begin(); iter!= P.end(); ++iter) cout << setw(7) << iter->second << " " << iter->first << endl; private: Paivakirja P; ; #endif Laskuri-luokan kaksi pääfunktiota ovat operator<<() ja tarkastus(): 858

37 Standardi mallikirjasto Kokeile itse - Laskuri-luokka Jos haluat testata Laskuri-luokkaa, voit käyttää seuraavaa lyhyttä ohjelmaa: // Esimerkki Laskuri-luokan ensimmäinen kokeilu #include <iostream> #include "Laskuri.h" using namespace std; int main() Laskuri A; cout << "1. tarkastus" << endl; A << "tapahtuma1"; A << "tapahtuma2"; A << "tapahtuma1"; A.tarkastus(); cout << "2. tarkstus" << endl; A << "torpedo"; A << "tapahtuma1"; A.tarkastus(); return 0; Esimerkin tulostus näyttää seuraavalta: 1. tarkastus 2 tapahtuma1 1 tapahtuma2 2. tarkstus 3 tapahtuma1 1 tapahtuma2 1 torpedo! Kääntäjäsi saattaa muodostaa joukon varoituksia, kun yrität kääntää tämän koodin. Tämä siitä syystä, että käyttämiemme olioiden nimet ovat varsin pitkät - palauta mieleen, että täydelliseen nimeen kuuluu kaikkien mallien nimet, joista oliosi luokka on muodostettu. Vaikka sen ei pitäisi aiheuttaa ongelmia itse kääntäjässä, jotkin debuggaustyökalut eivät pysty käsittelemään näitä hyvin pitkiä nimiä, joten kääntäjä varoittaa tästä. Pääset todennäköisesti eroon varoituksista, jos otat ne pois käytöstä #pragmaesikäsittelijäkomennolla, joka tulee sijoittaa lähdetekstitiedoston alkuun. Katso kääntäjäsi dokumenteista, miten tämä tulee tehdä. Kuten huomaat, funktion tarkastus() kutsu tulostaa yhteenvedon kutsuhetkeen saakka Laskuri-olioon suoritetuista lisäysoperaatioista. Käytämme nyt Laskuri-luokkaa toisen luokan määrittelyssä. 859

38 C++ Ohjelmoijan käsikirja STL:n vaatimukset säiliön alkiolle Papu-luokka määrittelee olion, jota voimme käyttää säiliön alkiona, kun vakoilemme säiliön sisäistä toimintaa. Eli sen sijaan, että käyttäisimme säiliötä vector<int>, käytämmekin säiliötä vector<papu>. Voit tallettaa Papu-olioita säiliöön ja voit milloin tahansa kutsua kaytto_raportti()-funktiota, joka tulostaa tarkasti, kuinka monta kertaa kutakin Papuoperaattoria on edellä kutsuttu. Papu-luokka käyttää Laskuri-luokkan oliota loki, joka pitää kirjaa, kuinka monta kertaa säiliö kutsuu kutakin Papu-operaatiota. Helpottaaksemme sen käyttöä, lasketut tapahtumat ovat merkkijonoja, jotka kuvaavat kutsutun operaation. Tähän sisältyy eräs niksi: Laskuri-olio loki on esitelty Papu-luokan staattiseksi jäsenmuuttujaksi. Emme yritä laskea tietyn Papu-olion operaatioita, vaan kaikkien säiliössä olevien Papu-olioiden operaatioita. Kun esittelemme lokiolion staattiseksi, kaikki Papu-oliot jakavat yhden ja saman tapahtumalaskurin. Katsotaan Papu-luokassa määriteltyjä operaattoreita. Ne ovat erittäin tärkeä ryhmä. // File "Papu.h" #ifndef PAPU_H #define PAPU_H #include <iostream> #include <string> #include "Laskuri.h" using namespace std; class Papu public: Papu(void) loki << "oletusmuodostinfunktio"; Papu(const Papu& x) loki << "kopiomuodostin"; // Oletusmuodostinfunktio // Kopiomuodostin Papu& operator=(const Papu& x) // Sijoitusoperaattori loki << ((this == &x)? "sijoitus_itseensa" : "sijoitus"); return *this; ~Papu() loki << "tuhoajafunktio"; bool operator==(const Papu& x) const loki << "yhtasuuruuden_testaus"; return true; bool operator<(const Papu& x) const loki << "pienempi_kuin_operaattori"; return false; // Tuhoajafunktio // Yhtäsuuruus // Vertailu friend void tyhjenna_papu_loki(void) loki.clear(); 860

39 Standardi mallikirjasto friend void kaytto_raportti(const string& otsikko = "") if (otsikko!= "") cout << otsikko << endl; loki.tarkastus(); cout << "-loppu-" << endl; loki.clear(); private: static Laskuri loki; ; #endif Papu-luokka määrittelee kaikki jäsenfunktionsa luokan esittelyssä. Yhtä Papu-luokan osaa emme kuitenkaan voi sijoittaa Papu.h-otsikkotiedostoon: meidän tulee esitellä staattinen muuttuja loki erillisessä käännösyksikössä, Papu.cpp, jolla varmistamme, että sitä ei määritellä moneen kertaan: // File "Papu.cpp" #include "Papu.h" Laskuri Papu::loki; Tämä kutsuu Laskuri-luokan oletusmuodostinfunktiota luodakseen Papu-luokalle loki-jäsenen. Muista lisätä papu.cpp-tiedoston käännöksessä muodostunut objektitiedosto yhdeksi linkitettäväksi olioksi, muutoin linkkerisi valittaa, että muuttujaa Papu::loki ei ole määritelty. Säiliöalkion vaatimukset Koska Papu-luokan on tarkoitus olla täydellinen säiliöalkio, jotta voimme vakoilla STL:n säiliöiden sisäistä toimintaa, voimme sen avulla havainnollistaa, millainen hyvän säiliöolion tulee olla. STL määrää säiliöön talletettavien alkioiden jäsenfunktioiden minimijoukon. Nämä on lueteltu seuraavassa taulukossa: Perhe muodostinfunktiot Vaaditut funktiot Alkio() Alkio(const Alkio& X) sijoitus Alkio& operator=(const Alkio& X) yhtäsuuruus bool operator==(const Alkio& X) const järjestys bool operator<(const Alkio& X) const Tämä lista ei ole kovinkaan pitkä - itse asiassa jokainen, vähänkään kiinnostavampi luokka sisältää kaikki nämä operaattorit. 861

40 C++ Ohjelmoijan käsikirja Huomaa, että operator<()-funktiota ei välttämättä tarvita. On kuitenkin järkevää määritellä alkioiden järjestys aina, kun se on mahdollista. Jos et niin tee, luokkaasi ei voida käyttää minkään assosiatiivisen säiliön (kuten map ja set) kanssa, eikä alkioiden järjestys toimi minkään lajittelualgoritmin kanssa. Lisäksi tämä taulukko olettaa, että std::rel_ops on voimassa, jolloin muodostetaan automaattisesti operaattorit!=, <=, > ja >=. Jos et ota käyttöön nimiavaruutta std::rel_ops, sinun täytyy itse määritellä nämä operaattorit. Äläkä unohda määritellä tuhoajafunktiota, jos luokkasi hoitaa itse muistinsa; ja varmista, että tuhoajafunktio on määritelty virtuaaliseksi, jos oliosi on mukana luokkahierarkiassa. Kokeile itse - Papu toiminnassa Kokeillaan nyt täydellistä Papu-alkiota käytännössä. // Esimerkki Papu toiminnassa #include "Papu.h" int main() Papu A; Papu B; A = B; if(a==b) cout << "Kaikki pavut on luotu samanlaisina" << endl << endl; if(a<b) cout << "Suuria ongelmia" << endl; kaytto_raportti("sijoitus, vertailu ja järjestys"); Papu* pb = new Papu[10]; delete[] pb; kaytto_raportti("uusi ja 10 alkion poisto"); return 0; Kun esimerkki suoritetaan, se tulostaa seuraavaa: Kaikki pavut on luotu samanlaisina 862 sijoitus, vertailu ja järjestys 2 oletusmuodostinfunktio 1 pienempi_kuin_operaattori 1 sijoitus 1 yhtasuuruuden_testaus -loppuuusi ja 10 alkion poisto 10 oletusmuodostinfunktio 10 tuhoajafunktio -loppu-

41 Standardi mallikirjasto Huomaa, että jokaisen raportin jälkeen tilastotiedot tyhjennetään. Voit vielä halutessasi parantaa tätä toimintaa muutamilla pienillä muutoksilla. Pidä luokkia Laskuri ja Papu aina saatavilla. Ne ovat aina hyödyllisiä, jos haluat ymmärtää taustalla tapahtuvia tapahtumia. Varsinkin jos työskentelet polymorfististen luokkien kanssa. Sijoittamalla Papu:n oikeaan paikkaan saat aina tarvittaessa nopeasti tietoa, milloin C++ kutsuu muodostinfunktioita, sijoitusoperaattoreita ja tuhoajafunktioita. vector-säiliön ja sen muistinhallinnan tarkastelu Tämä yksinkertainen mittausohjelma käyttää Papu-luokkaa vector-säiliön testaamiseen. Saat hyvän kuvan jokaisesta vector-säiliön funktiosta, kun vertaat lähdetekstiä ohjelman tuloksiin. Kokeile itse - vector-säiliön koko, kapasiteetti ja koon muutos Seuraavassa on esimerkin koodi: // Esimerkki vector-säiliön koko, kapasiteetti ja koon muutos #include <vector> #include "Papu.h" // Sisällyttää kaikki muut tarvittavat standardiotsikkotiedostot using namespace std; void muodostinfunktio_testit(void) tyhjenna_papu_loki(); vector<papu> V; kaytto_raportti("vector<papu> V"); vector<papu> W(3); kaytto_raportti("vector<papu> W(3)"); vector<papu> X(5,Papu()); kaytto_raportti("vector<papu> X(5,Papu())"); vector<papu> Y(X.begin(),X.end()); kaytto_raportti("vector<papu> Y(X.begin(),X.end())"); void koon_muutto_testit(int varattu = 0) vector<papu> V; if(varattu!= 0) cout << "koon_muutto_testit suoritettu, kun oli varattu tilaa " << varattu << " alkiolle" << endl; V.reserve(varattu); tyhjenna_papu_loki(); int koot[8] = 1, 10, 100, 20, 50, 101, 0 ; for(int i = 0; i < 7; ++i) V.resize (koot[i]); cout << "koon muutto V -> " << koot[i] << endl; kaytto_raportti(); 863

42 864 C++ Ohjelmoijan käsikirja void lisays_testit(void) Papu E; vector<papu> W; vector<papu> V; tyhjenna_papu_loki(); for(int j = 0; j < 1000; ++j) W.push_back(E); kaytto_raportti("1000 push_back() operaatiota"); for(int k = 0; k < 1000; ++k) V.insert(V.begin(), E); kaytto_raportti("1000 lisätty alkuun"); void sijoitus_ja_vertailu_testit(void) vector<papu> X(77); vector<papu> Y(456); cout << "X sisältää 77 jäsentä; Y sisältää 456 jäsentä" << endl; tyhjenna_papu_loki(); X==Y; kaytto_raportti ("vertailu: X==Y"); X=X; kaytto_raportti ("sijoitus itseensä: X=X"); X<Y; kaytto_raportti ("järjestys: X<Y"); X=Y; kaytto_raportti ("sijoitus: X=Y"); X==Y; kaytto_raportti ("vertailu: X==Y"); int main() muodostinfunktio_testit(); koon_muutto_testit(); koon_muutto_testit(100); lisays_testit(); sijoitus_ja_vertailu_testit(); return 0; Jos et halua, että kaikki tulostukset muodostetaan yhdellä kertaa, voit kommentoida main()- funktiossa olevia funktiokutsuja tarpeen mukaan. Seuraavassa jaamme tulostuksen käsittelyn kannalta sopiviin osiin. Kuinka se toimii: Muodostinfunktion testit Funktion muodostinfunktio_testit() tulostus on seuraava: vector<papu> V -loppuvector<papu> W(3) 3 kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppuvector<papu> X(5,Papu()) 5 kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppu-

43 Standardi mallikirjasto vector<papu> Y(X.begin(),X.end()) 5 kopiomuodostin -loppu- Tässä ei ole mitään ihmeellistä. Ensimmäinen raportti kertoo, että Papu-muodostinfunktiota ei ole kutsuttu, kun tyhjä vector-säiliö luotiin. vector-säiliön W esittely sisältää jo alkujaan 3 Papu-oliota - muodostinfunktio W(3) keksii, että käytetään Papu-olion parametriä oletusalkiona, aivan samaan tapaan kuin mekin kirjoitimme tämän. Näemme tämän tuloksena suoritetut funktion kutsut toisesta raportista. vector-säiliön X esittely on varsin samanlainen kuin W:n esittely. Jälleen huomaat tulostuksesta, että Papu-parametrin arvo muodostetaan ja tuhotaan funktion kutsun aikana. Tämä lisää työtä ja tapahtuu vector-säiliön muodostinfunktion ulkopuolella. Lopuksi raportti vector-säiliön Y esittelystä kertoo, että kopiomuodostinta kutsutaan - se kopioi alkiot X -> Y. Tässä tapauksessa oletusalkioita ei tarvita, joten emme näe oletusmuodostinfunktio- ja tuhoajafunktio-tapahtumia. Koon muutto -testit Funktion koon_muutto_testit() tulostus on seuraava: koon muutto V -> 1 1 kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppukoon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 2 tuhoajafunktio -loppukoon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 11 tuhoajafunktio -loppukoon muutto V -> 20 1 oletusmuodostinfunktio 81 tuhoajafunktio -loppukoon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppukoon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 51 tuhoajafunktio -loppukoon muutto V -> 0 1 oletusmuodostinfunktio 102 tuhoajafunktio -loppu- 865

44 866 C++ Ohjelmoijan käsikirja Tämä tulostus näyttää, että vector-säiliön muistinhallinta on raskasta - taustalla tapahtuu runsaasti olioiden tuhoamista ja kopiointia, kun vector varmistaa, että sillä on riittävästi varattua muistia koko sarjaa varten. Huomaa, mitä tapahtuu, kun V:n kooksi muutetaan näemme tulostuksesta, että STL on varannut uuden muistialueen koko sarjalle! Se kopio 10 olemassa olevaa alkiota uuteen muistipaikkaan, luo 90 muuta alkiota kopiomuodostimella ja vielä poistaa alkuperäiset 10 alkiota muistipaikoista, joissa ne olivat ennen kopiointia. Pidä myös mielessä, että kun vector-säiliö kerran kasvaa, se ei koskaan kutistu, ellet kutsu jotain funktiota (kuten resize()), joka kutistaa sen eksplisiittisesti. Kun vector kasvaa tehtävään riittävän suureksi, tuhoamisen ja kopioinnin vaatima lisätyö poistuu. Yleisesti voidaan sanoa, että kokonaislisätyö ei koskaan ole huomattavasti suurempi kuin pyydettyjen alkioiden maksimimäärä. Sinun ei tule turhaan murehtia tätä lisätyötä. Joskus on hyötyä siitä, että tietää, että vector säilyttää aina käyttämänsä muistin, myös silloin kun poistat alkioita - kunhan et poista viimeistä alkiota. Jos tällainen muistinvaraus muuttuu ongelmaksi - jos sinulla on esimerkiksi suuri, harva vector-säiliö, joka vie muistitilaa, jota tarvitset uudelle vector-säiliölle, voit käyttää sijoitusta tai swap()-funktiota. Seuraavassa on esimerkiksi funktio, joka typistää vector-säiliön varaaman muistin takaisin sellaiseksi, että se on vain käytössä olevien alkioiden kokoinen: template <typename T> void trim_vector(vector<t>& V) vector<t> apu(v.begin(), V.end()); V = apu; // Vaihtoehtoisesti käytä V.swap(apu);... //... sillä on sama vaikutus, mutta on tehokkaampi Molemmilla tavoilla V:n varaama muisti sisältää lopulta vain käytössä olevien alkioiden tarvitseman muistitilan. Alkuperäinen muistialue (joka on saattanut kasvaa hyvinkin suureksi) vapautetaan, koska alkuperäinen olio V tuhotaan. Lisää koon muutto -testejä Funktion koon_muutto_testit(100) tulostus on seuraava: koon muutto V -> 1 1 kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppukoon muutto V -> 10 9 kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppukoon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppukoon muutto V -> 20 1 oletusmuodostinfunktio 81 tuhoajafunktio -loppu-

45 Standardi mallikirjasto koon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 1 tuhoajafunktio -loppukoon muutto V -> kopiomuodostin 1 oletusmuodostinfunktio 51 tuhoajafunktio -loppukoon muutto V -> 0 1 oletusmuodostinfunktio 102 tuhoajafunktio -loppu- Vertaa tätä tulostukseen, kun funktion kutsu oli muotoa koon_muutto_testit(). Tässä olemme välttäneet 100-alkion muistin uudelleenvaraamisen aiheuttaman lisätyön, koska olemme käyttäneet sopivaa reserve()-funktiota vector-säiliön esittelyn yhteydessä. Tällöin käsittelyssä ei ole mitään lisätyötä, ellet lisää enemmän alkioita kuin varasit niitä. Lisäys alkuun ja loppuun -testit Funktion lisays_testit() tulostus on seuraava: 1000 push_back() operaatiota 2023 kopiomuodostin 1023 tuhoajafunktio -loppu lisätty alkuun 2023 kopiomuodostin sijoitus 1023 tuhoajafunktio -loppu- Tässä tulostuksessa esiintyvistä luvuista on helppo päätellä, että alkion lisäys vector-säiliöön johonkin muuhun sijaintikohtaan kuin end(), johtaa mielettömään työhön. Tämä on yksi syy siihen, että vector-säiliö on kaukana parhaasta mahdollisesta vaihtoehdosta. Jos ohjelmasi tarvitsee lisätä alkioita jatkuvasti sarjan keskelle, sinun tulisi vakavasti harkita listsäiliön käyttöä vector-säiliön sijaan. Itse asiassa, jos suorittaisit tämän saman testin list-säiliölle, huomaisit, että sekä push_back- että insert-operaatiot tarvitsisivat vain 1000 kopiomuodostimen kutsua - yksi kutakin lisättävää alkiota kohden. Käytämme list-säiliötä hetken päästä. Sijoitus- ja vertailutestit Funktion sijoitus_ja_vertailu_testit() tulostus on seuraava: X sisältää 77 jäsentä; Y sisältää 456 jäsentä vertailu: X==Y -loppusijoitus itseensä: X=X -loppujärjestys: X<Y 154 pienempi_kuin_operaattori -loppusijoitus: X=Y 867

46 C++ Ohjelmoijan käsikirja 456 kopiomuodostin 77 tuhoajafunktio -loppuvertailu: X==Y 456 yhtasuuruuden_testaus -loppu- Ensimmäisestä raportista näet, että ensimmäisen X==Y;-lauseen suoritus ei muodostanut tapahtumia - miten STL vertailee kahta vector-säiliötä ilman, että se vertailee alkiota? Itse asiassa on tehokkaampaa testata ensin kummankin koko size()-funktiolla. Jos ne ovat eri kokoiset, ne eivät voi olla samat. Samaan tapaan vector-säiliö on fiksu itseensä sijoittamisessa. Se tarkistaa, onko parametrinä saadun vector-säiliön osoite sama kuin sen oman vector-säiliön osoite. Jos osoitteet ovat samat vector voi olettaa, että alkioiden kopioiminen on tarpeetonta. Eli lauseen X=X; kohdalla ei ole mitään raportoitavaa. Miksi operator<() vaatii 154 vertailua eikä 77 vertailua? Tästä näkyy se, että sinun täytyy käyttää operator<()-funktiota kahteen kertaan, jotta saat selville, että alkiot ovat samat lajittelussa: alkiot_ovat_samat =!(a<b) &&!(b<a); // operator<() käytetään kahdesti Tämä tahtoo sanoa sitä, että jos kumpikaan ei tule toista ennen, voimme olettaa, että ne ovat yhtä suuret. Kuten aikaisemminkin mainitsimme, yhtäsuuruus ei välttämättä tarkoita samaa kuin että ne olisivat samat. Jos esimerkiksi operator<() olisi merkkikoosta riippumaton merkkijonon vertailu, niin tällöin alkiot, jotka eroaisivat vain merkkikokonsa puolesta, olisivat yhtä suuret, mutta se ei tee niistä samoja. Ajattele vaikka merkkijonoja macnee ja MacNee - ne eivät ole samat, mutta ne voidaan tulkita yhtä suuriksi merkkikoosta riippumattomassa lajittelussa. X:n ja Y:n järjestys määräytyy ensimmäisen sellaisen sijaintikohdan mukaan, jossa ne ovat erisuuret. Tässä esimerkissä X ja Y ovat täsmälleen samat, joten jokainen sijaintikohta tulee testata operator<()-operaattorilla kahteen kertaan. 868 list-säiliö Olemme käsitelleet vector-säiliön varsin tarkasti: olemme nähneet, miten se käyttää muistia, miten sen iteraattorit toimivat ja miten iteraattorit toimivat mallialgoritmien yleisinä parametreinä. Tämä kattaa STL:n arkkitehtuurin perusteet. list-säiliö kattaa suurelta osin saman rajapinnan kuin vector-säiliökin, mutta se on optimoitu erilaiseen käyttötilanteeseen. STL:n suunnittelijoiden tärkein tavoite oli tehdä tehokkaat mekanismit kaikkiin mahdollisiin eteen tuleviin tilanteisiin. vector-säiliö toimii varsin hyvin tilanteissa, joissa käytettäisiin muutoin C++:n taulukkoa - se on erittäin käyttökelpoinen, yleiskäyttöinen säiliö. On kuitenkin tilanteita, kuten olemme jo nähneet, joissa vector-säiliön käyttö ei ole järkevää. Esimerkissä havaitsimme, että vector-säiliön täytyy tehdä sisäisiä muistin uudelleenvarauksia ja joskus myös mielettömän monta sijoitusta, jotta lisäys (insert) ja poisto(erase) toimisivat. Se toimi varsin hyvin, kun lisäsimme aina säiliön loppuun, mutta todella huonosti, kun lisäsimme tai poistimme alkioita säiliön alkupäässä.

47 Standardi mallikirjasto list-säiliö on, toisin kuin vector-säiliö, erityisesti suunniteltu tilanteisiin, joissa lisäyksiä ja poistoja voidaan suorittaa missä sijaintikohdassa tahansa. Tutustutaan list-säiliöön palaamalla jo hyvinkin tuttuun esimerkkiohjelmaamme. Kokeile itse - list-säiliön käyttö Käsittelimme list-tietorakennetta luvussa 13, kun suunnittelimme Paketti-luokan, joka talletti laatikoita. Voimme nyt toteuttaa AutoKuorma:n käyttämällä STL:n ominaisuuksia. // Esimerkki AutoKuorma-säiliö toteutettuna STL:n list-säiliöllä // Vastaa esimerkkiä 13.1 #include <iostream> #include <algorithm> #include <list> using namespace std; class Laatikko public: Laatikko(double p = 1.0, double s = 1.0, double k = 1.0) : P(p), S(s), K(k) double tilavuus() const return P*S*K; bool operator<(const Laatikko& x) const return tilavuus() < x.tilavuus(); friend ostream& operator<<(ostream& tul, const Laatikko& ltk) tul << "(" << ltk.p << "," << ltk.s << "," << ltk.k << ")"; return tul; private: double P; double S; double K; ; class AutoKuorma typedef list<laatikko> Sisalto; public: AutoKuorma() AutoKuorma(const Laatikko& yksi_ltk) : Kuorma (1, yksi_ltk) template<typename FwdIter> AutoKuorma(FwdIter ensim, FwdIter viim) : Kuorma (ensim, viim) void lisaa_ltk(const Laatikko& uusi_ltk) Kuorma.push_back (uusi_ltk); typedef Sisalto::const_iterator const_iterator; const_iterator alku() const return Kuorma.begin(); const_iterator loppu() const return Kuorma.end(); 869

48 C++ Ohjelmoijan käsikirja private: Sisalto Kuorma; ; inline int satunnaisluku(int lkm) return 1 + static_cast<int>((lkm*static_cast<long>(rand()))/(rand_max+1)); inline Laatikko satunnais_ltk(int raja) return Laatikko(satunnaisluku(raja),satunnaisluku(raja),satunnaisluku(raja)); int main() AutoKuorma Rig(Laatikko(30,30,30)); for(int i = 0; i < 8; ++i) Rig.lisaa_ltk(satunnais_ltk(100)); cout << "Rig1:n sisältö" << endl; copy(rig.alku(), Rig.loppu(), ostream_iterator<laatikko> (cout, "\n")); cout << endl; typedef AutoKuorma::const_iterator LtkIter; LtkIter suuri = max_element(rig.alku(), Rig.loppu()); cout << "Rig1:n suurin laatikko on " << *suuri << ", jonka tilavuus on " << suuri->tilavuus() << endl; cout << endl; cout << "Kopioidaan kaikki laatikot alkaen kohdasta suuri -> Rig2" << endl; AutoKuorma Rig2(suuri, Rig.loppu()); cout << "Rig2:n sisältö" << endl; copy(rig2.alku(), Rig2.loppu(), ostream_iterator<laatikko> (cout, "\n")); cout << endl; return 0; Kun suoritat tämän esimerkin, se tulostaa seuraavaa: Rig1:n sisältö (30,30,30) (20,57,1) (48,59,81) (83,90,36) (86,18,75) (31,52,72) (37,10,2) (99,17,15) (1,12,45) Rig1:n suurin laatikko on (83,90,36), jonka tilavuus on

49 Standardi mallikirjasto Kopioidaan kaikki laatikot alkaen kohdasta suuri -> Rig2 Rig2:n sisältö (83,90,36) (86,18,75) (31,52,72) (37,10,2) (99,17,15) (1,12,45) Kuinka se toimii Ennen kuin katsomme, mitä main()-funktiossa tapahtuu, käsitellään hieman taustatietoja. Tunnemme jo entuudestaan Laatikko-luokan ja miten sitä käytetään. Laatikko-luokan tällä versiolla on kaksi operaattorifunktiota, joista ensimmäinen on: bool operator<(const Laatikko& x) const return tilavuus() < x.tilavuus(); Määrittelemällä operator<()-operaattorin, voimme vertailla kahta laatikkoa - tarvitsemme tätä, kun käytämme STL:n max_element()-funktiota. Lisäksi uudelleenmäärittelemme <<-operaattorin virtaa varten: friend ostream& operator<<(ostream& tul, const Laatikko& ltk) tul << "(" << ltk.p << "," << ltk.s << "," << ltk.k << ")"; return tul; Kun määrittelemme operator<<()-operaattorin ostream-oliolle, voimme helposti kirjoittaa Laatikko-olion tulostusvirtaan. Muista, että luokan ystäväfunktio ei ole luokan jäsen, joten se ei sisällä this-osoitinta. Tulostettava Laatikko-olio välitetään operator<<()-funktiolle eksplisiittisesti toisena parametrinä. Funktion paluuarvon tyyppi on ostream&. Tämä mahdollistaa operaattorin helpon käytön yhdistetyissä lauseissa, koska se mahdollistaa operaattorin käytön useissa peräkkäisissä virtaan kirjoituksissa. AutoKuorma-luokan määrittely on varsin yksinkertainen: class AutoKuorma typedef list<laatikko> Sisalto; // Julkiset jäsenet... private: Sisalto Kuorma; ; Tämä määrittely antaa meille oman rajapinnan sen päälle, mitä list-säiliö antaa meille ilmaiseksi. AutoKuorma-olion sisältö on Kuorma-jäsenmuuttujassa, joka on Laatikko-olioiden lista. Lähes kaikki määrittelemämme funktiot on toteutettu Kuorma-olion vastaavalla funktiolla. Käytämme typedef:ä Sisältö-nimen kohdalla, jotta muiden funktioiden määrittely olisi miellyttävämpää. Olisimme yhtä hyvin voineet kirjoittaa: private: list<laatikko> Kuorma; 871

50 C++ Ohjelmoijan käsikirja AutoKuorma-luokalla on kolme muodostinfunktiota: AutoKuorma() AutoKuorma(const Laatikko& yksi_ltk) : Kuorma (1, yksi_ltk) template<typename FwdIter> AutoKuorma(FwdIter ensim, FwdIter viim) : Kuorma (ensim, viim) Ensimmäinen muodostinfunktio on oletusmuodostinfunktio ja toinen muodostinfunktio alustaa AutoKuorma-olion siten, että se sisältää yhden Laatikko-alkion. Kolmas muodostinfunktio on mallimuodostin. Voimme välittää iteraattoreita tälle muodostinfunktiolle, jolloin voimme alustaa AutoKuorma-olion jo olemassa olevan Laatikkoolioiden sarjan kopiolla. Iteraattorit on esitelty mallin avulla; eli iteraattoreiden tyyppi on hyvin joustava - ne voivat olla osoittimia Laatikko-taulukkoon, iteraattoreita vector<>- tai list<>pohjaisiin laatikoiden kokoelmiin tai jopa iteraattoreita toisen AutoKuorma-olion sisältöön. Tämä on helppo toteuttaa. Kuorma-olio on list-säiliö, joten se sisältää jo mallipohjaisen muodostinfunktion, joka muodostaa alkiot olemassa olevasta sarjasta. Meidän tarvitsee ainoastaan välittää parametrit ensim ja viim, sellaisina kuin ne saimme, Kuorma-muodostinfunktiolle, joka tekee loput. Laatikko-olion lisääminen AutoKuorma-olioon tehdään funktiolla lisaa_tlk(): void lisaa_ltk(const Laatikko& uusi_ltk) Kuorma.push_back (uusi_ltk); Olisimme voineet käyttää push_front()-funktiota, joka olisi lisännyt Laatikko-olion Kuorma-listan alkuun. Autoja ei kuitenkaan yleensä lastata tällä tavalla, joten käytämme push_back()-funktiota ja lisäämme Laatikko-olion listan loppuun. Meillä on toinenkin typedef: typedef Sisalto::const_iterator const_iterator; 872 Tässä Sisältö on tyypin list<laatikko> synonyymi, joten Sisalto::const_iterator on list<laatikko>::const_iterator:n synonyymi. Se on hyödyllinen lyhennelmä, mikäli muistat, mitä synonyymit tarkoittavat! Sallimme AutoKuorma-luokan asiakkaiden käyttää kuorman sisällön iteraattoreita typedef:n jälkeen tulevien jäsenten avulla: const_iterator alku() const return Kuorma.begin(); const_iterator loppu() const return Kuorma.end(); Nämä ovat const-tyyppisiä iteraattoreita. Emmehän me halua, että asiakkaamme muuttavat AutoKuorma:n sisältöä selkämme takana! Kaksi funktiotamme, alku() ja loppu(), palauttavat suoraan Kuorma-iteraattorit. Tämä ei ole paras mahdollinen toteutustapa, koska paljastamme tässä toteutuksemme - eli, että käytämme sisäisesti list-säiliötä. Se on kuitenkin hyvin miellyttävää, ja jos asiakkaamme ovat hyvin käyttäytyviä, he eivät käytä hyödykseen sitä tietoa, että nämä iteraattorit osoittavat listsäiliöön, eikä johonkin muuhun mahdollisesti valitsemaamme säiliöön.

51 Standardi mallikirjasto Koska palautamme iteraattorit, AutoKuorma-oliomme käyttäytyy pitkälti säiliön tapaan. Tämä ei ole huono asia, koska AutoKuorma on paljolti säiliön kaltainen. Ohjelma käyttää globaaleita funktioita satunnaisluku() ja satunnais_ltk(), joiden avulla voimme helposti luoda satunnaisia laatikoita ohjelman käyttöön: inline int satunnaisluku(int lkm) return 1 + static_cast<int>((lkm*static_cast<long>(rand()))/(rand_max+1)); inline Laatikko satunnais_ltk(int raja) return Laatikko(satunnaisluku(raja),satunnaisluku(raja),satunnaisluku(raja)); Näimme tämän saman satunnaisluku()-funktion määrittelyn edellisessäkin AutoKuorma-esimerkissämme - sen toiminnan yksityiskohdat on kerrottu luvussa 13. Nyt voimme katsoa joitakin main()-funktion kiinnostavia osia. Tutkitaan ensin seuraavia lauseita: cout << "Rig1:n sisältö" << endl; copy(rig.alku(), Rig.loppu(), ostream_iterator<laatikko> (cout, "\n")); cout << endl; copy()-funktion kolmas parametri voidaan muuntaa seuraavanlaiseksi: ostream_iterator<laatikko> tul(cout, "\n") *tul = satunnais_ltk(); Tässä ostream_iterator luo väliaikaisen muuttujan tul. Muodostinfunktiolle välitetään kaksi parametriä: tulostusvirta, johon teksti kirjoitetaan ja valinnainen erotinmerkki, joka kirjoitetaan jokaisen alkion perään. Tässä tulostamme rivinvaihdon ( \n -merkin) jokaisen tulostetun Laatikko-olion perään. Muuttuja tul on ostream_iterator<>-tyyppinen iteraattori. Tämä tarkoittaa sitä, että voimme käyttää sitä samaan tapaan kuin muitakin tulostusiteraattoreita. Kun sijoitamme *tulmuuttujaan, Laatikko-olio sijoitetaan ja kirjoitetaan virtaan cout. Rivinvaihto lisätään automaattisesti. Eli luomme tässä itse asiassa olion, johon copy()-algoritmi voi kirjoittaa kopioitavan Laatikko-olion, eli Laatikko-olio tulostuu muotoiltuna cout-virtaan. copy()-algoritmin kaksi ensimmäistä parametriä on kopioitavien laatikoiden väli, joka tässä esimerkissä on koko AutoKuorma, ensimmäisestä alkiosta (alku()) viimeiseen alkioon (end()). STL:n oliot toimivat tavallisesti yhteen juuri tällä tavalla. Huomaa, että ostream_iterator käyttää Laatikko::operator<<(ostream, Laatikko)-operaattoriamme kontrolloimaan, miten Laatikko-olio kirjoitetaan cout-virtaan. Tämä kaikki voidaan sanoa lyhyesti ja ytimekkäästi tulosta kaikki laatikot. Meillä on lisäksi typedef-lause, joka määrittelee LtkIter:n, jota sitten käytetään esiteltäessä vakiotyyppinen iteraattori suuri: 873

52 C++ Ohjelmoijan käsikirja typedef AutoKuorma::const_iterator LtkIter; LtkIter suuri = max_element(rig.alku(), Rig.loppu()); Muista, että AutoKuorma::const_iterator on list<laatikko>::const_iterator:n synonyymi. Tällä kertaa tässä on pieni ero: asiakas ei nyt tiedä tätä, eikä tarvitsekaan tietää. Asiakkaan kannalta const_iterator on anonyymi tyyppi, AutoKuorma-olion tarjoama julkinen rajapinta, jota käytetään AutoKuorma::alku()- ja AutoKuorma::loppu()-funktioiden palauttamien iteraattoreiden avulla. Olemme käyttäneet tässä myös STL:n max_element()-algoritmia. Sen parametrit määrittelevät välin, josta se etsii suurinta arvoa ja se palauttaa iteraattorin löydettyyn suurimpaan arvoon. Jos väli on tyhjä, palautettava iteraattori on Rig.loppu(). STL:n työskentelyn perusominaisuuksia on se, että voimme käyttää tyyppejä, jotka on määritelty ohjelmasi olioissa. Kuten tässäkin esimerkissä LtkIter on jonkinlainen iteraattori, jonka AutoKuorma-luokka tarjoaa, mutta emme tällä tasolla tiedä, että se on todellisuudessa STL:n list-säiliön iteraattori. Ainoastaan AutoKuorma-olio tietää tällaisen sisäisen yksityiskohdan. AutoKuorma-olion osan, alkaen suurimmasta löydetystä arvosta, kopioimme seuraavilla lauseilla: cout << "Kopioidaan kaikki laatikot alkaen kohdasta suuri -> Rig2" << endl; AutoKuorma Rig2(suuri, Rig.loppu()); Käytämme kopiomuodostinta, jolle välitetään kopioitava iteraattoriväli. Tämä havainnollistaa myös sitä, että max_element()-algoritmin palauttama iteraattori toimii aivan samaan tapaan kuin mikä muukin iteraattori tahansa ja sitä voidaan käyttää viittaamaan AutoKuorma-olion Laatikkoolioihin. Kommentteja list-säiliöstä list-säiliöllä on useita etuja vector-säiliöön verrattuna. Suurin etu on siinä, että kaikki lisäykset vievät vakiomittaisen ajan. Tämä tarkoittaa sitä, että alkion lisäys tai alkion poisto mistä tahansa sijaintikohdasta on tehokkaampaa list-säiliön kohdalla. vector-säiliön kohdalla alkion lisäykset ja poistot mihin tahansa muuhun sijaintikohtaan kuin säiliön loppuun voivat olla erittäin raskaita. list-säiliöllä on lisäksi erikoisfunktio splise() - tämä funktio poistaa alkiot automaattisesti tietystä lähteestä ja siirtää ne kohteeseen. Se säästää erillisten insert- ja erase-vaiheiden käytöltä saman asian toteuttamiseksi. 874 Yksi list-säiliön huono puoli on se, että et enää voi käyttää hajasaantia operaattorin operator[] avulla. Jos hajasaanti on ohjelmallesi tärkeää, sinun tulee käyttää vector-säiliötä (tai sitä lähellä olevaa deque-säiliötä). list-säiliön iteraattorit ovat bidirectional-tyyppisiä. Voit siirtyä tehokkaasti eteen- ja taaksepäin, mutta et voi tehdä suuria hyppyjä. Monet algoritmit toimivat tehokkaasti list-säiliön kanssa, vaikka se ei tarjoakaan hajasaantia. Yleiskäyttöinen sort()-algoritmi on eräs niistä algoritmeista, jotka eivät toimi list-säiliön kanssa. list-säiliö tarjoaa kuitenkin oman sort()-funktionsa, jota voidaan käyttää yleisen sort()-algoritmin sijaan:

53 Standardi mallikirjasto vector<int> V; list<int> L; sort(v.begin(), V.end()); sort(l.begin(), L.end()); L.sort(); // OK // Väärin!!! sort()-funktio ei toimi list:n yhteydessä // OK, list sisältää oman sort()-funktion list- ja vector-säiliöt eroavat myös monilla muilla pienillä tavoilla. Lue lisää STL:n säiliöiden dokumenteista. Assosiatiivinen map-säiliö Tähän saakka olemme käsitelleet ainoastaan vector- ja list-säiliöitä. Olemme nähneet, miten ne pitävät kirjaa alkioistaan - alkiot ovat siinä järjestyksessä kuin olet ne säiliöön lisännyt. Ne kuuluvat peräkkäissäiliöihin (kolmas säiliö tässä ryhmässä on deque). Peräkkäissäiliöt eivät ole kovinkaan käyttökelpoisia, jos sinun täytyy käsitellä alkioita niiden arvojen perusteella. Alkiot lajitellaan, ja niihin päästään käsiksi tällöin avaimen perusteella. Voisimme esimerkiksi etsiä säiliöstä kaikki Henkilo-alkiot, joiden sukunimi on Koskela - sukunimi on avain tässä tapauksessa. Tietysti peräkkäissäiliöistä voidaan hakea tietoa tällä tavalla, mutta STL:n assosiatiivisäiliöt ovat sopivampia tällaiseen tarkoitukseen. Assosiatiivisäiliöt toimivat siten, että ne tallettavat alkiot sisäisesti avaimen mukaiseen järjestykseen. Kun lisäät uuden alkion, avainta käytetään alkiota säiliöön lisättäessä. Alkio lisätään siten, että lajiteltu järjestys säilyy. Assosiatiivisen säiliön alkiota käsitellään myöskin avaimen avulla. Jos tiettyä tietotyyppiä käytetään avaimena, tälle tyypille tulee olla määritelty operator<()- järjestysoperaattori. Alkioita, joille ei ole määritelty operator<()-funktiota, ei voida käyttää assosiatiivisen säiliön avaimina. Avaimina käytetylle tyypille ei ole muita rajoituksia - joten avain voi olla mitä tahansa sopivaa tyyppiä, jonka järjestys voidaan määritellä. Assosiatiivisäiliöitä on neljä erilaista: map, set, multimap ja multiset. Näiden eroja on helpompi käsitellä, jos tarkastelemme niitä pareittain. map- ja set-säiliöt sopivat tilanteisiin, joissa voimme olla varmoja, että avaimet ovat yksikäsitteisiä. multimap- ja multiset-säiliöt sopivat puolestaan tilanteisiin, joissa tarvitaan useita samoja avaimen arvoja. Toisaalta map- ja multimap-säiliön alkioilla on muitakin arvoja (ovat tavallisesti olioita); set- ja multisetsäiliöillä on vain avainarvot. Havainnollistetaan, mitä tämä tarkoittaa. Voimme esimerkiksi käyttää map- tai multimap-säiliötä tallettamaan nimiä ja puhelinnumeroita. Jokainen tällainen säiliö tallettaa alkioita, joissa avain on liitetty olioon - tässä tapauksessa nimi on avain ja puhelinnumero on siihen liitetty olio. Kumpaa meidän kannattaa käyttää? Jos käytämme map-säiliötä, avaimen tulee olla yksikäsitteinen: tämä tarkoittaa sitä, että jokaisella henkilöllä voi olla vain yksi puhelinnumero. Koska monilla ihmisillä on useampia kuin yksi puhelinnumero, meidän tulee olla mahdollista tallettaa useita samoja avaimia. multimap-säiliö mahdollistaa tämän. 875

54 C++ Ohjelmoijan käsikirja Jollain tavalla multimap on kaikkein yleisin assosiatiivisista säiliöistä. multimapsäiliön käytöllä on kuitenkin haittansakin. Kun käytät yleisempää multimap-säiliötä, et voi käyttää kätevää operator[]-operaattoria, jolla alkioita voidaan käsitellä avaimen perusteella. set-säiliöillä on avain, mutta (toisin kuin map-säiliöillä) niissä avaimeen ei ole liitetty oliota. Avain on itse asiassa samalla alkion arvo. Tämä tekee niistä map-säiliöitä yksinkertaisemman, mutta joustamattomamman. Katsotaan seuraavaksi, miten näitä assosiatiivisia säiliöitä käytetään ja miten valitsemme tehtävään parhaiten soveltuvan säiliön. Kokeile itse - map-säiliöiden käyttö Yksi tapa käyttää assosiatiivisia säiliöitä on laskea montako kertaa tietty sana esiintyy tekstissä. // Esimerkki Yksinkertainen sanojen laskenta #include <iostream> #include <iomanip> #include <string> #include <sstream> #include <map> using namespace std; const char* twister = "How much wood would a woodchuck chuck if a woodchuck " "could chuck wood? A woodchuck would chuck as much wood " "as a woodchuck could chuck if a woodchuck could chuck wood."; int main() typedef map<string, int> Sanat; typedef Sanat::const_iterator SanaIter; Sanat M; istringstream teksti(twister); istream_iterator<string> alku(teksti); istream_iterator<string> loppu; for( ; alku!= loppu ; ++alku) M[*alku]++; for(sanaiter m = M.begin() ; m!= M.end() ; ++m) cout << setw(6) << m->second << " " << m->first << endl; return 0; Esimerkin tulostus näyttää seuraavalta: A 1 How 4 a 2 as

55 Standardi mallikirjasto 5 chuck 3 could 2 if 2 much 2 wood 1 wood. 1 wood? 5 woodchuck 2 would Kuinka se toimii Sisällytämme ensin map-otsikkotiedoston: #include <map> Sekä map että multimap on esitelty otsikkotiedostossa <map>. Emme halua kirjoittaa joka kerta map<string,int>::const_iterator, joten hyödynnämme typedef-komentoa: typedef map<string, int> Sanat; typedef Sanat::const_iterator SanaIter; Sanat M; map<>-mallille välitetään kaksi parametriä. Ensimmäinen on avaimen tietotyyppi ja toinen on siihen liitetyn olion tyyppi. Tässä ohjelmassa avaimemme ovat string-tyyppisiä, joka mallintaa tekstistä löydettyä sanaa. Jokaiseen merkkijonoon liitetty arvo on int-tyyppinen, jota käytämme avaimen esiintymiskertojen laskentaan. Tulostamme sanat SanaIter:n avulla. Seuraavaksi käytämme string-virtaoliota iteraattorin kanssa: istringstream teksti(twister); istream_iterator<string> alku(teksti); istream_iterator<string> loppu; for( ; alku!= loppu ; ++alku) M[*alku]++; Olemme jo aikaisemminkin käyttäneet istringstream:a. Liitämme tekstin alku-iteraattoriin ja käymme läpi kaikki tekstin merkkiryhmät. Käymme todellakin läpi merkkiryhmät, eikä välttämättä sanat, koska istream_iterator ei ole kovinkaan älykäs - se osaa erottaa vain merkkiryhmiä, joiden välissä on tyhjä merkki, kuten seuraava tulostuskin kertoo: 2 wood 1 wood. 1 wood? Kuten huomaat, sana wood löytyy kolmesta erilaisesta merkkiryhmästä: wood, wood. ja wood?. Sanojen lisäys ja laskenta on niin yksinkertainen toimenpide, että se on saattanut jäädä sinulta huomaamatta. Ne tehdään molemmat samassa lauseessa: 877

56 C++ Ohjelmoijan käsikirja for( ; alku!= loppu ; ++alku) M[*alku]++; Lauseke *alku palauttaa merkkijonon, joka sisältää yhden tekstistä irottamamme merkkiryhmän. Säiliö M indeksoidaan tässä string-tyyppisellä avaimella. Assosiatiivisen säiliön alkioita käsitellään assosiatiivisesti - eli kun säiliölle annetaan avain, se palauttaa siihen liittyvän arvon. map-säiliön kohdalla operator[]-operaattori on itse asiassa insert()-funktion lyhyempi muoto. Tämä tarkoittaa, että sinun tulee olla joskus hieman varovainen. Tutkitaanpa seuraavia lauseita: if(m["lihakset"] > 0) cout << "liikaa muskelia" << endl; Tämä näyttää siltä, että se testaa, onko merkkijonon lihakset lukumäärä suurempi kuin 0, mutta varo tätä! Johtuen operator[]-operaattorin toteutustavasta map-säiliön kohdalla, tämä lause lisää lihakset merkkijonon säiliöön, jos se ei siellä vielä ole. Kun alkiot lisätään tällä tavalla, avaimeen liitetty olio alustetaan oletusmuodostinfunktiolla. Kokonaisluvun kohdalla tämä tarkoittaa, että olio alustetaan lausekkeella int(). Kuten kaikkien perustietotyyppien alustusfunktioiden kohdalla, int() palauttaa int-tyypin oletusarvon, joka on nolla. Eli kaikkien tällä tavalla lisättyjen avaimien lukumääräksi asetetaan aluksi nolla. Jos haluat testata tietyn avaimen olemassaolon, voit käyttää count()-metodia: if(m.count("lihakset")) cout << "liikaa muskelia" << endl; Lauseke M.count() palauttaa arvon 1, jos avain lihakset on olemassa ja arvon 0 muulloin. Eli tämä lauseke tarkistaa, onko liikaa muskelia, ilman edellisen esimerkin sivuvaikutuksia. Lauseke M[*alku] palauttaa viittauksen avaimeen *alku liitetyn arvon, joka on talletettu mapsäiliöön. Tämä mahdollistaa lukumäärän kasvattamisen yhdessä lausekkeessa M[*alku]++. Kun olemme laskeneet eri sanojen lukumäärät, voimme tulostaa ne yksinkertaisessa silmukassa: for(sanaiter m = M.begin() ; m!= M.end() ; ++m) cout << setw(6) << m->second << " " << m->first << endl; 878 Yritetään seuraavaksi saada sanat lukumäärän mukaiseen järjestykseen käyttämällä multimapsäiliötä. Kokeile itse - Sanojen laskenta multimap-säiliön avulla Laajennetaan esimerkkiä siten, että käytämme nyt multimap-säiliötä. // Esimerkki Sanojen laskenta multimap-säiliön avulla #pragma warning ( disable : 4786) #include <iostream> #include <iomanip> #include <string> #include <sstream> #include <map>

57 Standardi mallikirjasto using namespace std; const char* twister = "How much wood would a woodchuck chuck if a woodchuck " "could chuck wood? A woodchuck would chuck as much wood " "as a woodchuck could chuck if a woodchuck could chuck wood."; int main() typedef map<string, int> Sanat; typedef Sanat::const_iterator SanaIter; Sanat M; istringstream teksti(twister); istream_iterator<string> alku(teksti); istream_iterator<string> loppu; for( ; alku!= loppu ; ++alku) M[*alku]++; typedef multimap<int,string,greater<int> > SanaSija; typedef SanaSija::const_iterator SijaIter; SanaSija R; for(sanaiter m = M.begin() ; m!= M.end() ; ++m) R.insert(make_pair(m->second,m->first)); for(sijaiter r = R.begin() ; r!= R.end() ; ++r) cout << setw(6) << r->first << " " << r->second << endl; return 0; Esimerkki tulostaa seuraavaa: 5 chuck 5 woodchuck 4 a 3 could 2 as 2 if 2 much 2 wood 2 would 1 A 1 How 1 wood. 1 wood? Kuinka se toimii Olemme lisänneet kaksi uutta typedef:ä: typedef multimap<int,string,greater<int> > SanaSija; typedef SanaSija::const_iterator SijaIter; SanaSija R; 879

58 C++ Ohjelmoijan käsikirja Tällä kertaa käytämme multimap-säiliötä map-säiliön sijaan ja määrittelemme lajitteluperusteen. Funktio greater() on yksi STL:n vertailufunktioista. Kun käytämme greater<int>-funktiota oletusarvoisen less<>:n sijaan, voimme tulostaa sijat laskevaan järjestykseen. Olisimme tietysti voineet myös käydä SanaSija-taulun alkiot läpi käänteisessä järjestyksessä käyttämällä funktioita rbegin() ja rend(), mutta olemme käyttäneet niitä jo aikaisemmin. Seuraavaksi sijoitamme alkiot säiliöön R. Kun käytimme map-säiliötä esimerkissä 20.17, pystyimme lisäämään alkiot operaattorilla operator[](). Emme voi tehdä näin nyt, koska multimap-säiliöllä ei edes ole operaattoria operator[]() - tämä siitä syystä, että multimap-säiliön kohdalla indeksointioperaattorilla ei ole mitään merkitystä, koska avainten ei tarvitse olla yksikäsitteisiä. Sen sijaan käytämme R.insert()-metodia: for(sanaiter m = M.begin() ; m!= M.end() ; ++m) R.insert(make_pair(m->second,m->first)); Tässä on tärkeää, että R.insert()-metodille välitetään oikean tyyppinen parametri. Eli tässä tapauksessa R.insert() odottaa parametriä, joka on assosiatiivinen pari, joka yhdistää int-arvon ja string-arvon. Onneksi STL sisältää erikoisen tietotyypin pair<>, jota käytetään useissa yhteyksissä - säiliön alkioiden määrittely on yksi niiden käyttökohde. Tietotyyppi pair<> näyttää suunnilleen seuraavalta: template <typename Avain, class Arvo> struct Pair Pair(const Avain& f, const Arvo& s) : first(f), second(s) ; Avain first; Arvo second; ; Kuten huomaat, se on struktuurimalli, joka liittää kaksi erityyppistä tietoalkiota toisiinsa. On sovittu, että ensimmäistä alkiota kutsutaan termillä first ja arvo-alkiota kutsutaan termillä second. makepair()-funktio on yksi niistä harvoista funktioista, joka tekee juuri sen mitä maalaisjärkikin sanoo. Se määritellään seuraavasti: template <typename T1, class T2> pair<t1,t2> make_pair(const T1& avain, const T2& arvo) return pair<t1,t2>(avain,arvo); Eli make_pair() yksinkertaisesti palauttaa parin, joka vastaa parametrien tyyppejä. Eli tässä ohjelmassa voisimme korvata funktion make_pair() kutsun seuraavalla lauseella: 880 for(sanaiter m = M.begin() ; m!= M.end() ; ++m) R.insert(pair<int, string>(m->second,m->first)); Tämä havainnollistaa, mitä make_pair()-funktio saa aikaan. Ensimmäisen parametrin m->second tyyppi on int ja toisen parametrin m->first tyyppi on string - eli juuri sellainen pari kuin odotimmekin. Katsotaan tarkemmin kahden for-silmukan sisällä olevia lauseita: for(sanaiter m = M.begin() ; m!= M.end() ; ++m) R.insert(make_pair(m->second,m->first));

59 Standardi mallikirjasto for(sijaiter r = R.begin() ; r!= R.end() ; ++r) cout << setw(6) << r->first << " " << r->second << endl; Ensimmäinen on Sanat-iteraattorista ja toinen on SanaSija-iteraattorista. Muista, että SanaSija on Sanat-iteraattori käännettynä - eli meillä on tässä erittäin ruma ja vaarallinen tilanne. Ensimmäisessä lauseessa m->first on kirjainryhmä, mutta toisessa r->second on sanaryhmä - tässä on todella suuri sekaannuksen vaara! Näyttää helpolta pitää ne kaksi erillään tässä esimerkissä, mutta laajassa ohjelmassa, jossa on useita keskenään suhteessa olevia säiliöitä, first ja second voivat joskus mennä kiireessä väärin päin. Tällaisessa tilanteessa haluat varmastikin lisätä koodiin muutaman kommentin, jotka kertovat selkeästi, mistä on kyse. Suorituskyky ja erikoistuminen Tämän luvun alussa, kun ensimmäisen kerran tapasimme iteraattorit ja algoritmit, näimme monta tapaa, joilla funktio keskiarvo() voitaisiin kirjoittaa. Seuraavassa on keskiarvo()-funktion toteutus, jossa käytetään osoitinrajapintaa. Toteutus on yksinkertainen ja tehokas. // Yksinkertainen keskiarvo-funktio, kirjoitettu ilman STL:n funktioita double keskiarvo(float* ensim, float* viim) double summa = 0.0; for ( ; ensim!= viim ; ++ensim) summa += *ensim; return summa / (viim-ensim); Vaihtoehtoisesti voimme käyttää STL:n mallialgoritmia accumulate(): // Keskiarvo-funktio, jossa käytetään STL:n accumulate-funktiota template <typename RndIter> double keskiarvo(rnditer ensim, RndIter viim) return accumulate(ensim, viim, 0) / (viim-ensim); Tämä on myös varsin yksinkertainen, mutta mitä todella hyödymme korvaamalla kolme yksinkertaista riviä kirjastofunktiolla? Ennen kuin vastaamme tähän, katsotaan jotain, mitä saatat löytää STL:n agressiivisesta toteutuksesta: // Mallin template accumulate<> erikoistaminen tyypin float* iteraattorille #include <numeric> template<> float std::accumulate(float* ensim, float* viim, float init) double s0 = 0.0; double s1 = 0.0; double s2 = 0.0 double s3 = 0.0; int jako_osat = (viim - ensim) / 4; float* jako = ensim + 4 * jako_osat; for( ; first < burst ; first+=4) 881

60 C++ Ohjelmoijan käsikirja s0 += first[0]; s1 += first[1]; s2 += first[2]; s3 += first[3]; for( ; jako < viim ; ++jako) s0 += *jako; return init + (s0 + s1 + s2 + s3); Tätä tekniikkaa kutsutaan erikoistamiseksi. Tämä tarkoittaa sitä, että rutiinista tehdään lisäversio, joka toimii tietyssä tilanteessa paremmin kuin täysin yleinen algoritmi. Tämä erikoistunut funktio on tarkoitettu laskemaan yhteen osoittimien osoittama float-arvojen sarja. Se näyttää huomattavasti monimutkaisemmalta kuin yksinkertainen silmukkamme, mutta se laskee saman tuloksen: template<> float accumulate<float*>(float* ensim, float* viim, float init) Tämän erikoistamisen idea on seuraavissa neljässä lauseessa: 882 s0 += first[0]; s1 += first[1]; s2 += first[2]; s3 += first[3]; Ideana on luoda enemmän rinnakkaisuutta. Sen sijaan, että laskemme kaikki summat yhteen muuttujaan, josta voi tulla pullonkaula, tässä versiossa käytetään neljää osasummaa, jotka kasvavat rinnakkain. Nopeassa tietokoneessa nämä kaikki neljä lisäystä saatetaan suorittaa yhtä aikaa. Miten tämä versio pärjää verrattaessa yksinkertaiseen silmukkaan? No, tämä riippuu kääntäjästäsi, STL:n toteutuksestasi ja alkioiden lukumäärästä. Voi olla, että erikoistunut versio ei ole lainkaan nopeampi, mutta se voi olla jopa neljä kertaa nopeampi. On mielenkiintoista huomata, että erikoistumisen avulla voidaan saavuttaa myös suurempi tarkkuus. Kun ensimmäisen kerran käsittelimme liukuluku-tietotyyppiä luvussa 2, huomasimme, että liukulukuarvojen summaus on luonnostaan riskialtis operaatio. Jos sinulla on vähänkin huonoa onnea, tulos voi olla hyvin epätarkka, johtuen summauksessa tapahtuneista pyöristyksistä. Tämä erikoistunut malli käyttää sisäisissä laskennoissaan tarkempaa double-tyyppiä, joten se on myös yksinkertaista silmukkaratkaisua tarkempi, vaikka se toimiikin suuremmalla nopeudella. Todella hyvä erikoistunut versio - joka on vielä tätäkin esimerkkiä hienostuneempi - voi käyttää erilaisia temppuja siten, että lähes kaikki pyöristysvirheet häviävät ja suorituskyky säilyy silti hyvänä. Sellaisen koodin kirjoittaminen on hyvin konstikasta. Jopa kaikkein yksinkertaisimmat algoritmit voidaan kirjoittaa yllättävillä ja nokkelilla tavoilla. STL:n toteutukset paranevat koko ajan. Jos suorituskyky on tärkeää, käytä STL:n algoritmeja aina kuin se vain on mahdollista. On todennäköistä, että ne ovat hienostuneempia kuin mitä itse kirjoittaisit.

61 Yhteenveto Standardi mallikirjasto Tämä oli tavallaan lyhyt vaellus uuden maaston läpi. Yritimme näyttää kiinnostavat näkymät matkan varrelta. Silti emme ole käsitelleet kuin hyvin pienen murto-osan STL:n mahdollisuuksista, mutta nyt sinulla pitäisi olla tarpeeksi pohjaa omia tutkimuksiasi varten. Tämän luvun tärkeimpiä aiheita olivat: STL:n toiminnallisuus piilee kolmessa erityyppisessä mallissa: säiliöt, iteraattorit ja algoritmit. Säiliöiden avulla voidaan tallettaa ja järjestää tietyntyyppisiä olioita, kunhan talletettavat oliot täyttävät alkioiden perusvaatimukset. Iteraattorit ovat olioita, jotka voivat toimia kuten osoittimet. Iteraattorit ovat esimerkki fiksuista osoittimista. Iteraattoreita käytetään säiliön olioiden käsittelyssä tai olioiden syötössä virrasta. Iteraattoreita käytetään pareittain määrittelemään joukko olioita puoliavoimelta väliltä. Ensimmäinen olio otetaan mukaan väliin, mutta viimeistä ei oteta. Algoritmit ovat yleistettyjä standardifunktioita, joita voidaan käyttää iteraattoreiden avulla määritellylle oliojoukolle. Algoritmit ovat riippumattomia säiliöistä, mutta niitä voidaan käyttää periaatteessa minkä tahansa säiliön olioille iteraattoreiden avulla. Harjoituksia 20.1 Tee ohjelma, joka käyttää vector-säiliötä tallettamaan string-olioihin näppäimistöltä syötettyjä kaupunkien nimiä Lisää edellisen harjoituksen ohjelmaan koodi, joka käyttää sort()-algoritmia lajittelemaan kaupungit nousevaan järjestykseen ennen niiden tulostusta Tee ohjelma, joka lukee näppäimistöltä halutun määrän nimiä ja niihin liitettyjä puhelinnumeroita (esimerkiksi muodossa Laurel, Stan ) ja tallettaa ne map-säiliöön siten, että numero saadaan selville nimen perusteella. Tee muutama satunnainen haku map-säiliöstä sen jälkeen, kun säiliöön on luettu muutamia nimiä ja numeroita Lisää edellisen harjoituksen koodiin iteraattori, jonka avulla tulostat map-säiliön sisällön Lisää esimerkin koodi, joka korvaa välimerkit tyhjillä merkeillä ja tulostaa sanat nousevassa järjestyksessä. 883

62 884 C++ Ohjelmoijan käsikirja

Osoitin ja viittaus C++:ssa

Osoitin ja viittaus C++:ssa Osoitin ja viittaus C++:ssa Osoitin yksinkertaiseen tietotyyppiin Osoitin on muuttuja, joka sisältää jonkin toisen samantyyppisen muuttujan osoitteen. Ohessa on esimerkkiohjelma, jossa määritellään kokonaislukumuuttuja

Lisätiedot

815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 5 Vastaukset

815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 5 Vastaukset 815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 5 Vastaukset Harjoituksen aiheena ovat aliohjelmat ja abstraktit tietotyypit sekä olio-ohjelmointi. Tehtävät tehdään C-, C++- ja Java-kielillä.

Lisätiedot

Demo 6 vastauksia. 1. tehtävä. #ifndef #define D6T1 H D6T1 H. #include <iostream> using std::ostream; using std::cout; using std::endl;

Demo 6 vastauksia. 1. tehtävä. #ifndef #define D6T1 H D6T1 H. #include <iostream> using std::ostream; using std::cout; using std::endl; Demo 6 vastauksia 1. tehtävä #ifndef #define D6T1 H D6T1 H #include using std::ostream; using std::cout; using std::endl; #include using std::string; 10 template class

Lisätiedot

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta

Operaattoreiden ylikuormitus. Operaattoreiden kuormitus. Operaattoreiden kuormitus. Operaattoreista. Kuormituksesta C++ - perusteet Java-osaajille luento 5/7: operaattoreiden ylikuormitus, oliotaulukko, parametrien oletusarvot, komentoriviparametrit, constant, inline, Operaattoreiden ylikuormitus Operaattoreiden kuormitus

Lisätiedot

12 Mallit (Templates)

12 Mallit (Templates) 12 Mallit (Templates) Malli on määrittely, jota käyttämällä voidaan luoda samankaltaisten aliohjelmien ja luokkien perheitä. Malli on ohje kääntäjälle luoda geneerisestä tyyppiriippumattomasta ohjelmakoodista

Lisätiedot

Ohjelmointi 1 Taulukot ja merkkijonot

Ohjelmointi 1 Taulukot ja merkkijonot Ohjelmointi 1 Taulukot ja merkkijonot Jussi Pohjolainen TAMK Tieto- ja viestintäteknologia Johdanto taulukkoon Jos ohjelmassa käytössä ainoastaan perinteisiä (yksinkertaisia) muuttujia, ohjelmien teko

Lisätiedot

C++11 lambdat: [](){} Matti Rintala

C++11 lambdat: [](){} Matti Rintala C++11 lambdat: [](){} Matti Rintala bool(*)(int) Tarve Tarve välittää kirjastolle/funktiolle toiminnallisuutta Callback-funktiot Virhekäsittely Käyttöliittymät Geneeristen kirjastojen räätälöinti STL:n

Lisätiedot

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset 815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 3 vastaukset Harjoituksen aiheena ovat imperatiivisten kielten muuttujiin liittyvät kysymykset. Tehtävä 1. Määritä muuttujien max_num, lista,

Lisätiedot

Osoittimet. Mikä on osoitin?

Osoittimet. Mikä on osoitin? Osoittimet 7 Osoittimet On aika siirtyä käsittelemään osoittimia, C++:lle elintärkeätä ominaisuutta. Osoittimet ovat tärkeitä, koska ne luovat perustan muistin dynaamiselle varaukselle ja käytölle. Ne

Lisätiedot

Mallit standardi mallikirjasto parametroitu tyyppi

Mallit standardi mallikirjasto parametroitu tyyppi Mallit 18 Mallit Malli on tehokas mekanismi uusien luokkien generoimiseksi automaattisesti. Standardikirjaston suuri osa, standardi mallikirjasto, rakentuu kokonaan mallien määrittelymahdollisuuden ympärille,

Lisätiedot

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa.

C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa. Taulukot C-kielessä taulukko on joukko peräkkäisiä muistipaikkoja, jotka kaikki pystyvät tallettamaan samaa tyyppiä olevaa tietoa. Taulukon muuttujilla (muistipaikoilla) on yhteinen nimi. Jokaiseen yksittäiseen

Lisätiedot

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes)

Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes) Kääreluokat (oppikirjan luku 9.4) (Wrapper-classes) Kääreluokista Javan alkeistietotyypit ja vastaavat kääreluokat Autoboxing Integer-luokka Double-luokka Kääreluokista Alkeistietotyyppiset muuttujat (esimerkiksi

Lisätiedot

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op) ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 7. huhtikuuta 2017 Vastaa kaikkiin tehtäviin. Tee jokainen tehtävä erilliselle konseptiarkille. Kirjoittamasi luokat, funktiot ja aliohjelmat

Lisätiedot

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan.

Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan. Osoittimet Ohjelmassa muuttujalla on nimi ja arvo. Kääntäjä ja linkkeri varaavat muistilohkon, jonne muuttujan arvo talletetaan. Muistilohkon koko riippuu muuttujan tyypistä, eli kuinka suuria arvoja muuttujan

Lisätiedot

Ohjelman virheet ja poikkeusten käsittely

Ohjelman virheet ja poikkeusten käsittely Ohjelman virheet ja poikkeusten käsittely 17 Ohjelman virheet ja poikkeusten käsittely Poikkeukset ovat tapa ilmoittaa virheistä ja odottamattomista tilanteista C++-ohjelmassasi. Poikkeusten käyttö virheiden

Lisätiedot

13 Operaattoreiden ylimäärittelyjä

13 Operaattoreiden ylimäärittelyjä 248 13 C++-kielessä voidaan operaattoreita ylimäärittää. Ylimääriteltävää operaattoria voidaan pitää ikäänkuin metodina, joka esitellään luokan esittelyssä ja määritellään luokan ulkopuolella kuten metoditkin.

Lisätiedot

Virtuaalifunktiot ja polymorfismi

Virtuaalifunktiot ja polymorfismi Virtuaalifunktiot ja polymorfismi 16 Virtuaalifunktiot ja polymorfismi Polymorfismi on niin tehokas olio-ohjelmoinnin ominaisuus, että tulet varmastikin käyttämään sitä lähes kaikissa C++-ohjelmissasi.

Lisätiedot

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi

C++11 Syntaksi. Jari-Pekka Voutilainen Jari-Pekka Voutilainen: C++11 Syntaksi 1 C++11 Syntaksi Jari-Pekka Voutilainen 13.4.2012 2 Range-for Iteroi säiliön kaikki alkiot for-silmukassa. Säiliöltä vaaditaan begin- ja end-iteraattorit. Pätee kaikille C++11 STL-säiliöille, taulukoille,

Lisätiedot

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti: 1 (7) Tiedon lukeminen näppäimistöltä Scanner-luokan avulla Miten ohjelma saa käyttöönsä käyttäjän kirjoittamaa tekstiä? Järjestelmässä on olemassa ns. syöttöpuskuri näppäimistöä varten. Syöttöpuskuri

Lisätiedot

Tietueet. Tietueiden määrittely

Tietueet. Tietueiden määrittely Tietueet Tietueiden määrittely Tietue on tietorakenne, joka kokoaa yhteen eri tyyppistä tietoa yhdeksi asiakokonaisuudeksi. Tähän kokonaisuuteen voidaan viitata yhteisellä nimellä. Auttaa ohjelmoijaa järjestelemään

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 1.4.2009 T-106.1208 Ohjelmoinnin perusteet Y 1.4.2009 1 / 56 Tentti Ensimmäinen tenttimahdollisuus on pe 8.5. klo 13:00 17:00 päärakennuksessa. Tämän jälkeen

Lisätiedot

Kääntäjän virheilmoituksia

Kääntäjän virheilmoituksia OHJ-1101 Ohjelmointi 1e 2008-09 1 Kääntäjän virheilmoituksia Kun progvh2 ohjelma käännetään antaa tutg++ seuraavat virheilmoitukset ja varoitukset: proffa> tutg++ progvh2.cc progvh2.cc:29:13: warning:

Lisätiedot

Taulukot. Jukka Harju, Jukka Juslin 2006 1

Taulukot. Jukka Harju, Jukka Juslin 2006 1 Taulukot Jukka Harju, Jukka Juslin 2006 1 Taulukot Taulukot ovat olioita, jotka auttavat organisoimaan suuria määriä tietoa. Käsittelylistalla on: Taulukon tekeminen ja käyttö Rajojen tarkastus ja kapasiteetti

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 9.2.2009 T-106.1208 Ohjelmoinnin perusteet Y 9.2.2009 1 / 35 Listat Esimerkki: halutaan kirjoittaa ohjelma, joka lukee käyttäjältä 30 lämpötilaa. Kun lämpötilat

Lisätiedot

Java-kielen perusteet

Java-kielen perusteet Java-kielen perusteet Tunnus, varattu sana, kommentti Muuttuja, alkeistietotyyppi, merkkijono, literaalivakio, nimetty vakio Tiedon merkkipohjainen tulostaminen 1 Tunnus Java tunnus Java-kirjain Java-numero

Lisätiedot

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin

Luokan operaatiot. Osoittimet ja viittaukset luokan olioihin Luokan operaatiot 13 Luokan operaatiot Luokkien olioiden luomiseen ja tuhoamiseen liittyy monia hienouksia, joista sinun tulee olla selvillä, jotta luokkiesi olioiden operaatiot toimivat turvallisesti

Lisätiedot

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op) ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 22. huhtikuuta 2016 Vastaa kaikkiin tehtäviin. Tee jokainen tehtävä erilliselle konseptiarkille! Kirjoittamasi luokat, funktiot ja aliohjelmat

Lisätiedot

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa(); Sisällys 7. Oliot ja viitteet Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen. Viitteiden käsittelyä: sijoitus, vertailu ja varautuminen null-arvoon. Viite metodin paluuarvona.. 7.1 7.2 Olio

Lisätiedot

Lyhyt kertaus osoittimista

Lyhyt kertaus osoittimista , syksy 2007 Kertausta Luento 10 12.10.2007 Syksy 2007 1 Lyhyt kertaus osoittimista char *p; /* char, int, jne ilmoittavat, minkä tyyppisiä */ Keskusmuisti int *q; /* olioita sisältäviin muistilohkoihin

Lisätiedot

7. Oliot ja viitteet 7.1

7. Oliot ja viitteet 7.1 7. Oliot ja viitteet 7.1 Sisällys Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen. Viitteiden sijoitus. Viitteiden vertailu. Varautuminen null-arvoon. Viite metodin paluuarvona. Viite metodin

Lisätiedot

Periytyminen. Luokat ja olio-ohjelmointi

Periytyminen. Luokat ja olio-ohjelmointi Periytyminen 15 Periytyminen Tässä luvussa käsittelemme aihetta, joka on olio-ohjelmoinnin kaikkein tärkein osa - periytyvyys. Periytyvyyden avulla voimme luoda uusia luokkia uudelleenkäyttämällä ja laajentamalla

Lisätiedot

Olio-ohjelmointi 2. välikoe HYV5SN

Olio-ohjelmointi 2. välikoe HYV5SN Olio-ohjelmointi 2. välikoe 27.4.2007 HYV5SN 1. Tee ohjelma, joka sisältää laatikko-luokan. Luokan tietojäseninä ovat laatikon syvyys, leveys ja korkeus. Toteuta luokkaan muodostin, jonka avulla olio voidaan

Lisätiedot

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat: 1. Luokan jäsenet Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat: class luokan_nimi tyypit: enum, struct, class, typedef

Lisätiedot

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4) 2. Lisää Java-ohjelmoinnin alkeita Muuttuja ja viittausmuuttuja Vakio ja literaalivakio Sijoituslause Syötteen lukeminen ja Scanner-luokka 1 Muuttuja ja viittausmuuttuja (1/4) Edellä mainittiin, että String-tietotyyppi

Lisätiedot

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op) ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 20. huhtikuuta 2018 Vastaa kaikkiin tehtäviin. Tee kukin tehtävä omalle konseptiarkille. Noudata ohjelmointitehtävissä kurssin koodauskäytänteitä.

Lisätiedot

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5) Alkuarvot ja tyyppimuunnokset (1/5) Aiemmin olemme jo antaneet muuttujille alkuarvoja, esimerkiksi: int luku = 123; Alkuarvon on oltava muuttujan tietotyypin mukainen, esimerkiksi int-muuttujilla kokonaisluku,

Lisätiedot

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout:

C++ rautaisannos. Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout: C++ rautaisannos Kolme tapaa sanoa, että tulostukseen käytetään standardikirjaston iostreamosassa määriteltyä, nimiavaruuden std oliota cout: # include #include main ( ) main (

Lisätiedot

TIETORAKENTEET JA ALGORITMIT

TIETORAKENTEET JA ALGORITMIT TIETORAKENTEET JA ALGORITMIT Timo Harju 1999-2004 1 typedef link List; /* Vaihtoehtoisia nimiä */ typedef link Stack; /* nodepointterille */ typedef link Queue typedef struct node Node; /* itse nodelle

Lisätiedot

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit. 3. Muuttujat ja operaatiot Sisällys Imperatiivinen laskenta. Muuttujat. Nimi ja arvo. Muuttujan nimeäminen. Muuttujan tyyppi.. Operandit. Arvon sijoitus muuttujaan. Aritmeettiset operaattorit. Arvojen

Lisätiedot

Ohjelmoinnin peruskurssi Y1

Ohjelmoinnin peruskurssi Y1 Ohjelmoinnin peruskurssi Y1 CSE-A1111 30.9.2015 CSE-A1111 Ohjelmoinnin peruskurssi Y1 30.9.2015 1 / 27 Mahdollisuus antaa luentopalautetta Goblinissa vasemmassa reunassa olevassa valikossa on valinta Luentopalaute.

Lisätiedot

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä

Sisällys. 7. Oliot ja viitteet. Olion luominen. Olio Java-kielessä Sisälls 7. Oliot ja viitteet Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen.. Viitteiden vertailu. Varautuminen null-arvoon. Viite metodin paluuarvona.. Muuttumattomat ja muuttuvat merkkijonot.

Lisätiedot

Taulukot. Taulukon määrittely ja käyttö. Taulukko metodin parametrina. Taulukon sisällön kopiointi toiseen taulukkoon. Taulukon lajittelu

Taulukot. Taulukon määrittely ja käyttö. Taulukko metodin parametrina. Taulukon sisällön kopiointi toiseen taulukkoon. Taulukon lajittelu Taulukot Taulukon määrittely ja käyttö Taulukko metodin parametrina Taulukon sisällön kopiointi toiseen taulukkoon Taulukon lajittelu esimerkki 2-ulottoisesta taulukosta 1 Mikä on taulukko? Taulukko on

Lisätiedot

Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit

Esimerkkiprojekti. Mallivastauksen löydät Wroxin www-sivuilta. Kenttä Tyyppi Max.pituus Rajoitukset/Kommentit Liite E - Esimerkkiprojekti E Esimerkkiprojekti Olet lukenut koko kirjan. Olet sulattanut kaiken tekstin, Nyt on aika soveltaa oppimiasi uusia asioita pienen, mutta täydellisesti muotoiltuun, projektiin.

Lisätiedot

3. Muuttujat ja operaatiot 3.1

3. Muuttujat ja operaatiot 3.1 3. Muuttujat ja operaatiot 3.1 Sisällys Imperatiivinen laskenta. Muuttujat. Nimi ja arvo. Muuttujan nimeäminen. Muuttujan tyyppi. Operaattorit. Operandit. Arvon sijoitus muuttujaan. Aritmeettiset operaattorit.

Lisätiedot

Muuttujien roolit Kiintoarvo cin >> r;

Muuttujien roolit Kiintoarvo cin >> r; Muuttujien roolit Muuttujilla on ohjelmissa eräitä tyypillisiä käyttötapoja, joita kutsutaan muuttujien rooleiksi. Esimerkiksi muuttuja, jonka arvoa ei muuteta enää kertaakaan muuttujan alustamisen jälkeen,

Lisätiedot

INSIDE C++ Ohjelmoijan käsikirja. Ivor Horton WROX PRESS

INSIDE C++ Ohjelmoijan käsikirja. Ivor Horton WROX PRESS INSIDE C++ Ohjelmoijan käsikirja Ivor Horton WROX PRESS C++ Ohjelmoijan käsikirja Kirjoittanut Kääntäjä Kansi Kustantaja Ivor Horton Jouni Laaksonen Frank Chaumont IT Press PL 25 00511 HELSINKI Sähköpostiosoite

Lisätiedot

ITKP102 Ohjelmointi 1 (6 op)

ITKP102 Ohjelmointi 1 (6 op) ITKP102 Ohjelmointi 1 (6 op) Tentaattori: Antti-Jussi Lakanen 12. huhtikuuta 2019 Tee kukin tehtävä omalle konseptiarkille. Noudata ohjelmointitehtävissä kurssin koodauskäytänteitä. Yksi A4-kokoinen lunttilappu

Lisätiedot

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. V Geneerisyys

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. V Geneerisyys 812347A Olio-ohjelmointi, 2015 syksy 2. vsk V Geneerisyys Sisältö 1. Johdanto geneerisyyteen 2. Geneeriset funktiot 3. Geneeriset luokat 4. Standard Template Library (STL) 5. IOStream-kirjasto 812347A

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 2.3.2009 T-106.1208 Ohjelmoinnin perusteet Y 2.3.2009 1 / 28 Puhelinluettelo, koodi def lue_puhelinnumerot(): print "Anna lisattavat nimet ja numerot." print

Lisätiedot

A274101 TIETORAKENTEET JA ALGORITMIT

A274101 TIETORAKENTEET JA ALGORITMIT A274101 TIETORAKENTEET JA ALGORITMIT PERUSTIETORAKENTEET LISTA, PINO, JONO, PAKKA ABSTRAKTI TIETOTYYPPI Tietotyyppi on abstrakti, kun se on määritelty (esim. matemaattisesti) ottamatta kantaa varsinaiseen

Lisätiedot

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille: Merkkijonot C-kielessä merkkijono on taulukko, jonka alkiot ovat char -tyyppiä. Taulukon viimeiseksi merkiksi tulee merkki '\0', joka ilmaisee merkkijonon loppumisen. Merkkijono määritellään kuten muutkin

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 15.3.2010 T-106.1208 Ohjelmoinnin perusteet Y 15.3.2010 1 / 56 Tiedostoista: tietojen tallentaminen ohjelman suorituskertojen välillä Monissa sovelluksissa ohjelman

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 11.2.2009 T-106.1208 Ohjelmoinnin perusteet Y 11.2.2009 1 / 33 Kertausta: listat Tyhjä uusi lista luodaan kirjoittamalla esimerkiksi lampotilat = [] (jolloin

Lisätiedot

Javan perusteita. Janne Käki

Javan perusteita. Janne Käki Javan perusteita Janne Käki 20.9.2006 Muutama perusasia Tietokone tekee juuri (ja vain) sen, mitä käsketään. Tietokone ymmärtää vain syntaksia (sanojen kirjoitusasua), ei semantiikkaa (sanojen merkitystä).

Lisätiedot

Ohjelmointitaito (ict1td002, 12 op) Kevät 2008. 1. Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen raine.kauppinen@haaga-helia.

Ohjelmointitaito (ict1td002, 12 op) Kevät 2008. 1. Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen raine.kauppinen@haaga-helia. Ohjelmointitaito (ict1td002, 12 op) Kevät 2008 Raine Kauppinen [email protected] 1. Java-ohjelmoinnin alkeita Tietokoneohjelma Java-kieli ja Eclipse-ympäristö Java-ohjelma ja ohjelmaluokka

Lisätiedot

Java-kielen perusteet

Java-kielen perusteet Java-kielen perusteet Tunnus, varattu sana, kommentti Muuttuja, alkeistietotyyppi, merkkijono, Vakio Tiedon merkkipohjainen tulostaminen Ohjelmointi (ict1tx006) Tunnus (5.3) Javan tunnus Java-kirjain Java-numero

Lisätiedot

Sisältö. 2. Taulukot. Yleistä. Yleistä

Sisältö. 2. Taulukot. Yleistä. Yleistä Sisältö 2. Taulukot Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko operaation parametrina. Taulukko ja HelloWorld-ohjelma. Taulukko paluuarvona. 2.1 2.2 Yleistä

Lisätiedot

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo 15.2.2006

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo 15.2.2006 TURUN YLIOPISTO DEMO III Informaatioteknologian laitos tehtävät Olio-ohjelmoinnin perusteet / Salo 15.2.2006 1. Tässä tehtävässä tarkastellaan erääntyviä laskuja. Lasku muodostaa oman luokkansa. Laskussa

Lisätiedot

Listarakenne (ArrayList-luokka)

Listarakenne (ArrayList-luokka) Listarakenne (ArrayList-luokka) Mikä on lista? Listan määrittely ArrayList-luokan metodeita Listan läpikäynti Listan läpikäynti indeksin avulla Listan läpikäynti iteraattorin avulla Listaan lisääminen

Lisätiedot

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset 815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 4 vastaukset Harjoituksen aiheena ovat imperatiivisten kielten lauseisiin, lausekkeisiin ja aliohjelmiin liittyvät kysymykset. Tehtävä 1. Mitä

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 7.2.2011 T-106.1208 Ohjelmoinnin perusteet Y 7.2.2011 1 / 39 Kännykkäpalautetteen antajia kaivataan edelleen! Ilmoittaudu mukaan lähettämällä ilmainen tekstiviesti

Lisätiedot

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen Metodit Metodien määrittely Metodin parametrit ja paluuarvo Metodien suorittaminen eli kutsuminen Metodien kuormittaminen 1 Mikä on metodi? Metodi on luokan sisällä oleva yhteenkuuluvien toimintojen kokonaisuus

Lisätiedot

Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014

Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014 Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014 Kirjoita jokaiseen palauttamaasi konseptiin kurssin nimi, kokeen päivämäärä, oma nimi ja opiskelijanumero. Vastaa kaikkiin tehtäviin omille konsepteilleen.

Lisätiedot

1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa: Tietorakenteet, laskuharjoitus 10, ratkaisuja 1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa: SamaLuku(T ) 2 for i = 1 to T.length 1 3 if T [i] == T [i + 1] 4 return True 5 return

Lisätiedot

Sisällys. 18. Abstraktit tietotyypit. Johdanto. Johdanto

Sisällys. 18. Abstraktit tietotyypit. Johdanto. Johdanto Sisällys 18. bstraktit tietotyypit Johdanto abstrakteihin tietotyyppeihin. Pino ja jono. Linkitetty lista. Pino linkitetyllä listalla toteutettuna. 18.1 18.2 Johdanto Javan omat tietotyypit ovat jo tuttuja:

Lisätiedot

Omat tietotyypit. Mikä on olio?

Omat tietotyypit. Mikä on olio? Omat tietotyypit 11 Omat tietotyypit C++:n suuri vahvuus on sen oliopohjaisuudessa. Siihen liittyy runsaasti asiaa ja kulutammekin seuraavat viisi lukua tässä aiheessa. Tässä ja seuraavassa luvussa käsittelemme

Lisätiedot

Rakenteiset tietotyypit Moniulotteiset taulukot

Rakenteiset tietotyypit Moniulotteiset taulukot C! Rakenteiset tietotyypit Moniulotteiset taulukot 22.2.2018 Agenda Rakenteiset tietotyypit Vilkaisu 6. kierroksen tehtäviin Moniulotteiset taulukot Esimerkki Seuraava luento to 8.3. Ilmoittautuminen ohjelmointikokeeseen

Lisätiedot

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma. 2. Taulukot 2.1 Sisältö Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko operaation parametrina. Taulukko ja HelloWorld-ohjelma. Taulukko paluuarvona. 2.2 Yleistä

Lisätiedot

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta. Tehtävä 63. Kirjoita oma versio funktiosta strcmp(),joka saa parametrinaan kaksi merkkiosoitinta. Tee ohjelma, jossa luetaan kaksi merkkijonoa, joita sitten verrataan ko. funktiolla. Tehtävä 64. Kirjoita

Lisätiedot

7. Näytölle tulostaminen 7.1

7. Näytölle tulostaminen 7.1 7. Näytölle tulostaminen 7.1 Sisällys System.out.println- ja System.out.print-operaatiot. Tulostus erikoismerkeillä. Edistyneempää tulosteiden muotoilua. 7.2 Tulostusoperaatiot System.out.println-operaatio

Lisätiedot

18. Abstraktit tietotyypit 18.1

18. Abstraktit tietotyypit 18.1 18. Abstraktit tietotyypit 18.1 Sisällys Johdanto abstrakteihin tietotyyppeihin. Pino ja jono. Linkitetty lista. Pino linkitetyllä listalla toteutettuna. 18.2 Johdanto Javan omat tietotyypit ovat jo tuttuja:

Lisätiedot

Pong-peli, vaihe Aliohjelman tekeminen. Muilla kielillä: English Suomi. Tämä on Pong-pelin tutoriaalin osa 3/7. Tämän vaiheen aikana

Pong-peli, vaihe Aliohjelman tekeminen. Muilla kielillä: English Suomi. Tämä on Pong-pelin tutoriaalin osa 3/7. Tämän vaiheen aikana Muilla kielillä: English Suomi Pong-peli, vaihe 3 Tämä on Pong-pelin tutoriaalin osa 3/7. Tämän vaiheen aikana Jaetaan ohjelma pienempiin palasiin (aliohjelmiin) Lisätään peliin maila (jota ei voi vielä

Lisätiedot

Olio-ohjelmointi Geneerisyys. 1. Johdanto

Olio-ohjelmointi Geneerisyys. 1. Johdanto Olio-ohjelmointi Geneerisyys Aiemmin käsiteltiin kolme monimuotoisuuden muotoa. Tässä osassa tutustutaan niistä neljänteen geneerisyyteen. Esitys perustuu pääosin teoksen [Bud] lukuun 18. Java-kielen geneerisyyden

Lisätiedot

Algoritmit 2. Luento 7 Ti Timo Männikkö

Algoritmit 2. Luento 7 Ti Timo Männikkö Algoritmit 2 Luento 7 Ti 4.4.2017 Timo Männikkö Luento 7 Joukot Joukko-operaatioita Joukkojen esitystapoja Alkiovieraat osajoukot Toteutus puurakenteena Algoritmit 2 Kevät 2017 Luento 7 Ti 4.4.2017 2/26

Lisätiedot

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen Taulukot: Array Taulukko Javassa pitää aina perustaa (new) Yksinkertaisessa tilanteessa taulukon koko tiedetään etukäteen ja

Lisätiedot

etunimi, sukunimi ja opiskelijanumero ja näillä

etunimi, sukunimi ja opiskelijanumero ja näillä Sisällys 1. Algoritmi Algoritmin määritelmä. Aiheen pariin johdatteleva esimerkki. ja operaatiot (sijoitus, aritmetiikka ja vertailu). Algoritmista ohjelmaksi. 1.1 1.2 Algoritmin määritelmä Ohjelmointi

Lisätiedot

Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: {

Osa. Listaus 2.1. HELLO.CPP esittelee C++ -ohjelman osat. 14: #include <iostream.h> 15: 16: int main() 17: { Osa I 2. oppitunti C++-ohjelman osat Ennen kuin menemme yksityiskohtaisemmin sisälle C++-luokkiin, -muuttujiin jne, katsokaamme ensin, millaisista osista C++-ohjelma koostuu. Tämän tunnin aikana opit seuraavat

Lisätiedot

Harjoitus 7. 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Harjoitus 7. 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti: Harjoitus 7 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti: class Lintu //Kentät private int _siivenpituus; protected double _aivojenkoko; private bool _osaakolentaa; //Ominaisuudet public int

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 25.2.2009 T-106.1208 Ohjelmoinnin perusteet Y 25.2.2009 1 / 34 Syötteessä useita lukuja samalla rivillä Seuraavassa esimerkissä käyttäjä antaa useita lukuja samalla

Lisätiedot

Olio-ohjelmointi Javalla

Olio-ohjelmointi Javalla 1 Olio-ohjelmointi Javalla Olio-ohjelmointi Luokka Attribuutit Konstruktori Olion luominen Metodit Olion kopiointi Staattinen attribuutti ja metodi Yksinkertainen ohjelmaluokka Ohjelmaluokka 1 Olio-ohjelmointi

Lisätiedot

T740103 Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

T740103 Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010 12. Periytyminen Johdantoa Käytännössä vähänkään laajemmissa ohjelmissa joudutaan laatimaan useita luokkia, joiden pitäisi pystyä välittämään tietoa toisilleen. Ohjelmien ylläpidon kannalta olisi lisäksi

Lisätiedot

Tietorakenteet ja algoritmit

Tietorakenteet ja algoritmit Tietorakenteet ja algoritmit Pino Pinon määritelmä Pinon sovelluksia Järjestyksen kääntäminen Palindromiprobleema Postfix-lausekkeen laskenta Infix-lausekkeen muunto postfix-lausekkeeksi Sisäkkäiset funktiokutsut

Lisätiedot

Harjoitustyö: virtuaalikone

Harjoitustyö: virtuaalikone Harjoitustyö: virtuaalikone Toteuta alla kuvattu virtuaalikone yksinkertaiselle olio-orientoituneelle skriptauskielelle. Paketissa on testaamista varten mukana kaksi lyhyttä ohjelmaa. Ohjeita Noudata ohjelman

Lisätiedot

1 C++:n standardikirjasto

1 C++:n standardikirjasto TIE-20100 Tietorakenteet ja algoritmit 1 1 C++:n standardikirjasto Tässä luvussa käsitellään C++:n standardikirjaston tietorakenteita ja algoritmeja. Tarkoituksena on käsitellä sellaisia asioita, jotka

Lisätiedot

Tietorakenteet ja algoritmit

Tietorakenteet ja algoritmit Tietorakenteet ja algoritmit Rekursio Rekursion käyttötapauksia Rekursio määritelmissä Rekursio ongelmanratkaisussa ja ohjelmointitekniikkana Esimerkkejä taulukolla Esimerkkejä linkatulla listalla Hanoin

Lisätiedot

T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

T Olio-ohjelmointi Osa 3: Luokka, muodostin ja hajotin, this-osoitin Jukka Jauhiainen OAMK Tekniikan yksikkö 2010 11. Luokka Opetellaan seuraavaksi, miten omia luokkia kirjoitetaan. Aikaisemmin olikin jo esillä, että luokka on tietorakenne, joka sisältää sekä tiedot (attribuutit) että niitä käsittelevät aliohjelmat

Lisätiedot

Luokat. Luokat ja olio-ohjelmointi

Luokat. Luokat ja olio-ohjelmointi Luokat 12 Luokat Tässä luvussa laajennamme edellisessä luvussa käsittelemäämme struktuurityyppiä ja siirrymme yhteen C++-ohjelmoijan kaikkein tärkeimmistä välineistä: luokkiin. Käsittelemme myöskin joitakin

Lisätiedot

Tehtävä 1. TL5302 Olio-ohjelmointi Koe Malliratkaisuja. Tässä sekä a)- että b)-kohdan toimiva ratkaisu:

Tehtävä 1. TL5302 Olio-ohjelmointi Koe Malliratkaisuja. Tässä sekä a)- että b)-kohdan toimiva ratkaisu: TL5302 Olio-ohjelmointi Koe 19.4.2005 Malliratkaisuja Tehtävä 1 Tässä sekä a)- että b)-kohdan toimiva ratkaisu: #include using namespace std; int main() int taul[5]=1,2,3,4,5; int *p,&r=taul[0];

Lisätiedot

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti C! Perusteet 19.1.2017 Palautteesta (1. kierros toistaiseksi) Toistaiseksi helppoa Miksi vain puolet pisteistä? Vaikeinta oli ohjelmointiympäristön asennus ja käyttö Vaikeaa eroavuudet Pythonin ja C:n

Lisätiedot

Sisältö. 22. Taulukot. Yleistä. Yleistä

Sisältö. 22. Taulukot. Yleistä. Yleistä Sisältö 22. Taulukot Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko metodin parametrina. Taulukko ja HelloWorld-ohjelma. Taulukko paluuarvona. 22.1 22.2 Yleistä

Lisätiedot

11. oppitunti III. Viittaukset. Osa. Mikä on viittaus?

11. oppitunti III. Viittaukset. Osa. Mikä on viittaus? Osa III 11. oppitunti Viittaukset Kahdessa viime luvussa opit käyttämään osoittimia kohteiden käsittelyyn vapaalla muistialueella sekä viittaamaan noihin kohteisiin epäsuorasti. Tässä luvussa käsiteltävät

Lisätiedot

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++? JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS Teemu Saarelainen [email protected] Lähteet: http://java.sun.com/docs/books/tutorial/index.html Vesterholm, Kyppö: Java-ohjelmointi,

Lisätiedot

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin. 2. Ohjausrakenteet Ohjausrakenteiden avulla ohjataan ohjelman suoritusta. peräkkäisyys valinta toisto Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet

Lisätiedot

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti

ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti ITKP102 Ohjelmointi 1 (6 op), arvosteluraportti Tentaattori: Antti-Jussi Lakanen 8. kesäkuuta 2018 Yleistä Tentti 1 meni pistekeskiarvon (11.2) perusteella välttävästi. Omasta tehtäväpaperista saa kopion

Lisätiedot

1. Algoritmi 1.1 Sisällys Algoritmin määritelmä. Aiheen pariin johdatteleva esimerkki. Muuttujat ja operaatiot (sijoitus, aritmetiikka ja vertailu). Algoritmista ohjelmaksi. 1.2 Algoritmin määritelmä Ohjelmointi

Lisätiedot