OHJ-1150 Ohjelmointi II



Samankaltaiset tiedostot
Osoitin ja viittaus C++:ssa

Tietueet. Tietueiden määrittely

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

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

STL:n uudistukset. Seppo Koivisto TTY Ohjelmistotekniikka

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

Olio-ohjelmointi Syntaksikokoelma

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

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

Ohjelmoinnin perusteet Y Python

Taulukot. Jukka Harju, Jukka Juslin

Ohjelmointi 1 Taulukot ja merkkijonot

ITKP102 Ohjelmointi 1 (6 op)

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

Tieto- ja tallennusrakenteet

Ohjelmoinnin perusteet Y Python

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

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python

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

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

Tietorakenteet ja algoritmit - syksy

Ohjelmoinnin perusteet Y Python

18. Abstraktit tietotyypit 18.1

12 Mallit (Templates)

Ohjelmoinnin perusteet Y Python

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Muuttujien roolit Kiintoarvo cin >> r;

ITKP102 Ohjelmointi 1 (6 op)

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

A TIETORAKENTEET JA ALGORITMIT

Tietorakenteet ja algoritmit

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin peruskurssi Y1

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

Ohjelmoinnin perusteet Y Python

TTY Ohjelmointi I & II C++-kirjastoreferenssi versio 2.2

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

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

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

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

Rakenteiset tietotyypit Moniulotteiset taulukot

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

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

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

useampi ns. avain (tai vertailuavain) esim. opiskelijaa kuvaavassa alkiossa vaikkapa opintopistemäärä tai opiskelijanumero

Java-kielen perusteet

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

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

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin peruskurssien laaja oppimäärä

7. Oliot ja viitteet 7.1

List-luokan soveltamista. Listaan lisääminen Listan läpikäynti Listasta etsiminen Listan sisällön muuttaminen Listasta poistaminen Listan kopioiminen

ITKP102 Ohjelmointi 1 (6 op)

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

Olio-ohjelmointi 2. välikoe HYV5SN

Taulukot. Taulukon käsittely. Tämän osan sisältö. Esimerkki. Taulukon esittely ja luonti. Taulukon alustaminen. Taulukon koko

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

Lyhyt kertaus osoittimista

tietueet eri tyyppisiä tietoja saman muuttujan arvoiksi

Algoritmit 1. Luento 4 Ke Timo Männikkö

TIETORAKENTEET JA ALGORITMIT

Algoritmit 2. Luento 2 To Timo Männikkö

Imperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi

Algoritmit 1. Luento 10 Ke Timo Männikkö

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

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

Listarakenne (ArrayList-luokka)

Ohjelmoinnin perusteet Y Python

1. Mitä tehdään ensiksi?

Ohjelmoinnin perusteet Y Python

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

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

Tutoriaaliläsnäoloista

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Ohjelmointi 2. Jussi Pohjolainen. TAMK» Tieto- ja viestintäteknologia , Jussi Pohjolainen TAMPEREEN AMMATTIKORKEAKOULU

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

Algoritmit 1. Luento 3 Ti Timo Männikkö

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op. Tietorakenneluokkia 2: HashMap, TreeMap

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

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

Ohjelmoinnin perusteet, syksy 2006

Sisällys. 17. Ohjelmoinnin tekniikkaa. Aritmetiikkaa toisin merkiten. for-lause lyhemmin

16. Ohjelmoinnin tekniikkaa 16.1

Standardi mallikirjasto

Osoittimet ja taulukot

Java-kielen perusteet

Java-kielen perusteet

Zeon PDF Driver Trial

11/20: Konepelti auki

Ohjelmoinnin perusteet Y Python

Algoritmit 2. Luento 2 Ke Timo Männikkö

811120P Diskreetit rakenteet

811312A Tietorakenteet ja algoritmit II Perustietorakenteet

Tietorakenteet ja algoritmit

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet

Algoritmit 1. Luento 5 Ti Timo Männikkö

15. Ohjelmoinnin tekniikkaa 15.1

Sisällys. 16. Ohjelmoinnin tekniikkaa. Aritmetiikkaa toisin merkiten. Aritmetiikkaa toisin merkiten

Transkriptio:

OHJ-1150 Ohjelmointi II syksy 2013 Luentomoniste Ari Suntioinen arisuntioinen@tutfi Sisällysluettelo Sisältö OHJ-1150 Ohjelmointi II vector-tyyppi 252 Standard Template Library (STL) 261 STL-säiliöt 263 Sarjat 265 Lyhyitä esimerkkejä sarjoista 268 Assosiatiiviset säiliöt 276 Lyhyitä esimerkkejä set-rakenteesta 280 Lyhyitä esimerkkejä map-rakenteesta 283 STL-iteraattorit 288 STL-algoritmit 297 Monimutkaisten tyyppinimien yksinkertaistaminen 311 Muuttujan tyypin automaattinen määrittely 314 Abstraktiot 316 Abstraktit tietotyypit 321 Vektoriesimerkki 323 Luokat 330 Luokan esittelyn private-osa 332 Luokan esittelyn public-osa 334 Jäsenfunktioiden määrittely 336 Vektoriesimerkki hiukan toisin 338 Toteutuksen»kätkeminen» luokan sisällä 343 Mistä abstraktit tietotyypit tulevat? 347 Abstraktien tietotyyppien hyödyt 348 i Sisällysluettelo ii Funktioiden kuormittaminen 349 Funktion oletusparametrit 353 Rakentajan parametrit 356 Nimettömät eli lambda-funktiot 360 main-funktion parametrit 365 main-funktion paluuarvo 370 Modulaarisuus ja moduulit 373 TV-ohjelmatietokantaesimerkki 374 Moduulit 394 Moduulien toteuttaminen C++:lla 396 Esittely- eli hh-tiedostot 397 Määrittely- eli cc-tiedostot 402 Mistä moduulit tulevat? 405 Modulaarisuuden hyödyt 408 Automatisoitu käännös ja make 409 Versionhallinta ja rcs 415 Ajonaikaiset virheet ja gdb 422 Rekursio 427 Rekursio ohjelmoinnissa 429 Tietokoneen muisti 442 Osoittimet 446 Osoittimet struct- ja class-tietotyyppeihin 451 Taulukot ja osoittimet 453 Muuttujan näkyvyysalue ja elinikä 456 Dynaaminen muistinhallinta 460 Dynaamiset tietorakenteet 466 Dynaaminen pinotietorakenne-esimerkki 468 new:n onnistumisen tarkistaminen 475 Sisällysluettelo iii Poikkeukset 476 Pino toteutettuna dynaamisena taulukkona 482 Tietorakenteen kopiointi: alustus ja sijoitus 488 Puhelinluetteloesimerkki 499 Listat ja rekursio 511 Kahteen suuntaan linkitetty lista 521 Binäärihakupuu 527 Geneerisyys 535 Geneerinen pino-esimerkki 536 Geneerisyys C++:ssa 541

OHJ-1150 Ohjelmointi II 252 Esimerkki: vector-tyyppi Tutkitaan ennen varsinaiseen asiaan siirtymistä esimerkki vector-kirjastotyypin käytöstä Esimerkki johdattelee Standard Template Library (STL) -kirjaston perusajatuksiin, jotta perässä seuraava laajempi osa asiaa olisi ymmärrettävämpää Ajatellaan labyrinttia, jossa voidaan siirtyä seuraavaan risteykseen kertomalla ilmansuunta P (pohj), I (itä), E (etelä) tai L (länsi) Kirjoitetaan ohjelma, joka tulostaa optimoidun reitin, kun sille syötetään reitti, joka saadaan kirjaamalla jokainen siirtymä matkalla labyrintin alusta loppuun, kun labyrintissa on harhailtu sattumanvaraisesti Jos harhailemalla löydetty reitti on esimerkiksi: PLIPIPLIILEILLEI olisi optimoitu reitti: PI Siis aina kun reitissä on peräkkäin yhdistelmä LI, IL, PE tai EP, sievennetään se pois, koska ollaan palattu takaisinpäin OHJ-1150 Ohjelmointi II 253 Toteutettu ohjelma näyttää seuraavalta: #include <iostream> #include <vector> #include <string> using namespace std; void TulostaReitti(vector<char> reittivektori); int main( ) { string reitti; cout << "Syötä kuljettu reitti: "; getline(cin, reitti); vector<char> optimoitu_reitti; for ( string::size_type i = 0; i < reittilength( ); ++i ) { char uusi_suunta = reittiat(i); if ( optimoitu_reittisize( ) == 0 ) { optimoitu_reittipush_back(uusi_suunta); else { char edellinen_suunta = optimoitu_reittiback( ); if ( edellinen_suunta == I && uusi_suunta == L ) { optimoitu_reittipop_back( ); else if ( edellinen_suunta == L && uusi_suunta == I ) { optimoitu_reittipop_back( ); OHJ-1150 Ohjelmointi II 254 else if ( edellinen_suunta == P && uusi_suunta == E ) { optimoitu_reittipop_back( ); else if ( edellinen_suunta == E && uusi_suunta == P ) { optimoitu_reittipop_back( ); else { optimoitu_reittipush_back(uusi_suunta); TulostaReitti( optimoitu_reitti); void TulostaReitti(vector<char> reittivektori) { vector<char>::size_type i = 0; while ( i < reittivektorisize( ) ) { cout << reittivektoriat(i); ++i; cout << endl; OHJ-1150 Ohjelmointi II 255 Siitä opittua Vektori on dynaaminen korvike C++-kielen omalle taulukkotyypille ja se ratkaisee seuraavat taulukoihin liittyvät puutteet: Taulukot ovat staattisia rakenteita: alkioiden lukumäärää voi muuttaa vain kääntämällä ohjelman uudelleen Taulukkoa ei voi kopioida toiseen taulukkoon =-operaattorilla Kahta taulukkoa ei voi vertailla operaattoreilla ==,!=, <, >, <= ja >= Taulukkoa ei voi alustaa (asettaa alkuarvoa määrittelyn yhteydessä) toisesta taulukosta Taulukkoon talletettujen alkioiden lukumäärää ei yleensä saa selville, paitsi pitämällä siitä itse kirjaa Funktion parametrina taulukko käyttäytyy epäjohdonmukaisesti muihin tietotyyppeihin verrattuna: aina "viiteparametri"

OHJ-1150 Ohjelmointi II 256 Vektorityypin käyttämiseksi koodin alkuun on lisättävä: #include <vector> Vektorimuuttujan määrittely tapahtuu uudella erikoisella syntaksilla: vector<alkioiden_tyyppi> muuttuja; esimerkiksi: vector<int> lottonumerot; jonka jälkeen lottonumerot-muuttujaan voidaan tallentaa kokonaislukuja Vektorin loppuun voidaan lisätä alkioita yksi kerrallaan push_back-funktiolla: lottonumerotpush_back(32); jolloin vektorin koko (siihen talletettujen alkioiden lukumäärä) kasvaa yhdellä Viimeinen alkio voidaan poistaa pop_back-funktiolla: lottonumerotpop_back( ); jolloin vektorin koko pienenee yhdellä Jos vektori on tyhjä, pop_back-funktion kutsu kaataa ohjelman (tarkemmin: aiheuttaa poikkeuksen s 476, joka oletusarvoisesti keskeyttää ohjelman) OHJ-1150 Ohjelmointi II 257 Vektorin koko saadaan selville size-funktiolla: lottonumerotsize( ); Jos size-funktion paluuarvo halutaan tallettaa muuttujaan, on kyseisen muuttujan tyypin oltava: vector<alkiotyyppi>::size_type siis esimerkiksi vector<int>::size_type koko = lottonumerotsize( ); Tietotyyppi vector<alkiotyyppi>::size_type on aritmeettinen tyyppi ja sillä voidaan operoida kuten kokonaisluvuilla yleensä Syntaksi on hiukan vaikeaselkoinen, mutta kyseessä on vain yhdistelmä vektorin määrittelynotaatiota: vector<alkiotyyppi> ja merkkijonoista tuttua: string::size_type :: -notaatiota Vektorin ensimmäiseen ja viimeiseen alkioon päästään käsiksi front- ja back-funktioilla: cout << "eka alkio: " << lottonumerotfront( ) << endl << "vika alkio: " << lottonumerotback( ) << endl; Jos vektori on tyhjä, front- tai back-funktion kutsu kaataa ohjelman (aiheuttaa poikkeuksen s 476) OHJ-1150 Ohjelmointi II 258 Vektoria voi indeksoida [ ]-operaattorilla kuten taulukkoa: cout << lottonumerot[3] << endl; lottonumerot[0] = 5; Indeksointi alkaa tuttuun tapaan nollasta Operaattori [ ] ei tarkista, onko käsiteltävä alkio vektorissa vai ei tapahtuu kummia jos ei ole vastuu käyttäjällä kuten taulukoiden tapauksessa Operaattori [ ] ei lisää alkiota vektoriin, se ainoastaan käsittelee vektorissa jo ennestään olevaa tietoa Turvallisempi tapa indeksoida vektoria on at-funktio (string-tyypistä tuttu): cout << lottonumerotat(3) << endl; lottonumerotat(0) = 5; Tämä eroaa [ ]-operaattorista siten, että laiton indeksi aiheuttaa poikkeuksen (s 476) ja ohjelman suoritus keskeytyy tullaan tietoisiksi siitä, että ohjelmassa on looginen virhe OHJ-1150 Ohjelmointi II 259 Taulukoista poiketen vektori voi myös olla normaaliin tapaan funktion arvo- tai muuttujaparametri: void func1(vector<int> arvoparametri); void func2(vector<int>& muuttujaparametri); Arvoparametreihin sisältyy kuitenkin pieni kompa: arvoparametri on kopio todellisesta parametrista jos vektori on kooltaan suuri, tehdään paljon työtä, kun todellinen parametri kopioidaan muodolliseen parametriin Ongelma vältetään C++:ssa yleensä niin, että jos funktion parametri on kooltaan suuri tietorakenne, jonka ei haluta kopioituvan, tehdään muodollisesta parametrista nk vakioviite: void func3(const vector<int>& vakioviiteparametri); Nyt todellinen parametri ei kopioidu muodolliseen parametriin, mutta samalla const estää muodollisen parametrin (ja sitä kautta todellisen parametrin) muuttamisen Tämä ei tietenkään ole sama asia kuin arvoparametri, mutta riittävän lähellä useimmissa tapauksissa

OHJ-1150 Ohjelmointi II 260 Samaa idea toimii aina, kun funktion parametrina on tietotyyppi, jonka alkiot saattavat olla suuria merkkijonojen kanssa tehdään usein samoin: void func4(const string& vakioviitemerkkijono); Huomaa, että labyrinttiesimerkissä vakioviiteparametria ei käytetty yksinkertaisuuden vuoksi noudata silti vakioviite-ideaa omissa ohjelmissasi! Vektori voi olla myös funktion paluuarvona: vector<int> LueLottonumerotVersio1( ); lottonumerot = LueLottonumerotVersio1( ); Tähän liityy kuitenkin sama kompa kuin arvoparametreihin: paluuarvo joudutaan kopioimaan funktiosta kutsujalle toteuta suurikokoiset paluuarvot muuttujaparametrin avulla: void LueLottonumerotVersio2(vector<int>& numerovek); LueLottonumerotVersio2(lottonumerot); Nyt tulokset talletetaan muuttujaparametriin yhden kerran, eikä niitä tarvitse kopioda erikseen funktiosta palattaessa OHJ-1150 Ohjelmointi II 261 Standard Template Library (STL) C++:n standardikirjastoon kuuluu osana nk Standard Template Library (STL) STL tarjoaa ohjelmoijalle valmiit työkalut (tietorakenteita ja algoritmeja) monimutkaisten ja joustavien tietorakenteiden luontiin ja käsittelyyn kaikkea ei tarvitse tehdä alusta pitäen itse Loogisesti STL koostuu kolmesta osasta: STL-säiliöt (containers) eli varsinaiset yleiskäyttöiset tietorakenteet, joihin käsiteltävä tieto voidaan tallettaa (s 263) STL-iteraattorit (iterators) joiden avulla säiliöiden sisältämiä tietoalkioita voidaan käydä yksitellen läpi tai tarvittaessa esittää säiliön alkioiden osaväli (s 288) STL-algoritmit (algorithms) prosessoivat säiliöihin talletettuja alkioita (s 297) Se, mihin osaan säiliön sisältämistä alkioista algoritmin toimenpiteiden halutaan kohdistuvan, kerrotaan algoritmille iteraattoreiden avulla OHJ-1150 Ohjelmointi II 262 Iteraattorit luovat yhteyden säiliöiden ja algoritmien välille Tutkitaan seuraavassa kurssin kannalta riittävästi (muttei läheskään täydellisesti) kutakin STL:n osaa Päämääränä on paitsi yleissivistää niin myös tarjota hyvät työkalut STL:n käyttöön kurssilla niin, että esim harjoitustehtävissä voidaan keskittyä olennaisempaan asiaan Materiaali on tarkoituksella hiukan «käsikirjamainen», mikä toivottavasti auttaa käytännön työskentelyä STL:n kanssa OHJ-1150 Ohjelmointi II 263 STL-säiliöt Termi säiliö (container) tarkoittaa C++:ssa ja STL:ssä tietorakennetta, johon voidaan tallettaa useita jonkin muun tietotyypin alkioita STL-säiliöt ovat geneerisiä (yleiskäyttöisiä/tyyppiriippumattomia) dynaamisia (alkioden lukumäärää ei ole kiinnitetty) tietorakenteita: Geneerisyys STL ottaa vain yleisellä tasolla kantaa siihen, mitä ominaisuuksia tietotyypillä pitää olla, jotta sen alkioita voidaan tallettaa STL-säiliöihin Säiliömuuttujan määrittely tapahtuu aina säiliötyyppi<alkiotyyppi> muuttujan_nimi; syntaksilla Esimerkiksi vector, johon talletetaan opiskelijoita: vector<opiskelija> opiskelijarekisteri; jossa Opiskelija on jokin itse määritelty tietotyyppi yhden opiskelijan tietojen esittämiseen

OHJ-1150 Ohjelmointi II 264 Dynaamisuus Toisin kuin C++:n omalla taulukkotyyppillä (joka myös on eräänlainen säiliö), STL-säiliöillä ei ole kiinnitettyä maksimikokoa, vaan niiden koko kasvaa ja pienenee automaattisesti, kun uusia alkiota lisätään tai vanhoja poistetaan Säiliötyypit voidaan luokitella ominaisuuksiensa ja käyttäytymisensä perusteella kahteen kategoriaan: sarjat (sequence, sequence container)(s265) ja assosiatiiviset säiliöt (associative container)(s276) OHJ-1150 Ohjelmointi II 265 Sarjat (sequence, sequence container) Sarjat ovat tietorakenteita, jotka säilyttävät alkioiden järjestyksen Esimerkiksi vector-rakenteeseen ensimmäisenä lisätty alkio pysyy ensimmäisenä, jollei ohjelmoija erikseen suorita operaatiota, joka muuttaa sen paikkaa Jokaiselle sarjan alkiolle voidaan ajatella sijainnista riippuva järjestysnumero, sillä alkiot ovat rakenteessa peräkkäin (siitä nimi sequence) STL:ssä sarjoja ovat: vector (alkion lisäys ja poisto lopusta nopeaa, voidaan indeksoida kokonaisluvulla), deque (alkion lisäys ja poisto alusta ja lopusta nopeaa, voidaan indeksoida kokonaisluvulla), list (alkion lisäys ja poisto minne/mistä tahansa nopeaa, peräkkäissaantirakenne (s 213) ei voi indeksoida) ja jossain mielessä myös C++:n oma taulukkotyyppi voidaan ajatella säiliöksi, vaikka se ei olekaan STL-säiliö OHJ-1150 Ohjelmointi II 266 Sarjojen ominaisuudet ovat keskenään hiukan erilaisia kannattaa valita huolella oikea rakenne oikeaan tarkoitukseen Esimerkiksi ei ole hyvä ajatus valita tietorakenteeksi vector:ia, jos on tarve lisätä usein alkioita rakenteen alkuun (vrt edellisen sivun ominaisuuslista) Sarjatyyppeihin voidaan tallentaa minkä tahansa tietotyypin alkioita, kunhan kyseisellä tyypillä toimivat sijoitusoperaattori ja alustus toisesta samantyyppisestä muuttujasta Myös toisten STL-rakenteiden tallentaminen sarjoihin on mahdollista Kunkin sarjatyypin käyttämiseksi pitää ohjelman alkuun lisätä kyseisen tyypin include-direktiivi: #include <vector> // Voidaan käyttää vector-säiliötä, #include <deque> // deque-säiliötä ja #include <list> // list-säiliötä OHJ-1150 Ohjelmointi II 267 Tilanteita, joissa sarjat ovat käyttökelpoisia: on syystä tai toisesta tarpeen säilyttää järjestys, jossa tietoalkiot lisättiin rakenteeseen, tietoalkiot halutaan lajitella (asettaa järjestykseen), halutaan käsitellä tietoalkiota, kun sen järjestysnumero (indeksi) on tiedossa (tämä on mahdollista vain vector- ja deque-rakenteilla sekä C++-taulukoilla), halutaan tehdä järjestyksessä jotain kaikille alkioille (lineaarinen läpikäynti) ja koska vector ja deque ovat oikeastaan kehittyneempiä taulukoita niillä voidaan tehdä samoja asioita, jotka on totuttu tekemään taulukoiden avulla

OHJ-1150 Ohjelmointi II 268 Lyhyitä esimerkkejä sarjoista Kaikki sarjat (vector, deque ja list) ovat perusoperaatioiltaan hyvin samankaltaisia Merkittäviä poikkeuksia on oikeastaan vain kaksi: vector- ja deque-rakenteiden yksittäisiin alkioihin päästään suoraan käsiksi indeksoimalla [ ]-operaattorilla tai at-funktiolla, listan alkioita ei voi indeksoida deque- ja list-rakenteisiin alkioita voidaan lisätä ja poistaa myös alusta push_front- ja pop_frontoperaatioiden avulla, vektoriin näitä operaatioita ei voida kohdistaa Oletetaan esimerkeissä, että seuraavat on määritelty: vector<int> ivektori; deque<string> strdeque; list<opiskelija> olista; OHJ-1150 Ohjelmointi II 269 Alkioita voidaan lisätä ja poistaa lopusta: ivektoripush_back(4); strdequepush_back("the end"); olistapush_back(yhden_opiskelijan_tiedot); ivektoripop_back( ); strdequepop_back( ); olistapop_back(); Alkioita voidaan deque- ja list-rakenteen tapauksessa lisätä ja poistaa myös rakenteen alusta push_frontja pop_front-funktioilla: strdequepush_front("alussa deque oli autio ja tyhjä"); olistapush_front(joku_opiskelija); strdequepop_front( ); olistapop_front( ); Kaikkien STL-sarjojen ensimmäinen ja viimeinen alkio saadaan selville front- ja back-funktioilla: int i; i = ivektorifront( ); Opiskelija op; op = olistaback( ); Määrittelyn jälkeen rakenteet ovat automaattisesti tyhjiä (alkioiden lukumäärä nolla) OHJ-1150 Ohjelmointi II 270 vector- ja deque-rakenteita voidaan indeksoida [ ]-operaattorilla tai at-funktiolla, kun tiedetään halutun alkion indeksi: i = ivektori[4]; ivektoriat(3) = 42; string str; str = strdequeat(7); strdeque[666] = "the number of the beast"; Kaikki yksittäisiä alkioita käsittelevät toimenpiteet ovat laillisia vain, jos kyseinen alkio on olemassa: Indeksointi on laitonta luvulla, jonka mukaista alkiota rakenteessa ei ole at-funktiolla indeksointi on turvallisempaa kuin [ ]-operaattorilla, koska laiton indeksi keskeyttää ohjelman suorituksen front-, back-, pop_front- ja pop_back-funktiot keskeyttävät ohjelman, jos rakenne on tyhjä Edellä «ohjelman keskeyttäminen» tarkoittaa täsmällisesti ottaen poikkeuksen heittämistä, mutta siitä lisää myöhemmin (s476) OHJ-1150 Ohjelmointi II 271 Rakenteessa olevien alkoiden lukumäärä saadaan selville size-funktiolla tätä voidaan hyödyntää vector- ja deque-rakenteiden läpikäynnissä: // Huomaa että idea on sama kuin taulukoiden // läpikäynnissä: silmukkamuuttuja juoksee // järjestyksessä läpi kaikkien alkioiden indeksit deque<string>::size_type i = 0; while ( i < strdequesize( ) ) { cout << strdequeat(i) << " "; ++i; cout << endl; list-rakenteita voi käydä läpi vain nk iteraattoreiden avulla niistä lisää hetken kuluttua (s288) Funktiolla empty voidaan testata onko rakenne tyhjä (ei sisällä yhtään alkiota): if ( strdequeempty( ) ) { // Ei alkioita while (!olistaempty( ) ) { // olista:ssa on alkioita jäljellä

OHJ-1150 Ohjelmointi II 272 Kaikki STL-säiliöt määrittelevät sijoitusoperaation ja alustuksen toisesta samantyyppisestä säiliöstä: list<char> merkkilista_1; // merkkilista_2:n alkuarvoksi sama kuin // mitä merkkilista_1 sisältää list<char> merkkilista_2(merkkilista_1); // Sijoitetaan kaikki merkkilista_2:n // alkiot merkkilista_1:een merkkilista_1 = merkkilista_2; Alustukseen (siis alkuarvon asettamiseen määrittelyn yhteydessä) on muitakin vaihtoehtoja: Alkioiden lukumäärä kerrottu: // 500 kappaletta merkkijonoja vector<string> jasenet(500); Alkioiden lukumäärä ja alkuarvo kerrottu: // 24 reaalilukua, jotka alustettu nollaksi deque<double> lampotilat( 24, 00); Jos sarjarakenteelle ei määrittelyn yhteydessä kerrota alkioiden lukumäärää, rakenne on aluksi tyhjä (mutta alkioitahan voi lisätä) Vaikka alkioiden lukumäärä on kerrottu, alkioita voidaan silti myös lisätä ja poistaa OHJ-1150 Ohjelmointi II 273 Kaikille STL-sarjoille voi määrittelyn yhteydessä antaa alkuarvon (alkusisällön) alustuslistan avulla: vector<int> ivec = { 4, 7, 1 ; deque<string> strdeq = { "x", "yyyy" ; list<double> dlist = { 12, 34, 56, 78 ; Ajatus on täysin analoginen esimerkiksi structtyyppisten muuttujien alustukseen verrattuna: aaltosulkeiden sisällä luetellaan pilkuilla eroteltuina kaikki alustusarvot/-alkiot Säiliön alkukoko (siis size( )-funktion paluuarvo) on luonnollisesti sama kuin alustusalkioiden lukumäärä Alustuslistan avulla alustettuihin säiliöihin voi lisätä ja niistä voi poistaa alkioita normaalisti Alustuslistan sisältö voidaan myös sijoittaa entuudestaan olemassa olevaan muuttujaan: ivec = { 9, 8, 7, 6, 5 ; strdeq = { "aaa", "bbb", "ccc" ; dlist = { 99, 88, 77 ; Tämä on uuden c++-standardin tuoma ominaisuus, joka toimii Ohjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä Jos osallistut jatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissa opetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio kääntäjästä, eikä tämä ominaisuus toimi Älä siis tule liian riippuvaiseksi ominaisuuden olemassaolosta OHJ-1150 Ohjelmointi II 274 Sijoituksen jälkeen säiliö sisältää pelkästään alustuslistan alkiot samassa järjestyksessä ja sen koko on sama kuin alustuslistan alkioiden lukumäärä Alustuslistan alkioiden ei ole pakko olla literaalisia arvoja, vaan esimerkiksi seuraava on mahdollista: int func(string mjono) { int luku; ivec = { 1, func("abc"), 2, luku, 3 ; Alustuslista voi myös olla funktion todellisena parametrina, jos muodollisen parametrin tyyppi on STL-sarja: void tulosta(const vector<int>& v) { tulosta( { func("abc"), 2, luku ); OHJ-1150 Ohjelmointi II 275 Säiliö voidaan tyhjentää clear-funktiolla ivecclear( ); jonka jälkeen rakenteessa ei ole yhtään alkiota (mutta lisäys on edelleen mahdollista) Kahta samantyyppistä sarjaa voi tutkia vertailuoperaattoreilla: if ( merkkilista_1 == merkkilista_2 ) { // Listat yhtäsuuria: samat alkiot // samassa järjestyksessä Nämä olivat perusoperaatiot STL-sarjoille Muihin mahdollisuuksiin palataan iteraattorien (s288) ja algoritmien (s297) yhteydessä Kannattaa myös tutustua C++-kirjastoreferenssiin http://wwwcstutfi/~aps/doc/cpprefpdf Tämä on uuden c++-standardin tuoma ominaisuus, joka toimii Ohjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä Jos osallistut jatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissa opetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio kääntäjästä, eikä tämä ominaisuus toimi Älä siis tule liian riippuvaiseksi ominaisuuden olemassaolosta

OHJ-1150 Ohjelmointi II 276 Assosiatiiviset säiliöt (associative container) Assosiatiiviset säiliöt järjestävät rakenteeseen talletetut tiedot omien tarpeidensa mukaan: siten että lisäys- ja hakuoperaatiot ovat mahdollisimman nopeita Rakenne ei ole sellainen, jossa alkioiden voisi ajatella olevan peräkkäisjärjestyksessä, joten niiden järjestysnumerosta tai paikasta ei voi puhua Assosiatiiviseen säiliöön tieto talletetaan (haku)avaimen perusteella myös haku tapahtuu saman avaimen avulla STL:ssä assosiatiivisia säiliöitä ovat: set: analoginen matemaattisen joukon kanssa, multiset: joukko, johon sama alkio voi kuulua useammin kuin kerran, map: liittää avainarvoon varsinaisen tietoalkion ja multimap: yhteen avainarvoon voi liittyä useampia tietoalkioita OHJ-1150 Ohjelmointi II 277 Kuten sarjojen kanssa, assosiatiivisten säiliöiden käyttöönotto tapahtuu include-direktiivillä: #include <set> // sekä set että multiset #include <map> // sama sekä map että multimap set- ja multiset-rakenteet vaativat, että talletettavalle avaimelle toimivat sijoitusoperaattori, alustus ja vertailu toisen samantyyppisen tietoalkion kanssa map- ja multimap-rakenteisiin talletettavalta tiedolta vaaditaan, että sekä avaimelle että varsinaiselle tietoalkiolle toimivat sijoitus ja alustus toisesta samantyyppisestä tietoalkiosta Lisäksi avaimelle pitää toimia vertailu map- ja multimap-rakenteisiin talletettava tieto koostuu kahdesta osasta: avaimesta ja varsinaisesta tietoalkiosta map- ja multimap-säiliömuuttujan määrittely on muotoa: map<avaintyyppi, alkiotyyppi> map_muuttuja; multimap<avaintyyppi, alkiotyyppi> multimap_muuttuja; OHJ-1150 Ohjelmointi II 278 Ideoita assosiatiivisten säiliöiden käyttökohteista: set kun on tarpeen selvittää kuuluuko alkio joukkoon vai ei, mutta avaimeen ei liity lisäinformaatiota Esimerkiksi arvottujen lottonumeroiden joukko: set<int> arvotut_lottonumerot; multiset jos on tarpeen pitää kirjaa joukosta tietoalkioita, kun tietyn niminen/numeroinen/tunnisteinen alkio voi esiintyä joukossa useammin kuin kerran Esimerkiksi kauppakassin sisältö: multiset<string> ostokset; voi sisältää vaikka "maito"-nimisen tuotteen useammin kuin kerran map sitoo yksikäsitteisen avainarvon ja siihen liittyvän tiedon toisiinsa Esimerkiksi avaimena opiskelijanumero (yksikäsitteinen) ja arvona kyseisen opiskelijan muut tiedot: map<int, Opiskelija> opiskelijarekisteri; OHJ-1150 Ohjelmointi II 279 multimap kuten map, mutta avain ei ole yksikäsitteinen multimap voi sisältää useita identtisiä avaimia, joihin liittyy eri arvo Esimerkiksi puhelinluettelo, jossa voi olla useita samannimisiä ihmisiä (avain, joka ei ole yksikäsitteinen), joilla kullakin on oma puhelinnumeronsa: multimap<string, int> puhelinluettelo; Vaikka multiset ja multimap ovat hyödyllisiä, niihin ei kurssilla puututa sen tarkemmin Kannattaa myös pitää mielessä, että STL-säiliöiden (sekä sarjojen että assosiatiivisten säiliöiden) alkiot voivat olla toisia STL-säiliöitä, esimerkiksi: vektori jonka alkiot ovat vektoreita: vector<vector<double>> ratkaisumatriisi; map jonka alkiot ovat listoja: map<string, list<double>> pankkitilin_otot_ja_panot;

OHJ-1150 Ohjelmointi II 280 Lyhyitä esimerkkejä set-rakenteesta Määritellään joukko, johon voi tallentaa merkkijonoja: set<string> laiva_on_lastattu; Määrittelyn jälkeen joukko on automaattisesti tyhjä Joukoille toimii alustus ja sijoitus toisesta samantyyppisestä joukosta: set<int> lottonumerot_1; set<int> lottonumerot_2(lottonumerot_1); lottonumerot_1 = lottonumerot_2; Joukkoon saadaan lisättyä uusi alkio insert-funktiolla: laiva_on_lastattuinsert("koirilla"); Lisäys toimii silloinkin, jos alkio kuului joukkoon jo entuudestaan se vaan ei tee mitään Alkion kuulumista joukkoon voi tutkia find-funktiolla: if (laiva_on_lastattufind(sana) == laiva_on_lastattuend( ) ) { // sana ei kuulu laiva_on_lastattu-joukkoon else { // sana-merkkijono oli joukossa jo entuudestaan OHJ-1150 Ohjelmointi II 281 Funktio find palauttaa nk iteraattorin näistä hetken päästä lisää (s288) Tässä vaiheessa riittää tietää, että paluuarvo on yhtäsuuri kuin kyseisen joukon end-funktion paluuarvo vain, jos parametri ei kuulu joukkoon Yksittäinen alkio voidaan poistaa erase-funktiolla: if (laiva_on_lastattufind(sana) == laiva_on_lastattuend( ) ) { // Sana ei kuulu joukkoon: ei voi poistaa else { laiva_on_lastattuerase( sana); Poistonkin voi tehdä ilman, että erikseen tarkastaa, kuuluuko poistettava alkio joukkoon vai ei jos ei kuulu, ei tehdä mitään Joukosta voidaan poistaa kaikki alkiot funktiolla clear tuloksena tyhjä joukko: laiva_on_lastattuclear( ); OHJ-1150 Ohjelmointi II 282 Keskenään samantyyppisiä joukkoja voi tutkia vertailuoperaattoreilla: if ( lottonumerot_1!= lottonumerot_2 ) { // Joukkojen alkioissa eroja else { // Joukoissa täsmälleen samat alkiot Joukkoon kuuluvien alkioiden lukumäärän saa selville size-funktiolla: if ( lottonumerot_1size( )!= LOTTONUMEROITA ) { // Jokin mättää: väärä määrä lottonumeroita Funktio empty selvittää, onko joukko tyhjä Kaikki mitä sivuilla 273 274 todettiin alustuslistoista, pätee sellaisenaan myös set-rakenteisiin Joukon alkioiden läpikäynti toteutetaan iteraattorin avulla (s288) Tämä on uuden c++-standardin tuoma ominaisuus, joka toimii Ohjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä Jos osallistut jatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissa opetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio kääntäjästä, eikä tämä ominaisuus toimi Älä siis tule liian riippuvaiseksi ominaisuuden olemassaolosta OHJ-1150 Ohjelmointi II 283 Lyhyitä esimerkkejä map-rakenteesta Muista esitellyistä STL-rakenteista poiketen, maprakenteen määrittely sisältää sekä hakuavaimen että varsinaisen talletettavan tiedon tyypit: map<string, unsigned int> puhelinnumerot; Määrittelyn jälkeen map-rakenne on tyhjä Myös map-rakentelle on määritelty alustus ja sijoitus toisesta samantyyppisestä rakenteesta: map<string, double> hintalista_1; map<string, double> hintalista_2(hintalista_1); hintalista_1 = hintalista_2; map-rakennetta voi indeksoida [ ]-operaattorilla kuten vector- ja deque-rakenteita, mutta indeksin tyypin on oltava sama kuin hakuavaimen tyyppi: puhelinnumerot["matti"] = 123456789; cout << nimi << " " << puhelinnumerot[nimi] << endl;

OHJ-1150 Ohjelmointi II 284 Jälkimmäiseen käyttötilanteeseen sisältyy vaara: kun map:iä indeksoidaan, indeksoitu alkio lisätään rakenteeseen, vaikka se ei olisi siellä ennestään Jos tätä ei haluta, vaan tarkotus on käyttää arvoa vain jos se on rakenteessa, siitä on ensin varmistuttava: if (puhelinnumerotfind(nimi) == puhelinnumerotend( ) ) { // Hakuavain nimi ei ole luettelossa else { cout << nimi << " " << puhelinnumerot[nimi] << endl; Käytetty "find == end"-mekanismi toimii saman idean mukaisesti kuin joukkojen tapauksessa Kun tuon vaaran pitää mielessä, indeksointi on helpoin tapa käsitellä map-rakennetta Lisäksi on hyvä oivaltaa, että const- tai const-viitemap:iä ei voi indeksoida [ ]-operaattorilla Hakuavain-tieto -parin voi poistaa erase-funktiolla: if ( puhelinnumeroterase("ari") ) { // Poisto onnistui else { // Hakuavain "Ari" ei map:issä, epäonnistui OHJ-1150 Ohjelmointi II 285 Tarkasti ottaen em erase palauttaa poistettujen alkioiden lukumäärän, joka on map-rakenteen tapauksessa 0 tai 1 Kokonaislukujahan voi käyttää C-kielen perintönä ehdoissa nolla on false kaikki muut true Poiston voi myös tehdä tarkistamalla ensin käsin, onko avain rakenteessa: if (puhelinnumerotfind("ari")!= puhelinnumerotend( ) ) { puhelinnumeroterase("ari"); else { // Hakuavain "Ari" ei ole rakenteessa Rakenne tyhjennetääm clear-funktiolla: puhelinnumerotclear( ); Rakenteeseen talletettujen alkioiden lukumäärä ja tieto siitä, onko rakenne tyhjä, tuttuun tapaan sizeja empty-funktioilla OHJ-1150 Ohjelmointi II 286 Myös map-rakenteen alkioiden läpikäynti on toteutettava iteraattorien avulla, kuten set-rakenteellakin tehtiin (s288) Edellä annetut esimerkit ovat hyvin alkeellinen tapa käsitellä map-rakennetta Joustavampi tapa on pair-tyypin käyttö: pair<avaintyyppi, alkiotyyppi> tietopari; joka on vain STL:n kompakti tapa ilmaista, että muuttujan tietopari tyyppi on seuraava tietue: struct { avaintyyppi first; alkiotyyppi second; ; Edellisen kaltaisia pareja saadaan joustavasti luotua make_pair-funktiolla: pair<string, unsigned int> tietopari; tietopari = make_pair("topi", 212121212); Koska parit ovat struct-rakenteita, voidaan niitä käsitellä myös ""-operaattorilla: tietoparifirst = "Topi"; tietoparisecond = 212121212; OHJ-1150 Ohjelmointi II 287 Parin sisältämä hakuavain ja tietoalkio saadaan lisättyä map-rakenteeseen: puhelinnumerotinsert(tietopari); tai ilman tietopari-apumuuttujaa: puhelinnumerotinsert(make_pair("topi", 212121212) ); Vaikka pari-käsite tässä vaiheessa tuntuu tarpeettoman monimutkaiselta, käy ilmi, että maprakenteita iteraattorien avulla käsiteltäessä tietämys pair-tyypistä on välttämätön paha Kaikki mitä sivuilla 273 274 todettiin alustuslistoista, pätee sellaisenaan myös map-rakenteisiin yhdellä pienellä lisäyksellä Koska map-rakenne sisältää todellisuudessa pareja, on alustuslistankin muodostuttava pareista: map<string, unsigned int> puhelinnumerot = { { "Matti", 12345678, { "Maija", 87654321 ; Tämä on uuden c++-standardin tuoma ominaisuus, joka toimii Ohjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä Jos osallistut jatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissa opetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio kääntäjästä, eikä tämä ominaisuus toimi Älä siis tule liian riippuvaiseksi ominaisuuden olemassaolosta

OHJ-1150 Ohjelmointi II 288 STL-iteraattorit Iteraattorit ovat STL-kirjaston tarjoamia tietotyyppejä, joiden avulla voidaan käsitellä (tutkia ja muuttaa) säiliöihin talletettuja alkioita Tarkoituksena on tarjota yhdenmukaiset työkalut, jotta kaikkia säiliöitä voidaan käsitellä lähes samoin Iteraattoria voi ajatella kirjanmerkkinä, joka muistaa yhden säiliössä olevan alkion sijainnin Kahdella iteraattorilla voidaan esittää jokin osaväli säiliöön talletetuista alkioista: mikä on osavälin esimmäinen alkio ja mikä viimeinen alkio Jokainen STL-säiliö tarjoaa ohjelmoijalle joukon tietotyyppejä ja funkioita, joilla voidaan käsitellä kyseiseen säiliötyyppiin liittyviä iteraattoreita Tavallisimmin tarvitut iteraattorityypit ovat: vector<alkiotyyppi>::iterator deque<alkiotyyppi>::iterator list<alkiotyyppi>::iterator set<alkiotyyppi>::iterator map<avaintyyppi, alkiotyyppi>::iterator OHJ-1150 Ohjelmointi II 289 Edellisiä iteraattorityyppejä oleviin muuttujiin voidaan tallentaa tieto alkion sijaintipaikasta säiliössä Säiliötyypin jäsenfunktio begin kertoo säiliön ensimmäisen alkion sijainnin Jäsenfunktio end palauttaa iteraattorin joka osoittaa rakenteen loppuun: end ei osoita lailliseen alkioon vaan on eräänlainen loppumerkki Esimerkiksi yhdeksänalkioinen kokonaislukuvektori: vector<int> ivek(9) ivekbegin( ) ivekend( ) Iteraattorin osoittamaa alkiota voidaan käsitellä kohdistamalla iteraattoriin unaarinen *-operaattori Iteraattori saadaan siirrettyä osoittamaan säiliön seuraavaan alkioon ++-operaattorilla ja edelliseen alkioon -operaattorilla ==- ja! =-operaattoreilla voidaan testata, osoittavatko kaksi iteraattoria samaan vai eri alkioon OHJ-1150 Ohjelmointi II 290 Esimerkiksi koodinpätkä, joka käy vektorin alkiot läpi alusta loppuun, kertoo jokaisen alkion kahdella ja tulostaa muutetut alkiot näytölle: vector<int> lukuvektori; vector<int>::iterator iter = lukuvektoribegin( ); while ( iter!= lukuvektoriend( ) ) { *iter = 2 * *iter; // Alkion muuttaminen cout << *iter << endl; // Alkion arvon käyttäminen ++iter; // Seuraavaan alkioon siirtyminen Edellisestä näkyy selvästi, kuinka end-iteraattori ei enää osoita käsittelyä vaativaan alkioon, vaan sen avulla testataan, joko koko rakenne on käyty läpi Usein end-arvoa käytetään myös funktion paluuarvona osoittamaan, että säiliöstä ei löytynyt etsittyä alkiota (s280) Edellä esitetty läpikäyntimekanismi toimii kaikilla STL-säiliöillä toisena esimerkkinä joukon alkioiden tulostaminen (vaihtelun vuoksi for-silmukka): set<string> nimet; set<string>::iterator it; for ( it = nimetbegin( ); it!= nimetend( ); ++it ) { cout << *it << endl; OHJ-1150 Ohjelmointi II 291 Jos iteraattori osoittaa alkioon, joka on tietue (tai myöhemmin myös luokka), voidaan tietueen kenttiin viitata suoraan > -operaattorilla Kuten map-rakenteesta puhuttaessa todettiin, sen alkiot ovat todellisuudessa tietueita, jotka koostuvat hakuavaimesta (first-kenttä) ja varsinaisesta informaatiosta (second-kenttä) Tulostetaan puhelinluettelo-map-rakenteesta kaikkien "T"-kirjaimella alkavien puhelinnumerot: map<string, unsigned int> puhelinnumerot; map<string, unsigned int>::iterator puhiter; puhiter = puhelinnumerotbegin( ); while ( puhiter!= puhelinnumerotend( ) ) { // Avainkenttä: first (string) if ( puhiter >firstat(0) == T ) { // Informaatio: second (unsigned int) cout << puhiter >second << endl; ++puhiter;

OHJ-1150 Ohjelmointi II 292 OHJ-1150 Ohjelmointi II 293 Tähän mennessä esiteltyjen iteraattoreiden avulla voidaan operoida vain sellaisiin säiliöihin, joita on sallittua muuttaa ongelma jos funktioiden muodolliset parametrit ovat tehokkyyssyistä constvakioviitteitä Tämä ratkeaa seuraavien iteraattorityyppien avulla: vector<alkiotyyppi>::const_iterator deque<alkiotyyppi>::const_iterator list<alkiotyyppi>::const_iterator set<alkiotyyppi>::const_iterator map<avaintyyppi, alkiotyyppi>::const_iterator Esimerkiksi funktio, joka laskee kokonaislukulistan alkioiden summan: int Summaa(const list<int>& lukulista) { int summa = 0; list<int>::const_iterator it = lukulistabegin( ); while ( it!= lukulistaend( ) ) { summa = summa + *it; ++it: return summa; Kaikki STL-säiliöt map-rakennetta lukuunottamatta voidaan alustaa iteraattorien avulla toisista säiliöistä: vector<double> reavek; list<double> realista( reavekbegin( ), reavekend( ) ); Yllä siis realista alustetaan samassa järjestyksessä ja samoilla lukuarvoilla kuin mitä muuttujassa reavek oli Kannattaa huomata, että alustuksessa ei ole pakko käyttää begin- ja end-funktioita, vaan mikä tahansa iteraattoriväli käy Esimerkiksi lista olisi voitu alustaa siten, että vektorin ensimmäinen ja viimeinen alkio olisi jätetty pois: vector<double> reavek; vector<double>::iterator valin_alku = reavekbegin( ); ++valin_alku; vector<double>::iterator valin_loppu = reavekend( ); valin_loppu; list<double> realista( valin_alku, valin_loppu); const_iterator-tyypit eivät salli iteraattorin osoittaman alkion arvon muuttamista OHJ-1150 Ohjelmointi II 294 Tai jos sattuu tietämään, että vector- ja dequeiteraattoreihin voi lisätä tai vähentää kokonaisluvun ja saada siten uuden iteraattorin, joka osoittaa kyseisen lukumäärän verran alkioita eteentai taaksepäin Edellinen esimerkki menisi siis lyhemmin: list<double> realista( reavekbegin( ) + 1, reavekend( ) 1); Vaikka tähän saakka on jatkuvasti käytetty ilmaisua «iteraattori osoittaa säiliön alkioon»: vector<int> ivek(9) ivekbegin( ) ivekbegin( )+4 ivekend( ) Iteraattorien voi myös ajatella osoittavan alkioiden väliin, jolloin unaarinen *-operaattori ja > -operaattori tulkitaan siten, että halutaan käsitellä heti välin perässä seuraavaa alkiota: vector<int> ivek(9) OHJ-1150 Ohjelmointi II 295 Tämä näkemys auttaa tulkitsemaan tilannetta paremmin silloin, kun kahden iteraattorin avulla esitetään osajoukko säiliön peräkkäisistä alkioista Jos välttämättä haluaa ajatella iteraattorien osoittavan alkioihin välien sijaan, pitää kahdella iteraattorilla esitetyt alkiojoukot aina tulkita ylhäältä avoimiksi: siis ylempi osoitettu alkio ei enää kuulu käsiteltäviin alkioihin Kumpikin esitetty tulkinta on vain tapa ajatella asiaa, kannattaa valita se, joka tuntuu itselle luonnollisimmalta Kirjallisuudessa näkee molempia tapoja, joten kannattaa olla aina tarkkana siitä, kumpaa tapaa kirjoittaja noudattaa Vielä yksi olennainen asia, joka on syytä pitää mielessä: jos säiliöön on lisätty tai siitä on poistettu alkioita, et voi luottaa siihen, että ennen muutosta talletetut iteraattorit toimisivat muutoksen jälkeen oikein ivekbegin( ) ivekbegin( )+4 ivekend( )

OHJ-1150 Ohjelmointi II 296 OHJ-1150 Ohjelmointi II 297 Kärjistettynä esimerkkinä: jos iteraattori osoittaa alkioon, joka poistetaan säiliöstä, mihin iteraattori osoittaa poiston jälkeen? Nämä olivat perusideat STL-iteraattoreista Muihin käyttötarkoituksiin palataan heti jatkossa algoritmien yhteydessä Myös C++-kirjastoreferenssi kertoo lisää yksityiskohtia: http://wwwcstutfi/~aps/doc/cpprefpdf STL-algoritmit STL-kirjasto algorithm tarjoaa valmiina yleisimmät operaatiot, joita säiliön sisältämille alkioille on tarpeen tehdä Algoritmikirjaston käyttämiseksi on koodiin lisättävä: #include <algorithm> STL:n valmiit algoritmit ovat geneerisiä: ne eivät ota kantaa säiliön tyyppiin, vaan ne osaavat operoida millä tahansa säiliöllä, kunhan säiliön alkioihin voidaan viitata iteraattorien avulla Käsiteltäväksi haluttu osa säiliön alkoista kerrotaan algoritmifunktiolle iteraattorivälin avulla jokaisella funktiolla on aina vähintään kaksi iteraattoriparametria Joskus iteraattoriparametreja on useampiakin, jos funktio tallettaa tuloksia johonkin toiseen säiliöön OHJ-1150 Ohjelmointi II 298 OHJ-1150 Ohjelmointi II 299 Esimerkiksi algoritmi (siis funktio) sort osaa lajitella vector- ja deque-rakenteen kasvavaan järjestykseen, kunhan sille kerrotaan iteraattoreilla lajiteltavaksi haluttu osaväli: vector<int> ivek; deque<double> ddeq; sort(ivekbegin( ), ivekend( ) ); sort(ddeqbegin( ), ddeqend( ) ); Listaa ei voi lajitella sort-funktiolla (sort-algoritmi ei osaa lajitella peräkkäissaantirakennetta), mutta sillä on jäsenfunktio sort, joka osaa: list<string> slist; slistsort( ); Assosiatiivisia säiliöitä ei voi lajitella, sillä kuten muistetaan, set- ja map-rakennetta käytettäessä ohjelmoija ei voi vaikuttaa alkioiden järjestykseen STL-iteraattorit on siinäkin suhteessa nerokkaasti määritelty käsite, että tarpeen vaatiessa myös C++:n omia taulukoita voi ajatella sarjoina ja käsitellä algoritm-kirjaston funktioilla iteraattorimaisesti begin-iteraattoriksi tulkitaan tällöin taulukkomuuttujan_nimi ja end-iteraattoriksi lausekkeen taulukkomuuttujan_nimi + alkioiden_lukumäärä arvo Esimerkiksi kokonaislukutaulukko voitaisiin lajitella sort-funktiolla: int itaulukko[1000]; sort(itaulukko, itaulukko + 1000); Selitys sille, mitä lauseke itaulukko + 1000 oikeasti tarkoittaa, selviää vasta myöhemmin, kun tutustutaan osoittimiin (s446) Seuraavassa on esiteltynä hyvin lyhykäisesti käyttökelpoisimpia algorithm-kirjaston funktioita Kannattaa pitää mielessä, että kaikissa tapauksissa begin- ja end-iteraattorien paikalla mikä tahansa kahden iteraattorin esittämä osaväli on kelvollinen

OHJ-1150 Ohjelmointi II 300 count laskee säiliössä olevien tietyn arvoisten alkioiden lukumäärän: list<string> vihamiehet: cout << count(vihamiehetbegin( ), vihamiehetend( ), "Aki") << " kappaletta Aki-nimisiä vihamiehiä!" << endl; find etsii säiliöstä haluttua alkiota ja palauttaa iteraattorin ensimmäiseen löytämäänsä alkioon tai end-iteraattorin, jos ei löydy: deque<string> potilasjono; deque<string>::iterator iter; iter = find(potilasjonobegin( ), potilasjonoend( ), "Hanski"); if ( iter == potilasjonoend( ) ) { // Ei ole Hanskia potilasjonossa else { // Hanski löytyi ja sijaitse iter:in osoittamassa // paikassa: tulostetaan ja poistetaan jonosta cout << *iter << endl; potilasjonoerase(iter); Edellisessä erase ei ole algorithm-kirjaston funktio, vaan kaikille STL-säiliöille määritelty funktio, jolla iteraattorin osoittama alkio poistetaan säiliöstä (vrt http://wwwcstutfi/~aps/doc/cpprefpdf) OHJ-1150 Ohjelmointi II 301 min_element ja max_element etsivät säiliöstä arvoltaan pienimmän tai suurimman alkion ja palauttavat iteraattorin kyseiseen alkioon: set<int> lukum; set<int>::iterator pienin_it; pienin_it = min_element(lukumbegin( ), lukumend( ) ); cout << "Pienin lukumäärä: " << *pienin_it << endl; Seuraavat alkioita tai niiden järjestystä muuttavat algoritmit toimivat vain sarjoilla (miksi?) remove "poistaa" (siirtää loppuun) säiliöstä kaikki tietyn arvoiset alkiot: deque<string> potilasjono; remove(potilasjonobegin( ), potilasjonoend( ), "Hanski"); replace korvaa tietyt arvot uusilla arvoilla: replace(sanalistabegin( ), sanalistaend( ), "TTKK", "TTY"); jossa siis kaikki sanat "TTKK" korvataan sanalla "TTY" reverse kääntää alkioiden järjestyksen takaperin: reverse(potilasjonobegin( ), potilasjonoend( ) ); // Taulukon alkiot takaperin int luvut[100]; reverse(luvut, luvut + 100); OHJ-1150 Ohjelmointi II 302 random_shuffle sekoittaa alkiot satunnaiseen järjestykseen: vector<pelikortti> korttipakka(52); random_shuffle(korttipakkabegin( ), korttipakkaend( ) ); copy kopioi säiliön alkiot johonkin toiseen säiliöön: list<double> lukulista; vector<double> lukuvektori(lukulistasize( ) ); copy(lukulistabegin( ), lukulistaend( ), lukuvektoribegin( ) ); Kopioinnin kohteessa (siis siellä minne alkiota kopioidaan) pitää olla valmiiksi tilaa tarjolla yllä lukuvektori on valmiiksi alustettu sisältämään yhtä monta alkiota kuin lukulista Tämä pätee kaikkiin STL-algoritmeihin, jotka tallettavat tuloksia säiliöön: kohdesäiliössä pitää olla valmista tilaa kaikille talletettaville alkioille Tämä voidaan välttää nk lisäysiteraattorien avulla: list<double> lukulista; vector<double> lukuvektori; copy(lukulistabegin( ), lukulistaend( ), back_inserter(lukuvektori) ); OHJ-1150 Ohjelmointi II 303 Jolloin kaikki tallennukset lisäävät uuden alkion lukuvektorin loppuun lukuvektorin koko kasvaa niin monella alkiolla kuin mitä copy kopioi Tämä toimii myös, jos kohdesäiliössä oli alkioita jo ennestään Kahden säiliön sisältämät alkiot voidaan siis yhdistää kolmanteen: list<string> ekasailio; vector<string> tokasailio; deque<string> kolmassailio; copy(ekasailiobegin( ), ekasailioend( ), back_inserter(kolmassailio) ); copy(tokasailiobegin( ), tokasailioend( ), back_inserter(kolmassailio) ); Tämä ei ole ainoa eikä välttämättä paraskaan tapa yhdistää säiliöitä, mutta havainnollistaa lisäysiteraattoria mukavasti On myös olemassa front_inserter-funktio, joka palauttaa säiliön alkuun lisäykset suorittavan iteraattorin lisättyjen alkioiden järjestys muuttuu päinvastaiseksi

OHJ-1150 Ohjelmointi II 304 OHJ-1150 Ohjelmointi II 305 for_each-algoritmi kutsuu käyttäjän määräämää aliohjelmaa kaikille iteraattorivälin alkioille: set<string> sanajoukko; cout << "Joukkoon kuuluvat sanat: " << endl; for_each(sanajoukkobegin( ), sanajoukkoend( ), TulostaYksiString); Huomaa, että for_each:ille on annettu parametrina TulostaYksiString-funktio, ei suinkaan TulostaYksiString-funktion paluuarvo Kyseessä on nk funktioparametri TulostaYksiString-funktio pitää määritellä seuraavasti, jotta sen voisi antaa for_each:ille parametrina: void TulostaYksiString(const string& str) { cout << str << endl; for_each:ille parametrina annettavan funktion on noudatettava seuraavia sääntöjä: Paluuarvon tyyppi on void Parametrin tyyppi on sama kuin käsiteltävän säiliön alkioiden tyyppi Jos for_each:in kutsuman funktion muodollinen parametri on viite- eli muuttujaparametri, funktio voi muuttaa alkuperäisen säiliön alkioita: list<int> lukulista; // Kerrotaan kaikki lukulistan alkiot luvulla 2 for_each(lukulistabegin( ), lukulistaend( ), KerroKahdella); jossa KerroKahdella on määritelty seuraavasti: void KerroKahdella(int& luku) { luku = 2 * luku; transform kuvaa säiliön alkioiden arvot uusiksi arvoiksi ja tallettaa saadut arvot toiseen säiliöön: list<string> sanat; vector<int> pituudet; transform(sanatbegin( ), sanatend( ), back_inserter(pituudet), MerkkijononPituus); jossa MerkkijononPituus olisi määritelty: int MerkkijononPituus(const string& str) { return strlength( ); OHJ-1150 Ohjelmointi II 306 OHJ-1150 Ohjelmointi II 307 Edellinen koodi siis kuvaisi MerkkijononPituusfunktion avulla sanat-listassa olevat merkkijonot niiden pituuksiksi ja tallettaisi saadut pituudet samassa järjestyksessä pituudet-vektoriin transform-algoritmin kanssa käytettävän kuvausfunktion täytyy noudattaa seuraavia sääntöjä: Parametrin tyyppi on sama kuin lähtöarvosäiliön alkioiden tyyppi Paluuarvon tyyppi on sama kuin kohdesäiliön alkioiden tyyppi Lähes kaikista algorithm-kirjaston funktioista on versio, jonka toimintaa voidaan säätää funktioparametrin avulla Esimerkiksi count-funktiosta on olemassa versio count_if, joka laskee vain tietyn ehdon täyttävien alkioiden lukumäärän: set<int> joukko; // Montako paritonta luku on joukossa int parittomia = count_if(joukkobegin( ), joukkoend( ), OnkoPariton); Edellisessä OnkoPariton olisi määritelty: bool OnkoPariton(int luku) { // Pariton luku on sellainen, jonka jako- // jäännös kahdella jaettaessa ei ole nolla return luku % 2!= 0; Tapauksissa joissa edellisen kaltainen valintafunktio on käytettävissä, sen täytyy noudattaa seuraavia sääntöjä: Paluuarvon tyyppi on bool Parametrin tyyppi on sama kuin operoitavan säiliön alkioiden tyyppi (voi olla myös const-viite kyseiseen tyyppiin) Paluuarvo true, jos parametrina saatuun alkioon halutaan kohdistaa algoritmifunktion mukainen toimenpide, muutoin paluuarvo false Ainakin seuraavat valintafunktiota hyödyntävät algoritmit on käytettävissä: count_if, find_if, replace_if ja remove_if

OHJ-1150 Ohjelmointi II 308 Toinen hyödyllinen tapa säätää joidenkin STLalgoritmin toimintaa ovat vertailufunktiot: funktiot jotka vertailevat kahden alkion suuruutta Esimerkiksi sort-funktiolle voi antaa lisäparametrin, joka kertoo, kuinka alkioiden suuruutta pitäisi tulkita: struct Opiskelija { string nimi; int opnum; ; vector<opiskelija> rekisteri; // Lajittelu nimen mukaiseen järjestykseen sort(rekisteribegin( ), rekisteriend( ), VertaileNimia); // Lajittelu opiskelijanumeron mukaiseen järjestykseen sort(rekisteribegin( ), rekisteriend( ), VertaileOpnumeja); jossa funktiot on määritelty: bool VertaileNimia( const Opiskelija& o1, const Opiskelija& o2) { return o1nimi < o2nimi; OHJ-1150 Ohjelmointi II 309 Vertailufunktioiden on täytettävä seuraavat vaatimukset: Paluuarvon tyyppi on bool Parametreja on kaksi ja niiden tyyppien oltava sama kuin käsiteltävän säiliön alkioiden tyyppi Paluuarvo on true, jos ensimmäinen parametri tulkitaan pienemmäksi kuin toinen parametri, false muussa tapauksessa Myös min_element- ja max_element-funktiot hyväksyvät valinnaisesti kolmannen parametrin, joka on em kaltainen vertailufunktio: // Tulostetaan opiskelijanumeroltaan // suurimman opiskelijan tiedot vector<opiskelija>::iterator suurin_opnum; suurin_opnum = max_element(rekisteribegin( ), rekisteriend( ), VertaileOpnumeja); cout << suurin_opnum >nimi << " " << suurin_opnum >opnum << endl; bool VertaileOpnumeja(const Opiskelija& o1, const Opiskelija& o2) { return o1opnum < o2opnum; OHJ-1150 Ohjelmointi II 310 Nyt STL-algoritmien perusajatusten pitäisi olla hallussa Hiukan lisäinformaatiota löytyy tutusta osoitteesta: http://wwwcstutfi/~aps/doc/cpprefpdf Hyvin yksityiskohtainen ja melko selkeä kuvaus C++-standardikirjastosta (STL mukaanlukien) löytyy kirjasta: Josuttis: The C++ Standard Library (Addison-Wesley) OHJ-1150 Ohjelmointi II 311 Monimutkaisten tyyppinimien yksinkertaistaminen (typedef) Monimutkaisia tietotyyppejä käytettäessa käy usein niin, että tyyppien nimistä muodostuu pitkiä, vaikeaselkoisia ja/tai työläitä kirjoittaa C++:ssa on mekanismi, joilla ohjelmoija voi antaa tietotyypeille uusia nimiä, jotka käyttäytyvät identtisesti alkuperäisen tyypin kanssa Ajatellaan seuraavaa määrittelyä: typedef unsigned long int ulong; Mitä tuossa tapahtuu on se, että tietotyypille unsigned long int annetaan uusi nimi ulong Tämän jälkeen C++ ei näe mitään eroa sillä, kumpaa tyyppinimeä käytetään muuttujien jne määrittelyyn: unsigned long int jokumuuttuja; tarkoittaa samaa kuin: ulong jokumuuttuja;

OHJ-1150 Ohjelmointi II 312 Tyyppien uudelleenimeäminen tapahtuu siis varatun sanan typedef avulla typedef:in syntaktinen logiikka on: 1 Kirjoitetaan muuttujanmäärittely, jossa muuttujan tyyppinä on lisänimeä kaipaava (alkuperäinen) tietotyyppi: unsigned long int ulong; 2 Lisätään edellisen muuttujanmäärittelyrivin eteen varattu sana typedef, jolloin määrittelyn merkitys muuttuu tyypin nimeämiseksi: typedef unsigned long int ulong; 3 Alkuperäisen tietotyypin uusi lisänimi on se, jota kohdassa 1 oli käytetty muuttujan nimenä (ulong) typedef on hyvä työkalu selkeyttämään ohjelmaa silloin, kun käytettyjen tietotyyppien nimet ovat syystä tai toisesta hankalia Erityisen hyvä esimerkki tästä ovat STL-säiliöt ja -iteraattorit OHJ-1150 Ohjelmointi II 313 Ajatellaan seuraavia tuttuja STL-määrittelyitä: vector<int> vek; vector<int>::iterator iter; vector<int>::size_type koko; Koodia saisi ainakin jossain mielessä selkeämmäksi, jos lisäisi sen alkuun typedef-määrittelyt: typedef vector<int> int_vector; typedef int_vector::iterator int_vector_iterator; typedef int_vector::size_type int_vector_size_type; Huomaa kuinka int_vector-tyyppinimeä voi käyttää heti sen määrittelyn jälkeen myös osana toisia typedef-määrittelyitä Myös sivun ylälaidan muuttujamääritelyt voidaan nyt kirjoittaa yksinkertaisemmin: int_vector vek; int_vector_iterator iter; int_vector_size_type koko; OHJ-1150 Ohjelmointi II 314 Muuttujan tyypin automaattinen määrittely Jos muuttuja alustetaan määrittelyvaiheessa jollain arvolla, kääntäjä osaa haluttaessa määritellä muuttujan tyyppiksi saman kuin alustuslausekkeen tyyppi on Tämä tapahtuu merkitsemällä muuttujan tyypiksi varattu sana auto: auto tulos = 345 * a; Kääntäjää huomaa nyt, että alustuslausekkeen 345 * a arvo on tyypiltään double, jolloin muuttujan tulos tyypiksi tulee myös double Aivan sama olisi saatu aikaan myös kirjoittamalla perinteisemmin: double tulos = 345 * a; Tämä on uuden c++-standardin tuoma ominaisuus, joka toimii Ohjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä Jos osallistut jatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissa opetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio kääntäjästä, eikä tämä ominaisuus toimi Älä siis tule liian riippuvaiseksi ominaisuuden olemassaolosta OHJ-1150 Ohjelmointi II 315 Varsinainen hyöty saavutetaan silloin, kun määritellään muuttujia, joiden tyypit ovat monimutkaisia tai muuten vain pitkiä ja työläitä kirjoittaa toistuvasti Vertaa esimerkiksi seuraavia täsmälleen samat lopputulokset tuottavia määrittelyitä: tai map<string, vector<list<string>>>::iterator m_iter = mbegin( ); vector<list<string>>::iterator v_iter = mbegin( ) >secondbegin( ); auto m_iter = mbegin( ); auto v_iter = mbegin( ) >secondbegin( ); Myös for-silmukan silmukkamuuttujan tyyppi voidaan määritellä vastaavasti: for ( auto iter = mbegin( ); iter = mend( ); ++iter ) { Muuttujan tyypin määräytyminen alustuslausekkeen tyypin perusteella on mitä ilmeisimmin elämää helpottava apuväline Siitä ei kuitenkaan kannata tulla liian riippuvaiseksi, koska jossain tilanteissa alkuperäisiä tyyppejä on pakko käyttää (esim funktioiden muodollisten parametrien määrittely)

OHJ-1150 Ohjelmointi II 316 Abstraktiot Sivistyssanastosta ➀ voi löytää seuraavat määritelmät: abstrakti ajatuksessa eristetty, semmoisenaan katsottu tai ajateltu, käsitteellinen abstraktio epäolennaisten ainesten erottaminen ajatuksessa itse oliosta, käsitteenmuodostus (tai sen tulos), yleistys, käsite Määritelmissä ei sinänsä ole mitään vikaa, ne ovat vain liian yleisellä tasolla esitettyjä, ➁ jotta niitä voisi ymmärtää ilman esimerkkejä Tutkitaan muutama esimerkki sekä elävästä elämästä että tietokoneohjelmoinnin maailmasta ➀ Hagfors, Manninen: Jokamiehen sivistyssanasto, 11painos, WSOY, 1972, ISBN 951 0 01686 1 ➁ Siis liian abstrakteja! OHJ-1150 Ohjelmointi II 317 Puhutun kielen sana auto Sana auto tarkoittaa laitetta, jolla pääsee nopeasti paikasta toiseen Jokainen ymmärtää tuon merkityksen ja pystyy keskustelemaan autoista, vaikkei hänellä olisikaan ymmärrystä auton todellisesta luonteesta: esim kuinka polttomoottori tai vaihdelaatikko toimivat, saatika sitten kuinka suunnitella ja rakentaa auto omin käsin Auto on siis abstraktio, joka ihmisten ajatusmaailmassa kuvaa erittäin monimutkaisen laitteen niin, että sitä voi ajatella ja siitä voi keskustella takertumatta epäolennaisiin yksityiskohtiin Itse asiassa kaikki puhutun kielen sanat ovat abstraktioita aivan samoilla perusteilla Toki samasta asiasta voi olla eri tasoisia abstraktioita (abstraktiotasoja) vrt Risto Rikkaan, Antti Autoilijan, Matti Mekaanikon ja Seppo Suunnittelijan näkemys autosta OHJ-1150 Ohjelmointi II 318 Abstraktiot ohjelmoinnissa Kaikki tieto, jota tietokoneohjelmissa käsitellään, on abstraktia Esimerkiksi tietotyyppi int Paitsi että kokonaisluvut itsessään ovat abstraktioita, niin niiden esitys ohjelmointikielessä (siis int) on sitä myös: int:in toteutuksesta konekieli- tai puolijohdetasolla ei ole mitään varmuutta Kuitenkin int-tyyppiä voidaan käyttää, kun ymmärretään sen käyttäytyvän kuin kokonaisluku Usein puhutaan tietoabstraktiosta (data abstraction) Ohjelmointiin liittyy myös toinen abstraktiokäsite: toiminnallinen abstraktio (functional abstraction) Hyvä esimerkki toiminnallisesta abstraktiosta ovat funktiot ja aliohjelmat OHJ-1150 Ohjelmointi II 319 Funktio tai aliohjelma on sarjalle toimintoja annettu abstrakti nimi Esimerkiksi, kun on päätetty, että luvun itseisarvo saadaan funktiolla double Itseisarvo(double d) niin sen jälkeen on yhdentekevää, kuinka funktio on toteutettu, kunhan se tuottaa itseisarvon määritelmän mukaisen tuloksen parametristaan: double Itseisarvo(double d) { if (d < 0) { return d; else { return d; tai #include <cmath> double Itseisarvo(double d) { return abs(d); // kirjastofunktio abs( ) tai jollain vielä aivan toisella tavalla

OHJ-1150 Ohjelmointi II 320 Mitä (tietokone)ohjelmointi nyt tämän uuden tiedon valossa on? (vrts3) Uusien abstraktioiden opettamista kääntäjälle/ koneelle, jotta ohjelmoijan olisi helpompi kuvata halutun ongelman ratkaisu muuten niin suppealla ohjelmointikielellä Tosiasiassa abstraktioiden parissa siis painittiin jo koko Ohjelmointi I-kurssi, vaikka asiaa ei näin syvällisesti ja filosofis metafyysisesti pohdittukaan Kun ohjelmoinnin yhteydessä puhutaan abstraktiosta, niin tarkoitetaan toteutuksen ja käytön erottamista toisistaan: tietotyyppejä voi käyttää ja ne toimivat odotetusti ja funktioita ja aliohjelmia voi kutsua ja ne suorittavat tehtävänsa oikein, vaikka toteutuksen yksityiskohdista ei tiedettäisi mitään OHJ-1150 Ohjelmointi II 321 Abstraktit tietotyypit Abstraktit tietotyypit ovat ohjelmoijan määrittelemiä tietotyyppejä, joissa tietoisesti pyritään soveltamaan»toteutuksen ja käytön erottelu»-periaatetta Tarkoitus on suunnitella ja rakentaa tietotyyppejä 1 Joiden toteutuksesta käyttäjällä ei ole tietoa, ja jos onkin, niin hän ei voi vahingossa (tai tarkoituksella) käyttää valitun toteutuksen ominaisuuksia hyväkseen (tiedon kätkentä, data/information hiding) 2 Ohjelmoija voi kuitenkin hyödyntää abstraktia tietotyyppiä, sillä sen alkuperäisen toteuttaja on kirjoittanut myös joukon funktioita ja aliohjelmia, joilla kaikki tarpeelliset operaatiot saadaan suoritettua Tällaista joukkoa abstraktiin tietotyyppiin liittyviä funktioita ja aliohjelmia kutsutaan sen rajapinnaksi tai julkiseksi rajapinnaksi OHJ-1150 Ohjelmointi II 322 Esimerkki jo entuudestaan tutusta abstraktista tietotyypistä on fstream-kirjaston ifstream: 1 Sen toteutuksesta ei ole tietoa: jollain»maagisella» tavalla se kuitenkin esittää fyysiset levytiedostot C++-ohjelmasta käytettävässä muodossa 2 Mukana rajapintafunktiot, joiden avulla fstreamtyyppiä käytetään: getline, peek, ignore ja monta muuta Rajapinnan funktiot kuvaavat abstraktin tietotyypin olennaiset ominaisuudet sillä abstraktiotasolla, jolla tyyppiä on tarkoitus käyttää Usein rajapinnan funktioita kutsutaan myös metodeiksi tai jäsenfunktioiksi OHJ-1150 Ohjelmointi II 323 Yksinkertainen esimerkki Suunnitellaan ensimmäinen abstrakti tietotyyppimme Vektori, mutta ei oteta vielä kantaa sen toteutukseen: katsotaan millainen sen rajapinta on ja miten sitä käytettäisiin ohjelmassa Vektori on kaksiulotteisen avaruuden vektori, jota on tarkoitus käyttää seuraavasti: ohjelmassa voi olla Vektori-tyyppisiä muuttujia, vektorin arvo (double-tyyppiset x- ja y-koordinaatti) voidaan lukea käyttäjältä näppäimistöltä, vektorista on mahdollista saada selville sen x- ja y-koordinaatit, vektoreita voi tulostaa näytölle x y-koordinaattitai polaarikoordinaattimuodossa ja vektoreita voidaan kiertää origon ympäri halutun kulman verran myötäpäivään

OHJ-1150 Ohjelmointi II 324 Pieni pääohjelma joka käyttää Vektori-tietotyyppiä: #include <iostream> #include <cstdlib> #include <cmath> using namespace std; vektori-tietotyypin määrittely puuttuu tästä: ks s 327 int main( ) { Vektori vek; vektulosta_xy_muodossa( ); // onko alustettu? vektulosta_polaarimuodossa( ); if (!veklue_nappaimistolta( ) ) { cerr << "Luku epäonnistui!" << endl; return EXIT_FAILURE; vektulosta_xy_muodossa( ); vektulosta_polaarimuodossa( ); vekkierra_origon_ympari(25); vektulosta_xy_muodossa( ); vektulosta_polaarimuodossa( ); double x = vekhae_x_koord( ); double y = vekhae_y_koord( ); cout << "Koordinaatit: " << x << ", " << y << endl; OHJ-1150 Ohjelmointi II 325 Ohjelman suoritus näyttäisi tältä: proffa> /vektorit (x, y) = (1, 0) (a, r ) = (0, 1) Syötä vektori: 30 40 (x, y) = (3, 4) (a, r ) = (531301, 5) (x, y) = (44094, 235738) (a, r ) = (281301, 5) Koordinaatit: 44094, 235738 proffa> Esimerkissä funktioiden kutsunotaatio on stringtyypistä ja tietovirroista tuttu muuttujafunktio() Tätä notaatiota tullaan jatkossakin käyttämään abstraktien tietotyyppien kanssa Olennaiset huomiot esimerkistä: Tietotyyppiä vektori pystyi käyttämään täydellä teholla ja koodi oli ymmärrettävää, vaikka tyypin toteutuksesta ei ollut mitään tietoa Rajapinnan funktiot määräävät, mitä abstraktilla tietotyypillä voi tehdä: kaikki muuttujalle vek tehdyt operaatiot tapahtuivat sopivaa rajapinnan funktiota kutsumalla OHJ-1150 Ohjelmointi II 326 On hyödyllistä havaita myös, että osa rajapinnan funktioista voidaan jakaa loogisiin ryhmiin: rakentajat eli konstruktorit Alustavat abstraktin tietotyypin muuttujan eli asettavat sille järkevän alkuarvon (esimerkin rakentaja on»piilossa» katso toteutusta) valitsimet eli selektorit Palauttavat toteutukseen kätkettyä tietoa: hae_x_koord( ) ja hae_y_koord( ) muuttajat eli mutaattorit Muuttavat alkion tilaa (eli toteutukseen kätkettyä tietoa): lue_nappaimistolta( ) ja kierra_origon_ympari( ) purkajat eli destruktorit Vapauttavat muuttujan varaamat resurssit sen jälkeen, kun muuttujaa ei enää tarvita Esimerkissä ei purkajaa (mutta myöhemmin tärkeä käsite) Muitakin ryhmiä on, mutta ne eivät tässä vaiheessa ole tärkeitä OHJ-1150 Ohjelmointi II 327 Vektori-tyypin toteutus C++-kielen työkalu abstraktien tietotyyppien toteuttamiseen on luokka (class) Luokkiin sisältyy kuitenkin C++:ssa kiusallisen paljon yksityiskohtia, joten näinkin yksinkertainen esimerkki muodostuu monimutkaiseksi class Vektori { // rajapinnan esittely public: // rakentaja Vektori( ); // valitsimet double hae_x_koord( ) const; double hae_y_koord( ) const; // mutaattorit bool lue_nappaimistolta( ); void kierra_origon_ympari(double kulma); // muut rajapinnan tarjoamat palvelut void tulosta_xy_muodossa( ) const; void tulosta_polaarimuodossa( ) const; // kätketyt toteutusyksityiskohdat private: double x; double y; ;