● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● Sisällysluettelo i Sisältö OHJ-1100 Ohjelmointi I Mitä on ohjelmointi? ....................................... 1 Tietokoneen ohjelmointi ..................................... 3 Harhaluulo ohjelmoinnista ................................... 5 Oikea malli ohjelmoinnista ................................... 7 Algoritmit ................................................ 9 Algoritmien muodostaminen ................................. 23 Ohjelmointikielistä ......................................... 25 Lausekielet ............................................... 26 Ohjelman tulkkaaminen ..................................... 27 Ohjelman kääntäminen ..................................... 28 Kääntäjän toiminta ......................................... 29 Ensimmäinen C++-ohjelma ................................... 30 Lähdekoodista suoritettavaksi ohjelmaksi ........................ 32 Editointi ................................................. 33 Emacs-editorin käytöstä ..................................... 34 Kääntäminen ............................................. 38 Virhetilanteet ............................................. 39 Varatut sanat ............................................. 42 C++-kielen varatut sanat ..................................... 43 Vastusesimerkki ............................................ 44 Perustietotyypit............................................ 46 Lausekeet ................................................ 48 Operaattorit .............................................. 49 ▼ ▼ ▼ ▼ ▼ ▼ ▼
553
Embed
kalvot - TUNI...OHJ-1100 Ohjelmointi I 1 Mitä on ohjelmointi? Ohjelmointi tarkoittaa yleisellä tasolla sitä, kun jokin systeemi, laite, ihminen tms. saadaan suorittamaan jonkun
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
▼ Ohjelmointi tarkoittaa yleisellä tasolla sitä, kun jokinsysteemi, laite, ihminen tms. saadaan suorittamaanjonkun toisen antamat toimenpiteet/käskyt.
▼ Ohjelmointia on tämän määritelmän mukaan esim:
– tallentavan digiboksin ajastaminen,
– lennonjohto antaa lentäjälle lento-ohjeet,
– ajo-ohjeiden selittäminen pizza-taxille ja
– aivopesu (propaganda, mainonta?),
▼ Ohjelmoinnin kohteelta (videot, lentokapteeni jne)ohjelmointi vaatii kykyä:
1. vastaanottaa ja ymmärtää annetut ohjeet/käskyt ja
2. suorittaa ne (oikeassa) järjestyksessä.
▼ Systeemi on ohjelmoitava, jos sillä on nämä kyvyt.
▼ Usein ohjelmoitava systeemi tarvitsee käskyt erittäintarkasti määritellyssä muodossa, jotta se pystyisi niitänoudattamaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 2▼ Käytännössä ohjelmointi koostuu vaiheista:
1. ongelman/halutun lopputuloksen ymmärtäminen,
2. ongelman ratkaisun kehittäminen ja
3. ratkaisun yksityiskohtainen selittäminen ohjelmoi-tavalle systeemille sen ymmärtämässä muodossa.
▼ Vaihe 2 on usein hankalin, koska se vaatii näkemystäja tietämystä siitä, kuinka kyseinen ongelma yleensäottaen ratkaistaan.
▼ Kuten ongelmanratkaisu yleensäkin, vaihe 2 vaatiiluovuutta.
▼ Vaihe 3 taas vaatii erityistietämystä ongelmanratkaisuun käytettävästä ohjelmoitavasta systeemistä(esimerkiksi sen ymmärtämistä käskyistä jne).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 3
Tietokoneen ohjelmointi
▼ Kurssin nimessä ja tästä eteenpäin sanalla »ohjel-mointi» tarkoitetaan tietokoneen ohjelmointia elisitä prosessia, jolla tietokone saadaan suorittamaankäyttäjän (lue ohjelmoijan) haluamat toiminnot.
▼ Tietokone on ohjelmoitava systeemi (vrt. kalvo 1).
▼ Käskyt, joita nykyiset tietokoneet suoraan ymmär-tävät, ovat erittäin alkeellisia (konekieli).
▼ Tyypilliset konekieliset käskyt ovat monimutkaisuu-deltaan verrattavissa seuraaviin:
– seuraavien suoritettavien käskyjen valinta riippuenvertailun tuloksesta ja
– luvun siirtäminen paikasta toiseen
▼ Muunkin tyyppisiä käskyjä toki on, mutta niidensuorittamat toiminnot eivät ole merkittävästi noitamonimutkaisempia.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 4▼ Ennen kuin mitään ongelmaa voi tietokoneella
ratkaista, ratkaisu täytyy tavalla tai toisella pukeajoukoksi konekielen käskyjä ➠ kaikkia ongelmiatietokoneella ei voi ratkaista.
▼ Konekieli on erittäin alkeellista ➠ hiukankinisomman ongelman selittäminen tietokoneellepelkästään konekielellä on kohtuuttoman työlästä.
▼ On olemassa ns. korkean tason ohjelmointikieliä,joiden käskyt »tekevät enemmän» ➠ sama ongelmasaadaan esitettyä pienemmässä tilassa ja vähemmällävaivalla.
▼ Tietokoneet ymmärtävät vain omaa konekieltään ➠ennen korkean tason kielisen ohjelman suorittamistase on ensin muutettava konekielelle.
▼ Kurssin opetuskieli C++ on korkean tason kieli.
▼ Ohjelmointikielistä lisää myöhemmin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 5
Harhaluulo ohjelmoinnista
▼ Asiaan perehtymättömillä on sellainen käsitys, ettäohjelmointi on sitä, kun istutaan tietokoneen ääressäja syötetään näppäimistöltä koneelle ohjelmaa.Kun naputtelu on ohi, kone suorittaa ohjelmanongelmitta ja näyttää tarvittavan oikean tuloksen.
▼ Kun tätä käsitystä ohjelmoinnista vertaa kalvolla 2esitettyyn yleiseen malliin, niin jotain puuttuu.
▼ Harhaluuloinen käsitys koostuu pelkästään yleisenmallin viimeisestä vaiheesta ➠ ongelmaa eivälttämättä ole ymmärretty kunnolla eikä senratkaisua mietitty loppuun saakka.
▼ Tämä väärä lähestymistapa johtaa ikävyyksiin:
– ongelmaa ei ole ymmärretty ➠ ratkaistaan vääräongelma,
– ratkaisutapa virheellinen ➠ ohjelman antamalopputulos on väärä ja
– ohjelmaa pitää korjata ➠ hukataan aikaa ja rahaa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 6▼ Tämä virheellinen käsitys on lähes kaikilla ohjel-
mointia aloittelevilla ja siitä kannattaa hankkiutuaeroon välittömästi.
▼ Harhaluulo on erikoinen myös siksi, että se tuntuuesiintyvän vain ohjelmoinnin yhteydessä.
▼ Kaikilla muilla elämänaloilla tuottavaan työhönsisältyy aina paljon suunnittelua ja valmistelua ennenvarsinaiseen työhön ryhtymistä ➠
– suurempi varmuus lopputuloksen kelvollisuudesta ja
▼ Vaikka virheellinen lähestymistapa saattaakin toimiapienissä ohjelmointiprojekteissa, ongelman koonkasvaessa homma karkaa käsistä, jos sitä ei olesuunniteltu kunnolla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 7
Oikea malli ohjelmoinnista
▼ Kokemus osoittaa, että seuraava kalvon 2 mallistahiukan kehittyneempi versio kuvaa onnistuneenohjelmointiprojektin vaiheita:
1. Ongelman ratkaisuvaihe
a . Analysointi ja määrittely: ymmärrä ongelma jaselvitä, mitä ratkaisun on tarkoitus tehdä.
b. Yleinen ratkaisu (algoritmi): kehitä looginensarja ohjeita, joita noudattamalla ongelmallesaadaan haluttu lopputulos.
c . Tarkistus: testaa kynällä ja paperilla, ettäkehittämäsi algoritmi todella toimii.
2. Ratkaisun toteutusvaihe
a . Konkreettinen ratkaisu: toteuta algoritmisopivalla ohjelmointikielellä.
b. Testaus: suorita ohjelmaa eri syötteillä ja vertailetuloksia tunnettuihin oikeisiin ratkaisuihin: josvirheitä löytyy, mieti »miksi?» ja korjaa ne.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 83. Ylläpitovaihe
a . Käyttö: käytä ohjelmaa suunniteltuun tarkoituk-seen.
b. Ylläpito: korjaa löytyviä virheitä ja lisäämahdollisia uusia tarvittavia ominaisuuksia.
▼ Karkea sääntö: ratkaisuvaihe/toteutusvaihe tulisiolla 50%/50%, kun vertaillaan käytettyä aikaa jaresursseja.
▼ »Väärää lähestymistapaa» käyttävillä suhde onusein 10%/90% tai huonompi: ongelmaa pohdittukahvikupin verran ja rynnätty suoraan toteuttamaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 9
Algoritmit
▼ Algoritmi: sarja toimenpiteitä tehtävän/ongelmansuorittamiseksi/ratkaisemiseksi.
▼ Esimerkiksi seuraava algoritmi esittää, kuinka neliön-muotoisesta paperiarkista taitellaan origami-pulu:
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 10▼ Seuraava algoritmi taas kertoo, kuinka saadaan
keitettyjä perunoita:
Algoritmi: Keitetyt perunat
1. pese perunat
2. etsi kattila
3. laita pestyt perunat kattilaan
4. lisää kattilaan vettä kunnes perunat peittyvät
5. laita kattila levylle
6. käännä levy päälle
7. anna kiehua kunnes perunat ovat kypsiä
8. käännä levy pois päältä
9. kaada keitinvesi viemäriin
▼ Algoritmilta vaaditaan seuraavat ominaisuudet(ainakin hyvältä algoritmilta):1. yksikäsitteinen (ei tulkittavissa väärin),
2. yksityiskohtainen (tyhmäkin osaa seurata),
3. saavuttaa tavoitteensa (eli toimii oikein),
4. määrää toimenpiteiden suoritusjärjestyksen(seuraus kohdasta 1) ja
5. sisältää äärellisen määrän toimenpiteitä (muutentehtävä ei tulisi suoritetuksi).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 11▼ Kohdat 1 ja 2 ovat jossain mielessä tulkinnanvaraisia:
milloin algoritmi on riittävän yksikäsitteinen jayksityiskohtainen?
▼ Perunoidenkeittoalgoritmin yksikäsitteisyydessäon parantamisen varaa: nykyistä algoritmia joku»tyhmä» tai ilkeämielinen voisi tulkita väärin.
▼ Yksikäsitteisempi versio olisi (muutokset lihavoitu):
Algoritmi: Keitetyt perunat
1. pese perunat
2. etsi riittävän suuri kattila
3. laita em. pestyt perunat em. kattilaan
4. lisää em. kattilaan vettä kunnes siellä olevat
perunat juuri peittyvät
5. laita em. kattila hellan levylle
6. käännä em. levyn maksimiteho päälle
7. anna em. kattilassa olevan veden kiehua
kunnes siellä olevat perunat ovat kypsiä
8. käännä em. levy pois päältä
9. kaada keitinvesi em. kattilasta viemäriin
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 12▼ Yksityiskohtaisuuden suhteen molemmat algoritmit
ovat puutteellisia.
▼ Esimerkiksi pulun nokan taittelu on sen verranhankala operaatio, että sitä kannattaisi tarkentaa:
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 13▼ Henkilö, joka ei ole koskaan kokannut, saattaisi
tarvita tarkemmat ohjeet perunoiden keittämiseen:
Algoritmi: Keitetyt perunat
1. pese perunat
1.1. laita tulppa tiskialtaaseen
1.2. laita perunat em. tiskialtaaseen
1.3. laske vettä em. tiskialtaaseen kunnes
em. perunat peittyvät
1.4. ota juuresharja
1.5. harjaa em. perunat yksitellen \em. juuresharjalla kunnes puhtaita
1.6. palauta em. juuresharja paikalleen
1.7. avaa em. tulppa ja laske vesi pois
2. etsi riittävän suuri kattila
2.1. avaa kattilakaapin ovi
2.2. valitse sopiva kattila
2.3. ota em. kattila
2.4. sulje em. ovi
2.5. aseta em. kattila tiskipöydälle
▼ Algoritmin loppuja kohtia 3–9 voitaisiin tarkentaasamaan tapaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 14▼ Jos algoritmi ei vieläkään ole riittävän yksityiskoh-
tainen, niin tarkennetaan lisää:
Algoritmi: Keitetyt perunat
1. pese perunat
1.1. laita tulppa tiskialtaaseen
1.1.1. etsi tiskialtaan tulppa
1.1.2. ota em. tulppa käteen
1.1.3. paina em. tulppa tiiviisti
em. altaan pohjassa olevaan
reikään
1.1.4. laske irti em. tulpasta
▼ Koko loppualgoritmi voitaisiin tietysti tarkentaasamaan tapaan ja niin edelleen aina vain yksityiskoh-taisemmin, kunnes haluttu taso saavutetaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 15▼ Riittävä yksikäsitteisyys ja yksityiskohtaisuus ovat
tulkinnanvaraisia suureita ja riippuvat tilanteesta.
▼ Aikuiselle alkuperäinen peruna-algoritmi olisi ollutriittävä, pikkulapselle luultavasti ei.
▼ Tietokoneet ovat »rajoittuneita» ➠ suunniteltaessatietokonealgoritmeja niiden on oltava äärimmäisenyksikäsitteisiä ja yksityiskohtaisia.
▼ Usein algoritmiin liittyy ehdollisuutta ja toistoa:
1. Jonkin algoritmin osan suorittaminen saattaariippua olosuhteista (ehdollisuus).
2. Jotain algoritmin osaa saatetaan tarvittaessasuorittaa useita kertoja peräkkäin (toisto).
▼ Peruna-algoritmissa on sekä ehdollisuutta ettätoistoa, joihin törmätään, jos joitain kohtia yritetäänvielä entisestään yksityiskohtaistaa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 16▼ Esimerkiksi haluttaessa tarkentaa algoritmin kohtaa
»2. etsi kattila» törmätään ehdollisuuteen:
Algoritmi: Etsi kattila
2. etsi riittävän suuri kattila
2.1. avaa kattilakaapin ovi
2.1.1. JOS kaapin ovi on kiinni NIIN
2.1.1.1. tartu kaapin kahvaan
2.1.1.2. vedä ovi auki
2.1.1.3. laske irti kahvasta
2.2. valitse sopiva kattila
2.3. ota kattila
2.4. sulje ovi
2.5. aseta kattila tiskipöydälle
2.5.1. JOS tiskipöydällä on tilaa NIIN
2.5.1.1. aseta kattila vapaaseen
paikkaan
MUUTEN
2.5.1.2. raivaa tilaa kattilan
verran
2.5.1.3. aseta kattila raivattuun
paikkaan
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 17▼ Algoritmissa ehdollisuus on siis esitetty muodossa
JOS väite NIIN
toimenpiteet1
MUUTEN
toimenpiteet2
▼ Tätä tulkitaan siten, että mikäli väite on tosi, onalgoritmin toteuttamiseksi suoritettava toimenpiteet1.
▼ Mikäli väite taas ei pidä paikkaansa, suoritetaantoimenpiteet2.
▼ Väite voi siis periaatteessa olla mikä tahansatoteamus, kunhan siitä tavalla tai toisella voidaanpäätellä, pitääkö se paikkansa vai ei.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 18▼ Toistoa peruna-algoritmista löytyy tarkentamalla
esimerkiksi kohtaa »7. anna veden kiehua kunnesperunat ovat kypsiä»:
Algoritmi: perunoiden keittäminen kypsiksi
7. anna kattilassa olevan veden kiehua kunnes
siellä olevat perunat ovat kypsiä
7.1. NIIN KAUAN KUIN vesi ei kiehu
7.1.1. odota hetki
7.1.2. tarkasta joko vesi kiehuu
7.2. NIIN KAUAN KUIN perunat eivät ole
kypsiä
7.2.1. odota hetki
7.2.2. ota haarukka
7.2.3. työnnä haarukka johonkin
perunaan
7.2.4. JOS ei tuntunut vastusta NIIN
7.2.4.1. totea perunat kypsiksi
MUUTEN
7.2.4.2. totea perunat raaoiksi
7.2.5. laita haarukka pöydälle
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 19▼ Eli toisto voidaan esittää algoritmissa seuraavasti:
NIIN KAUAN KUIN väite
toimenpiteet
jota tulkitaan suoraviivaisesti siten, että toimenpiteitä
toistetaan yhä uudelleen niin kauan kuin esitettyväite on tosi.
▼ Kuten aiemminkin väite saa olla mitä tahansa, jossiitä vain voidaan päätellä, onko se tosi vai epätosi.
▼ Algoritmikielessä »JOS-NIIN-MUUTEN» ja »NIIN
KAUAN KUIN» ovat syntaktisia (l. kieliopillisia)rakenteita, joiden semantiikka (l. merkitys) onehdollisuus ja toisto.
▼ Monista ohjelmointikielistä löytyy vastaavankaltaisetrakenteet »IF-THEN-ELSE» ja »WHILE» ➠ jatkonkannalta on selvempää, jos käytämme niitäsuomenkielisten rakenteiden sijaan.
▼ Tällaista ohjelmointikielen ja puhutun kielenyhdistelmää kutsutaan pseudokoodiksi ja se onerittäin hyödyllinen työkalu.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 20▼ Selvyyden vuoksi vielä koko perunankeittoalgoritmi
(Huom! Edelleen puutteellinen):
Algoritmi: Keitetyt perunat
1. pese perunat
1.1. laita tulppa tiskialtaaseen
1.1.1. etsi tiskialtaan tulppa
1.1.2. ota tulppa käteen
1.1.3. paina tulppa tiiviisti altaan
pohjassa olevaan reikään
1.1.4. laske irti tulpasta
1.2. laita perunat tiskialtaaseen
1.3. laske vettä tiskialtaaseen kunnes
perunat peittyvät
1.4. ota juuresharja
1.5. harjaa perunat yksitellen
juuresharjalla kunnes puhtaita
1.6. palauta juuresharja paikalleen
1.7. avaa tulppa ja laske vesi pois
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 212. etsi riittävän suuri kattila
2.1. avaa kattilakaapin ovi
2.1.1. IF kaapin ovi on kiinni THEN
2.1.1.1. tartu kaapin kahvaan
2.1.1.2. vedä ovi auki
2.1.1.3. laske irti kahvasta
2.2. valitse sopiva kattila
2.3. ota kattila
2.4. sulje ovi
2.5. aseta kattila tiskipöydälle
2.5.1. IF tiskipöydällä on tilaa THEN
2.5.1.1. aseta kattila vapaaseen
paikkaan
ELSE
2.5.1.2. raivaa tilaa kattilan verran
2.5.1.3. aseta kattila raivattuun
paikkaan
3. laita pestyt perunat kattilaan
4. lisää kattilaan vettä kunnes siellä olevat
perunat juuri peittyvät
5. laita kattila levylle
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 226. käännä levyn maksimiteho päälle
7. anna kattilassa olevan veden kiehua kunnes
siellä olevat perunat ovat kypsiä
7.1. WHILE vesi ei kiehu
7.1.1. odota hetki
7.1.2. tarkasta joko vesi kiehuu
7.2. WHILE perunat eivät ole kypsiä
7.2.1. odota hetki
7.2.2. ota haarukka
7.2.3. työnnä haarukka johonkin
perunaan
7.2.4. IF ei tuntunut vastusta THEN
7.2.4.1. totea perunat kypsiksi
ELSE
7.2.4.2. totea perunat raaoiksi
7.2.5. laita haarukka pöydälle
8. käännä levy pois päältä
9. kaada keitinvesi kattilasta viemäriin
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 23
Algoritmien muodostaminen
▼ Hyväksi havaittu tapa algoritmien muodostamiseenon jäsentää kokonaisuutta pienempiin osiin ➠
– pieniä kokonaisuuksia on helpompi hallita ja
– kokonaisuus saadaan pienistä osista yhdistämällä,kunhan osien algoritmit ovat oikein muodostettuja liitetty toisiinsa.
▼ »Hajoita ja hallitse»
▼ Idea on siis jokseenkin sama, joka esiintyi perunan-keittoalgoritmia kehiteltäessä.
▼ Jäsentämistä jatketaan, kunnes osatoiminnot ovatriittävän yksinkertaisia ➠ algoritmin toiminnot ovatmonimutkaisuudeltaan samalla tasolla kuin mitäalgoritmin suorittava systeemi ymmärtää käskyinään.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 24▼ Tietämystä algoritmin suorittavasta systeemistä siis
tarvitaan jo algoritmia suunniteltaessa ➠
– tiedetään, milloin algoritmi on jäsennelty riittävänpitkälle, ja
– jäsennystä osataan ohjata oikeaan suuntaanniin, että se sopii hyvin halutun systeeminsuoritettavaksi.
▼ Tietokoneelle algoritmia muodostettaessa ratkaisunkuvaus on kirjoitettava täsmällisesti ja täydellisesti,koska tietokoneelta puuttuu maalaisjärki ➠ se eiosaa päätellä epätäsmällisten ohjeiden tarkoitusta
▼ Puhuttuun kieleen verrattuna ohjelmointikielet ovatyksinkertaisia ja koska tietokoneelle tarkoitetut algo-ritmit täytyy aina lopulta pukea ohjelmointikieleksi,jotta ne saadaan suoritettua ➠ algoritmin oltavariittävän yksinkertaisessa muodossa, niin että se onhelposti esitettävissä ohjelmointikielellä.
▼ Algoritmien muodostaminen ei ole helppoa ja senoppiminen vaatii harjoittelua.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 25
Ohjelmointikielistä
▼ Ohjelmointikieli on se kieli, jolla tietokoneen kanssavoidaan kommunikoida (l. kuvata algoritmit koneellesen ymmärtämässä muodossa).
▼ Se on selkeästi analoginen puhutun kielen kanssa,mutta sangen yksinkertainen.
▼ Kaikki tietokoneet ymmärtävät pohjimmiltaan vainomaa konekieltään: sopivassa järjestyksessä koneenmuistiin tallennettuja lukuja 0 ja 1 (tosiasiassajännitetasoja).
▼ Tietyssä järjestyksessä esiintyvät nollat ja ykkösettietokone osaa tulkita alkeellisiksi käskyiksi, konekäs-
kyiksi.
▼ Alunperin kaikki ohjelmointi oli puhtaasti konekielelläohjelmointia: johtoja ja vipuja.
▼ Konekieli on kovin hankalakäyttöistä ➠ symbolinen
konekieli ➠ korkean tason ohjelmointikielet
(esim. lausekielet).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 26
Lausekielet
▼ Lausekielet ovat konekieltä ilmaisuvoimaisempia:niillä halutun tehtävän selittäminen tietokoneelleon helpompaa ja lopputulos ymmärrettävämpääihmiselle.
▼ Yksi lausekielellä kirjoitettu käsky vastaa useita, jopatuhansia, konekäskyjä.
▼ Ennen kuin tietokone voi suorittaa jollain muullakuin sen omalla konekielellä kirjoitetun ohjelman, setäytyy tavalla tai toisella muuntaa konekieleksi:
– kääntäminen (compiling)
– tulkkaaminen (interpreting)
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 27
Ohjelman tulkkaaminen
▼ Ohjelman tulkkaaminen perustuu siihen, ettäohjelmointikielen tulkki suorittaa ohjelmaa käsky(lause/lauseke) kerrallaan.
Etuja:
– ohjelma voidaan testata pala kerrallaan ja
– tulkki huolehtii virheilmoituksista ja auttaavirheiden etsinnässä.
Haittoja:
– ohjelman suoritus vaatii aina tulkin ja
– ohjelmaan saattaa jäädä virheitä, jos testaus eiole täydellinen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 28
Ohjelman kääntäminen
▼ Ohjelman kääntäminen perustuu siihen, että ohjel-makoodi käännetään kokonaisuudessaan konekielelleerityisellä kääntäjäohjelmalla (kääntäjällä) ennenkuin se voidaan suorittaa.
Etuja:
– syntaksi- ja semanttiset virheet havaitaan aina ja
– kääntäjää ei enää tarvita uudelleen.
Haittoja:
– ajonaikaiset virheet jäävät yleensä ohjelmoijanhoidettavaksi,
– ohjelman oltava kokonaan valmis ja
– testaus on hankalampaa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 29
Kääntäjän toiminta
Lähdekoodi
Kääntäjä
Konekieli Kirjastot
Linkkeri
Suoritettava ohjelma
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 30
Ensimmäinen C++-ohjelma
▼ Käydään yksityiskohtaisesti läpi pikkuruinenC++-kielinen ohjelma:
// Lähdekooditiedosto: forever.cc
// – – – – – – – – – – – – – – – – – – – – –
// Ohjelma tulostaa näytölle lausahduksen:
// "I plan to live forever
// or die trying."
#include <iostream>
using namespace std;
int main( ) {
cout << "I plan to live forever" << endl;
cout << "or die trying." << endl;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 31▼ Pienessä ohjelmassa on monta tärkeää asiaa:
– Teksti //-kirjainyhdistelmästä rivin loppuun onkommentti, joka ei vaikuta ohjelman toimintaan.
– Varsinaiset ohjelman suorittamat toiminnot ovatmain:ia seuraavien aaltosulkeiden sisällä.
– Käskyt (lauseet) tuntuvat päättyvän ";":een.
– Jos C++-ohjelmassa halutaan lukea syötteitätai kirjoittaa tulosteita, sen alkuun on lisättävä#include <iostream> ja using namespace std.
– cout on C++:ssa olio, johon voidaan<<-operaattorilla ohjata tulosteita, jotka sittenilmestyvät ohjelmaa suoritettaessa näytölle.
– Lainausmerkkien väliin kirjoitettua tekstiä kutsutaanmerkkijonoksi, mikä tarkoittaa selkokielellätekstimuotoista tietoa.
4. jos tuli käännösvirheitä, niintakaisin editoriin korjaamaan ne
5. käännetyn ohjelman testaaminen,jotta nähdään, toimiiko se oikein
6. jos ei toimi oikein, niinmieti miksi ei japalaa takaisin editoriin korjaamaan viat
7. muutenohjelma on valmis
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 33
Editointi
▼ C++-ohjelmia voi kirjoittaa millä tahansa editoriohjel-malla.
▼ Ohjelmien editointiin kannattaa kuitenkin, josmahdollista, käyttää emacs-editoria.
▼ Kun emacs:illa käsittelee tiedostoa, jonka nimipäättyy .cc:hen, emacs menee automaattisestiC++-tilaan, joka osaa esimerkiksi sisentää ohjelma-koodin automaattisesti, kun vain painaa tabulaattori-näppäintä.
▼ Periaatteessa tällä kurssilla ei ole tarkoitus uppoutuaeditoriohjelman käyttöön: jonkin editorin tuntemuspitäisi jokaisella olla TiTePk:sta.
▼ Seuraavassa kuitenkin pikaopas emacs:in tärkeimpiinominaisuuksiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 34
Emacs-editorin käytöstä
▼ Emacsin saa käyntiin kirjoittamalla:
proffa> emacs tiedosto.cc
jolloin käsiteltäväksi otetaan tiedosto tiedosto.cc.
▼ Jos tiedostoa ei ennestään ole olemassa, se luodaanensimmäisen talletuksen yhteydessä.
▼ C++-lähdekooditiedostojen nimen pitää päättyä.cc:hen, jonka myös emacs tajuaa ja meneesen seurauksena C++-tilaan, jossa on ohjelmienkirjoittamisen avuksi monenlaisia ominaisuuksia.
▼ Käynnistyttyään emacs näyttää käsiteltäväntiedoston, jota sitten voidaan muuttaa joko suoraantekstiä kirjoittamalla tai eri näppäinyhdistelmiinsidotuilla komennoilla.
▼ Seuraavaan kerätty pikku lista hyödyllisiä emacsinnäppäinyhdistelmiin sidottuja komentoja.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 35ZYQR
Nuolinäppäimillä kursorin voi liikuttaa tekstinseassa haluamaansa paikkaan.
C+xC+sTallettaa tiedoston levylle. Muista aina tallettaatiedosto ennen kääntämistä.
C+zKeskeyttää emacsin suorituksen ja hyppääproffan komentotulkkiin, jolle voi syöttää UNIX-komentoja, esimerkiksi käännöskomennon. Kunkomennot on annettu, pääsee emacsiin takaisinkomennolla fg:
proffa> fg
jolloin emacs palaa samaan tilanteeseen, missä seoliC+z:aa painettaessa.
C+xC+cTappaa emacsin lopullisesti: fg-komennolla eipääse takaisin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 36
Xxgoto..- lineEnnnEKursori hyppää tiedoston riville nnn. Hyödyllinenkun halutaan päästä riville, jolla kääntäjä väittivirheen sijaitsevan.
XxcompileEMahdollistaa käännöskomennon antamisenemacsista. Ehdottaa aluksi komentoa make –k,josta pääsee eroon painamalla =-näppäintäriittävän monta kertaa, jonka jälkeen voi naputellahaluamansa käännöskomennon aivan, kuten UNIX-
komentoriviltä.E:in painalluksen jälkeenemacs avaa uuden ikkunan ja suorittaa annetunkomennon siten, että kaikki virheilmoituksetilmestyvät uuteen ikkunaan.
C+xoSiirtää kursorin emacsin ikkunasta toiseen:tällä komennolla pääsee lähdekoodi-ikkunastavirheilmoitusikkunaan ja takaisin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 37C+cC+c
SiirtymälläC+xo:lla virheilmoitusikkunaan,siellä nuolinäppäimillä virheilmoitusrivin päälle jasuorittamalla tämän komennon hyppää emacssiihen kohtaan lähdekooditiedostossa, jolla virhesijaitsi.
C+x1Emacs sulkee kaikki muut ikkunat, paitsi, senjossa kursori on. Tällä tavoin pääsee eroonvirheilmoitusikkunasta, kunhan muistaa ensinsiirtää kursorin lähdekoodi-ikkunaan.
C+xb tiedostonnimiEJos vahingossa tulee sulkeneeksi ikkunan,jossa lähdekoodi oli, saa sen haettua takaisintällä komennolla: antaa vain tiedostonnimeksi
lähdekooditiedoston nimen.
TPainamalla tabulaattoria lähdekoodirivillä emacsyrittää sisentää sen siististi parhaan kykynsämukaan. Vastuu on kuitenkin kirjoittajalla!
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 38
Kääntäminen
▼ Ohjelman kääntämiseen käytämme tällä kurssillaohj1c++-nimistä kääntäjää.
▼ ohj1c++:lle luetellaan komentorivillä kaikkiC++-kieliset lähdekooditiedostot, ja se kääntääniistä suoritettavan konekielisen ohjelman:
proffa> ohj1c++ forever.cc
▼ Jos ohjelma on virheetön, tuloksena syntyyajokelpoinen ohjelma a.out, joka voidaan suorittaakirjoittamalla sen nimi komentotulkille:
proffa> ./a.out
I plan to live forever
or die trying.
▼ a.out ei ole kuvaava nimi, mutta ohj1c++:lle voikertoa, minkä nimisen ohjelman haluaa tuloksena:
proffa> ohj1c++ –o plan forever.cc
proffa> ./plan
I plan to live forever
or die trying.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 39
Virhetilanteet
▼ Periaatteessa ohjelmissa on kolmenlaisia virheitä:
– kielioppi- eli syntaksivirheitä,
– semanttisia eli »merkitysvirheitä» ja
– loogisia eli »ajatusvirheitä».
Syntaksivirheet
▼ Syntaksivirheet ovat virheitä, jotka syntyvät, kunlähdekoodi ei ole käytetyn ohjelmointikielenkielioppisääntöjen mukaista.
▼ Esimerkiksi cout- tai return-komennon (lauseen)lopusta puuttuva ";"-merkki olisi C++:ssa syntaksivirhe.
▼ Kääntäjä löytää aina syntaksivirheet.
▼ Virheen löydyttyä kääntäjä tulostaa ilmoituksen,joka kertoo virheen syyn ja (suuntaa-antavan)rivinumeron, jolla virhe lähdekooditiedostossa senmielestä sijaitsi.
▼ Tyypillisesti virheilmoitus alkaa sanoilla »syntaxerror» tai »parse error».
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 40Semanttiset virheet
▼ Semanttiset virheet syntyvät, kun ohjelmakoodi onsyntaktisesti oikein, mutta kääntäjä ei silti pystyymmärtämään sitä.
▼ Jos esimerkkiohjelmassa olisi vahingossa kirjoitettundl, kun oikea vaihtoehto on endl, olisi tuloksenasemanttinen virhe.
▼ Semanttiset virheetkin kääntäjä löytää aina.
▼ Semanttisesta virheestä tulostuu virheilmoitus,jonka jälkeen kääntäjä tyypillisesti sekoaa ja väittäälöytävänsä koodista samalta ja myöhemmiltäriveiltä kaikennäköisiä virheitä ➠ korjaa virheet ainakääntäjän ilmoittamassa järjestyksessä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 41Loogiset virheet
▼ Loogiset virheet syntyvät, kun ohjelman kirjoittajaajattelee jotain väärin tai unohtaa ottaa huomioonjonkun joskus hyvinkin vähäpätöisen seikan.
▼ Loogiset virheet ovat tavallisesti kirjoitusvirheitä taialgoritmin suunnittelussa tapahtuneita virheitä.
▼ Loogiset virheet ilmenevät siten, että ohjelma toimiiväärin.
▼ Kääntäjällä ei ole mahdollisuuksia havaita loogisiavirheitä, koska se vaatisi ohjelmoijan ajatuksen-juoksun ymmärtämistä.
▼ Ainoa tapa välttää loogisia virheitä on ohjelmanhuolellinen suunnittelu ja kirjoittaminen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 42Varatut sanat
▼ Kuten puhutuissa kielissä, on ohjelmointikielissäkinsanoja, joilla on jokin kiinteä merkitys.
▼ Ohjelmointikielistä puhuttaessa tällaisia sanojakutsutaan varatuiksi sanoiksi.
▼ Varatuille sanoille on siis ohjelmointikielessämääritelty merkitys, eikä ohjelmoija yleensä voimuuttaa tätä merkitystä, koska tällöin kyseisen sananalkuperäinen tarkoitus hukkuisi.
▼ Joskus kielen varattuja sanoja kutsutaan myöskäskyiksi.
▼ Sanaa »käsky» käytetään kuitenkin usein hiukanvapaammin, esimerkiksi tarkoittamaan yhtä ohjelmanlausetta.
▼ Varatut sanat luovat perustan koneen ja ohjelmoijankommunikoinnille käytettäessä korkean tasonohjelmointikieltä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 43
C++-kielen varatut sanat
▼ Seuraavassa on lueteltu C++-kielen varatut sanat:
and dynamic_cast not_eq throwand_eq else operator trueasm enum or trybitand explicit or_eq typedefbitor export private typeidbool false protected typenamebreak float public unioncase for register unsignedcatch friend reinterpret_cast usingchar goto return virtualclass if short voidcompl inline signed volatileconst int sizeof wchar_tconst_cast long static whilecontinue main static_cast xordefault mutable struct xor_eqdelete namespace switchdo new templatedouble not this
▼ Osaan kurssilla tutustutaan, muttei läheskäänkaikkiin.
OHJ-1100 Ohjelmointi I 45▼ Kun ohjelma käännetään ja suoritetaan:
proffa> ohj1c++ –o vastukset vastukset.cc
proffa> ./vastukset
Sarjaan: 15
Rinnan: 3.33333
▼ Siitä opittua:
– C++-kieli selvästikin ymmärtää lukuja ja sallii niillälaskemisen.
– cout:in avulla voidaan tulostaa myös lukuja(laskulausekkeen arvoja).
– C++ ei ole kovin tarkka siitä, kuinka jaammeohjelmamme riveille.
– Kuinka sisennetään siististi useamman rivinkokoinen C++-lause.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 46
Perustietotyypit
▼ Tietokoneohjelmat koostuvat kahdenlaisista olioista:
– tiedosta ja
– toimenpiteistä, jotka käsittelevät sitä.
▼ Jotta ohjelmointikielellä voisi tehdä tietoa käsitteleviäohjelmia, on oltava joku tapa tiedon esittämiseen jakäsittelyyn ➠
– perustietotyypit (alkeistietotyypit) tiedonesittämiseen ja
– varatut sanat ja operaattorit sen käsittelyyn.
▼ Yleisesti ottaen tietotyypillä käsitetään joukkoaalkioita, joilla on samat ominaisuudet, eli niitävoidaan käsitellä samoilla operaattoreilla.
▼ Perustietotyypit ovat sellaisia tiedon esitysmuotoja,jotka ohjelmointikieli tuntee automaattisesti.
▼ Tavallisesti perustietotyyppien alkioiden käsittelyynon valmiit käskyt konekielessä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 47▼ Kaikella tiedolla on joku tyyppi.
▼ Alussa riittää tutustua perustietotyyppeihin, joitaC++-kielessä kutsutaan aritmeettisiksi tyypeiksi,eli tyypeiksi, joille voidaan suorittaa normaalejalaskuoperaatioita (kuten yhteen- ja kertolasku).
▼ Tälläisia tyyppejä ovat C++:ssä:
– int kokonaisluvuille (–2, 123456),
– double (float) reaaliluvuille (3.1415, 1E10) ja
– char kirjainmerkeille ( ’a’, ’\n’).
▼ Todellisuudessa C++ käsittelee kirjainmerkkejäkinpieninä kokonaislukuina (vrt. ASCII-koodi jaISO-8859).
▼ Myös merkkijonot (string, tekstimuotoinen tieto,lainausmerkkien sisällä oleva teksti) on käytän-nössä tässä vaiheessa perustietotyyppi, vaikkatodellisuudessa se ei sitä olekaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 48
Lausekeet
▼ Lauseke on tietokoneohjelmassa pohjimmiltaansama kuin se on matematiikassa: jokin kokonaisuusjolle voidaan laskea (eli evaluoida) arvo.
▼ Esimerkiksi
(8 + 16 * 4) / 2
on lauseke, jolle evaluoituu arvo 36.
▼ Ohjelmointikielissä lauseke on kuitenkin huomatta-vasti laajempi käsite kuin mihin perusmatematiikassaon totuttu.
▼ Lausekkeen lähtöarvoina käsitelty tieto ei välttämättämuodostu luvuista, eikä lopputuloksenkaan tarvitseolla luku.
▼ Pidetään mielessä, että ohjelmointikielissä lausek-keella tämä erityisen laaja tulkinta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 49
Operaattorit
▼ Seuraava taulukko on esittelee lähes kaikki C++-kielenoperaattorit, niiden laskujärjestyksen (presedenssin)ja sitomisjärjestyksen (assosioitumisen):
→ : :
→ . –> [ ] ( ) muuttuja++ muuttuja ––
← ~ ! – lauseke + lauseke ++muuttuja –– muuttuja
&muuttuja * lauseke sizeof new delete
→ * / %
→ + –
→ << >>
→ < <= > >=
→ == !=
→ &
→ ^
→ |
→ &&
→ | |
← = *= /= %= += –= <<= >>= &= |= ^=
→ lauseke ? lauseke : lauseke
→ lauseke , lauseke
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 50
Laskujärjestys (presedenssi)
▼ Operaattori lasketaan (evaluoidaan) sitä aikai-semmin, mitä ylemmässä lokerossa se on taulukossa.
▼ Suluilla evaluointijärjestys voidaan määrätä halutuksi.
▼ Jotkut operaattorit (+, –, * ja &) esiintyvät taulukossakahteen kertaan:
– korkeammalla olevat unaari-operaattorit tarvit-sevat yhden operandin ja
– alemmat binääri-operaattorit tarvitsevat kaksi.
▼ Esimerkiksi lauseke:
a = 1 * 2 + –3 / 4;
evaluoituu samoin kuin jos olisi kirjoitettu:
(a = ( (1 * 2) + ( ( –3) / 4) ) ) ;
▼ Kyseessä siis on aivan sama asia kuin matematiikastatutuissa laskujärjestyssäännöissä, paitsi että C++:ssaoperaattoreita on paljon enemmän.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 51▼ C++:ssa (lähes) kaikkien operaattorien tuloksena
evaluoituu arvo, jota voidaan käyttää operandinamuille operaattoreille.
▼ Aritmeettisten operaatioiden tulos on samaa tyyppiäkuin niiden operandien tyypit ➠ kannattaa ollaerityisen varovainen jakolaskuoperaation kanssa,esimerkiksi 1 / 2 tarkoittaa kokonaislukujakolaskua,eli tulos onkin nolla!
▼ Jos kokonaislukujen jakolaskusta on tarve saada»tarkka» reaalilukutulos, täytyy toinen operandimuuttaa reaaliluvuksi käsin käyttämällä tyyppi-
muunnosoperaattoria:
static_cast<double>(1) / 2
jolloin suoritettava laskutoimitus on:
1.0 / 2
eli myös tulos tulee olemaan reaaliluku 0.5.
▼ Nyrkkisääntönä: jos operandit ovat eri tyyppiä,tuloksena on esitysalueeltaan laajempi tyyppi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 52
Sitomisjärjestys (assosioituminen)
▼ Presedenssisäännöt eivät kerro kaikkea lausekkeenevaluointijärjestyksestä: jos samassa lausekkeessa onperäkkäin useita samalla tasolla olevia operaattoreita.
▼ Tällaiset tilanteet ratkaisee operaattorin sitomisjär-jestys, joka voi olla:
– vasemmalta oikealle, jolloin operaatiot suoritetaanvasemmalta oikealle tai
– oikealta vasemmalle vastaavasti.
▼ Taulukossa sitomisjärjestys on merkitty nuolellavasempaan sarakkeeseen.
▼ Esimerkiksi:
1 – 2 – 3 – 4
evaluoidaan C++:ssa siis:
( ( (1 – 2) – 3) – 4)
▼ Sitomisjärjestyksellä on merkitystä esimerkiksi yli- jaalivuototilanteissa ja pyöristysvirheiden kanssa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 53
Yli- ja alivuoto
▼ Sisäisesti tietokone esittää luvut kaksikantaisina(binäärilukuina) jollain äärellisellä määrällä nollia jaykkösiä (bittejä) ➠ lukualueet, joita se voi esittää,ovat rajallisia.
▼ Esimerkiksi useat tietokoneet nykyään esittävätkokonaisluvut (int) 32:lla bitillä, jolloin niiden suurinmahdollinen arvo 232 − 1 (= 4294967295).
▼ Tosiasiassa suurin arvo on 231 − 1 (= 2147483647),koska yksi bitti käytetään etumerkin esittämiseen.
▼ Tilannetta, jossa laskutoimituksen tuloksena syntyyniin iso luku, ettei tietokone voi sitä esittää, kutsutaanylivuodoksi.
▼ Vastaavasti alivuoto on tilanne, jossa reaaliluvuillalaskettaessa syntyy itseisarvoltaan niin pieni tulos,ettei esitystarkkuus riitä.
▼ Eri tietokoneissa on erilaiset lukualueet ja laskenta-tarkkuudet.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 54
Esimerkki
Ratkaistava ongelma
Tulosta yhden Celsius-asteen välein käyttäjänantaman alarajan ja ylärajan väliltä Celsius-lämpötiloja vastaavat Fahrenheit-lämpötilat.Tulostuksen selkeyttämiseksi numeroi tulostetut rivit.
proffa> ohj1c++ –o celsius celsius.ccproffa> ./celsiusAnna alin tulostettava Celsius-lämpötila: –10Anna ylin tulostettava Celsius-lämpötila: 101: –10 C = 14 F2: –9 C = 15.8 F3: –8 C = 17.6 F4: –7 C = 19.4 F5: –6 C = 21.2 F6: –5 C = 23 F7: –4 C = 24.8 F8: –3 C = 26.6 F9: –2 C = 28.4 F10: –1 C = 30.2 F11: 0 C = 32 F12: 1 C = 33.8 F13: 2 C = 35.6 F14: 3 C = 37.4 F15: 4 C = 39.2 F16: 5 C = 41 F17: 6 C = 42.8 F18: 7 C = 44.6 F19: 8 C = 46.4 F20: 9 C = 48.2 F21: 10 C = 50 F
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 57Mitä siitä opittiin
▼ Ohjelman käyttöön voidaan määritellä »tiedontallennuspaikkoja» eli muuttujia (esimerkiksi:alin_celsius, rivinumero ja fahrenheit).
▼ Muuttujaan voidaan tallettaa arvo sijoitusope-
raattorilla (=) tai lukemalla käyttäjän näppäimis-tösyötettä (cin >>).
▼ Myöhemmin ohjelmassa esiintyvä muuttujan nimikorvataan siihen kaikkein viimeisimpänä sijoitetullaarvolla (poikkeuksia on: =-operaattorin vasenoperandi ja ++).
▼ while-silmukkarakenteella voidaan toistaa joitainkäskyjä halutun monta kertaa.
▼ ++-operaattori kasvattaa kokonaislukuoperandinsaarvoa yhdellä (tämä on vain osatotuus asiasta).
▼ +-operaattori toimii oikein vaikka toinen operandiolisikin double- ja toinen int-tyyppinen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 58
while-silmukka
▼ while-silmukan yleinen muoto on:
while ( ehto ) {suoritettava_lause1;
· · ·suoritettava_lausen;
}
tai jos suoritettavia lauseita on vain yksi:
while ( ehto )
suoritettava_lause;
tässäkin tapauksessa saa käyttää aaltosulkuja.
▼ while toistaa rungossaan olevia lauseita niin kauankuin ehto evaluoituu ennen uutta kierrosta todeksi.
▼ Heti, kun ehdosta evaluoituu ennen uutta kierrostaepätosi, ohjelman suoritus jatkuu while-rakennettaseuraavasta käskystä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 59▼ Ehto evaluoidaan aina ensimmäisenä ennen uuden
kierroksen aloittamista ➠ jos se on heti aluksiepätosi, silmukan runkoa ei suoriteta kertaakaan.
▼ Ehto voi olla mikä tahansa lauseke, jolle evaluoituuarvo tosi tai epätosi ➠ lauseke, jossa käytetäänvertailu- tai loogisia operaattoreita (== , != , < , <= ,> , >= , ! , && , | | ).
▼ C++:ssa on erillinen perustietotyyppi bool, jonkalailliset arvot ovat true (tosi) tai false (epätosi)
➠ edellä lueteltuja operaattoreita sisältävistälausekkeista evaluoituu arvo, joka on tyypiltään bool.
▼ Myös bool-tyyppistä muuttujaa voi käyttää ehtona.
▼ Historiallisena jäänteenä C-kielestä kokonaisluvuttulkitaan totuusarvoiksi ➠ luku 0 on epätosi ja kaikkimuut luvut tosia.
▼ Jatkossa opitaan myös, että joissain tilanteissamuitakin tietotyyppejä voi käyttää ehtona.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 60
Vertailuoperaattorit
▼ C++-kielessä on perustietotyyppien vertailuunkäytettävissä seuraavat vertailuoperaattorit:
Operaattori Merkitys
== yhtäsuuri kuin
!= erisuuri kuin
< pienempi kuin
<= pienempi tai yhtäsuuri kuin
> suurempi kuin
>= suurempi tai yhtäsuuri kuin
▼ Kaikki vertailuoperaattorit ovat binäärioperaattoreita:ne vertailevat kahta tietoalkiota.
▼ Vertailun tuloksena evaluoituu bool-tyyppinen arvotrue (tosi) tai false (epätosi) riippuen siitä, pitikövertailulausekkeessa esitetty väite paikkansa vai ei.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 61
Muuttujat
▼ Muuttujia voi ajatella tiedon tallennuspaikkoina.
▼ Toisaalta ne ovat myös jollekin tietoalkiolle annettujanimiä.
▼ Kolmanneksi niitä voi ajatella muistipaikan sisällölleannettuna symbolisena nimenä.
▼ Muuttujilla on kolme ominaisuutta:
– tyyppi ja
– nimi, jotka kerrotaan muuttujaa määriteltäessä,esimerkiksi: int rivinumero; ja
– arvo, joka yleensä asetetaan =-operaattorilla,esimerkiksi: rivinumero = 1;
▼ Muuttujat pitää aina määritellä (ja vain kerran),ennen kuin niitä voi käyttää.
▼ Pääsääntöisesti muuttujiin voi tallettaa vain tietoa,joka on saman tyyppistä kuin muuttujan oma tyyppi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 62▼ Poikkeus: aritmeettisia tietotyyppejä C++ osaa
automaattisesti muuntaa toisikseen, jos se onmahdollista. On oikein sanoa: int i = 5.5; ➠huomaa kuitenkin pyöristysvirheet ja ylivuodot.
▼ Määrittelyn jälkeen ohjelmassa esiintyvän muuttujannimen paikalle evaluoituu siihen viimeisimpänäsijoitettu arvo (muutamia poikkeuksia on, kutenaiemmin todettiin).
▼ Muuttujia voi C++:ssa määritellä kahdessa paikassa:
– Minkä tahansa lohkon (eli aaltosulkuparin { })
sisällä sellaisessa kohdassa, jossa voisi olla lause.Tällaista muuttujaa kutsutaan paikalliseksi
muuttujaksi ja se on käytettävissä vain sen lohkonsisällä (sulkevaan }-merkkiin saakka), jossa se onmääritelty.
– Kaikkien lohkojen ulkopuolella, jolloin kyseessäglobaali muuttuja.
▼ Älä käytä globaaleja muuttujia!
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 63▼ Muuttujan nimen pitää alkaa merkillä A–Z tai a–z,
mutta loput merkit voivat olla myös numeroita taialaviivoja (_).
▼ Muuttuja voi olla myös vakiomuuttuja, jokakäyttäytyy aivan kuin normaali muuttuja, mutta senarvoa ei määrittelyn jälkeen voi muuttaa.
▼ Vakiomuuttujia määritellään lisäämällä varattu sanaconst määrittelyn eteen: const int paino = 115;
▼ Tällaisille vakioille pitää alustaa arvo määrittelynyhteydessä, sillä se ei myöhemmin ole mahdollista.
▼ Muista aina muuttujaa määritellessäsi pysähtyämiettimään, minkälaista tietoa siihen on tarkoitustallentaa ➠ tiedon tyyppi määrää muuttujan tyypin.
▼ Literaalit ovat tietoalkioita, joilla on vain tyyppi jaarvo mutta ei nimeä.
▼ Literaaleja voi kutsua myös nimettömiksi vakioiksi.
▼ Esimerkiksi literaaleja ovat seuraavat:’x’, 12345, 1.2345 ja "DFW".
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 64
Esimerkki
Ratkaistava ongelma
Tee ohjelma, joka laskee (miespuolisen) käyttäjänpainoindeksin ja kertoo, onko henkilöllä yli-, ali- tmspainoa. Ohjelman toiminta näytää seuraavalta:
proffa> ./painoindeksiMies, anna painosi kiloina: 115Pituutesi metreinä: 1.92Painoindeksi on: 31.1957 eli paljon ylipainoa
Taustatietoa
Painoindeksi lasketaan seuraavalla kaavalla:painoindeksi =
paino kg
pituus 2m
ja miehille sen arvoa tulkitaan seuraavasti:
Painoindeksi Tulkinta
indeksi ≤ 20.7 alipainoa
20.7 < indeksi ≤ 26.4 normaali
26.4 < indeksi ≤ 27.8 lievää ylipainoa
27.8 < indeksi ≤ 31.1 ylipainoa
31.1 < indeksi paljon ylipainoa
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 65Ratkaisualgoritmi
Algoritmi: Painoindeksi
kysy käyttäjän paino kiloina
paino← käyttäjän syöte
kysy käyttäjän pituus metreinä
pituus← käyttäjän syöte
painoindeksi← paino / (pituus * pituus)
tulosta painoindeksin arvo
IF painoindeksi ≤ 20.7 THEN
tulosta "alipainoa"
ELSE IF 20.7 < painoindeksi ≤ 26.4 THEN
tulosta "normaali"
ELSE IF 26.4 < painoindeksi ≤ 27.8 THEN
tulosta "lievää ylipainoa"
ELSE IF 27.8 < painoindeksi ≤ 31.1 THEN
tulosta "ylipainoa"
ELSE
tulosta "paljon ylipainoa"
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 66Algoritmin toteutus C++:lla
▼ C++-kielen if-rakenteella saadaan aikaan ehdollista
suoritusta: joidenkin käskyjen suorittaminenriippuu ohjelmoijan määräämästä ehdosta.
▼ Looginen operaattori && (ja-operaattori) saaoperandeinaan kaksi totuusarvoa ja evaluoituutodeksi vain, jos molemmat operandit olivat tosia.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 68
if-rakenne
▼ If-rakenne on monipuolisin tapa toteuttaa ehdollistasuoritusta (haarautumista) C++-kielessä.
▼ If-rakenteen yleinen muoto on:
if ( ehto1 ) {suoritettava_lause1.1;
. . .
suoritettava_lause1.i;
} else if ( ehto2 ) {suoritettava_lause2.1;
. . .
suoritettava_lause2.j;
}// . . . tarvittava määrä else-if:ejä tähän . . .
else {suoritettava_lausen.1;
. . .
suoritettava_lausen.k;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 69▼ Ehtoja lähdetään tutkimaan ylhäältä alas, ja kun
löytyy ehto, joka evaluoituu todeksi, suoritetaansiihen liittyvät lauseet ja jatketaan if-rakennettaseuraavasta lauseesta.
▼ Else-haaran lauseet suoritetaan vain, jos mikäänedeltävä ehto ei evaluoitunut todeksi.
▼ Else if- ja else-haarat eivät ole pakollisia.
▼ If ei välttämättä suorita mitään haaraa, jos else
puuttuu, eikä mikään ehto evaluoidu todeksi.
▼ Aaltosulut eivät ole pakollisia, jos haarassa on vainyksi suoritettava lause.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 70
Loogiset operaattorit
▼ Usein silmukan ehto tai if-rakenteen haaranvalitsemiseen käytetty ehto on monimutkaisempikuin yhden vertailuoperaattorin tulos.
▼ Mielivaltaisia ehtorakennelmia saadaan aikaanyhdistelemällä sopivasti loogisia operaattoreita:
! ei-operaattori (unaarinen), tosi josoperandi on epätosi.
&& ja-operaattori (binäärinen), tosi josmolemmat operandit ovat tosia.
| | tai-operaattori (binäärinen), tosi josedes toinen operandi on tosi.
▼ Edellä kuvatun kolmen operaattorin sijaan voikäyttää myös varattuja sanoja not, and ja or.
▼ Ja- ja tai-operaattoreita voi olla monta perätysten ➠
– presedenssi edellisen taulukon mukaan laskeva ja
– sitomisjärjestys &&- ja | | -operaattoreilla → ja!-operaattorilla←
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 71▼ Ja- ja tai-operaattorit ovat oikosulkuoperaattoreita:
toisin kuin aritmeettiset operaattorit, ne eivät aluksievaluoi molempia operandejaan, vaan suorittavatevaluoinnin operandi kerrallaan vasemmalta oikealleja lopettavat heti, kun tulos on pääteltävissä.
▼ Tällä on merkitystä tilanteissa, joissa halutaan pukeaehdoksi lauseke, jonka evaluointi ei kaikissa tilanteissaole laillista.
▼ Jos esimerkiksi halutaan testata onko muuttujanluku arvo tasan jaollinen muuttujan jakaja arvolla:
▼ Lisäämällä ohjelman alkuun rivi#include <iomanip> saadaan käyttöön valmiitatyökaluja, joilla tulostuksen ulkoasua voidaanohjailla. Esimerkiksi tulostuskentän leveys saadaanasetettua setw ohjauskomennolla.
▼ Ohjelman monimutkaisuutta voidaan helpomminhallita jakamalla sen toiminnot osiin aliohjelmien
(proseduurien) avulla: jokainen aliohjelmatoteuttaa jonkun ohjelman osatoiminnon, jokasaadaan suoritettua aliohjelmaa kutsumalla.
▼ Aliohjelmalle voidaan tarvittaessa välittää myösparametreja, jotka »hienosäätävät» sen toimintaa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 77
Tulostuksen muotoilu
▼ Muista: #include <iomanip> ohjelman alkuun.
▼ Seuraavassa on esitelty lyhykäisesti tärkeimmättulostuksen muotoiluun käytetyt ohjauskomennot.
▼ Niitä ei käydä läpi sen yksityiskohtaisemmin, muttaon silti hyvä, että ne on kerätty yhteen listaan:
Ohjauskomento Merkitys
setw(leveys) Tulostettava tieto vie aina vähin-tään leveys merkkiä tilaa.
left Jos tulostettava tieto vievähemmän tilaa kuin mikätämänhetkinen tulostuskentänleveys on, tulostetaan kentänvasempaan laitaan.
right Kuten edellinen, mutta tuloste-taan kentän oikeaan laitaan.
setfill(merkki) Kentän täytteenä käytetään(setw’n yhteydessä) merkkiä.
fixed Em. tarkkuus tulkitaan desimaa-lipisteen oikealle puolelle tulos-tettavien desimaalien määräksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 78
Aliohjelmat eli proseduurit
▼ Aliohjelmat ovat ohjelmointikielen tarjoama työkaluohjelman pilkkomiseen hallittaviin osiin.
▼ Päämääränä aliohjelmien käytössä on tietysti sevanha tuttu: jos ongelma on liian iso, pilko sitäpienempiin palasiin, kunnes jäljellä on niin pieniäosia, että pää ei sekoa.
▼ Toimiva ohjelma saadaan, kun syntyneet pienetosaset yhdistellään toimimaan kokonaisuutena.
▼ C++:ssa aliohjelma määritellään seuraavasti:void AliohjelmanNimi( ) {
aliohjelman_runko
}
tai mikäli aliohjelmalla on parametreja:void AliohjelmanNimi(muodolliset_parametrit ) {
aliohjelman_runko
}
▼ AliohjelmanNimi voi olla mikä tahansa, kunhan senoudattaa samoja sääntöjä kuin muuttujien nimet.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 79▼ Aliohjelmalle kannattaa pyrkiä valitsemaan nimi, joka
kuvaa hyvin sen toimintaa ➠ ohjelma selkiytyy.
▼ aliohjelman_runko saa olla mitä tahansa suoritettaviaC++-lauseita ➠ ne määräävät, mitä aliohjelma tekee.
▼ Koska koko idea aliohjelmissa oli se, että iso ongelmapaloitellaan pienemmiksi osiksi ➠ aliohjelman_runko
ei saisi olla liian pitkä.
▼ Hyvä nyrkkisääntö kuuluu seuraavasti:
Jos aliohjelma on pidempi kuin mitä näytölle
mahtuu kerralla rivejä, mieti kuinka sen voisi jakaa
kahdeksi tai useammaksi uudeksi aliohjelmaksi,
jotka yhdessä toteuttavat sen toiminnot.
▼ muodolliset_parametrit näyttää listalta pilkulla ero-teltuja muuttujan määrittelyitä, mitä se käytännössäonkin.
▼ Jokainen muodolliset_parametrit-listassa määriteltymuuttuja käyttäytyy aliohjelman_rungossa kutenuusi paikallinen muuttuja, paitsi että sen alkuarvoksion automaattisesti alustettu vastaavasta kutsussa
OHJ-1100 Ohjelmointi I 80▼ Muodolliset parametrit ovat tapa välittää tietoa
aliohjelman kutsujalta aliohjelman käyttöön.
▼ Aliohjelmalla voi olla muodollisia parametreja nollatai useampia, tilanteesta riippuen.
▼ Aliohjelman käskyt (runko) saadaan suoritettuakirjoittamalla haluttuun kohtaan ohjelmakoodissaaliohjelman nimi ja sen perään suluissa
– samassa järjestyksessä
– sama määrä
– saman tyyppisiä
lausekkeita, kuin mitä aliohjelman muodollisetparametrit määräävät (tyhjät sulut, jos parametrejaei ole).
▼ Tätä nimitetään usein myös aliohjelmakutsuksi.
▼ Aliohjelmaa kutsuttaessa hypätään suorittamaanaliohjelman käskyjä järjestyksessä kunnes neloppuvat, minkä jälkeen palataan alkuperäiseenkutsukohtaan ja jatketaan siitä normaalisti eteenpäin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 81
Sama esimerkki vain hiukan toisin
▼ Vertaile toisiinsa edellistä ja seuraavaa ohjemaa:
#include <iostream>
#include <iomanip>
using namespace std;
void TulostaErotinVaakarivi( ) ;void TulostaOtsikkorivi( ) ;void TulostaKertotaulurivi( int kertoja);
int main( ) {TulostaErotinVaakarivi( ) ;TulostaOtsikkorivi( ) ;TulostaErotinVaakarivi( ) ;
int luku = 1;while ( luku <= 9) {
TulostaKertotaulurivi( luku);++luku;
}
TulostaErotinVaakarivi( ) ;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 82void TulostaErotinVaakarivi( ) {
void TulostaKertotaulurivi( int kertoja) {cout << " | " << kertoja << " | ";
int kerrottava = 1;while (kerrottava <= 9) {
cout << setw(3) << kertoja * kerrottava;++kerrottava;
}
cout << " | " << endl;}
▼ Ainoat erot alkuperäiseen ohjelmaan verrattuna:– main on nyt määritelty ensimmäisenä ja muut
kolme aliohjelmaa vasta sen jälkeen ja– ennen main:ia muut kolme aliohjelmaa on
kuitenkin esitelty (laatikossa).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 83
Määrittely vs. esittely
▼ Aliohjelman määrittelyllä tarkoitetaan aliohjelmankirjoittamista ohjelmakoodiin kokonaisuudessaan.
▼ Esimerkiksi seuraava onaliohjelman TulostaKertotaulurivi määrittely:
void TulostaKertotaulurivi( int kertoja) {cout << " | " << kertoja << " | ";
int kerrottava = 1;while (kerrottava <= 9) {
cout << setw(3) << kertoja * kerrottava;++kerrottava;
}
cout << " | " << endl;}
▼ Aliohjelman esittely taas tarkoittaa sitä, kunaliohjelmasta kerrotaan vain sen nimi ja muodollistenparametrien tyypit.
▼ Seuraavassa TulostaKertotaulurivi:n esittely:
void TulostaKertotaulurivi( int kertoja);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 84
▼ Aliohjelman esittelyä kutsutaan myös prototyypiksi.
▼ Esittely kertoo aliohjelmasta kaiken sen, mitäkääntäjän tarvitsee tietää, kun se tarkistaa, onkoohjelmakoodiin kirjoitettu aliohjelmakutsu laillinen(ei sisällä semanttisia virheitä).
▼ Määrittely taas kertoo aliohjelmasta kaiken samankuin esittelykin, mutta sen lisäksi myös käskyt, jotkapitää suorittaa aliohjelmaa kutsuttaessa.
▼ Käytännössä tämä tarkoittaa seuraavaa:
1. Aliohjelma pitää aina määritellä (ja vain kerran)jossain päin ohjelmakoodia, sillä mistä muutentiedettäisiin, mitä pitää tehdä, kun aliohjelmaakutsutaan.
2. Jotta kääntäjä voisi suorittaa virhetarkistuksia,aliohjelma pitää joko esitellä tai määritellä
ohjelmakoodissa ennen kuin sitä kutsutaan
ensimmäisen kerran.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 85
Mistä aliohjelmat tulevat?
▼ Huolellisesti suunnitellusta algoritmista on suhteel-lisen helppo löytää sopivat aliohjelmaehdokkaat:
1. Jokainen algoritmin kohta (tai alakohta), jokavaatii tarkentamista, kannattaa yleensä toteuttaaaliohjelmana. Esimerkiksi kertotaulualgoritmintoiminto »tulosta luvun kertotaulurivi».
2. Jokainen algoritmissa moneen kertaan saman-laisena toistuva toiminto. Kertotaulualgoritmissaesimerkiksi »tulosta erotinvaakarivi».
▼ Jo näillä säännöillä selviää pitkälle.
▼ Mutta toki kokemus ja harjoittelu antavatlisänäkemystä.
▼ Lisäksi kannattaa pitää mielessä, mitä sovittiinaliohjelmista, joiden koko on suurempi kuin näytöllekerralla mahtuvien rivien määrä (s.79).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 86
Miksi aliohjelma on ystävä?
▼ Aliohjelmilla saavutetaan ainakin seuraavat hyödyt:
– Hyvin valitut aliohjelmien nimet selkeyttävätohjelmaa ja kertovat itsessään jotain ohjelmantoiminnasta.
– Kun aliohjelman toiminnot toteuttava ohjelma-koodi esiintyy vain yhdessä paikassa, siinä olevatvirheet tarvitsee korjata vain kerran.
– Ohjelman toiminnan muuttaminen tarvittaessaon helpompaa, koska muutos tarvitsee tehdä vainyhteen paikkaan.
– Aliohjelmat ovat tärkeä työkalu ohjelmanjakamisessa osakokonaisuuksiin (strukturointi)eli niiden avulla ohjelmalle voi antaa hallittavanrakenteen (hajoita ja hallitse).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 87
Esimerkki
Ratkaistava ongelma
Tee ohjelma, joka ratkaisee yhtälön ax2 + bx + cnollakohdat. Kertoimet a, b ja c kysytään käyttäjältä.
Ohjelma osaa tunnistaa eri vaihtoehdot (0, 1 tai2 reaalijuurta) ja informoida käyttäjää tilanteesta.Seuraavassa esimerkkejä ohjelman suorituksesta:
double Diskriminantti(double a, double b, double c) {return b * b – 4 * a * c;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 92Mitä siitä opittiin
▼ C++ tarjoaa mahdollisuuden määritellä ja käyttääfunktioaliohjelmia eli funktioita.
▼ Funktioiden esittelyt ja määrittelyt ovat kutenaliohjelmien määrittelyt, paitsi että funktiolla onaina joku tyyppi (aliohjelman edessähän käytettiinaina varattua sanaa void, kun taas funktion nimenedessä on joku tietotyypin nimi).
▼ Funktiokutsua voi käyttää lausekkeessa sellaisessakohdassa, jossa voisi käyttää jotain muuta samantyyppistä tietoalkiota kuin funktion tyyppi.
▼ Funktioiden rungossa näyttää aina olevankäsky return.
▼ Matematiikkakirjastosta (#include <cmath>)saadaan käyttöön yleishyödyllisiä matemaattisiafunktioita. Esimerkiksi abs ja sqrt.
▼ Reaalilukujen yhtäsuuruutta ei pidä mennävertailemaan ==-operaattorilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 93
Funktiot
▼ Funktiot ovat lähes joka suhteessa samanlaisia kuinaliohjelmat.
▼ Ainoa ero on, että funktiolla on paluuarvo eli sepalauttaa tietoa siihen kohtaan lauseketta, jossa sitäkutsutaan.
▼ Tai toisin sanoen: funktiokutsulle evaluoituu arvo,jota voidaan käyttää parametrina muille funktioilletai aliohjelmille, operaattoreiden operandina jne.
▼ Funktiot ovat siis jotakuinkin samanlaisia olioita, joitamatematiikassakin kutsutaan funktioiksi.
▼ Funktiot määritellään seuraavasti:funktion_tyyppi FunktionNimi( ) {
funktion_runko
}
tai mikäli funktiolla on parametreja:funktion_tyyppi FunktionNimi(muodolliset_parametrit ) {
funktion_runko
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 94▼ FunktionNimelle ja muodollisille_parametreille
pätevät kaikki samat säännöt ja ominaisuudet kuinaliohjelmien tapauksessakin (ks. ss. 78–80).
▼ Jokaisen funktion määrittelyyn (ja esittelyyn) kuuluufunktion_tyyppi, joka määrää, minkä tyyppistä tietoafunktio palauttaa kutsujalleen (käytännössä siis sen,minkälaisessa lausekkeessa funktiota saa kutsua).
▼ Funktion_tyyppi saa olla mikä tahansa tietotyyppi.
▼ Funktion_runko saa olla mitä tahansa suoritettavialauseita, kuitenkin niin että yksi return-käsky tulee
aina suoritetuksi (funktion on palautettava arvo).
▼ Yleisesti ottaen return-käsky näyttää seuraavalta:
return paluuarvo;
jossa paluuarvo on mikä vain lauseke, jolle evaluoituvaarvo on tyypiltään sama kuin funktion_tyyppi.
▼ return-käsky lopettaa funktion suorituksen välittö-mästi ➠ jatketaan kutsukohdasta eteenpäin.
▼ Paluuarvo-lausekkeen arvo määrää, mikä arvofunktiolle evaluoituu sitä kutsuvassa lausekkeessa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 95▼ Funktion rungossa voi siis olla useampiakin return-
käskyjä, mutta funktiosta paluu tapahtuu heti, kunyksikin niistä suoritetaan, esimerkiksi:
double Itseisarvo(double x) {if ( x > 0 ) {
return x;} else {
return –x;}
}
▼ Myös aliohjelman suoritus saadaan return-käskylläkeskeytettyä, mutta tällöin return:ia käytetäänyksinään ilman paluuarvo-lauseketta:
return;
▼ Ainoa todellinen ero aliohjelmien ja funktioidenvälillä on:
– kutsujan ja aliohjelman välillä tietoa siirtyy vainyhteen suuntaan (kutsuja −→ aliohjelma).
– kutsujan ja funktion välillä tietoa siirtyy kahteensuuntaan (kutsuja −→←− funktio).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 96▼ Funktioidenkin tapauksessa rajoittava tekijä on se,
että funktio voi sanan varsinaisessa merkityksessäpalauttaa vain yhden arvon.
▼ Syvin totuus aliojelmista ja funktioista on se, ettävoid on tosiasiassa tietotyyppi, johon ei kuulumitään arvoja: siis eräänlainen tyhjä tietotyyppi ➠aliohjelman »tyyppi» on void ➠ aliohjelma on siisfunktio, joka ei palauta mitään.
▼ Funktion esittelylle ja määrittelylle pätevät samatsäännöt kuin aliohjelmallekin (ks. s. 84).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 97
Mistä funktiot tulevat?
▼ Ohjelmaan tarvittavat funktiot löydetään samoinperustein algoritmia tutkiskelemalla kuin aliohjel-matkin löydettiin (ks. s. 85).
▼ Lisäksi pitää miettiä, mitä on se tieto, jota funktiontarvitsee lähettää takaisin kutsujalleen.
▼ Hyvin usein funktio on tunnistettavissa siitä, ettäalgoritmissa toiminto esiintyy osana jotain lauseketta,esimerkiksi:
taitulosta astelukua vastaava fahrenheit-lämpötila
taisosiaaliturvatunnus← kysy käyttäjän sosiaaliturvatunnus
joissa potentiaaliset funktiot kursiivilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 98▼ Usein funktio saattaa algoritmissa olla jaettuna
kahdelle tai useammalle riville, kannattaa siis ollatarkkana:
tulosta kehote, joka tiedustelee sosiaaliturvatunnusta
lue käyttäjän näppäilemä syöte
WHILE syöte ei ole laillinen sosiaaliturvatunnus
tulosta ilmoitus virheellisestä syötteestä
tulosta kehote, joka tiedustelee sosiaaliturvatunnusta
lue käyttäjän näppäilemä syöte
sosiaaliturvatunnus← käyttäjän syöttämä tunnus
jossa kursiivit toiminnot yhdessä muodostaisivatfunktion KysySosiaaliturvatunnus( ) .
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 99
Matematiikkakirjasto
▼ Tarjoaa valmiina tärkeimmät matemaattiset funktiot.
▼ Funktioita voi ohjelmassaan käyttää, kun vain muis-taa lisätä lähdekoodin alkuun rivin #include <cmath>
▼ Seuraavassa funktioista hyödyllisimmät:
funktio kutsulle evaluoituva arvo
double abs(double x) |x |double sqrt(double x)
√x
double pow(double x, double y) xy
double exp(double x) ex
double log(double x) logex
double log10(double x) log10 x
double sin(double x) sin x
double cos(double x) cos x
double tan(double x) tan x
double asin(double x) arcsin x eli sin−1 x
double acos(double x) arccos x eli cos−1 x
double atan(double x) arctan x eli tan−1 x
Trigonometrisissa funktioissa kulmat ovat radiaaneina.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 100
Miksi reaalilukuja ei saa vertailla
==- ja !=-operaattoreilla?
▼ Tietokone esittää reaaliluvut (kuten kokonaisluvutkin)jollain rajallisella määrällä bittejä (0 ja 1).
▼ Tuosta seuraa luonnollisesti, että laskutoimitustentulokset ovat monissa tapauksissa likiarvoja todellisillevastauksille: oikeassa tuloksessa voi olla enemmänmerkitseviä lukuja kuin mihin esitystarkkuus pystyy.
▼ Toisaalta johtuen tavasta, jolla kymmenjärjestelmänreaaliluvut koodataan vastaaviksi 2-järjestelmänreaaliluvuiksi, se ei ole kaikissa tilanteissa edesmahdollista tarkasti.
▼ Asian syvin olemus on se, että reaaliluvuilla lasket-taessa syntyy aina pyöristysvirheitä ja lopputulos onsiten likiarvo.
▼ Esimerkiksi lausekkeen 20.1 – 20.0 – 0.1 tulos ei vält-tämättä olekaan tasan 0.0, vaan pyöristysvirheidentuloksena 0.0 · · ·
︸︷︷︸paljon nollia
00141553
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 101▼ Tämän seurauksena vertailuoperaattorit == ja !=
saattavat toimia järjenvastaisesti, esimerkiksi ohjelma
tulostaa suoritettaessa näytölle tekstin »Väärin meni!»
▼ Tästä ikävästä ominaisuudesta selvitään sillä, ettähyväksytään pyöristysvirheiden olemassaolo jatulkitaan kaksi reaalilukua samoiksi, jos ne ovat»riittävän» lähellä toisiaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 102▼ Tämän seurauksena reaalilukuvertailu a == b pitäisi
siis kirjoittaa |a − b| < ε, jossa ε on jokin riittävänpieni virhetoleranssi, esimerkiksi 1E–8.
▼ C++:lla reaalilukujen a == b siis kirjoitettaisiin// Määritellään jossain ohjelman alussa// virhetoleranssille ε jokin sopiva arvo:const double EPSILON = 1E–8;· · ·
// Myöhemmin ohjelmassa kun halutaan vertailla// kahden reaaliluvun a ja b yhtäsuurutta:abs(a – b) < EPSILON
▼ Erisuuruutta vertailtaisiin vastaavasti:abs(a – b) >= EPSILON
tai! ( abs(a – b) < EPSILON )
▼ Todellisuudessa edellinen ideakin on puutteellinen,koska se tutkii lukujen absoluuttista eroa.
▼ Parempi olisi yhtäsuuruutta tutkittaessa katsoa,kuinka lähellä toisiaan luvut ovat suhteessa niidensuuruusluokkaan:
|a – b|max(|a| , |b|)
< ε
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 103
Funktion parametrit:
arvo- ja viiteparametrit
▼ Funktioiden ja aliohjelmien parametrit ovat C++-kielessä normaalisti arvoparametreja, millä tarkoi-tetaan sitä, että funktion muuttaessa muodollisenparametrinsa arvoa, todellisen parametrin arvo säilyykuitenkin ennallaan:
void JaaKahdella( int parametri) {parametri = parametri / 2;
}
int main( ) {int luku = 42;
JaaKahdella(luku);cout << luku << endl;
}
▼ Edellisen ohjelman tulostus on siis 42 eli todellinenparametri ei muuttunut.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 104▼ Usein tulee vastaan tilanne, jossa olisi hyödyllistä, jos
funktio pystyisi muuttamaan todellisen parametrinarvoa.
▼ Esimerkiksi jos funktion tarvitsee palauttaa useampikuin yksi arvo, voitaisiin tietoa »palauttaa» todellistenparametrien arvoja muuttamalla.
▼ C++-kielen normaalin parametrinvälitysmekanismin(arvonvälitys, arvoparametrit) lisäksi kieli tarjoaamyös viitteenvälityksen (viiteparametrit, muuttu-
japarametrit).
▼ Viiteparametrit toimivat juuri siten, että funktionmuuttaessa muodollisen parametrinsa arvoa myöstodellisen parametrin arvo muuttuu.
▼ Parametri voidaan määritellä viiteparametriksilisäämällä sen tyypin ja nimen väliin merkki &.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 105▼ Edellinen esimerkki voitaisiin siis toteuttaa viitepara-
metreilla seuraavaan tapaan:
// Ainoa muutos alkuperäiseen verrattuna on// seuraavalle riville lisätty &-merkki.void JaaKahdella( int& parametri) {
parametri = parametri / 2;}
int main( ) {int luku = 42;
JaaKahdella(luku);cout << luku << endl;
}
▼ Nyt ohjelma tulostaakin 21 eli funktio JaaKahdella
pystyi muuttamaan todellisen parametrin arvon.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 106
Laajempi esimerkki
Halutun ohjelman kuvaus
▼ Suunnitellaan ja toteutetaan hiukan totuttuaisokokoisempi esimerkkiohjelma, jossa voidaankäyttää hyväksi kaikkea tähän mennessä opittua.
▼ Periaatteessa olisi tarkoitus saada aikaan seuraavankaltaisesti toimiva ohjelma:
proffa> ./viikonpaivaAnna päivä: 24Anna kuukausi: 2Anna vuosi: 199924.2.1999 on keskiviikkoproffa> ./viikonpaivaAnna päivä: 31Anna kuukausi: 6Anna vuosi: 191231.6.1912 ei ole laillinen päivämäärä!
▼ Ohjelma osaa siis päätellä päivämäärästä, mikäviikonpäivä se on tai että se on laiton.
▼ Sovitaan lisäksi, että ohjelman ei tarvitse toimiakuin päivämäärillä, jotka ovat 1.1.1800 jälkeen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 107Ratkaisun idea
▼ Ennen kuin ongelmaa voidaan ratkaista edes sillätasolla, että pystytään kirjoittamaan siitä algoritmi,täytyy keksiä idea, jolla ohjelmaa yleensäkäänottaen voisi lähestyä.
▼ Muutenhan seuraavan algoritmin tarvittavienvaiheiden yksityiskohtaistaminen ei onnistu:
Algoritmi: Viikonpäivä
päivä← kysy käyttäjältä päivä
kuukausi← kysy käyttäjältä kuukausi
vuosi← kysy käyttäjältä vuosi
IF annettu päivämäärä on laillinen THEN
tulosta päivämäärän viikonpäivä
ELSE
tulosta virheilmoitus
▼ Mitä ilmeisemmin ennen yhtään pidemmälleetenemistä on syytä tutustua ongelman aihepiiriinja ideoida mahdollisia ratkaisutapoja.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 108▼ Syntynyt idea esimerkin avulla:
– Oletetaan että käyttäjä haluaa tietää, mikäviikonpäivä oli 8.7.1947.
– Valitaan vertailupäivämääräksi 31.12.1799 jakatsotaan jonkun muun tekemästä kalenterista,että se oli tiistai.
– Lasketaan sen jälkeen montako päivää onkulunut välillä 31.12.1799–8.7.1947:
OHJ-1100 Ohjelmointi I 114Algoritmi: Kuukausista kertynyt päivät
päiviä kertynyt← 0
tutkittava kuukausi← 1
WHILE tutkittava kuukausi < päivämäärän kuukausi
IF tutkittava kuukausi on
1, 3, 5, 7, 8, 10 tai 12 THEN
päiviä kertynyt← päiviä kertynyt + 31
ELSE IF tutkittava kuukausi on 4, 6, 9 tai 11 THEN
päiviä kertynyt← päiviä kertynyt + 30
ELSE IF tutkittava kuukausi on 2
ja vuosi on karkausvuosi THEN
päiviä kertynyt← päiviä kertynyt + 29
ELSE
päiviä kertynyt← päiviä kertynyt + 28
tutkittava kuukausi← tutkittava kuukausi + 1
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 115Ohjelmakoodi
#include <iostream>
using namespace std;
// Funktioiden ja aliohjelmien esittelytbool OnkoPaivamaaraLaillinen( int pp, int kk, int vvvv);bool OnkoKarkausvuosi(int vuosi) ;void TulostaViikonpaiva( int pp, int kk, int vvvv);int LaskeViikonpaivaNumero( int pp, int kk, int vvvv);int VuosistaKertyneetPaivat( int vvvv);int KuukausistaKertyneetPaivat( int kk, int vvvv);bool OnkoHelmikuu(int kuu);bool OnkoLyhytKuukausi(int kuu); // Onko 30–päiväinen?bool OnkoPitkaKuukausi( int kuu); // Onko 31–päiväinen?
// Vakioiden määrittelytconst int ALOITUSVUOSI = 1800;
const int NORMAALIVUODEN_PITUUS = 365;const int KARKAUSVUODEN_PITUUS = 366;
const int PITKAN_KUUN_PITUUS = 31;const int LYHYEN_KUUN_PITUUS = 30;const int HELMIKUUSSA = 28;const int KARKAUSHELMIKUUSSA = 29;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 116const int TAMMI = 1;const int HELMI = 2;const int MAALIS = 3;const int HUHTI = 4;const int TOUKO = 5;const int KESA = 6;const int HEINA = 7;const int ELO = 8;const int SYYS = 9;const int LOKA = 10;const int MARRAS = 11;const int JOULU = 12;
const int PAIVIA_VIIKOSSA = 7;
const int MAANANTAI = 6;const int TIISTAI = 0;const int KESKIVIIKKO = 1;const int TORSTAI = 2;const int PERJANTAI = 3;const int LAUANTAI = 4;const int SUNNUNTAI = 5;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 117int main( ) {
int paiva;cout << "Anna päivä: ";cin >> paiva;
int kuu;cout << "Anna kuukausi: " ;cin >> kuu;
int vuosi;cout << "Anna vuosi: " ;cin >> vuosi;
if (OnkoPaivamaaraLaillinen(paiva, kuu, vuosi) ) {TulostaViikonpaiva(paiva, kuu, vuosi) ;
} else {cout << paiva << "." << kuu << "." << vuosi
<< " ei ole laillinen päivämäärä!" << endl;}
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 118bool OnkoPaivamaaraLaillinen( int pp, int kk, int vvvv) {
if (pp < 1) {return false;
} else if (kk < TAMMI | | kk > JOULU) {return false;
} else if (vvvv < ALOITUSVUOSI) {return false;
} else if (OnkoPitkaKuukausi(kk)&& pp > PITKAN_KUUN_PITUUS) {
return false;} else if (OnkoLyhytKuukausi(kk)
&& pp > LYHYEN_KUUN_PITUUS) {return false;
} else if (OnkoHelmikuu(kk)&& OnkoKarkausvuosi(vvvv)&& pp > KARKAUSHELMIKUUSSA) {
return false;} else if (OnkoHelmikuu(kk)
&& !OnkoKarkausvuosi(vvvv)&& pp > HELMIKUUSSA) {
return false;} else {
return true;}
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 119bool OnkoKarkausvuosi(int vuosi) {
if ( (vuosi % 4 == 0 && vuosi % 100 != 0)
| | vuosi % 400 == 0) {return true;
} else {return false;
}}
int LaskeViikonpaivaNumero( int pp, int kk, int vvvv) {int paivia_kulunut
OHJ-1100 Ohjelmointi I 126▼ Jotkut string-funktiot tarvitsevat lisäparametreja ➠
ne kirjoitetaan normaalisti sulkeiden sisään:mjono.replace(4, 3, "xy");
▼ Edellinen replace-funktiokutsu korvaisi merkki-jonossa mjono viidennestä merkistä "d" alkaenkolme seuraavaa merkkiä "def" tekstillä "xy".
▼ Kaikissa niissä funktioissa, joiden parametrit taipaluuarvo viittaavat merkkijonon merkkien sijaintiin(esim. edellä »viidennestä merkistä alkaen»), pitäämuistaa, että numerointi aloitetaan nollasta!
"a b c d e f gh"0 1 2 3 4 5 6 7 8
▼ Viimeisen merkin järjestysnumero (indeksi) onyhtä pienempi kuin merkkijonon pituus eli siis yhtäpienempi kuin length()-funktion paluuarvo.
▼ Merkkijono voi olla myös tyhjä, jolloin se ei sisällämitään tekstiä, eli sen pituus on nolla:
mjono = ""; // Kaksi lainausmerkkiä yhteen kirjoitettuna.cout << mjono.length( ) << endl; // Tulostaa nollan.
▼ Tyhjä merkkijono ei ole sama asia kuin merkkijono,joka sisältää pelkän välilyöntimerkin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 127▼ Seuraavassa on esitelty lyhyesti olennaisimmat
merkkijonokirjaston funktiot.
▼ Esitys on tarkoituksella listamainen, jottamyöhemmin omia ohjelmia toteutettaessa tieto olisihelposti löydettävissä.
▼ Funktiot on kuvattu funktioiden esittelyn syntaksilla
➠ parametrien ja paluuarvon tyypit näkyvissä.
▼ Usein mukana on tietotyyppi string::size_type.
▼ Kyseessä on string-kirjaston määrittelemä tyyppi,jota on tarkoitus käyttää aina, kun käsitelläänarvoja, jotka kuvaavat merkkijonon indeksejä, eli siismerkkien järjestysnumeroita merkkijonon sisällä.
▼ Tyypin nimi saattaa vaikuttaa oudolta, koska sesisältää kaksoispisteitä, mutta kyseessä on silti vaintietotyypin nimi ➠ sitä voidaan tarvittaessa käyttäämuuttujien, parametrien ja paluuarvojen tyyppinä.
▼ Toteutusteknisesti kyseessä on jokin kokonaisluku-tyyppi ➠ sen avulla voidaan suorittaa laskutoimi-tuksia jne. kuten kokonaisluvuilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 128
Merkkijonokirjaston funktioita
string::size_type length( );
Paluuarvoksi evaluoituu merkkijonon merkkienlukumäärä (kaikki merkit lasketaan, myös välilyönnitja muut näkymättömät merkit).
string::size_type pituus = mjono.length( );cout << "pituus on " << pituus << " merkkiä" << endl;
bool empty( );
Arvoksi evaluoituu true vain, jos merkkijono onnollan mittainen (tyhjä).
if ( mjono.empty( ) ) {// tähän päästään, jos mjono on tyhjä.
}
string + string
Kahden merkkijonon yhteenlaskun arvoksievaluoituu merkkijonot peräkkäin liimattuina.
+ on operaattori, ei funktio, mutta se on silti hyvämainita tässä yhteydessä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 129void push_back(char lisays) ;string append(string lisays);
Nämä funktiot lisäävät merkkijonon loppuun yhdenmerkin (push_back) tai merkkijonon (append) ➠kumpikin muuttaa alkuperäistä merkkijonoa. Lisäksiappend-kutsun arvoksi evaluoituu merkkijonon uusimuuttunut sisältö.
sijaitseva merkki. Kutsu voi olla myös sijoitusope-raattorin (=) vasempana operandina ➠ voidaanmuuttaa merkkijonon yksittäisiä merkkejä.
cout << "viides merkki on " << mjono.at(4) << endl;mjono.at(1) = ’x’; // toinen merkki muuttuu x:ksi.
Jos parametrina annetaan indeksi, joka on suurempikuin merkkijonon viimeisen merkin indeksi,ohjelman suoritus keskeytyy.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 130getline(cin, string& rivi) ;
Funktio getline on erikoinen tapaus, sillä sitäkutsutaan normaalifunktioiden tapaan. Se lukeesyötteestä yhden rivin tekstiä ja tallentaa sen(viite)parametriin rivi.
Poistaa merkkijonosta merkkejä. Ensimmäinenversio poistaa kaikki merkit alkuindeksistämerkkijonon loppuun saakka. Jälkimmäinen poistaaalkuindeksistä alkaen maara seuraavaa merkkiä.Paluuarvoksi evaluoituu muuttunut merkkijono.
Tyhjentää merkkijonon, eli toisin sanoen asettaa senpituuden nollaksi. Aivan sama tulos saataisiin aikaansijoittamalla muuttujan arvoksi tyhjä merkkijono(mjono = "";).
void swap(string& mjono1, string& mjono2);
Vaihtaa kahden merkkijonomuuttujan (viitepara-metreja) sisällöt keskenään. Tämäkin on normaali-funktio siinä mielessä, että molemmat merkkijonotannetaan sille parametreina sulkeiden sisällä.
find-funktiosta on useita eri versioita, mutta kaikilleniille on yhteistä, että ne etsivät merkkijonostahaluttua merkkiä tai alimerkkijonoa ja palauttavattiedon, mistä kohdasta (indeksi) etsitty osa löytyi.
Jos parametreja on kaksi, jälkimmäinen kertoo, senmerkin indeksin, josta eteenpäin etsintä suoritetaan.
Mikäli etsittyä merkkiä tai alimerkkijonoa eilöydy, find palauttaa arvon string::npos, joka onstring-kirjastossa määritelty const-vakio.
getline(cin, mjono);string::size_type paikka = mjono.find("def") ;if ( paikka == string::npos ) {
cout << "merkkijono ei sisältänyt def:ää" << endl;} else {
cout << "def löytyi indeksistä " << paikka << endl;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 134
Osoitelappuohjelma
Toteutettava ohjelma
Toteutetaan edellisten string-kirjastofunktioidenavulla käyttäjän tekstisyötteitä käsittelevä ohjelma.
Ohjelman olisi tarkoitus toimia seuraavasti:
proffa> ./osoitelappuSyötä osoitetiedot pilkuilla eroteltuina:Teemu Teekkari,Mikontalo,33720 Tampere
Vastaanottaja: Teemu TeekkariMikontalo33720 Tampere
proffa>
Ohjelman avulla on tarkoitus pystyä tulostamaanosoitelappuja, jotka voidaan saksia irti tulosteesta jaliimata sitten kirjekuoreen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 135Ohjelman toteutus string-kirjaston avulla
▼ Ohjelma (käytännössä paloittele-funktio) on jossainmäärin haastava, mutta myös realistinen esimerkkistring-tyypin käytöstä.
▼ Mikäli ohjelma ei avaudu lukemalla, luennon jälkeenlähdekoodin saa kurssin kotisivuilta, jolloin sitä voitestailla itse.
▼ Hyvä lähestymistapa on tutkia koodin käyttäytymistälisäämällä sinne testitulosteita, joilla seurataanmuuttujien arvojen kehittymistä.
▼ Esimerkiksi paloittele-funktiossa kannattaa seurataainakin muuttujien alkukohta, loytokohta japilkkuja_loytynyt arvoja aina, kun niitä muutetaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 138
Lokalisaatio
▼ Lokalisaatiokirjasto (#include <locale>) sisältääpalveluita, joita tarvitaan, kun ohjelma halutaansaada toimimaan eri kieli- ja kulttuuriympäristöissä.
▼ Ohjelmointi I -kurssilla lokalisointi (localization,internationalization, i18n) ei ole olennainenasia itsessään, mutta C++:n locale-kirjasto sisältäämuutamia erittäin hyödyllisiä funktioita merkkienkäsittelyyn ➠ tutustutaan niihin lyhyesti.
▼ Katsotaan lyhyt esimerkki siitä, kuinka ohjelmavoidaan säätää toimimaan suhteellisen hyvinlokalisaatiokirjaston avulla, jos käytössä on 8-bittinenmerkkiympäristö (ISO latin 8859-1), kuten kurssillaainakin toistaiseksi on.
▼ Todellisuudessa lokalisointi on erittäin vaikeaaihepiiri, jotain ongelmia on odotettavissa varsinkinskandinaavisten kirjainten (åäöÅÄÖ) kanssa ➠ eistressata asiasta kovin paljoa.
Käytännössä tarkoitus on siis erottaa sanoiksi kaikkikirjain- tai numeromerkeistä koostuvat yhdistelmät,muuttaa kirjainmerkit isoiksi kirjaimiksi ja tulostaasaadut sanat yksi per rivi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 140Ohjelman toteutus
#include <iostream>
#include <string>
#include <locale> // Tämä tarvitaan!
using namespace std;
// Luodaan suomi–locale, joka esittää suomalaisesta
// kieliympäristöstä riippuvat ohjelman toimintaan
// vaikuttavat asiat (esim. mitkä merkit ovat kirjaimia,
// numeroita, välimerkkejä jne.). Jos haluat selvittää,
// mitä kieliympäristöjä käyttämäsi (Linux– )kone tukee,
if ( ! edellinen_tulostettu_oli_endl ) {cout << endl;
}}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 142Siitä opittua
▼ Ei varsinaisesti uusi asia, mutta ensimmäinenesimerkki merkkijonon yksittäisen merkinvalitsemisesta at-funktiolla.
▼ Esimerkissä on myös käytetty ovelasti bool-tyyppistä muuttujaa pitämään kirjaa siitä,tulostuiko silmukan edeltäneellä kierroksellarivinvaihto vai ei.
▼ Varsinainen uusi asia on locale-kirjasto ja senis-alkuiset funktiot ja toupper-funktio.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 143
Merkkien käsittely
locale-kirjaston avulla
▼ locale-kirjastosta löytyvät seuraavat hyödyllisetfunktiot yksittäisten merkkien käsittelyyn (lista ei olekattava, mutta näillä pärjää hyvin).
bool isalpha(char merkki, locale kielialue);
Palauttaa true, vain jos merkki on kirjainmerkki(suomessa: a–ö tai A–Ö).
bool isdigit(char merkki, locale kielialue);
Palauttaa true, vain jos merkki onnumeromerkki (0–9).
bool isspace(char merkki, locale kielialue);
Palauttaa true, vain jos merkki on sanavälimerkki(välilyönti, tabulaattori, rivinvaihto jne.).
bool ispunct(char merkki, locale kielialue);
Palauttaa true, vain jos merkki onvälimerkki ( . , : ; ? ! jne.).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 144bool islower(char merkki, locale kielialue);
Palauttaa true, vain jos merkki on pieni kirjain(suomessa: a–ö).
bool isupper(char merkki, locale kielialue);
Palauttaa true, vain jos merkki on iso kirjain(suomessa: A–Ö).
char tolower(char merkki, locale kielialue);
Palauttaa merkkiä vastaavan pienen kirjaimen. Josmerkki ei ole kirjainmerkki (isalpha), palauttaasen itsessään.
char toupper(char merkki, locale kielialue);
Palauttaa merkkiä vastaavan ison kirjaimen. Josmerkki ei ole kirjainmerkki (isalpha), palauttaasen itsessään.
▼ Kaikilla edellä kuvatuilla funktioilla jälkim-mäinen parametri kielialue on locale-tyyppinenarvo, joka on alustettu kuvaamaan haluttuakieli/kulttuurialuetta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 145
Esimerkki
Ohjelman kuvaus
▼ Tee seuraavan kaltaisesti toimiva ohjelma:proffa> ./lampotilagraafianna lampötila kello 0.00: 0.3anna lampötila kello 1.00: –4.1anna lampötila kello 2.00: –8.2anna lampötila kello 3.00: –9.0· · · rivejä pois tilan säästämiseksi · · ·
anna lampötila kello 20.00: 25.5anna lampötila kello 21.00: 15.1anna lampötila kello 22.00: 7.2anna lampötila kello 23.00: 2.7
–40 –30 –20 –10 0 10 20 30 40| | | | | | | | |
0.00: *
1.00: *
2.00: *
3.00: *
· · · rivejä pois tilan säästämiseksi · · ·20.00: *
21.00: *
22.00: *
23.00: *
| | | | | | | | |–40 –30 –20 –10 0 10 20 30 40
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 146Algoritmi
Algoritmi: Lämpötilagraafi
tunti← 0
WHILE tunti < 24
lämpötila tunti← kysy lämpötila kello tunti
tunti← tunti + 1
tulosta otsikkorivi ylälaitaan
tunti← 0
WHILE tunti < 24
tulosta graafin rivi ajalle tunti
tunti← tunti + 1
tulosta otsikkorivi alalaitaan
▼ Selkeästi joitain kohtia voitaisiin taas yksityiskoh-taistaa, mutta annetaan olla tällä kertaa.
▼ Potentiaaliset funktiot kursiivilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 147Syntynyt C++-toteutus
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
// Vakioiden määrittelytconst int YLALAITAAN = 0;const int ALALAITAAN = 1;
const int TUNTEJA_PAIVASSA = 24;
// Funktioiden ja aliohjelmien esittelytdouble KysyLampotila( int tunti) ;void TulostaGraafiRivi( int tunti, double lammot[ ] ) ;void TulostaOtsikkorivi( int minne);
int valit= static_cast<int>( (lammot[tunti] + 40) / 2 + 0.5);
while (valit > 0) {cout << " ";– –valit;
}
cout << "*" << endl;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 151
Taulukot
▼ Taulukko on rakenteinen tietotyyppi, jolla onseuraavat ominaisuudet:
– se on järjestetty,
– sillä on joku määrätty koko ja
– tietoalkiot, joista se koostuu, kuuluvat keskenäänsamaan tietotyyppiin.
▼ C++:ssa taulukkomuuttujia määritellään seuraavasti:
alkion_tyyppi nimi[koko];
▼ Tuloksena saadaan taulukkotyyppinen muuttuja nimi,johon voidaan tallettaa koko kappaletta tietoalkioita,joiden tyyppi on alkion_tyyppi.
▼ Alkion tyyppi voi olla mikä tahansa tietotyyppi.
▼ Koon pitää olla vakiolauseke.
▼ Esimerkiksi yhdeksän alkion kokonaislukutaulukkosaataisiin kirjoittamalla:
int luvut[9];
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 152▼ Intuitiivisesti taulukkoa voi ajatella joukkona
laatikoita, joista jokaisella on järjestysluku, jollalaatikon sisältöä voidaan tutkia ja muuttaa.
▼ Jokaiseen laatikkoon mahtuu yksi tietoalkio, jonkatyyppi on alkion tyyppi.
▼ C++-kielessä laatikoiden numerointi lähtee nollasta
ja päättyy koko – 1:een.
▼ Esimerkiksi int luvut[9] näyttää siis seuraavalta:
luvut:
0 1 2 3 4 5 6 7 8
viimeinen alkio on siis järjestysluvultaan yhtäpienempi kuin taulukon koko.
▼ Taulukon alkioihin viitataan (taulukkoa indeksoi-
daan) hakasulkuoperaattorilla [ ]:
luvut[8] = 2* luvut[0];
joka asettaisi luvut-taulukon viimeisen alkion arvoksisen ensimmäisen alkion arvon kerrottuna kahdella.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 153▼ Hakasulkuoperaattorin »sisällä» (operandina) voi olla
mikä tahansa lauseke, jonka tyyppi on kokonaisluku.
▼ C++ ei mitenkään tarkasta, onko taulukon käsittelyssäkäytetty indeksi sallituissa rajoissa:
int a = 1000;
luvut[a] = 42; // looginen virhe
joka menisi kääntäjästä läpi virheittä, mutta on siltiväärin: se viittaa alkioon, jota ei ole olemassa.
▼ Luonnollisesti taulukoille pätee sama kuin muuttujilleyleisestikin: taulukko (sen alkiot) pitää alustaa jollainjärkevillä arvoilla ennen kuin sitä voi käyttää.
▼ Taulukko voidaan alustaa määrittelyn yhteydessä:
int luvut[9] = { 1, 3, 5, 7, 9, 2, 4 } ;
josta saataisiin tuloksena taulukko:
luvut: 1 3 5 7 9 2 4 0 0
0 1 2 3 4 5 6 7 8
▼ Jos alustusalkioita on vähemmän kuin taulukon koko,alustetaan loput nolliksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 154▼ Taulukkoa alustettaessa voidaan koko jättää
kertomatta, jolloin se määräytyy automaattisestialustusalkioiden lukumäärästä:
int luvut[ ] = { 1, 3, 5, 7, 9, 2, 4, 6, 8 } ;
▼ Tämä merkintätapa toimii vain taulukkoa määrittelynyhteydessä alustettaessa: se ei ole yleiskäyttöinentaulukkoon sijoitusmenetelmä.
▼ Kokonaisia taulukoita ei voi sijoittaa toisiinsa=-operaattorilla, eikä niiden yhtä- ja erisuuruutta voivertailla ==- ja !=-operaattoreilla ➠ kaikki tehtäväitse alkio kerrallaan.
▼ Taulukot ovat staattisia tietorakenteita, mikätarkoittaa, että niiden kokoa ei voi enää muuttaa,kun se on kerran määrittelyn yhteydessä annettu.
▼ Staatisen tietorekenteen vastakohta on dynaaminen
tietorakenne, jolla ei ole ennalta määrättyämaksimikokoa.
▼ Dynaamisista rakenteista tulee puhetta mm. kurssillaOHJ-1150 Ohjelmointi II.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 155
Funktion parametrit: taulukot
▼ Vaikka funktion parametrit C++:ssa ovatkin oletusar-voisesti arvoparametreja, käyttäytyvät taulukotparametrina ollessaan »kummallisesti».
▼ Tämä johtuu tavasta, jolla C++ ajattelee taulukoita.
▼ Tällä kurssilla asiaa ei sen kummemin pohdita,omaksutaan vain faktanomaisesti pari taulukoidenerikoista ominaisuutta:
1. Jos muodollinen parametri on taulukko, siitäei mitenkään voi päätellä, montako alkiotatodellisessa parametrissa on ➠ funktiolle pitääerikseen kertoa alkioiden lukumäärä (globaaliconst-vakio tai ylimääräinen parametri).
2. Jos muodollinen parametri on taulukko ja funktiomuuttaa taulukon yksittäisiä alkioita, niin vastaavatalkiot muuttuvat myös todellisessa parametrissa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 156▼ Näitä ominaisuuksia havainnollistaa seuraava
yksinkertainen esimerkki:
#include <iostream>
using namespace std;
const int MAX_LUKUJA = 1000;const int VIRHE = –1;const double LOPETUSLUKU = –1.0;
int LueLuvut(double luvut[ ] ) ;double LaskeKeskiarvo(double luvut[ ], int montako);
int main( ) {double luvut[MAX_LUKUJA];int montako_luettiin;
montako_luettiin = LueLuvut(luvut);
if (montako_luettiin != VIRHE) {cout << "Lukujen keskiarvo: "
if ( luvut[montako] == LOPETUSLUKU) {return montako;
}
++montako;}
return VIRHE;}
▼ Ja sitä olisi pääohjelmassa kutsuttu:
montako_luettiin = LueLuvut( luvut, MAX_LUKUJA);
▼ Usein kannattaa suosia jälkimmäistä tapaa, koska sentuloksena saadaan yleiskäyttöisempi funktio.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 160
Esimerkki
Ohjelman kuvaus
▼ Tehtävänä on saada aikaan seuraavan kaltaisestitoimiva ohjelma:
proffa> ./tenttituloksetOpiskelijan nimi tai "loppu": Teemu TeekkariOpiskelijanumero: 12345Anna tehtävän 1 pisteet (0 – 6): 5Anna tehtävän 2 pisteet (0 – 6): 3Anna tehtävän 3 pisteet (0 – 6): 0Anna tehtävän 4 pisteet (0 – 6): 212345 Teemu Teekkari: arvosana 0
Opiskelijan nimi tai "loppu": Tiina HumanistiOpiskelijanumero: 23456Anna tehtävän 1 pisteet (0 – 6): 6Anna tehtävän 2 pisteet (0 – 6): 6Anna tehtävän 3 pisteet (0 – 6): 5Anna tehtävän 4 pisteet (0 – 6): 623456 Tiina Humanisti: arvosana 5
Anna opiskelijan nimi tai loppu: loppuproffa>
▼ Pisterajat ovat seuraavat:0–10: 0 14–17: 2 21–22: 4
11–13: 1 18–20: 3 23–24: 5
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 161Algoritmi
Algoritmi: Tenttitulokset
WHILE opiskelija← lue opiskelijan tiedot onnistui
tulosta opiskelijan tiedot ja laskettu arvosana
Algoritmi: Lue opiskelijan tiedot
opiskelijan nimi← kysy nimi
IF opiskelijan nimi on "loppu" THEN
opiskelijan tietojen lukeminen epäonnistui
opiskelijanumero← kysy opiskelijanumero
tehtävä← 1
WHILE tehtävä ≤ 4
pisteet tehtävästä tehtävä← lue yksi piste
tehtävä← tehtävä + 1
opiskelijan tietojen lukeminen onnistui
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 162Algoritmi: Laske arvosana
// Vakioiden määrittelyt:const string LOPETUSKASKY = "loppu";const unsigned short int PISTEVIRHE
= numeric_limits<unsigned short int>::max( );
// Omien tietotyyppien määrittelyt:struct Opiskelija {
string nimi;unsigned int opnum;unsigned short int t1; // Tehtävän 1 pisteetunsigned short int t2; // jneunsigned short int t3;unsigned short int t4;
};
// Funktioiden esittelyt (tultava tyyppimäärittelyiden// jälkeen, jos käytetään omia tyyppejä):bool LueOpiskelijanTiedot(Opiskelija& opisk);unsigned short int LaskeArvosana(Opiskelija opisk);unsigned short int
LuePisteet(unsigned short int mika_tehtava);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 164
int main( ) {Opiskelija op;
// Seuraavassa op viiteparametri: funktio muuttaa
// sitä vastaamaan käyttäjältä luettuja arvoja.while (LueOpiskelijanTiedot(op) ) {
unsigned short int arvosana= LaskeArvosana(op);
if (arvosana != PISTEVIRHE) {cout << op.opnum << " "
OHJ-1100 Ohjelmointi I 166// Funktio laskee ja palauttaa opiskelijan
// pisteistä määräytyvän arvosanan tai
// virhetilanteessa vakion PISTEVIRHE.unsigned short int LaskeArvosana(Opiskelija opisk) {
unsigned short int summa= opisk.t1 + opisk.t2 + opisk.t3 + opisk.t4;
if (summa <= 10) {return 0;
} else if (11 <= summa && summa <= 13) {return 1;
} else if (14 <= summa && summa <= 17) {return 2;
} else if (18 <= summa && summa <= 20) {return 3;
} else if (21 <= summa && summa <= 22) {return 4;
} else if (23 <= summa && summa <= 24) {return 5;
} else {return PISTEVIRHE;
}}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 167// Funktio lukee ja palauttaa tehtavän mika_tehtava
// pistemäärän. Hyväksyy vain virheettömän syötteen.unsigned short intLuePisteet(unsigned short int mika_tehtava) {
const unsigned short int PIENIN = 0;const unsigned short int SUURIN = 6;
cout << "Anna tehtävän " << mika_tehtava<<" pisteet ( "<< PIENIN << " – " << SUURIN<< " ): " ;
unsigned short int pisteet;cin >> pisteet;
while (pisteet > SUURIN) {cout << "Virheellinen pistemäärä!" << endl;
cout << "Anna tehtävän " << mika_tehtava<<" pisteet ( "<< PIENIN << " – " << SUURIN<< " ): " ;
cin >> pisteet;}
cin.get( ) ; / / Luetaan yksi merkki cin:stä, mutta
// ei talleteta sitä mihinkään. Miksi?
return pisteet;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 168Siitä opittua
▼ Tärkein uusi asia esimerkissä on tietue- elistruct-tietotyypit.
▼ Tietueet ovat käyttäjän itse määrittelemiärakenteisia tietotyyppejä, joiden avulla voidaankäsitellä yhtenä kokonaisuutena useita keskenäänmahdollisesti erityyppisiä tietoalkioita.
▼ Realistinen esimerkki viiteparametrien käytöstäLueOpiskelijanTiedot-funktiossa.
▼ unsigned int -tyypit, joiden avulla voidaankäsitellä ei-negatiivisia kokonaislukuja.
▼ getline-funktiolla saadaan luettua syötteestä(esim. cin) rivillinen tekstiä merkkijonoon.
▼ cin.get-funktiolla voidaan lukea käyttäjänsyötteestä yksittäinen merkki, joka saadaankäyttöön funktion paluuarvona.
▼ getline:sta ja cin.get:stä lisää tiedostojen käsittelynyhteydessä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 169
unsigned, short ja long
▼ Kokonaisluku- (int) ja joissain tilanteissa merkki-tyyppisen (char) tiedon tyyppiä voidaan tarkentaalisämääreillä unsigned, short ja long.
▼ Nämä lisämääreet informoivat kääntäjää siitä, ettäjos vain suinkin mahdollista, niin rajoitetaan tailaajennetaan syntyvän tietoalkion tai muuttujantyyppiä jollain tavoin:
unsigned int kokonaisluku, esitetään yhtä monellabitillä kuin int, arvoalue: 0 japositiiviset.
short int kokonaisluku, esitetään korkeintaan
yhtä monella bitillä kuin int, arvoalue:esitystarkkuuden rajoissa positiiviset janegatiiviset.
long int kokonaisluku, esitetään vähintään
yhtä monella bitillä kuin int, arvoalue:esitystarkkuuden rajoissa positiiviset janegatiiviset.
unsigned short int kuten short int mutta arvoalue:0 ja positiiviset.
unsigned long int kuten long int mutta arvoalue:0 ja positiiviset.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 170▼ Lisäksi uudemmissa kääntäjissä on myös tyypit
long long int ja unsigned long long int.
▼ Reaalilukutyyppejäkin on useampia: float ja double.
▼ Lisäksi uudemmat kääntäjät tunnistavat myöslong double -tyypin.
▼ Jos erityistä syytä ei ole, kannattaa pitäytyä double-tyypissä: se on hyvä kompromissi.
▼ Varsinkin float-tyypille on kovin vähän perusteltuakäyttöä ja sitä kannattaa välttää: sen esitystarkkuuson todennäköisesti huonompi kuin double-tyypillä
➠ pyöristysvirheet kumuloituvat nopeammin.
▼ Jos reaalilukulaskuissa tarvitaan suurempaa tarkkuuttaja kääntäjä ja käytetty prosessoriarkkitehtuuri tukevatsitä, long double -tyyppi on mahdollinen valinta.
▼ Reaalilukutyyppien kanssa ei voi käyttää lisämäärettäunsigned.
▼ Se, monellako bitillä kukin tietotyyppi esitetään,riippuu ainakin käytettävästä prosessorista, käyttö-järjestelmästä, kääntäjästä ja kääntäjän versiosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 171▼ Esimerkkinä muutama käännösympäristö:
Ympäristö Tyyppi Bittejä Pienin Suurin
short int 16 −215 215 − 1Intel int 16 −215 215 − 1Win98 32-bit long int 32 −231 231 − 1(pöytäkone) unsigned short int 16 0 216 − 1
unsigned int 16 0 216 − 1unsigned long int 32 0 232 − 1
Intel short int 16 −215 215 − 1WinXP 32-bit int 32 −231 231 − 1(pöytäkone) long int 32 −231 231 − 1— long long int 64 −263 263 − 1Proffa unsigned short int 16 0 216 − 1Linux unsigned int 32 0 232 − 1(palvelin) unsigned long int 32 0 232 − 1
unsigned long long int 64 0 264 − 1
short int 16 −215 215 − 1int 32 −231 231 − 1
Intel long int 64 −263 263 − 1Linux 64-bit long long int 64 −263 263 − 1(palvelin) unsigned short int 16 0 216 − 1
unsigned int 32 0 232 − 1unsigned long int 64 0 264 − 1unsigned long long int 64 0 264 − 1
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 172
Perustietotyyppien ääriarvot:
limits-kirjasto
▼ Joskus on tarve saada selville numeerisen tietotyypinmaksimi- ja minimiarvot.
▼ Joihinkin tietotyyppeihin saattaa myös kuuluaerikoisarvoja: esimerkiksi monissa arkkitehtuureissadouble-tyyppi voi esittää arvon∞, joka voi syntyätuloksena vaikkapa nollalla jakamisesta.
▼ Nämä arvot riippuvat monesta muustakin asiastakuin C++-kääntäjästä ➠ kieli ei määrittele arvojayksikäsitteisesti.
▼ Sen sijaan C++-kieleen kuuluu kirjasto limits, jonkaavulla ääri- ja muut erikoisarvot voidaan selvittääohjelmakoodissa.
▼ Kuten muidenkin kirjastojen kanssa, limits-kirjastonkäyttö vaatii ohjelman alkuun sopivan include-rivin:
#include <limits>
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 173▼ Tämän jälkeen käytettävissä on seuraavat apuvälineet
(ei läheskään kattava lista):
numeric_limits<tyyppinimi>::min( );
Funktio, jonka paluuarvo kertoo pienimmänmahdollisen arvon, jonka tietotyyppi voi esittää:
int pienin_int = numeric_limits<int>::min( );
numeric_limits<tyyppinimi>::max( );
Paluuarvo on suurin mahdollinen arvo, jonkatietotyyppi voi esittää:
cout << numeric_limits<double>::max( ) << endl;
numeric_limits<reaalilukutyyppi>::infinity( ) ;
Jos käännösympäristössä on mahdollista esittääreaalilukutyyppinen arvo∞, se saadaan tämänfunktion paluuarvona:
const-vakio, joka on true vain, jos reaalilukutyyppi
pystyy esittämään arvon NaN.✶
✶ Tämä on melkoinen yksinkertaistus, mutta hyväksyttäneen se tässävaiheessa koulutusta. NaN (Not a Number) kuvaa määrittelemätöntäarvoa (esimerkiksi 0.0/0.0,
√–1, jne.).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 175▼ NaN on erikoinen arvo sikäli, että vertaillaanpa sen
yhtäsuuruutta mihin tahansa toiseen reaalilukuun,tulos on aina false, jopa silloin kun molemmatvertailtavat ovat NaN.
▼ Tai toisin sanoen: NaN ei ole yhtäsuuri edes itsensäkanssa.
▼ Tätä ominaisuuttaa voidaan hyödyntää, jos halutaantietää, onko jonkin reaalilukulausekkeen arvon NaN.
▼ Järkevintä on toteuttaa tähän tarkoitukseen funktio:
bool onko_NaN(double arvo) {if ( arvo != arvo ) {
return true;
} else {return false;
}}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 176
Tietue- eli struct-tietotyypit
▼ Tietuetyyppejä käytetään silloin, kun on tarvekäsitellä yhtenä kokonaisuutena useita keskenään
mahdollisesti erityyppisiä tietoalkioita.
▼ Tietueita (kuten taulukoitakin) kutsutaan rakentei-
siksi tietotyypeiksi: niillä on ohjelmoijan määräämärakenne.
▼ Tietuetyypin määrittely tapahtuu varatun sananstruct avulla:
▼ Tämän seurauksena kieleen ilmestyy uusi tietotyyppinimeltään tyypin_nimi, jota voidaan käyttää normaa-listi muuttujien, parametrien jne määrittelyyn:
tyypin_nimi muuttujan_nimi;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 177▼ Tietuetyypin yksittäiset kentät käyttäytyvät kuten
muuttujat, joiden tyyppi on kentän tyyppi, kun niitäkäsitellään "."-operaattorin (piste) avulla:
▼ Suurin osa ohjelmointikielistä ei aseta vaatimuksiaohjelmakoodin asettelulle ➠ niin kauan kuinohjelmakoodi täyttää kielen syntaktiset ja semanttisetvaatimukset, kääntäjä tai tulkki ei välitä.
▼ Monissa ohjelmointikielissä koko ohjelman voiesimerkiksi kirjoittaa yhdelle pitkälle riville.
▼ Käytännössä ohjelmakoodia kuitenkin aina joutuvatlukemaan ja muokkaamaan myös ihmiset, muutkinkuin koodin alkuperäinen kirjoittaja.
▼ Tämän vuoksi on kriittisen tärkeää, että ohjelmaakirjoitettaessa kiinnitetään huomiota myös koodinulkoasuun ja pyritään tekemään siitä mahdolli-simman selkeä.
▼ Selkeä ja johdonmukainen rakenne auttaa ihmislu-kijaa ymmärtämään koodin tarkoituksen.
▼ Noudattamalla muutamia yksinkertaisia periaatteitakoodista saa selkeää varsin pienellä lisävaivalla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 180Kommentointi
– Kommentoinnin tarkoituksena on selittäämiksi jokin asia on koodissa tai mikä on sen
tarkoitus: hyvä kommentti on hyvä vastausmiksi-kysymykseen.
– Kommentoitaessa saa olettaa, että lukija ym-märtää käytettyä ohjelmointikieltä suvereenisti.
– Seuraavat kommentit ovat huonoja:
// Sijoitetaan muuttujaan d arvo x*x+y*y.d = x * x + y * y;· · ·
// Palautetaan muuttujan d neliöjuuri.return sqrt(d);
koska ne eivät tarjoa mitään uutta informaatio-sisältöä lukijalle, joka osaa C++:aa.
– Vältä siis koodissasi ohjelmointikielen ominai-suuksien ja ilmiselvyyksien kommentointia.
– Huomaa kuinka paljon hyödyllisempiä seuraavatkommentit ovat:
// Lasketaan hypotenuusan neliö kateeteista x ja y.d = x * x + y * y;· · ·
// Siitä saadaan hypotenuusa.return sqrt(d);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 181– Nämä kommentit eivät enää keskity itsestään-
selvyyksiin, vaan selittävät yleisemmällä tasollakoodirivien tarkoitusta.
– Ideaalitilanteessa ohjelmakoodi tulisi kommen-toida niin tarkasti, että siitä ei voi esittää "miksi"-tai "mikä on tämän tarkoitus"-kysymystä, johonei löytyisi vastausta kommenteista.
– Yleensä jokaisen lähdekooditiedoston alkuunkannattaa lisätä lyhyt kommentti, joka kertookoodin tekijän ja sen tarkoituksen (siis lyhyenkuvauksen ohjelman toiminnasta).
– Jokaisen aliohjelman ja funktion alkuun onhyvä lisätä kommentti selittämään funktiontarkoitusta, parametreja ja paluuarvoa:
– Kannattaa pitää mielessä myös se, ettäkommentoinnissakaan määrä ei korvaa laatua.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 182Sisennykset
– Sisennys tarkoittaa loogisesti yhteenkuuluvienkoodirivien asettelua samalle tasolle (yhtä kauaskoodin vasemmasta laidasta) ➠ ihmislukijanon helppo hahmottaa, mikä on osa mitäkinkokonaisuutta.
– Sisennysten periaate on hyvin yksinkertainen:aina uuden lohkon✶ alussa kasvatetaanrivien sisennystä tietyn vakiomäärän verran(4–5 välilyöntiä) ja lohkon lopussa palautetaansisennys lohkoa edeltäneelle tasolle.
– Sisennykset ovat ehkä tärkein ja helppokäyttöisinyksittänen tekijä, jonka avulla koodista saaselkeämpää.
– Monisteen esimerkit on sisennetty huolellisestija niistä kannattaa ottaa mallia.
– Jotkut editorit (esim. emacs) osaavat auttaakoodin sisentämisessä ➠ viime kädessä vastuuon kuitenkin koodaajan, koska aputyökalut eivätole erehtymättömiä.
✶ Siis aaltosulkuparin { } sisällä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 183Tyhjä tila
– Tyhjät rivit ja ylimääräiset välilyönnit (muuallakuin rivien alussa) ovat hienovarainen työkalukoodin ulkoasun selkeyttämiseen.
– Tyhjää tilaa on helppo käyttää huonosti ➠ liikaatai liian vähän.
– "Tyylikeino" jonka käyttö vaatii näkemystä ➠vaikea antaa absoluuttisia ohjeita.
– Esimerkiksi seuraava koodi:
int i=0;//Silmukkamuuttuja tulostamiseen.while( i<10) {
cout<<i<<endl;if(i==9) {
TulostaSumma(1,9);}
}
näyttää paremmalta, kun ei nuukaile tyhjäntilan kanssa aivan noin paljon:
int i = 0; // Silmukkamuuttuja tulostamiseen.
while ( i < 10) {cout << i << endl;
if ( i == 9) {TulostaSumma(1, 9);
}} ▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 184Rivinpituus
– Pidä koodiriviesi pituus alle 80 merkissä.
– Tämä tyyliohje perityy historian aamuhämärästä,jolloin näyttöpäätteiden leveys oli 80 merkkiä.
– Ohje on kuitenkin pätevä vielä nykypäivänäkin.
– Jos haluaa 19-tuumaiselle näytölle rinnakkainkaksi ikkunaa (esimerkiksi editori ja käännös-ikkuna) eivät ne voi olla juuri 80 merkkiäleveämpiä tai teksti täytyy skaalata niin pieneksi,ettei sitä heikkosilmäinen enää lue.
– Jos koodiisi väkisin tulee C++-lauseita tai-lausekkeita, joiden pituus on yli 79 merkkiä, jaane jotenkin selkeästi useammalle riville.
– Tästä mekanismista on luentomonisteessapaljonkin malleja (esimerkiksi pitkän cout-tulostuksen jakaminen monelle riville).
– Yleensä jatkorivejä kannattaa sisentää enemmänkuin ensimmäistä riviä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 185Nimeämiskäytäntö
– Muuttujat, funktiot ja tietotyypit on järkevänimetä siten, että niiden nimestä pystyy suoraanlukemaan tarkoituksen.
jälkimmäinen on kuitenkin lukijan kannaltapaljon selkeämpi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 186– On useita koulukuntia siitä, kuinka valitut nimet
pitäisi kirjoittaa.
– Luentomonisteen tyyli on kirjoittaa
· funktioiden ja tietotyyppien nimissä jokainensana isolla alkukirjaimella ilman sanavälejä,
· muuttujien nimet pienillä kirjaimilla, sanavä-leinä alaviiva ja
· vakioiden nimet kokonaan isoilla kirjaimilla,sanaväleinä alaviiva.
– Tämä ei ole missään tapauksessa ainoa oikeatapa.
– Minkä tavan ikinä valitseekin, tärkeintä onnoudattaa sitä uskollisesti.
– Hyvin valitut nimet vähentävät usein kommen-toinnin tarvetta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 187Vakiomuuttujat (const-vakiot)
– Vakiomuuttujien avulla voidaan nimetä koodinepämääräiset ja vaikeaselkoiset vakioarvot.
– Hyvin valitut nimet selkeyttävät koodia:int paiva = 29;
on vaikeatajuisempi kuin
const int KARKAUSPAIVA = 29;· · ·
int paiva = KARKAUSPAIVA;
– Jos jotain vakioarvoa täytyy koodissa muuttaa,on se helpompi tehdä kerran const-vakionmäärittelykohdassa kuin käydä koko koodi läpija muuttaa kaikki literaaliarvot käsin.
– Vakiomuuttujienkin nimet kannattaa valitahuolella kuvaamaan niiden tarkoitusta.
– Usein nimittäin näkee aloittelevien ohjelmoijiensyyllistyvän seuraavaan tyylivirheeseen:
const int VIISI = 5;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 188– Tämä ei tuo ohjelman selkeyteen mitään lisä-
arvoa vaan koodiin on kirjattu itsestäänselvyys.
– Parempi olisi miettiä, mitä literaaliarvo 5 esittää,ja keksiä nimi sen perusteella vaikkapa:
const int TENTTITEHTAVIA = 5;
tai mistä sitten ikinä onkaan kyse.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 189
Tiedon lajittelu
▼ Tiedon lajittelulla tarkoitetaan (yleensä taulukon)tietoalkioiden järjestämistä jonkin säännön mukai-sesti: esimerkiksi kasvavaan järjestykseen.
▼ Lajitteluongelma on tietojenkäsittelyssä useintoistuva operaatio ja sitä on tutkittu paljon ➠lajittelualgoritmeja on kymmeniä.
▼ Tutustutaan yhteen näistä algoritmeista:valintalajitteluun ja sen toteutukseen C++:lla.
▼ Valintalajittelu ei ole erityisen tehokas algoritmi,itseasiassa se on kurja ja tehoton, varsinkin joslajiteltavia alkioita on paljon.
▼ Pieniin (alkiota alle 1000) lajittelutehtäviin se onkuitenkin riitävä.
▼ Valintalajittelun hyvä puoli on sen helppotajuisuus.
▼ Parempia, tehokkaampia ja elegantimpia algoritmejaon alan kirjallisuus pullollaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 190▼ Valintalajittelun (kasvavaan järjestykseen) idea:
1. lajiteltavan taulukon kaikki alkiot käydään läpi javalitaan niistä pienin,
2. vaihdetaan löytynyt pienin alkio oikealla paikalleen(ensimmäiseksi),
3. käydään taulukko uudelleen läpi samalla tavoin,mutta aloitetaan ensimmäisestä alkiosta, joka eivielä ole oikealla paikallaan,
4. vaihdetaan jäljellä olevista alkioista löytynyt pieninaloitusalkion kanssa ja
5. jatketaan, kunnes aloitusalkio on viimeinen alkio,joka tietysti on kaikkein suurin ja valmiiksi oikeallapaikallaan.
▼ Sama algoritmi osaisi tietysti lajitella alkiot myöspienenevään järjestykseen, kun vain valitaan ainavaihdettavaksi alkioksi kaikkein suurin alkio.
▼ Pienuus ja suuruus ovat algoritmin toiminnankannalta abstrakteja käsitteitä ➠ algoritmilla voilajitella mitä tahansa tietoalkioita, joille ohjelmoijaosaa määrätä jonkun suuruusjärjestyksen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 191▼ Algoritmikielellä valintalajittelu näyttää seuraavalta:
if (pienin != aloitusalkio) {Vaihda(alkiot[pienin], alkiot[aloitusalkio] ) ;
}
++aloitusalkio;}
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 195// Vaihtaa todellisten parametrien arvot keskenään.void Vaihda( int & a, int & b) {
int apu = a;a = b;b = apu;
}
void TulostaTaulukko(int taulukko[ ], int koko) {int i = 0;
while ( i < koko) {cout << taulukko[i] << " ";++i;
}
cout << endl;}
▼ Huomaathan, että varsinainen lajittelualgoritmitoimii muillakin tietotyypeillä kuin int, kunhan vainlaatikoidut osat koodia muutetaan oikeiksi:
1. int siksi tietotyypiksi, jonka tyyppisiä alkioitasisältävä taulukko halutaan lajitella ja
2. alkiot[tutkittava] < alkiot[pienin] vertaile-maan kahta kyseisen tietotyypin alkiota keskenään.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 196
Tiedon etsintä
▼ Lajittelun lisäksi toinen tärkeä ja usein vastaantulevatehtävä on tiedon etsintä jonkun osatiedonperusteella, esimerkiksi osoitteita saatettaisiinpostitussovelluksessa etsiä nimen perusteella.
▼ Etsinnän perusteena olevaa osatietoa kutsutaanjoskus hakuavaimeksi tai lyhyesti avaimeksi.
▼ Periaatteessa tietoa voitaisiin etsiä siten, että käydäänhakuaineistoa läpi järjestyksessä, kunnes
1. haluttu avain tulee vastaan tai
2. kaikki tieto on käyty läpi, jolloin tiedetään etteihaettu tieto ollut hakuaineistossa.
▼ Tällainen lineaarinen etsintä/haku on kuitenkintehotonta: pienellä miettimisellä havaitsee, että n:ntietoalkion joukosta täytyy keskimäärin tutkia n / 2
alkiota, ennen kuin haluttu alkio löytyy.
▼ Ikävin tapaus on se, jossa etsittävä tieto ei olehakuaineistossa: kaikki n alkiota pitää asiantoteamiseksi käydä turhaan läpi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 197▼ Lähtökohta tehokkaammalle tiedon etsinnälle
on, että tieto järjestetään johonkin erityiseenjärjestykseen, josta etsintä on nopeampaa.
▼ Yksinkertainen, mutta silti tehokas etsintäalgoritmi,on puolitushaku, joka vaatii toimiakseen, että tietoon hakuavaimen mukaan kasvavaan tai laskevaanjärjestykseen lajiteltua.
▼ Puolitushakualgoritmi pseudokoodina:
Algoritmi: Puolitushaku
vasen← 0
oikea← alkioiden määrä – 1
WHILE vasen ≤ oikea
keski← (vasen + oikea) / 2
IF alkiot[keski].avain == etsitty avain THEN
tieto löytyi taulukon kohdasta keski
ELSE IF alkiot[keski].avain < etsitty avain THEN
vasen← keski + 1
ELSE
oikea← keski – 1
etsittävä tieto ei ollut taulukossa
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 198▼ Kuvallinen esimerkki puolitushausta kokonaislu-
kuavaimilla: oletetaan että etsittävä avain on 4taulukosta, jossa on avaimet 0–10:
vasen
vasen
vasen
vasen
keski
keski
keski
keski
oikea
oikea
oikea
oikea
0
0
0
0
1
1
1
1
2
2
2
2
3
3
3
3
4
4
4
4
5
5
5
5
6
6
6
6
7
7
7
7
8
8
8
8
9
9
9
9
10
10
10
10
▼ Etsittävien alkioiden lukumäärä suunnilleen puolittuujokaisella hakukierroksella ➠ puolitushaku erittäintehokas isoillakin tietomäärillä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 199▼ Esimerkki puolitushausta merkkijonoavaimella:
while (nimi != LOPETUSKASKY) {int indeksi = Puolitushaku(nimi, NIMET, NIMIA);
if (indeksi == TUNTEMATON) {cout << nimi << " tuntematon!" << endl;
} else {cout << NIMET[indeksi].etunimi << endl;
}
cout << "Syötä nimi: ";cin >> nimi;
}}
▼ Huomaa, että Puolitushaku-funktio on taas yleis-käyttöinen, kunhan laatikoidut kohdat muutetaantilanteeseen sopiviksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 202
Esimerkki
Ohjelman kuvaus
▼ Halutaan tehdä ohjelma, joka lukee annetustatiedostosta (koneen kovalevyltä) tuotteen nimi- jatuotekoodipareja, lajittelee ne nimen perusteellakasvavaan järjestykseen ja tallettaa lajitellussajärjestyksessä tulostiedostoon.
▼ Syötetiedoston rakenne on seuraava:– jokainen tuotekoodi–nimi-pari on omalla
rivillään kaksoispisteellä erotettuna:
tuotekoodi :nimi
– tiedoston rivit eivät välttämättä ole missäänerityisessä järjestyksessä,
– tuotekoodi on luku väliltä 10000–99999 ja
– tuotteen nimi on merkkijono, jossa voi olla mitätahansa merkkejä, paitsi ei kaksoispistettä.
▼ Tulostiedosto on rakenteeltaan vastaava, muttarivit ovat tuotteen nimen mukaisesti kasvavassajärjestyksessä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 203▼ Esimerkiksi jos tiedosto tiedot.dat sisältää rivit:
▼ Yleisellä tasolla ratkaisualgoritmi olisi mitäilmeisimmin seuraava:
Algoritmi: Lajittele tuoterekisteri
lue tiedot syötetiedostosta taulukkoon
lajittele taulukko tuotteen nimen mukaan
talleta lajiteltu taulukko tulostiedostoon
▼ Algoritmia kuuluisi tietysti vielä yksityiskohtaistaaroimasti, mutta jo tuollaisena se ilmentää kahtauutta toimenpidettä, jotka eivät vielä ole tuttuja:
1. tiedoston lukeminen ja
2. tiedostoon kirjoittaminen.
▼ Tehdään ohjelma tuon ylimalkaisen algoritminpohjalta, jotta näemme, mitä kaikkea tiedostojenkäsittelyyn liittyy.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 205Ohjelmakoodi
#include <iostream>
#include <fstream> // Tarvitaan tiedostojen käsittelyyn!
#include <string>
#include <cstdio> // EOF–vakio määritelty täällä
using namespace std;
// Tietotyyppien määrittelytstruct Tuote {
string nimi;int koodi;
};
/ / Vakioiden määrittelytconst int MAX_TUOTTEITA = 1000;const char EROTINMERKKI = ’:’;const int VIRHE = –1;
// Funktioiden esittelytint LueTuotteet(Tuote taulu[ ], int tilaa) ;bool TalletaTuotteet(Tuote taulu[ ], int maara);void LajitteleTuotteet(Tuote taulu[ ], int maara);void Vaihda(Tuote& t1, Tuote& t2);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 206int main( ) {
Tuote tuotteet[MAX_TUOTTEITA];int montako;
montako = LueTuotteet(tuotteet, MAX_TUOTTEITA);
if (montako != VIRHE) {LajitteleTuotteet( tuotteet, montako);
if ( !TalletaTuotteet(tuotteet, montako) ) {cerr << "Tietokannan talletus epäonnistui!"
<< endl;}
}}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 207int LueTuotteet(Tuote taulu[ ], int tilaa) {
▼ Tiedostosta lukeminen ja tiedostoon kirjoittaminen(tarvittavat esitelyt: #include <fstream>).
▼ Yleisellä tasolla tiedostonkäsittelyn vaiheet ovat:
– avaaminen (stream-muuttujan alustaminen),
– käsittely (luku- ja kirjoitusoperaatiot) ja
– sulkeminen (close).
▼ Virhetilanteiden tunnistaminen ja niihin reagointitiedostojenkäsittelyn yhteydessä.
▼ Ajatus levytiedostosta »tietovirtana» (stream),johon operaatiot (luku ja kirjoitus) kohdistuvatsiihen kohtaan, jossa ollaan menossa.
▼ Virheilmoitusten tulostaminen cerr:iin.
▼ cin, cout ja cerr ovat valmiiksi alustettuja stream-tyyppisiä muuttujia: jokseenkin kaikki, mikä olisallittua niille, on myös sallittua ifstream- jaofstream-tyyppisille muuttujille ja kääntäen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 212
Tietovirran (stream) idea
▼ Usein ohjelmoidessa tulee tarve
– lukea ohjelman käyttöön tietoa, joka on talletet-tuna tiedostossa koneen kovalevyllä tai
– tallettaa ohjelman tuottamia tuloksia tiedostoonkovalevylle myöhempää käyttöä varten.
▼ Jotta ohjelma voisi käsitellä tiedostoja, täytyyohjelmointikielessä olla tapa esittää reaalimaailmanlevytiedosto (pätkä magneettiraitaa kovalevynpinnalla).
▼ Tavallisesti ohjelmointikielet ratkaisevat tämänongelman erityisellä tietotyypillä, joka tarjoaaoperaatiot tiedon lukemiseen ja kirjoittamiseen.
▼ C++:ssa ja monissa muissa ohjelmointikielissä tällaistatietotyyppiä kutsutaan tietovirraksi (stream).
▼ Itse asiassa tietovirran käsite on laajempi kuin pelkkäliittymä levytiedostoihin: kaikki ohjelman syötteet
ja tulosteet ovat tietovirtoja.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 213▼ Tietovirran voi määritellä jonona merkkejä, jotka
liikkuvat järjestyksensä säilyttäen yhteen suuntaan ➠
– ainoastaan syötevirran ensimmäisen merkin voilukea (operaatio get) ja
– ainoastaan tulostusvirran perään voi kirjoittaamerkin (operaatio put).
▼ Kaikki muut toimenpiteet voidaan toteuttaa get:in japut:in avulla.
– Jos käyttäjä kirjoitaa näppäimistöltä merkkisarjan»abcdef», niin toki ohjelma syötevirtaa (cin)lukiessaan saa merkit käyttöönsä samassajärjestyksessä.
– Jos ohjelma kirjoittaa levytiedostoon sidottuuntulostusvirtaan merkkejä tietyssä järjestyksessä,niin toki on tarkoituksenmukaista, että merkittallettuvat tiedostoon juuri samassa järjestyksessä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 214▼ Tietovirrat ovat peräkkäissaantirakenteita: niiden
siirtämää tietoa voidaan käydä läpi vain siinäjärjestyksessä, jossa se on virrassa ➠ jos ohjelmaakiinnostaa käyttäjän näppäimistösyötteen viidesmerkki, niin ennen sen lukemista täytyy lukea neljäedeltävää merkkiä.
▼ Sama pätee oletusarvoisesti myös syötevirtaan, jokaon sidottu levytiedostoon: tiedosto on luettava läpijärjestyksessä alusta loppuun.
▼ Tosiasiassa levytiedostoja on mahdollista käsitellämyös hajasaantirakenteina, eli niiden sisältövoidaan lukea ja kirjoittaa hyppien sinne tänne.
▼ Yleensä tietovirta kulkee ohjelman ja jonkinoheislaitteen (näppäimistö, levytiedosto jne) välillä,mutta joskus myös kahden ohjelman välillä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 215
Tietovirrat ja niiden käyttö C++:ssa
▼ C++-kielessä tietovirrat esitetään tietotyyppeinä, jotkaovat toteutettu kirjastoissa:
#include <iostream>
istream syötevirrat jotka on yleensä valmiiksisidottu näppäimistöön (cin).
ostream tulostusvirrat jotka yleensä valmiiksisidottu näyttöön (cout ja cerr).
#include <fstream>
ifstream syötevirrat jotka käyttäjän on itsesidottava levytiedostoon.
ofstream tulostusvirrat jotka käyttäjän on itsesidottava levytiedostoon.
#include <sstream>
istringstream syötevirrat jotka käyttäjän on itsesidottava merkkijonoon.
ostringstream tulostusvirrat joiden avulla voidaan»tulostaa» merkkijonoon.
▼ Ohjelmoijan ei tarvitse huolehtia cin:in, cout:in jacerr:in sitomisesta: ne ovat #include <iostream>:injälkeen automaattisesti käytettävissä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 216▼ Tietovirtojen käyttö koostuu yleensä kolmesta
vaiheesta (cin, cout ja cerr siis poikkeuksia):
1. alustaminen (avaaminen, sitominen),
2. käsittely (eli operointi: luku, kirjoitus jne) ja
3. sulkeminen.
▼ Ennen fstream- ja sstream-kirjastojen tarjoamillatietovirroilla operointia, ne on alustettava (vaihe 1):
ifstream Luodaan ifstream-tyyppinen muuttuja jakerrotaan, minkä nimisestä tiedostosta syöteluetaan:
ifstream virran_nimi( tiedoston_nimi) ;
ofstream Luodaan ofstream-tyyppinen muuttuja jakerrotaan, minkä nimiseen tiedostoon syötekirjoitetaan:
ofstream virran_nimi( tiedoston_nimi) ;
istringstream Luodaan istringstream-muuttuja ja kerro-taan merkkijono, josta syöte luetaan:
istringstream virran_nimi(merkkijono) ;
ostringstream Luodaan ostringstream-muuttuja:
ostringstream virran_nimi;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 217▼ Edellisessä taulukossa tiedoston_nimi ei ole string-
tyyppinen, vaan vanhempi C-kielestä periytyvämerkkijonotyyppi ➠ ohjelmoijan on annettavatiedoston nimi oikean tyyppisenä:
OHJ-1100 Ohjelmointi I 218▼ Operaatiot, jotka voidaan kohdistaa kaiken tyyppisiin
tulostusvirtoihin (esimerkkinä käytetty cout:ia):
cout << arvo << endl Tulostetaan lausekkeen arvo
virtaan: arvo voi olla tyypiltäänperustietotyyppi tai string. Rivin-vaihto saadaan tulostettua endl:navulla.
cout.put(merkki) Tulostetaan yksi merkki (char)virtaan.
cout << setw( leveys) ja kaikki muut sivulla 77 esitellyttulostuksen ohjauskomennot ovatkäytettävissä, kunhan muistaasanoa #include <iomanip>.
▼ Lisäksi ostringstream-tyyppiseen virtaan voidaankohdistaa operaatio:
virta .str( ) Palauttaa kaiken virtaan tulostetuntiedon merkkijonona (string).
▼ Olennaisia tulostukseen liittyviä operatioita ei oletämän enempää: melkein kaiken saa hoidettua<<-operaattorilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 219▼ Operaatiot, jotka voidaan kohdistaa kaiken tyyppisiin
syötevirtoihin (esimerkkinä käytetty cin:iä):
cin >> muuttuja Luetaan virrasta seuraava sana, tulki-taan se muuttujan tyyppiseksi arvoksi jatalletetaan arvo muuttujaan, joka voi ollatyypiltään perustietotyyppi tai string.
cin.get( ) Lukee ja palauttaa virran seuraavanmerkin. Syötteen lopussa paluuarvo EOF.
cin.get(merkki) Lukee virrasta seuraavan merkin muut-tujaparametriin merkki, jonka on oltavatyypiltään char.
getline(cin, str ) Lukee virrasta rivin tekstiä ja tallettaa senstring-tyyppiseen muuttujaparametriin str.
getline(cin, str, chr )Kuten edellä, mutta lukee vain ensimmäi-seen vastaantulevaan merkkiin chr saakka.
cin.ignore(montako, merkki)Lukee virtaa korkeintaan montako merkkiätai kunnes vastaan tulee ensimmäinenmerkki. Heittää luetut merkit menemään.Montako on tyypiltään int ja merkki char.
cin.peek( ) Paluuarvona on seuraava syötevirrassaoleva merkki. Merkkiä ei lueta poisvirrasta, vaan seuraava lukuoperaatiopalauttaa myös sen. Syötteen lopussapaluuarvo EOF.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 220▼ Joskus on tarve hypätä virran lukukohdasta
OHJ-1100 Ohjelmointi I 223▼ Edellisessä kohdassa oli käytetty virhetilanteesta
»parantumiseen» jäsenfunktiota clear( ) .
▼ Aina, kun tietovirtaa käsitellessä tapahtuu virhe,niin virta menee tilaan, jossa mitkään laillisetkaanoperaatiot eivät enää toimi ➠ joskus tästä tilastapäästään pois clear( )-funktiolla, jonka kutsunjälkeen voidaan (yrittää) jatkaa normaalisti.
▼ Joskus virhe on niin paha, ettei sitä voi clear( ) :llakorjata (esim. levytiedosto luettu loppuun).
▼ Kuinka erottaa toisistaan virran loppuminen ja jokumuu virhetilanne:
while ( getline(virta, rivi) ) {...
}
if ( !virta.eof( ) ) {// Lukeminen epäonnistui jostain muusta// syystä kuin virran loppuminen... virhe
}
▼ Jäsenfunktio eof( ) palauttaa arvon tosi vain, josvirrasta on yritetty lukea sen loputtua.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 224▼ Myös esimerkkiohjelmassa (s.207) käytetty seuraavan
merkin kurkistaminen peek( ) -jäsenfunktiolla onjoskus käyttökelpoinen tapa päätellä, onko syöte joluettu loppuun vai ei.
▼ Viimeisenä esimerkkinä, kuinka lukea läpi syötevirta,joka muodostuu riveistä:
puhelinnumero1 nimi1puhelinnumero2 nimi2
... ...
▼ Seuraava koodinpätkä havainnollistaa asian:
int numero;string nimi;
while (virta >> numero && getline(virta, nimi) ) {// Käsitellään luettu numero ja nimi...
}
if ( !virta.eof( ) ) {// Virheellinen rivi syötevirrassa...
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 225
Standardivirrat cin, cout ja cerr
▼ Virrat cin, cout ja cerr ovat ilman muita alustuksiaohjelmoijan käytettävissä #include <iostream>:injälkeen.
▼ cin:iä ja cout:ia käytetään tuttuun tapaan näppäi-mistösyötteen lukemiseen ja näytölle tulostamiseen.
▼ cerr toimii aivan samoin kuin cout, mutta sinne ontarkoitus tulostaa vain ohjelman virheilmoituksia.
▼ Kaikki muut tulosteet laitetaan cout:iin.
▼ Noudata tätä käytäntöä myös omissa ohjelmissasi!
▼ Tuohon käytäntöön on ihan oikea syykin, muttasiitä voidaan jutella joko harjoituksissa tai kurssillaOhjelmointi II.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 226
Tietovirta funktion parametrina
▼ Tärkein asia joka pitää muistaa, jos haluaa antaafunktiolle parametrina tietovirran: muodollisen
parametrin pitää olla viite- eli muuttujaparametri.
▼ Tuo on järkeenkäypä vaatimus, sillä jos funktio operoitietovirralla, niin virtahan muuttuu ➠ muutoksen onvälityttävä myös todelliseen parametriin.
▼ Pääsääntöisesti todellisen parametrin pitää ollasamaa tyyppiä kuin funktion muodollinen parametri,paitsi jos:
muodollisen para-
metrin tyyppi on
niin todellinen parametrin
tyyppi saa olla myös
istream& ifstream tai istringstream
ostream& ofstream tai ostringstream
▼ Käytännössä tuo mahdollistaa yleiskäyttöisempienfunktioiden kirjoittamisen: sama funktio voi operoidaerityyppisillä virroilla.
▼ Miten ja miksi tämä on mahdollista, siitä enemmänOhjelmointi II -kurssilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 227▼ Tyydytään tässä pieneen esimerkkifunktioon:
▼ Tässä esimerkissä on ajateltu, että käyttäjä ylläpitääkäsin jonkin editorin (Jed, Emacs) avulla yksinkertaistakalenteritiedostoa, jonka rivit ovat muodossa:
päivä/kuu/vuosi :kalenterimerkintä
▼ Lisäksi tiedostossa voi olla #-merkillä alkaviakommenttirivejä ja tyhjiä rivejä, joille ei ole tarkoitustehdä mitään.
▼ Kelvollinen tiedosto voisi näyttää seuraavalta:
################################### Tämä tiedosto sisältää kalenterini. #################################### Kevätlukukausi1/5/2016:Vappu.
# Syyslukukausi22/7/2016:Valmistaudu henkisesti opintoihin.2/11/2016:Aika alkaa lukea tenttiin.24/12/2016:Joulupukki tulee.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 229▼ Toteutetaan ohjelma, joka tulostaa kalenteritiedoston
hiukan siistimmässä asussa näytölle.
▼ Esimerkiksi, jos edellä havainnollistuksena käytettytiedosto haluattaisiin tulostaa ohjelmalla, näyttäisisuoritus seuraavalta:
proffa> ./kalenteriAnna kalenteritiedoston nimi: kalenteri.txt01.05.2011 Vappu.22.07.2011 Valmistaudu henkisesti opintoihin.02.11.2011 Aika alkaa lukea viimeiseen tenttiin.24.12.2011 Joulupukki tulee.proffa>
▼ Ohjelma siis ei tee muuta kuin muokkaa päivämää-rien tulostusasua, jotta kaikki kalenteritapahtumattulostuvat selkeästi sarakkeisiin.
▼ Tulostusasun muokkaaminen iomanip-kirjastonavulla ei ole mitään uutta, joten ei kannatahämääntyä siitä ➠ tehtävään kätkeytyy montamuuta uutta pikku ideaa.
▼ Jos numeroinnin lopettaa kesken, niin loput alkiot
C++ numeroi automaattisesti siten, että seuraavaalkio on arvoltaan aina yhtä isompi kuin edellinen:
// PUN← 8, VIH← 9, SIN← –1 ja KEL← 0enum Vari { PUN = 8, VIH, SIN = –1, KEL } ;
▼ Kahdella tai useammalla alkiolla voi olla sama arvo.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 240▼ Luettelotyypin alkioita voi käyttää kaikkialla, mihin
sopisi kokonaisluku- tai char-tyyppinen arvo ➠ alkio
tulkitaan sitä vastaavaksi kokonaisluvuksi.
▼ Luettelo- ja kokonaislukutyyppisellä tiedolla voirauhassa operoida keskenään ikään kuin ne olisivatkeskenään samaa tyyppiä.
▼ Luettelotyyppisen tietoalkion paikalla ei kuitenkaanvoi sijoituslauseessa tai funktiokutsun todellisenaparametrina käyttää kokonaislukuarvoa ilmaneksplisiittistä tyyppikonversiota:
▼ switch-rakenteella saadaan aikaan ehdollistasuoritusta tilanteissa, joissa
1. testattava arvo on tulkittavissa kokonaisluvuksi(int, char tai enum).
2. vaihtoehdot, joihin testattavaa arvoa vertaillaan,ovat kokonaisluvuiksi tulkittavissa olevia vakioita.
➠ C++:n switch ei ole yhtä joustava kuin if–else.
▼ Tiettyihin tilanteisiin se kuitenkin sopii erinomaisesti.
▼ switch-rakenne näyttää seuraavalta:
switch ( testattava_arvo ) {case vaihtoehto1 :
käskyt1 ;break ;
...case vaihtoehton :
käskytn ;break ;
default :käskytdef ;break ;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 242▼ switch vertaa testattavaa arvoa järjestyksessä
vaihtoehtoihin.
▼ Kun vastaan tulee vaihtoehto, joka on yhtäsuurikuin testattava arvo, niin suoritetaan vastaavaancase-haaraan liittyvät käskyt.
▼ Jos mikään vaihtoehto ei täsmää, suoritetaandefault-haaran käskytdef.
▼ default voi puuttua ➠ on mahdollista, ettäswitch-rakenne ei tee mitään.
▼ Kun suoritettava haara on kerran valittu, suoritusjatkuu, kunnes törmätään break-käskyyn.
▼ break keskeyttää switch-rakenteen suorituksen ➠suoritus jatkuu rakenteen perästä.
▼ Mikäli jostain haarasta puuttuu break, niin suoritus»putoaa» seuraavan haaran puolelle.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 243▼ Tätä ominaisuutta käytetään usein hyväksi silloin,
kun useaan vaihtoehtoon liittyy samat toiminnot:
switch ( vastaus ) {case ’e ’ :case ’E’ :
suoritettavat toiminnot vastauksen ollessa e tai E
break;...
}
▼ break:in voi jättää pois, mikäli haarasta(tarkemmin: koko funktiosta) poistutaan return:illa:
switch ( kuukausi ) {case 1:
return "tammikuu";case 2:
...case 12:
return "joulukuu";default :
return "virhekuu";}
▼ Esimerkkiohjelmassa tätä ei käytetty hyväksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 244
for-silmukka
▼ for-silmukan yleinen muoto on:
for (alustus ; ehto ; kasvatus ) {runko ;
}
▼ Alustus suoritetaan vain kerran, juuri ennen kuinehtoa testataan ensimmäistä kertaa.
▼ Kuten while-silmukassakin ehto testataan ainaennen rungon suorittamista.
▼ Jos ehdon arvoksi evaluoituu true, niin suoritetaanrungon käskyt.
▼ Jos ehto on false, niin jatketaan silmukkaa seuraavastakäskystä.
▼ Kun kaikki rungon käskyt on tehty (saavutaanloppuaaltosulkuun), niin suoritetaan kasvatus japalataan takaisin testaamaan ehtoa.
▼ On mahdollista ettei runkoa suoriteta kertaakaan:jos ehto on heti alussa false.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 245
do–while-silmukka
[bonus: ei esimerkkohjelmassa]
▼ C++:ssa on kolmaskin silmukkarakenne while- jafor-rakenteiden lisäksi.
▼ do–while-silmukan yleinen muoto on:
do {runko ;
} while ( ehto ) ;
▼ Runko koostuu yhdestä tai useammasta käskystä,jotka suoritetaan ainakin kerran.
▼ Jos ehdon arvoksi evaluoituu rungon suorituksenjälkeen true, niin suoritetaan runko uudelleen.
▼ Jos ehdon arvoksi evaluoituu false, niin jatketaansilmukkaa seuraavasta käskystä.
▼ do–while-silmukalle ei ole niin paljon käyttöä kuinmuille silmukkarakenteille, mutta sen olemassaolostaon hyvä olla tietoinen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 246
break, continue ja silmukat
▼ Silmukan rungon suoritus voidaan keskeyttääbreak- ja continue-käskyillä:
break
lopettaa silmukan suorituksen välittömästi jajatkaa silmukkaa seuraavasta käskystä.
continue
aloittaa uuden silmukan kierroksen(hyppää rungon loppusulkuun).
▼ Käskyt vaikuttavat vain sen sisimmän silmukantoimintaan, jonka sisällä ne suoritetaan.
▼ Tapahtumien kulku eri silmukkarakenteissa:
while-silmukka:
ehto runkotrue
false break
continue
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 247do–while-silmukka:
ehtorunko
true
false
break
continue
for-silmukka:
ehto runkoalustus
kasvatus
true
false break
continue
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 248
Käskyt −→←− tieto
▼ Tietokoneohjelmat koostuvat tiedosta ja silläoperoivista toimenpiteistä eli käskyistä (s.46).
▼ Todellisuudessa jako ei ole noin selvärajainen, vaanjoissain tilanteissa tieto voi korvata osan käskyistä taipäinvastoin.
▼ Tätä käytetään hyväksi nk. tieto-ohjatussa suunnitte-lussa ja ohjelmoinnissa (data-driven/data-directed
design/programming).
▼ Tieto-ohjatun ohjelmoinnin idea on suunnitellaja toteuttaa ohjelmaan tietorakenteita, joidenavulla tietoa käsittelevien käskyjen määrä saadaanpienemmäksi ➠ korvataan käskyjä tiedolla.
▼ Tämä kaikki on helpompi ymmärtää muutamanesimerkin avulla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 249▼ Ajatellaan aluksi funktiota, jossa ei ole hyödynnetty
string OsavaltioNimeksi(Osavaltio osav) {for (int i = 0; i < MONTAKO; i++) {
if (INFO[i].koodi == osav) {return INFO[i].nimi;
}}return "Virheellinen osavaltiokoodi" ;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1100 Ohjelmointi I 251▼ Näiden kahden esimerkin ero on selvä: kun nähtiin
hiukan vaivaa sopivan tietorakenteen suunnitelussaja alustamisessa, niin varsinainen OsavaltioNimeksi-funktio supistui minimaaliseksi.
▼ Jos tietorakenne on sopivasti/hyvin suunniteltu, siitäkoituva hyöty voi moninkertaistua uudelleenkäytet-tävyytenä (esim. NimiOsavaltioksi-funktio).
▼ Toteutustekniikkana on aina (tavalla tai toisella)etukäteen prosessoitu informaatio, joka muodostuulähtöarvoista ja niistä seuraavasta lopputuloksesta.
▼ Tieto-ohjatun ohjelmoinnin hyötyjä ovat:
1. Syntyvät ohjelmat ovat yleensä lyhyempiä, muttaniiden selkeys ei silti kärsi.
2. Virheiden mahdollisuus pienenee.
3. Ohjelmien laajennettavuus ja ylläpidettävyys onhelpompaa.
▼ Varma merkki tieto-ohjatun uudelleensuunnitteluntarpeesta ohjelmassa on liian pitkäksi kasvava if- taiswitch-rakenne.
▼ 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ä seuraavaanristeykseen 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 kirjaamallajokainen 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 palattutakaisinpäin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 253▼ Toteutettu ohjelma näyttää seuraavalta:
▼ Tämä eroaa [ ]-operaattorista siten, että laitonindeksi aiheuttaa poikkeuksen (s. 476) ja ohjelmansuoritus 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 muodolliseenparametriin.
▼ Ongelma vältetään C++:ssa yleensä niin, että josfunktion parametri on kooltaan suuri tietorakenne,jonka ei haluta kopioituvan, tehdään muodollisestaparametrista nk. vakioviite:
▼ Nyt todellinen parametri ei kopioidu muodolliseenparametriin, mutta samalla const estää muodollisenparametrin (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ä vakioviite-parametria 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 arvopara-metreihin: paluuarvo joudutaan kopioimaan funk-tiosta kutsujalle ➠ toteuta suurikokoiset paluuarvotmuuttujaparametrin avulla:
▼ Nyt tulokset talletetaan muuttujaparametriin yhdenkerran, eikä niitä tarvitse kopioda erikseen funktiostapalattaessa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 261
Standard Template Library (STL)
▼ C++:n standardikirjastoon kuuluu osanank. Standard Template Library (STL).
▼ STL tarjoaa ohjelmoijalle valmiit työkalut (tietoraken-teita ja algoritmeja) monimutkaisten ja joustavientietorakenteiden luontiin ja käsittelyyn ➠ kaikkea eitarvitse tehdä alusta pitäen itse.
▼ Loogisesti STL koostuu kolmesta osasta:
– STL-säiliöt (containers) eli varsinaiset yleis-käyttöiset tietorakenteet, joihin käsiteltävä tietovoidaan tallettaa (s. 263).
– STL-iteraattorit (iterators) joiden avulla säiliöidensisältämiä tietoalkioita voidaan käydä yksitellenläpi tai tarvittaessa esittää säiliön alkioiden osaväli(s. 288).
– STL-algoritmit (algorithms) prosessoivat säiliöihintalletettuja alkioita (s. 297). Se, mihin osaan säiliönsisältämistä alkioista algoritmin toimenpiteidenhalutaan kohdistuvan, kerrotaan algoritmilleiteraattoreiden 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östarjota 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 jonkinmuun tietotyypin alkioita.
▼ STL-säiliöt ovat geneerisiä (yleiskäyttöisiä/tyyppi-riippumattomia) dynaamisia (alkioden lukumäärääei ole kiinnitetty) tietorakenteita:
Geneerisyys
– STL ottaa vain yleisellä tasolla kantaa siihen,mitä ominaisuuksia tietotyypillä pitää olla, jottasen alkioita voidaan tallettaa STL-säiliöihin.
– Säiliömuuttujan määrittely tapahtuu aina
säiliötyyppi<alkiotyyppi> muuttujan_nimi;
syntaksilla. Esimerkiksi vector, johon talletetaanopiskelijoita:
vector<Opiskelija> opiskelijarekisteri;
jossa Opiskelija on jokin itse määritelty tieto-tyyppi yhden opiskelijan tietojen esittämiseen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 264Dynaamisuus
– Toisin kuin C++:n omalla taulukkotyyppillä (jokamyös on eräänlainen säiliö), STL-säiliöillä ei olekiinnitettyä maksimikokoa, vaan niiden kokokasvaa ja pienenee automaattisesti, kun uusiaalkiota lisätään tai vanhoja poistetaan.
▼ Säiliötyypit voidaan luokitella ominaisuuksiensa jakäyttäytymisensä perusteella kahteen kategoriaan:
▼ Sarjat ovat tietorakenteita, jotka säilyttävät alkioidenjärjestyksen.
▼ Esimerkiksi vector-rakenteeseen ensimmäisenä lisättyalkio pysyy ensimmäisenä, jollei ohjelmoija erikseensuorita operaatiota, joka muuttaa sen paikkaa.
▼ Jokaiselle sarjan alkiolle voidaan ajatella sijain-nista 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 lopustanopeaa, voidaan indeksoida kokonaisluvulla),
– list (alkion lisäys ja poisto minne/mistä tahansanopeaa, peräkkäissaantirakenne (s.213) ➠ ei voiindeksoida) 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 rakenneoikeaan tarkoitukseen.
▼ Esimerkiksi ei ole hyvä ajatus valita tietorakenteeksivector:ia, jos on tarve lisätä usein alkioita rakenteenalkuun (vrt. edellisen sivun ominaisuuslista).
▼ Sarjatyyppeihin voidaan tallentaa minkä tahansatietotyypin alkioita, kunhan kyseisellä tyypillätoimivat sijoitusoperaattori ja alustus toisesta
samantyyppisestä muuttujasta.
▼ Myös toisten STL-rakenteiden tallentaminen sarjoihinon mahdollista.
▼ Kunkin sarjatyypin käyttämiseksi pitää ohjelmanalkuun 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ärjestys-numero (indeksi) on tiedossa (tämä on mahdollistavain 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 perusoperaa-tioiltaan hyvin samankaltaisia.
▼ Merkittäviä poikkeuksia on oikeastaan vain kaksi:
– vector- ja deque-rakenteiden yksittäisiin alkioihinpäästään suoraan käsiksi indeksoimalla[ ]-operaattorilla tai at-funktiolla, listan alkioita eivoi indeksoida.
– deque- ja list-rakenteisiin alkioita voidaan lisätäja poistaa myös alusta push_front- ja pop_front-operaatioiden avulla, vektoriin näitä operaatioitaei voida kohdistaa.
▼ Oletetaan esimerkeissä, että seuraavat on määritelty:vector<int> ivektori;deque<string> strdeque;list<Opiskelija> olista;
▼ Määrittelyn jälkeen rakenteet ovat automaattisestityhjiä (alkioiden lukumäärä nolla).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 269▼ Alkioita voidaan lisätä ja poistaa lopusta:
▼ Alkioita voidaan deque- ja list-rakenteen tapauksessalisätä ja poistaa myös rakenteen alusta push_front-ja pop_front-funktioilla:
strdeque.push_front("alussa deque oli autio ja tyhjä");olista.push_front(joku_opiskelija) ;
strdeque.pop_front( );olista.pop_front( ) ;
▼ Kaikkien STL-sarjojen ensimmäinen ja viimeinen alkiosaadaan selville front- ja back-funktioilla:
int i;i = ivektori.front( ) ;
Opiskelija op;op = olista.back( );
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 270▼ vector- ja deque-rakenteita voidaan indeksoida
[ ]-operaattorilla tai at-funktiolla, kun tiedetäänhalutun alkion indeksi:
i = ivektori[4];ivektori.at(3) = 42;
string str;str = strdeque.at(7);strdeque[666] = "the number of the beast";
▼ Kaikki yksittäisiä alkioita käsittelevät toimenpiteetovat laillisia vain, jos kyseinen alkio on olemassa:
– Indeksointi on laitonta luvulla, jonka mukaistaalkiota rakenteessa ei ole.
– at-funktiolla indeksointi on turvallisempaa kuin[ ]-operaattorilla, koska laiton indeksi keskeyttääohjelman suorituksen.
– front-, back-, pop_front- ja pop_back-funktiotkeskeyttävät ohjelman, jos rakenne on tyhjä.
– Edellä «ohjelman keskeyttäminen» tarkoittaatäsmällisesti ottaen poikkeuksen heittämistä,mutta siitä lisää myöhemmin (s.476).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
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 < strdeque.size( ) ) {
cout << strdeque.at(i) << " ";++i;
}cout << endl;
▼ list-rakenteita voi käydä läpi vain nk. iteraattoreiden
avulla ➠ niistä lisää hetken kuluttua (s.288).
▼ Funktiolla empty voidaan testata onko rakenne tyhjä(ei sisällä yhtään alkiota):
if ( strdeque.empty( ) ) {// Ei alkioita.· · ·
}· · ·while ( !olista.empty( ) ) {
// 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äärittelynyhteydessä) 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, 0.0);
▼ Jos sarjarakenteelle ei määrittelyn yhteydessä kerrotaalkioiden lukumäärää, rakenne on aluksi tyhjä (muttaalkioitahan voi lisätä).
▼ Vaikka alkioiden lukumäärä on kerrottu, alkioitavoidaan silti myös lisätä ja poistaa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 273▼ Kaikille STL-sarjoille voi määrittelyn yhteydessä antaa
▼ Ajatus on täysin analoginen esimerkiksi struct-tyyppisten muuttujien alustukseen verrattuna:aaltosulkeiden sisällä luetellaan pilkuilla eroteltuinakaikki alustusarvot/-alkiot.
▼ Säiliön alkukoko (siis size( )-funktion paluuarvo) onluonnollisesti 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 entuudes-taan olemassa olevaan muuttujaan: ✶
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden olemassaolosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 274▼ Sijoituksen jälkeen säiliö sisältää pelkästään alustus-
listan alkiot samassa järjestyksessä ja sen koko onsama kuin alustuslistan alkioiden lukumäärä.
▼ Alustuslistan alkioiden ei ole pakko olla literaalisiaarvoja, vaan esimerkiksi seuraava on mahdollista:
▼ Alustuslista voi myös olla funktion todellisenaparametrina, jos muodollisen parametrin tyyppi onSTL-sarja: ✶
void tulosta(const vector<int>& v) {· · ·
}· · ·tulosta( { func("abc") , 2, luku } ) ;
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden olemassaolosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 275▼ Säiliö voidaan tyhjentää clear-funktiolla
ivec.clear( ) ;
jonka jälkeen rakenteessa ei ole yhtään alkiota (muttalisäys on edelleen mahdollista).
▼ Kahta samantyyppistä sarjaa voi tutkia vertailu-operaattoreilla:
if ( merkkilista_1 == merkkilista_2 ) {// Listat yhtäsuuria: samat alkiot// samassa järjestyksessä.· · ·
}
▼ Nämä olivat perusoperaatiot STL-sarjoille ➠
– Muihin mahdollisuuksiin palataan iteraattorien(s.288) ja algoritmien (s.297) yhteydessä.
– Kannattaa myös tutustua C++-kirjastoreferenssiinhttp://www.cs.tut.fi/~aps/doc/cppref.pdf
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 276
Assosiatiiviset säiliöt
(associative container)
▼ Assosiatiiviset säiliöt järjestävät rakenteeseentalletetut tiedot omien tarpeidensa mukaan: sitenettä lisäys- ja hakuoperaatiot ovat mahdollisimmannopeita.
▼ Rakenne ei ole sellainen, jossa alkioiden voisiajatella olevan peräkkäisjärjestyksessä, joten niidenjärjestysnumerosta tai paikasta ei voi puhua.
▼ Assosiatiiviseen säiliöön tieto talletetaan(haku)avaimen perusteella ➠ myös haku tapahtuusaman avaimen avulla.
▼ STL:ssä assosiatiivisia säiliöitä ovat:
– set: analoginen matemaattisen joukon kanssa,
– multiset: joukko, johon sama alkio voi kuuluauseammin 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ä talletettavalleavaimelle toimivat sijoitusoperaattori, alustus javertailu toisen samantyyppisen tietoalkion kanssa.
▼ map- ja multimap-rakenteisiin talletettavaltatiedolta vaaditaan, että sekä avaimelle ettävarsinaiselle tietoalkiolle toimivat sijoitus ja alustustoisesta samantyyppisestä tietoalkiosta. Lisäksiavaimelle pitää toimia vertailu.
▼ map- ja multimap-rakenteisiin talletettava tietokoostuu kahdesta osasta: avaimesta ja varsinaisestatietoalkiosta ➠ map- ja multimap-säiliömuuttujanmäärittely on muotoa:
OHJ-1150 Ohjelmointi II 278▼ Ideoita assosiatiivisten säiliöiden käyttökohteista:
set
kun on tarpeen selvittää kuuluuko alkio joukkoonvai 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/tunnisteinenalkio voi esiintyä joukossa useammin kuin kerran.Esimerkiksi kauppakassin sisältö:
multiset<string> ostokset;
voi sisältää vaikka "maito"-nimisen tuotteenuseammin kuin kerran.
map
sitoo yksikäsitteisen avainarvon ja siihen liittyväntiedon toisiinsa. Esimerkiksi avaimena opiskelija-numero (yksikäsitteinen) ja arvona kyseisenopiskelijan muut tiedot:
map<int, Opiskelija> opiskelijarekisteri;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 279multimap
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 omapuhelinnumeronsa:
multimap<string, int> puhelinluettelo;
▼ Vaikka multiset ja multimap ovat hyödyllisiä, niihinei kurssilla puututa sen tarkemmin.
▼ Kannattaa myös pitää mielessä, että STL-säiliöiden(sekä sarjojen että assosiatiivisten säiliöiden) alkiotvoivat 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 samantyyp-pisestä joukosta:
} else {// Joukoissa täsmälleen samat alkiot.· · ·
}
▼ Joukkoon kuuluvien alkioiden lukumäärän saa selvillesize-funktiolla:
if ( lottonumerot_1.size( ) != 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 iteraattorinavulla (s.288).
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden olemassaolosta. ▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 283
Lyhyitä esimerkkejä map-rakenteesta
▼ Muista esitellyistä STL-rakenteista poiketen, map-rakenteen 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 sijoitustoisesta samantyyppisestä rakenteesta:
tai ilman tietopari-apumuuttujaa:puhelinnumerot.insert(make_pair("Topi", 212121212) );
▼ Vaikka pari-käsite tässä vaiheessa tuntuu tarpeet-toman monimutkaiselta, käy ilmi, että map-rakenteita iteraattorien avulla käsiteltäessä tietämyspair-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:
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden 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 muistaayhden säiliössä olevan alkion sijainnin.
▼ Kahdella iteraattorilla voidaan esittää jokin osavälisäiliöön talletetuista alkioista: mikä on osavälinesimmäinen alkio ja mikä viimeinen alkio.
▼ Jokainen STL-säiliö tarjoaa ohjelmoijalle joukontietotyyppejä ja funkioita, joilla voidaan käsitelläkyseiseen säiliötyyppiin liittyviä iteraattoreita.
OHJ-1150 Ohjelmointi II 289▼ Edellisiä iteraattorityyppejä oleviin muuttujiin
voidaan tallentaa tieto alkion sijaintipaikastasäiliössä.
▼ Säiliötyypin jäsenfunktio begin kertoo säiliönensimmäisen alkion sijainnin.
▼ Jäsenfunktio end palauttaa iteraattorin joka osoittaarakenteen loppuun: end ei osoita lailliseen alkioonvaan on eräänlainen loppumerkki.
▼ Esimerkiksi yhdeksänalkioinen kokonaislukuvektori:
vector<int> ivek(9)
ivek.begin( ) ivek.end( )
▼ Iteraattorin osoittamaa alkiota voidaan käsitelläkohdistamalla iteraattoriin unaarinen * -operaattori.
▼ Iteraattori saadaan siirrettyä osoittamaan säiliönseuraavaan alkioon ++-operaattorilla ja edelliseenalkioon –– -operaattorilla.
▼ ==- ja != -operaattoreilla voidaan testata, osoitta-vatko 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 jatulostaa muutetut alkiot näytölle:
vector<int> lukuvektori;· · ·vector<int>::iterator iter = lukuvektori.begin( );while ( iter != lukuvektori.end( ) ) {
*iter = 2 * *iter; // Alkion muuttaminencout << *iter << endl; // Alkion arvon käyttäminen++iter; // Seuraavaan alkioon siirtyminen
}
▼ Edellisestä näkyy selvästi, kuinka end-iteraattori eienää osoita käsittelyä vaativaan alkioon, vaan senavulla testataan, joko koko rakenne on käyty läpi.
▼ Usein end-arvoa käytetään myös funktion paluu-arvona osoittamaan, että säiliöstä ei löytynyt etsittyäalkiota (s.280).
▼ Edellä esitetty läpikäyntimekanismi toimii kaikillaSTL-säiliöillä ➠ toisena esimerkkinä joukon alkioidentulostaminen (vaihtelun vuoksi for-silmukka):
set<string> nimet;· · ·set<string>::iterator it;for ( it = nimet.begin( ); it != nimet.end( ); ++it ) {
cout << *it << endl;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 291▼ Jos iteraattori osoittaa alkioon, joka on tietue (tai
myöhemmin myös luokka), voidaan tietueen kenttiinviitata suoraan –> -operaattorilla.
▼ Kuten map-rakenteesta puhuttaessa todettiin, senalkiot ovat todellisuudessa tietueita, jotka koostuvathakuavaimesta (first-kenttä) ja varsinaisestainformaatiosta (second-kenttä).
// Avainkenttä: first (string)if ( puhiter–>first.at(0) == ’T’ ) {
// Informaatio: second (unsigned int)cout << puhiter–>second << endl;
}++puhiter;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 292▼ Tähän mennessä esiteltyjen iteraattoreiden avulla
voidaan operoida vain sellaisiin säiliöihin, joitaon sallittua muuttaa ➠ ongelma jos funktioidenmuodolliset parametrit ovat tehokkyyssyistä const-vakioviitteitä.
▼ Tämä ratkeaa seuraavien iteraattorityyppien avulla:vector<alkiotyyppi>::const_iteratordeque<alkiotyyppi>::const_iteratorlist<alkiotyyppi>::const_iteratorset<alkiotyyppi>::const_iteratormap<avaintyyppi, alkiotyyppi>::const_iterator
▼ Esimerkiksi funktio, joka laskee kokonaislukulistanalkioiden summan:
int Summaa(const list<int>& lukulista) {int summa = 0;list<int>::const_iterator it = lukulista.begin( );while ( it != lukulista.end( ) ) {
summa = summa + *it;++it:
}return summa;
}
▼ const_iterator-tyypit eivät salli iteraattorin osoit-taman alkion arvon muuttamista.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 293▼ Kaikki STL-säiliöt map-rakennetta lukuunottamatta
voidaan alustaa iteraattorien avulla toisista säiliöistä:vector<double> reavek;· · ·list<double> realista(reavek.begin( ), reavek.end( ) ) ;
▼ Yllä siis realista alustetaan samassa järjestyksessä jasamoilla lukuarvoilla kuin mitä muuttujassa reavek oli.
▼ Kannattaa huomata, että alustuksessa ei ole pakkokäyttää begin- ja end-funktioita, vaan mikä tahansaiteraattoriväli käy.
▼ Esimerkiksi lista olisi voitu alustaa siten, että vektorinensimmäinen ja viimeinen alkio olisi jätetty pois:
OHJ-1150 Ohjelmointi II 294▼ Tai jos sattuu tietämään, että vector- ja deque-
iteraattoreihin voi lisätä tai vähentää koko-
naisluvun ja saada siten uuden iteraattorin, jokaosoittaa kyseisen lukumäärän verran alkioita eteen-tai taaksepäin.
▼ Edellinen esimerkki menisi siis lyhemmin:list<double> realista(reavek.begin( ) + 1, reavek.end( ) – 1);
▼ Vaikka tähän saakka on jatkuvasti käytetty ilmaisua«iteraattori osoittaa säiliön alkioon»:
vector<int> ivek(9)
ivek.begin( ) ivek.begin( )+4 ivek.end( )
▼ Iteraattorien voi myös ajatella osoittavanalkioiden väliin, jolloin unaarinen * -operaattorija –> -operaattori tulkitaan siten, että halutaankäsitellä heti välin perässä seuraavaa alkiota:
vector<int> ivek(9)
ivek.begin( ) ivek.begin( )+4 ivek.end( )
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 295▼ Tämä näkemys auttaa tulkitsemaan tilannetta
paremmin silloin, kun kahden iteraattorin avullaesitetään osajoukko säiliön peräkkäisistä alkioista.
▼ Jos välttämättä haluaa ajatella iteraattorien osoittavanalkioihin välien sijaan, pitää kahdella iteraattorillaesitetyt alkiojoukot aina tulkita ylhäältä avoimiksi:siis ylempi osoitettu alkio ei enää kuulu käsiteltäviinalkioihin.
▼ Kumpikin esitetty tulkinta on vain tapa ajatellaasiaa, kannattaa valita se, joka tuntuu itselleluonnollisimmalta.
▼ Kirjallisuudessa näkee molempia tapoja, jotenkannattaa olla aina tarkkana siitä, kumpaa tapaakirjoittaja noudattaa.
▼ Vielä yksi olennainen asia, joka on syytä pitäämielessä: jos säiliöön on lisätty tai siitä on poistettualkioita, et voi luottaa siihen, että ennen muutostatalletetut iteraattorit toimisivat muutoksen jälkeenoikein.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 296▼ Kärjistettynä esimerkkinä: jos iteraattori osoittaa
alkioon, joka poistetaan säiliöstä, mihin iteraattoriosoittaa poiston jälkeen?
▼ Nämä olivat perusideat STL-iteraattoreista ➠
– Muihin käyttötarkoituksiin palataan heti jatkossaalgoritmien yhteydessä.
– Myös C++-kirjastoreferenssi kertoo lisää yksityis-kohtia: http://www.cs.tut.fi/~aps/doc/cppref.pdf
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 297
STL-algoritmit
▼ STL-kirjasto algorithm tarjoaa valmiina yleisimmätoperaatiot, joita säiliön sisältämille alkioille ontarpeen tehdä.
▼ Algoritmikirjaston käyttämiseksi on koodiin lisättävä:#include <algorithm>
▼ STL:n valmiit algoritmit ovat geneerisiä: ne eivät otakantaa säiliön tyyppiin, vaan ne osaavat operoidamillä tahansa säiliöllä, kunhan säiliön alkioihin
voidaan viitata iteraattorien avulla.
▼ Käsiteltäväksi haluttu osa säiliön alkoista kerrotaanalgoritmifunktiolle iteraattorivälin avulla ➠ jokaisellafunktiolla on aina vähintään kaksi iteraattoripara-metria.
▼ Joskus iteraattoriparametreja on useampiakin, josfunktio tallettaa tuloksia johonkin toiseen säiliöön.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 298▼ Esimerkiksi algoritmi (siis funktio) sort osaa lajitella
vector- ja deque-rakenteen kasvavaan järjestykseen,kunhan sille kerrotaan iteraattoreilla lajiteltavaksihaluttu osaväli:
▼ Listaa ei voi lajitella sort-funktiolla (sort-algoritmi eiosaa lajitella peräkkäissaantirakennetta), mutta silläon jäsenfunktio sort, joka osaa:
list<string> slist;· · ·slist.sort( ) ;
▼ Assosiatiivisia säiliöitä ei voi lajitella, sillä kutenmuistetaan, set- ja map-rakennetta käytettäessäohjelmoija ei voi vaikuttaa alkioiden järjestykseen.
▼ STL-iteraattorit on siinäkin suhteessa nerokkaastimääritelty käsite, että tarpeen vaatiessa myös C++:nomia taulukoita voi ajatella sarjoina ja käsitelläalgoritm-kirjaston funktioilla iteraattorimaisesti.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 299▼ begin-iteraattoriksi tulkitaan tällöin
taulukkomuuttujan_nimi
ja end-iteraattoriksi lausekkeentaulukkomuuttujan_nimi + alkioiden_lukumäärä
arvo.
▼ Esimerkiksi kokonaislukutaulukko voitaisiin lajitellasort-funktiolla:
int itaulukko[1000];· · ·sort(itaulukko, itaulukko + 1000);
▼ Selitys sille, mitä lauseke itaulukko + 1000
oikeasti tarkoittaa, selviää vasta myöhemmin, kuntutustutaan osoittimiin (s.446).
▼ Seuraavassa on esiteltynä hyvin lyhykäisestikäyttökelpoisimpia algorithm-kirjaston funktioita.
▼ Kannattaa pitää mielessä, että kaikissa tapauksissabegin- ja end-iteraattorien paikalla mikä tahansakahden iteraattorin esittämä osaväli on kelvollinen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 300▼ count laskee säiliössä olevien tietyn arvoisten
▼ find etsii säiliöstä haluttua alkiota ja palauttaaiteraattorin ensimmäiseen löytämäänsä alkioon taiend-iteraattorin, jos ei löydy:deque<string> potilasjono;· · ·deque<string>::iterator iter;iter = find(potilasjono.begin( ), potilasjono.end( ), "Hanski") ;if ( iter == potilasjono.end( ) ) {
// Ei ole Hanskia potilasjonossa.· · ·
} else {// Hanski löytyi ja sijaitse iter:in osoittamassa// paikassa: tulostetaan ja poistetaan jonosta.cout << *iter << endl;potilasjono.erase(iter) ;
}
▼ Edellisessä erase ei ole algorithm-kirjaston funktio,vaan kaikille STL-säiliöille määritelty funktio, jollaiteraattorin osoittama alkio poistetaan säiliöstä(vrt. http://www.cs.tut.fi/~aps/doc/cppref.pdf).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 301▼ min_element ja max_element etsivät säiliöstä
▼ Kopioinnin kohteessa (siis siellä minne alkiotakopioidaan) 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, jotkatallettavat tuloksia säiliöön: kohdesäiliössä pitää ollavalmista tilaa kaikille talletettaville alkioille.
▼ Tämä voidaan välttää nk. lisäysiteraattorien avulla:list<double> lukulista;· · ·vector<double> lukuvektori;copy(lukulista.begin( ), lukulista.end( ),
back_inserter(lukuvektori) );
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 303▼ Jolloin kaikki tallennukset lisäävät uuden alkion
lukuvektorin loppuun ➠ lukuvektorin koko kasvaaniin monella alkiolla kuin mitä copy kopioi.
▼ Tämä toimii myös, jos kohdesäiliössä oli alkioita joennestään.
▼ Kahden säiliön sisältämät alkiot voidaan siis yhdistääkolmanteen:
▼ Tämä ei ole ainoa eikä välttämättä paraskaantapa yhdistää säiliöitä, mutta havainnollistaalisäysiteraattoria mukavasti.
▼ On myös olemassa front_inserter-funktio, jokapalauttaa säiliön alkuun lisäykset suorittavaniteraattorin ➠ lisättyjen alkioiden järjestys muuttuupäinvastaiseksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 304▼ 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(sanajoukko.begin( ), sanajoukko.end( ),
TulostaYksiString);
▼ Huomaa, että for_each:ille on annettu para-metrina TulostaYksiString-funktio, ei suinkaanTulostaYksiString-funktion paluuarvo.
▼ Kyseessä on nk. funktioparametri.
▼ TulostaYksiString-funktio pitää määritellä seuraa-vasti, jotta sen voisi antaa for_each:ille parametrina:
int MerkkijononPituus(const string& str) {return str.length( );
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 306▼ Edellinen koodi siis kuvaisi MerkkijononPituus-
funktion avulla sanat-listassa olevat merkkijonotniiden pituuksiksi ja tallettaisi saadut pituudetsamassa järjestyksessä pituudet-vektoriin.
▼ transform-algoritmin kanssa käytettävän kuvaus-funktion 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 onversio, jonka toimintaa voidaan säätää funktiopara-metrin avulla.
▼ Esimerkiksi count-funktiosta on olemassa versiocount_if, joka laskee vain tietyn ehdon täyttävienalkioiden lukumäärän:
set<int> joukko;· · ·// Montako paritonta luku on joukossaint parittomia = count_if( joukko.begin( ), joukko.end( ),
OnkoPariton);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 307▼ 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 valintafunktioon käytettävissä, sen täytyy noudattaa seuraaviasääntöjä:– Paluuarvon tyyppi on bool.– Parametrin tyyppi on sama kuin operoitavan
säiliön alkioiden tyyppi (voi olla myös const-viitekyseiseen tyyppiin).
– Paluuarvo true, jos parametrina saatuun alkioonhalutaan kohdistaa algoritmifunktion mukainentoimenpide, muutoin paluuarvo false.
▼ Ainakin seuraavat valintafunktiota hyödyntävätalgoritmit 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 STL-
algoritmin toimintaa ovat vertailufunktiot: funktiotjotka vertailevat kahden alkion suuruutta.
▼ Esimerkiksi sort-funktiolle voi antaa lisäparametrin,joka kertoo, kuinka alkioiden suuruutta pitäisi tulkita:
OHJ-1150 Ohjelmointi II 310▼ Nyt STL-algoritmien perusajatusten pitäisi olla
hallussa ➠
– Hiukan lisäinformaatiota löytyy tutusta osoitteesta:http://www.cs.tut.fi/~aps/doc/cppref.pdf.
– Hyvin yksityiskohtainen ja melko selkeä kuvausC++-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 useinniin, että tyyppien nimistä muodostuu pitkiä,vaikeaselkoisia ja/tai työläitä kirjoittaa.
▼ C++:ssa on mekanismi, joilla ohjelmoija voi antaatietotyypeille uusia nimiä, jotka käyttäytyvätidenttisesti alkuperäisen tyypin kanssa.
▼ Ajatellaan seuraavaa määrittelyä:typedef unsigned long int ulong;
▼ Mitä tuossa tapahtuu on se, että tietotyypilleunsigned long int annetaan uusi nimi ulong.
▼ Tämän jälkeen C++ ei näe mitään eroa sillä, kumpaatyyppinimeä 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 muuttujantyyppinä on lisänimeä kaipaava (alkuperäinen)tietotyyppi:
unsigned long int ulong;
2. Lisätään edellisen muuttujanmäärittelyrivin eteenvarattu sana typedef, jolloin määrittelyn merkitysmuuttuu 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 ohjelmaasilloin, kun käytettyjen tietotyyppien nimet ovatsyystä tai toisesta hankalia.
▼ Erityisen hyvä esimerkki tästä ovat STL-säiliötja -iteraattorit.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 313▼ Ajatellaan seuraavia tuttuja STL-määrittelyitä:
▼ Jos muuttuja alustetaan määrittelyvaiheessa jollainarvolla, kääntäjä osaa haluttaessa määritellämuuttujan tyyppiksi saman kuin alustuslausekkeentyyppi on.
▼ Tämä tapahtuu merkitsemällä muuttujan tyypiksivarattu sana auto:
auto tulos = 34.5 * a;
▼ Kääntäjää huomaa nyt, että alustuslausekkeen34.5 * a arvo on tyypiltään double, jolloinmuuttujan tulos tyypiksi tulee myös double.
▼ Aivan sama olisi saatu aikaan myös kirjoittamallaperinteisemmin:
double tulos = 34.5 * a;
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden olemassaolosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 315▼ Varsinainen hyöty saavutetaan silloin, kun määritel-
lään muuttujia, joiden tyypit ovat monimutkaisia taimuuten vain pitkiä ja työläitä kirjoittaa toistuvasti.
▼ Vertaa esimerkiksi seuraavia täsmälleen samatlopputulokset tuottavia määrittelyitä:
▼ Myös for-silmukan silmukkamuuttujan tyyppivoidaan määritellä vastaavasti:
for ( auto iter = m.begin( ); iter = m.end( ); ++iter ) {...
}
▼ Muuttujan tyypin määräytyminen alustuslausekkeentyypin perusteella on mitä ilmeisimmin elämäähelpottava apuväline.
▼ Siitä ei kuitenkaan kannata tulla liian riippuvaiseksi,koska jossain tilanteissa alkuperäisiä tyyppejä onpakko käyttää (esim. funktioiden muodollistenparametrien määrittely).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 316
Abstraktiot
▼ Sivistyssanastosta➀ voi löytää seuraavat määritelmät:
abstrakti
ajatuksessa eristetty, semmoisenaan katsottu taiajateltu, käsitteellinen.
abstraktio
epäolennaisten ainesten erottaminen ajatuksessaitse oliosta, käsitteenmuodostus (tai sen tulos),yleistys, käsite.
▼ Määritelmissä ei sinänsä ole mitään vikaa, ne ovatvain liian yleisellä tasolla esitettyjä,➁ jotta niitä voisiymmärtää ilman esimerkkejä.
▼ Tutkitaan muutama esimerkki sekä elävästä elämästäettä tietokoneohjelmoinnin maailmasta.
WSOY, 1972, ISBN 951–0–01686–1.➁ Siis liian abstrakteja!
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 317Puhutun kielen sana auto
▼ Sana auto tarkoittaa laitetta, jolla pääsee nopeastipaikasta toiseen.
▼ Jokainen ymmärtää tuon merkityksen ja pystyykeskustelemaan autoista, vaikkei hänellä olisikaanymmärrystä auton todellisesta luonteesta:esim. kuinka polttomoottori tai vaihdelaatikkotoimivat, saatika sitten kuinka suunnitella jarakentaa auto omin käsin.
▼ Auto on siis abstraktio, joka ihmisten ajatusmaa-ilmassa kuvaa erittäin monimutkaisen laitteenniin, että sitä voi ajatella ja siitä voi keskustellatakertumatta epäolennaisiin yksityiskohtiin.
▼ Itse asiassa kaikki puhutun kielen sanat ovatabstraktioita aivan samoilla perusteilla.
▼ Toki samasta asiasta voi olla eri tasoisia abstrak-tioita (abstraktiotasoja) vrt. Risto Rikkaan,Antti Autoilijan, Matti Mekaanikon ja SeppoSuunnittelijan näkemys autosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 318Abstraktiot ohjelmoinnissa
▼ Kaikki tieto, jota tietokoneohjelmissa käsitellään,on abstraktia.
▼ Esimerkiksi tietotyyppi int.
▼ Paitsi että kokonaisluvut itsessään ovat abstrak-tioita, niin niiden esitys ohjelmointikielessä (siis int)on sitä myös: int:in toteutuksesta konekieli- taipuolijohdetasolla ei ole mitään varmuutta.
▼ Kuitenkin int-tyyppiä voidaan käyttää, kunymmärretään sen käyttäytyvän kuin kokonaisluku.
▼ Usein puhutaan tietoabstraktiosta (data
abstraction).
▼ Ohjelmointiin liittyy myös toinen abstraktio-käsite: toiminnallinen abstraktio (functional
abstraction).
▼ Hyvä esimerkki toiminnallisesta abstraktiosta ovatfunktiot ja aliohjelmat.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 319▼ Funktio tai aliohjelma on sarjalle toimintoja
annettu abstrakti nimi.
▼ Esimerkiksi, kun on päätetty, että luvun itseisarvosaadaan funktiolla
double Itseisarvo(double d)
niin sen jälkeen on yhdentekevää, kuinka funktioon toteutettu, kunhan se tuottaa itseisarvonmää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? (vrt.s.3)
▼ Uusien abstraktioiden opettamistakääntäjälle/koneelle, jotta ohjelmoijan olisihelpompi kuvata halutun ongelman ratkaisumuuten niin suppealla ohjelmointikielellä.
▼ Tosiasiassa abstraktioiden parissa siis painittiinjo koko Ohjelmointi I -kurssi, vaikka asiaa ei näinsyvällisesti ja filosofis–metafyysisesti pohdittukaan.
▼ Kun ohjelmoinnin yhteydessä puhutaan abstrak-tiosta, niin tarkoitetaan toteutuksen ja käytön
erottamista toisistaan:
– tietotyyppejä voi käyttää ja ne toimivatodotetusti ja
– funktioita ja aliohjelmia voi kutsua jane suorittavat tehtävänsa oikein,
vaikka toteutuksen yksityiskohdista ei tiedettäisimitään.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 321
Abstraktit tietotyypit
▼ Abstraktit tietotyypit ovat ohjelmoijan määrit-telemiä tietotyyppejä, joissa tietoisesti pyritäänsoveltamaan »toteutuksen ja käytön erottelu»-peri-aatetta.
▼ 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 (taitarkoituksella) käyttää valitun toteutuksenominaisuuksia hyväkseen (tiedon kätkentä,data/information hiding).
2. Ohjelmoija voi kuitenkin hyödyntää abstraktiatietotyyppiä, sillä sen alkuperäisen toteuttaja onkirjoittanut 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 levytiedostotC++-ohjelmasta käytettävässä muodossa.
2. Mukana rajapintafunktiot, joiden avulla fstream-tyyppiä käytetään: getline, peek, ignore ja montamuuta.
▼ Rajapinnan funktiot kuvaavat abstraktin tietotyypinolennaiset ominaisuudet sillä abstraktiotasolla, jollatyyppiä on tarkoitus käyttää.
▼ Usein rajapinnan funktioita kutsutaan myösmetodeiksi tai jäsenfunktioiksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 323
Yksinkertainen esimerkki
▼ Suunnitellaan ensimmäinen abstrakti tietotyyp-pimme Vektori, mutta ei oteta vielä kantaa sentoteutukseen: katsotaan millainen sen rajapinta onja miten sitä käytettäisiin ohjelmassa.
▼ Vektori on kaksiulotteisen avaruuden vektori, jotaon tarkoitus käyttää seuraavasti:
– ohjelmassa voi olla Vektori-tyyppisiä muuttujia,
– vektorin arvo (double-tyyppiset x- ja y-koordi-naatti) voidaan lukea käyttäjältä näppäimistöltä,
– vektorista on mahdollista saada selville sen x- jay-koordinaatit,
– vektoreita voi tulostaa näytölle x–y-koordinaatti-tai polaarikoordinaattimuodossa ja
– vektoreita voidaan kiertää origon ympäri halutunkulman verran myötäpäivään.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 324▼ Pieni pääohjelma joka käyttää Vektori-tietotyyppiä:
void Vektori::tulosta_polaarimuodossa( ) const {// Tämä funktio ei tosiasiassa toimi aivan oikein:// jos esimerkiksi toinen koordinaatti on negatiivinen// ja toinen positiivinen, niin kulma saattaa olla// väärässä neljänneksessä. Ei vaikea korjata,// mutta esimerkin kannalta epäoleellinen.double r = sqrt(pow(x, 2) + pow(y, 2) );double a = atan(y / x) * 180 / PII;
cout << " (a, r) = ( " << a << "◦, " << r << " ) "<< endl;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 330
Luokat
▼ C++-kielen tarjoama työkalu abstraktien tietotyyppientoteuttamiseen on luokka (class).
▼ Luokat esitellään varatun sanan class avulla:class luokan_nimi {public:
...rajapinnan funktioiden esittelyt
...private:
...kätkettyjen tietoalkioiden esittelyt
...} ;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 331▼ Luokan esittelyn seurauksena käyttöön saadaan
uusi abstrakti tietotyyppi luokan_nimi, jolla voidaanoperoida vain rajapinnan funktioiden avulla.
▼ Tyypin toteutus määräytyy muuttujista kätketyt
tietoalkiot, joita ei ohjelmassa voi käyttää suoraan,mutta rajapinnan funktiot pääsevät niihin käsiksi.
▼ Rakentajaa✶ lukuunottamatta kaikkia luokanjäsenfunktioita kutsutaan käyttäen merkintätapaa:
muuttuja . funktio(parametrit ) ;
▼ Luokkaa voi jossain mielessä ajatella tietueena(struct), jonka kenttiin ei pääsekään käsiksi"."-operaattorilla, vaan on käytettävä luokanmukana tulevia rajapinnan funktioita.
▼ Todellisuudessa luokat ovat paljon enemmän, muttaaloittelijan kannalta tuo on hyvä analogia.
▼ Tutustutaan tarkemmin sekä luokan esittelynprivate-osaan että public-osaan ja niihin liittyviinyksityiskohtiin.
✶ ja sivulla 335 mainittua automaattista purkajaa
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 332
Luokan esittelyn private-osa
▼ Yleensä private-osassa esitellään muuttujat, joihinon tarkoitus tallettaa tietotyypin alkion kuvaava tieto(jäsenmuuttujat tai attribuutit).
▼ Esimerkiksi kaksiulotteisen avaruuden vektori voitiinesittää kahdella reaaliluvulla: sen x- ja y-koordinaatilla.
▼ Luokan käyttäjän kannalta private-osan sisällöllä eikuitenkaan ole merkitystä niin kauan kuin rajapinnanfunktiot toimivat odotusten mukaisesti.
▼ Vektori-tyyppi olisi voitu yhtä hyvin toteuttaa siten,että private-osassa olisikin kirjoitettu:
private:double kulma; // vektorin suuntakulmadouble pituus; // ja sen pituus
taiprivate:
double x_ja_y[2]; // koordinaatit taulukossa
▼ Kun jäsenfunktiot muutetaan uuden toteutuksenkanssa yhdenmukaisiksi, ei käyttäjä huomaa mitään.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 333▼ Jäsenmuuttujien tärkein ominaisuus on se, että ne
ovat käytettävissä jäsenfunktioissa ➠ voidaankirjoittaa funktioita, jotka pystyvät operoimaankätketyllä informaatiolla.
▼ Jäsenmuuttujien määrä ei ole mitenkään rajoitettu.
▼ Jäsenmuuttujat voivat olla tyypiltään mitä tahansakääntäjän tuntemaa tyyppiä, vaikka toisia luokkia.
▼ Ei ole yksikäsitteisesti oikeaa tapaa valita jonkinabstraktin tietotyypin jäsenmuuttujat (vrt. Vektori-tyypille keksityt kolme erilaista).
▼ Periaatteessa private-osassa voi esitellä myösjäsenfunktioita ➠ yksityisen rajapinnan funktiot,joita voivat kutsua vain muut jäsenfunktiot.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 334
Luokan esittelyn public-osa
▼ Luokan public-osassa saa olla vain rajapinnanfunktioiden esittelyitä.
▼ Jäsenfunktioiden esittelyssä vaihtoehdot ovat:
Rakentajat
– Oltava samanniminen kuin luokka, jonkarakentaja se on.
– Rakentajalla ei ole paluuarvoa, eikä sitenpaluuarvon tyyppiä.
– Parametreja voi olla aivan normaalisti.
– Kutsutaan automaattisesti aina, kun ohjelmassaluodaan abstraktin tietotyypin muuttuja.
Valitsimet
– Esitellään kuten funktiot yleensä, muttaesittelyn perässä varattu sana const ➠ kertooettei funktio muuta jäsenmuuttujien arvoja ➠voidaan kutsua const-vakioilla.
– Valitsimilla voi olla parametreja ja paluuarvo.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 335Mutaattorit
– Esitellään kuten normaalit funktiot.
– Voivat modifioida jäsenmuuttujien arvoja
➠ ei voi kutsua const-vakioilla.
– Mutaattoreilla voi olla parametreja ja paluuarvo.
Purkajat
– Esimerkkiohjelmassa ei käytetty purkajia.
– C++:ssa on rakentajien kanssa analoginenmekanismi, jonka avulla purkajia kutsutaanautomaattisesti, kun abstraktin tietotyypinmuuttujan elinkaari päättyy.
– Purkajiin palataan kurssin loppupuolelladynaamisten tietorakenteiden yhteydessä ➠sitä ennen niille ei ole käyttöä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 336
Jäsenfunktioiden määrittely
▼ Jäsenfunktiot määritellään muutamaa poikkeustalukuunottamatta kuten funktiot yleensäkin ottaen.
▼ Funktioiden nimen edessä pitää kuitenkin kertoa,minkä abstraktin tietotyypin jäsenfunktiosta on kyse:
bool Vektori::lue_nappaimistolta( ) {...
}
▼ Valitsimissa pitää olla mukana const parametrilistanperässä:
double Vektori::hae_x_koord( ) const {...
}
▼ Funktion runko saa olla normaaliin tapaan mitä vainsuoritettavaa C++-koodia.
▼ Jäsenmuuttujat ovat käytettävissä jäsenfunktioidenrungossa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 337▼ Jos rakentajan halutaan alustavan jäsenmuuttujia,
niin se saadaan aikaan alustuslistan avulla:
Vektori::Vektori( ) : x(1.0), y(0.0) {}
▼ Eli jäsenmuuttujat, joille halutaan automaattisestiantaa alkuarvo, luetellaan parametrilistan perässäpilkuilla eroteltuina ja tarvittavat alkuarvot kerrottaansuluissa muuttujan nimen jälkeen.
▼ Rakentajalla voi olla parametreja; niistä esimerkkejämyöhemmin.
▼ Rakentajan rungossa voi olla käskyjä, mutta perus-tellusta syystä tätä ominaisuutta ei Ohjelmointi II:ssajuurikaan hyödynnetä (paitsi ehkä muutamassakohdassa).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 338
Sama esimerkki toisin
▼ Toteutetaan Vektori-tietotyyppi uudelleen havain-nollistaen sen avulla muutama olennainen asia(muutokset alkuperäiseen sävytettynä):
cout << " (a, r) = ( " << a << ", " << r << " ) "<< endl;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 342
Siitä opittua
▼ Abstraktin tietotyypin jäsenfunktiot voivat kutsuatoisiaan.
▼ Tällöin ei käytetä pistenotaatiota, vaan funktiotakutsutaan »normaaliin» tapaan:
funktio(parametrit ) ;
▼ Tälläkin tavoin kutsuttu jäsenfunktio kohdistuukuitenkin samaan abstraktin tietotyypin muuttujaan,jonka välityksella alkuperäistä jäsenfunktiota
kutsuttiin:muuttuja . jäsenfunktio(parametrit ) ;
▼ Olennaisempi asia esimerkissä oli yksinkertainenluokan sisäinen tiedon kätkentä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 343
Toteutuksen »kätkeminen»
luokan sisällä
▼ Yksi abstraktien tietotyyppien tärkeistä ominaisuuk-sista on se, että tyypin toteutusta voidaan tarvittaessamuuttaa, kunhan rajapinta pysyy ennallaan.✶
▼ Muutosten tekemiseen tulee tarve esimerkiksihaluttaessa toteuttaa tyyppi tehokkaammin.
▼ Muutostyö on sitä helpompaa, mitä suurempiosa jäsenfunktioiden toteutuksesta voidaan eristäävarsinaisesta kätketystä tiedosta.
▼ Eli käytännössä: mitä suurempi osa jäsenfunktioistavoidaan toteuttaa pelkästään toisten jäsenfunktioidenavulla.
▼ Tämä käy esimerkkien tapauksessa paremmin selvillekuvallisesta esityksestä.
✶ Tai ainakin yhteensopivana alkuperäisen rajapinnan kanssa, jos siihenvaikka uusia jäsenfunktioita lisättäisiinkin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 344▼ Alkuperäisessä Vektori-luokan toteutuksessa kaikki
jäsenfunktiot käsittelivät suoraan jäsenmuuttujia:
x yhae_x_koord hae_y_koord
lue_nappaimistolta kierra_origon_ympari
tulosta_xy_muodossatulosta_polaarimuodossa
▼ Jos luokan kätkettyä toteutusta (x ja y) nytmuutetaan, niin kaikkia kuutta jäsenfunktiotajoudutaan myös säätämään.
▼ Jos jäsenfunktioita olisi enemmän, niin tehtävienmuutosten määrä kasvaisi tietenkin samassasuhteessa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 345▼ Uudemmassa versiossa ainoastaan hae- ja aseta-
funktiot käsittelevät x:ää ja y:tä suoraan.
▼ Muut jäsenfunkiot hoitavat työnsä niiden avulla:
x y
hae_x_koord hae_y_koord
aseta_x_koord aseta_y_koord
lue_nappaimistolta kierra_origon_ympari
tulosta_xy_muodossatulosta_polaarimuodossa
▼ Jos kätkettyä toteutusta nyt muutetaan, niinainoastaan hae- ja aseta-alkuisia jäsenfunktioita(4 kpl) pitää säätää, vaikka muita jäsenfunktioita olisienemmänkin, kunhan ne vain on toteutettu samanidean mukaisesti.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 346▼ Tällaisella luokan sisällä tapahtuvalla toteutuksen
kätkemisellä saavutetaan tietotyypille selkeämpirakenne ja helpompi ylläpidettävyys.
▼ Abstraktiorajat kuvaavat tietotyypin jollain abstrak-tiotasolla ➠ mitä sisempi raja, sitä enemmän seottaa kantaa toteutuksen yksityiskohtiin.
▼ Tai kääntäen, mitä ulompi abstraktioraja, sitäabstraktimpana käsitteenä se tietotyypin esittää.
▼ Abstraktiorajojen rikkomista✶ pidetään yleisestikuolemansyntinä ja se on usein vihje huonostisuunnittelusta tyypistä/rajapinnasta.
▼ Yleensä kannattaa pyrkiä siihen, että tietoa käsitelläänmahdollisimman korkealla abstraktiotasolla ➠ mitäsisempi abstraktioraja, sitä vähemmän sen läpimenee riippuvuutta kuvaavia nuolia.
✶ Kutsutaan funktiota, joka kahta tai useampaa abstraktiotasoa kauempana.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 347
Mistä abstraktit tietotyypit tulevat?
▼ Aina kun ohjelmassa olisi tarkoituksenmukaistakäyttää rakenteista tietotyyppiä (taulukko tai tietue),se voidaan muuttaa abstraktiksi tietotyypiksi.
▼ Ohjelmissaan kannattaa lähes aina suosia abstraktiatietotyyppiä yli rakenteisen tietotyypin, vaikka sentoteuttamisessa onkin enemmän työtä.
▼ Jos rakenteinen muuttuja kuitenkin on vakio ja sitäkäytetään vain yhdessä kohdassa koko ohjelmassa,niin abstrakti tietotyyppi saattaisi olla yliampumista.
▼ Esimerkiksi seuraava pikku funktio on järkevätoteuttaa pelkän taulukon avulla:
enum Paiva { SU, MA, TI, KE, TO, PE, LA, PAIVAVIRHE };...
for (int i = 0; i <= LA; ++i) {if ( lyhenne == LYHENTEET[i] ) {
return Paiva(i) ;}
}
return PAIVAVIRHE;} ▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 348
Abstraktien tietotyyppien hyödyt
▼ Abstraktien tietotyyppien suunnitteluun ja toteut-tamiseen liittyy jonkin verran enemmän työtä, kuinabstraktiorajoista piittaamattomaan rakenteistentietotyyppien käyttöön.
▼ Ne ovat kuitenkin ylimääräisen vaivan arvoisia:
– Mahdollistavat toteutuksen muuttamisen.
– Voidaan olla varmoja tiedon eheydestä:konstruktorit ja mutaattorit voivat huolehtia, ettäjäsenmuuttujat saavat vain laillisia arvoja.
– Mahdollistavat yhteenkuuluvien ohjelmistonosien esittamisen kokonaisuutena, mikä yleensäjohtaa ohjelmiston selkeämpään rakenteeseen,helpompaan testaamiseen ja ylläpidettävyyteen.
– Ovat helppokäyttöisempiä ja helpomminymmärrettäviä, koska muistettavia yksityiskohtiaon vähemmän.
– Abstraktit tietotyypit ovat usein uudelleenkäytet-
täviä (reusable).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 349
Funktioiden kuormittaminen
▼ Funktion kuormittamisella (overloading) tarkoi-tetaan sitä, että ohjelmassa voi olla useita samannimisiä funktioita, kunhan niiden parametrit ovatmäärältään ja/tai tyypiltään erilaisia.
▼ Kun ohjelmassa kutsutaan funktiota, joka onkuormitettu, kääntäjä tutkii kutsun todellistenparametrien määrän ja tyypit ja valitsee niidenpohjalta kutsuttavan funktion oikean version.
▼ Funktioiden kuormittaminen on arvokas työkalu,jos ohjelmassa on tarve suorittaa loogisesti samaoperaatio erityyppisille lähtötiedoille.
▼ Ajatellaan esimerkkinä operaatiota max, jolla ontarkoitus selvittää jonkin alkiojoukon suurin alkio.
▼ Lisäksi suunniteluvaiheessa on havaittu, ettätoteutuksessa täytyy välillä selvittää, mikä onkahdesta kokonaisluvusta suurin, ja välillä taas, mikäon kokonaislukutaulukon suurin alkio.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 350▼ Molemmat tapaukset ovat loogisesti suurimman
alkion valitsemista, mutta ne valitsevat alkionerityyppisistä lähtötiedoista.
▼ Jos kieli ei tukisi funktioiden kuormittamista, jäisiohjelmoijan vastuulle määritellä kaksi erinimistä
funktiota ja kutsua niitä oikeissa paikoissa:
#include <iostream>
using namespace std;
int max_kahdesta(int luku1, int luku2);int max_taulukosta( int taulukko[ ], int maara);
int taulu[ ] = { 99, 20, 78, 64, 1001, 721 };const int koko = 6;cout << "Taulukon suurin alkio on "
<< max(taulu, koko) << endl;
}
// max–funktioiden määrittely tähän.
// Katso seuraavalta sivulta...
▼ Huomaa kuinka molemmat versiot ovat nytnimeltään max, mutta muodollisten parametrientyypeissä on eroja.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 352▼ Funktioiden toteutus olisi aivan normaali, olkoonkin
että niillä sattuu olemaan samat nimet:
int max(int luku1, int luku2) {if ( luku1 > luku2) {
return luku1;} else {
return luku2;}
}
int max(int taulukko[ ], int maara) {int suurin = taulukko[0];for (int i = 1; i < maara; i++) {
if (suurin < taulukko[i] ) {suurin = taulukko[i];
}}return suurin;
}
▼ Erilainen paluuarvon tyyppi ei tee esittelyltäänmuuten identtisistä funktioista kuormitettuja ➠seuraava yritys tuottaa käännösvirheen:
unsigned int max(int luku1, int luku2);int max(int luku1, int luku2); // VIRHE!
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 353
Funktion oletusparametrit
▼ Osa tai kaikki funktion parametrit voivat ollaoletusparametreja (default arguments) ➠ josohjelmoija ei funktiota kutsuessaan anna kaikkiatodellisia parametreja, C++ käyttää puuttuvilleparametreille automaattisesti oletusarvoja.
▼ Tutkitaan hieman naiivi esimerkki: oletetaan ettähalutaan tehdä funktio max, jolle voidaan antaaparametrina 2–4 luonnollista lukua, joista sepalauttaa suurimman.
▼ Ongelmaa voisi lähestyä kuormittamalla ➠määriteltäisiin funktiosta max kolme eri versiota:yksi kutakin tilannetta kohti.
▼ Kuormittaminen ei kuitenkaan ole tässä tilanteessahyvä ratkaisu, koska se johtaisi useaan tietyssämielessä identtiseen funktioon.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 354▼ Seuraava esimerkki havainnolistaa max-funktion
if (u2 > suurin) { suurin = u2; }if (u3 > suurin) { suurin = u3; }if (u4 > suurin) { suurin = u4; }
return suurin;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 355▼ Oletusparametrien arvot kerrotaan funktion
esittelyssä kirjoittamalla muodollisen parametrinperään "=" ja parametrin oletusarvo.
▼ Esimerkin funktiolla max on neljä parametria, muttakutsuttaessa ei välttämättä tarvitse antaa kuin kaksi.
▼ Jos kutsussa ei ole u3:a ja u4:ää vastaavia todellisiaparametreja, niiden tilalla käytetään max:inesittelyssä kerrottuja oletusarvoja (0 ja 0).
▼ Oletusarvoja voi antaa vain loppupään parametreille.▼ Tarvittaessa kaikilla parametreilla voi olla oletusarvo.▼ Oletusparametri voi olla myös lauseke ➠ se evaluoi-
daan uudelleen joka kerta funktiota kutsuttaessa(mikäli oletusparametria tarvitaan).
▼ Kuormittaminen ja oletusparametrit ovat useinkeskenään vaihtoehtoisia ➠ kannattaa harkitatarkkaan kumpi niistä soveltuu tilanteeseen paremin.
▼ Kuormitus ja oletusparametrit voivat toki ollakäytössä yhtä aikaa:
int max(int l1, int l2, int l3 = INT_MIN);int max(int taulukko[ ], int maara);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 356
Rakentajan parametrit
▼ Koska abstraktin tietotyypin rakentaja on periaat-teessa funktio muiden joukossa, voi silläkin ollaparametreja, sitä voi kuormittaa ja parametreilla voiolla oletusarvoja.
▼ Ajatellaan esimerkkinä luokkaa:
class Murtoluku {public:
Murtoluku( );Murtoluku(int os, int nim = 1);· · · muut jäsenfunktiot tässä · · ·
▼ Eksplisiittistä rakentajan kutsua voi käyttää, jostarvitaan abstraktia tietotyyppiä olevaa »literaalia»
➠ vältytään turhien apumuuttujien käytöltä.
✶ Tämä on tarkasti ottaen virheellinen ilmaisu. Kyseessä on nimettömänmuuttujan luonti ja alustus. "Rakentajan eksplisiittinen kutsu" on kuitenkintässä vaiheessa niin havainnollinen ilmaisu, että se suotaneen anteeksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 359▼ Rakentajan eksplisiittisen kutsun arvoksi evaluoituu
siis abstraktin tietotyypin alkio, joka on alustettuparametrien mukaan sopivaa rakentajaa kutsumalla.
▼ Rakentajan eksplisiittisen kutsun voi tulkita myöstyyppikonversioksi (erityisesti siinä tilanteessa, kunkutsussa annetaan vain yksi parametri).
▼ Esimerkiksi seuraavassa koodissa
int kokonaisluku;Murtoluku murtoluku;
...murtoluku = Murtoluku(kokonaisluku);
rakentajan kutsu muuntaa muuttujan kokonaisluku
arvon vastaavaksi murtoluvuksi kokonaisluku1
.
▼ Myös string-tyypin ja STL-säiliöiden rakentajia voikutsua eksplisiittisesti:
▼ Tosiasiassa monissa ohjelmointikielissä raja ohjelmankäsittelemän tiedon ja funktioiden välillä on hyvinepämääräinen tai on kadonnut tyystin.
▼ Funktiotkin ajatellaan tiedoksi, jota voidaan tallentaamuuttujiin, välittää toisille funktioille parametrina janiin edelleen.
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden olemassaolosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 361▼ Ohjelmointi I:ssä opittiin joskus käsitteet literaali ja
muuttuja.
▼ Literaali on tietoalkio, jolla on tyyppi ja arvo, muttaei nimeä (1234, "abc").
▼ Muuttuja on tietoalkio, jolla on tyyppi, arvo ja nimi(tulos, i, sukunimi).
▼ Funktioiden yhteydessä voidaan samaa logiikkanoudattaen puhua nimetyistä ja nimettömistäfunktioista.
▼ Nimetyissä funktioissa ei ole mitään uutta: ne ovatniitä samoja tuttuja funktioita, joita on käytetty jaosattu itsekin toteuttaa jo Ohjelmointi I:stä lähtien.
▼ Nimettömät funktiot eli lambda-funktiot ovat erään-laisia funktioliteraaleja: koko funktio määriteltynäjuuri siihen kohtaan koodia, jossa sitä tarvitaan(ilman nimeä).
▼ Ohjelmointi II-kurssin näkökulmasta nimettömienfunktioiden käyttökelpoisuus tulee siitä, että niitä voiantaa parametreina toisille funktioille.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 362▼ Käytännössä tämä tarkoittaa STL-algorithm-kirjaston
funktioita, joiden käyttäytymistä voidaan ohjatafunktioparametrin avulla.
▼ Esimerkiksi funktio sort, jolle voidaan kahdeniteraattorin lisäksi antaa parametrina vertailufunktio.
}· · ·vector<string> svec;· · ·// Lajitellaan laskevaan järjestykseen:// suurin ensin, pienin viimeiseksi.sort(svec.begin( ), svec.end( ), suurempi);
▼ Edellisessä toteutuksessa ei ole mitään vikaa, muttajos suurempi-funktiota ei käytetä ohjelmassamihinkään muuhun, sen määrittely erillisenäfunktiona saattaa tuntua turhalta.
▼ Ainoa syy, miksi esimerkin vertailufunktiolle onannettu nimi (suurempi) on, jotta se voidaankirjoittaa sort-funktion viimeiseksi parametriksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 363▼ Esimerkissä käytetyn kaltaiselle «kertakäyttö-
funktiolle» ei ole välttämätöntä antaa nimeä, vaansama voitaisiin toteuttaa lambda-funktiona:
▼ Edellä esitetty riittää kuitenkin, jos ainoa mitä niidenavulla haluaa toteuttaa, on «kertakäyttöfunktiot»STL-algoritmien parametreiksi.
▼ Monissa muissakin ohjelmointikielissä✶ lambda-funktiot (tai lambda-lausekkeet) ovat käytettävissäosana kielen työkalupakkia.
▼ Muissa kielissä lambda-funktioiden syntaksi on useinselkeämpi, koska ne ovat olleet mukana kielensuunnittelussa alusta alkaen.
▼ Yleissivistävänä asiana tästä kappaleesta olisi hyväjäädä mieleen, että lambda-funktiot mahdollistavat
funktioiden käsittelyn tietoalkioina ➠ muuttujiintalletus, parametrina välittäminen, funktionpaluuarvo ja niin edelleen.
✶ Aivan erityisesti jokseenkin kaikissa tulkattavissa kielissä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 365
main-funktion parametrit
▼ Ajatellaan ohjelmaa akro, jolle voitaisiin käynnis-tysvaiheessa syöttää komentoriviltä sanoja, joidenalkukirjaimista se muodostaisi akronyymin:
proffa> ./akro Central Intelligence AgencyCIAproffa> ./akro Adaptive Radio Sampling EquipmentARSEproffa>
▼ Komentorivillä lueteltu tieto saadaan C++:ssaohjelman käyttöön main-funktiolle sopivastimääriteltyjen muodollisten parametrien välityksellä:
int main(int argc, char* argv[ ] ) {...
}
▼ Parametri argc kertoo komentorivillä annettujensanojen määrän (ohjelman nimi mukaanlukien) ➠argc on aina vähintään yksi.
▼ Parametri argv on taulukko, jonka alkiot sisältävätjärjestyksessä kaikki komentorivillä annetut sanat.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 366▼ Alkiossa argv[0] on ohjelman nimi.
▼ argv:n alkiot ovat C-kielestä periytyvää merkkijo-notyyppiä (char*), eivät kurssilla tutuksi tulluttastring-tyyppiä ➠ jos ja kun ohjelma haluaa käsitelläargv:n alkiota string-tyyppisinä, tyyppimuunnos onohjelmoijan vastuulla.
▼ Esimerkiksi tilanteessa:
proffa> ./akro Central Intelligence Agency
main:in muodollisissa parametreissa on arvot:
– argc:ssä sanojen määrä 4 ja
– argv sisältää merkkijonot:
./akro
Central
Intelligence
Agency
argv[0]
argv[1]
argv[2]
argv[3]
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 367▼ Akronyymiohjelman toteutus näyttää seuravalta:
#include <iostream>
#include <cstdlib>
#include <string>
using namespace std;
int main(int argc, char* argv[ ] ) {// Onko komentorivillä muuta kuin komennon nimi?if (argc > 1) {
// Silmukka lähtee liikkelle 1:stä, koska
// ohjelman nimeä ei ole tarkoitus käsitellä.for (int i = 1; i < argc; ++i) {
// Alkiossa argv[i] C–merkkijonona oleva
// komentorivin i:s sana muutetaan
// string–tyyppiseksi eksplisiitisellä
// tyyppikonversiolla string(... ).string sana = string(argv[i] ) ;
/ / Tulostetaan sanan ensimmäinen kirjain,
// sillä se on mukana akronyymissä.cout << sana.at(0);
}cout << endl;
}return EXIT_SUCCESS;
}
▼ Olennaiset asiat kommentteina ohjelmakoodissa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 368▼ Perinteisesti main:in muodollisia parametreja
kutsutaan argc:ksi ja argv:ksi,✶ mutta todellisuudessanimet voivat olla mitä tahansa, kunhan niidenjärjestys ja tyypit ovat oikein (int ja char*[ ]).
▼ Kannattaa vielä kerran korostaa, että main:inargumentit ovat C-merkkijonoja ➠ jos niidensisältämää informaatiota tarvitaan ohjelmassa jonkinmuun tyyppisenä (string, int, double jne) ontyyppimuunnos ohjelmoijan vastuulla.
▼ Lopuksi vielä yksityiskohta, jolla ei varsinaisesti olemitään tekemistä C++:n kanssa, mutta siihen törmääusein käytännön tilanteissa.
▼ Jos UNIX-komentotulkille syöttää käskyn:
proffa> ./etaisyys Boston Los Angeles
niin main-funktio saa argv:ssä neljä sanaa.
▼ Jos halutaan, että Los Angeles tulkitaan yhdeksisanaksi, se on kirjoitettava lainausmerkkien sisään:
proffa> ./etaisyys Boston "Los Angeles"
✶ Lyhenteitä ilmaisuista argument count ja argument vector.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 369▼ Jos char* -tyyppisten C-merkkijonojen käsittely
tuntuu epämiellyttävältä, voi argv-taulukonmuuntaa helposti STL-vektoriksi:
vector<string> argvec(argv, argv+argc);
▼ Tuloksena saadaan merkkijonovektori argvec, jossaon kaikki argv:n sanat samassa järjestyksessä.
▼ Edellinen alustus perustuu siihen, että
– STL-säiliö voidaan alustaa jossain toisessa säiliössäiteraattorivälillä olevilla alkioilla,
– STL-mahdollistaa taulukoiden käsittelyn säiliöinä,jolloin iteraattorina toimii taulukkomuuttujan nimilisättynä tai vähennettynä jollain kokonaisluvulla ja
– char* -merkkijonosta on määritelty automaattinentyyppikonversio string-merkkijonoksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 370
main-funktion paluuarvo
▼ C++:ssa main-funktion paluuarvon tyyppi on int:
int main(...
▼ main:in suorituksen pitää siis aina päättyä käskyynreturn int_lauseke, joka määrää minkä arvon main
palauttaa.
▼ Normaalisti funktion paluuarvo evaluoituu funk-tiokutsun arvoksi, mutta koska main:ia kutsutaanautomaattisesti, niin minne sen paluuarvo päätyy?
▼ main:in paluuarvo välitetään tavalla tai toisellaohjelman käynnistäneelle ympäristölle.
▼ Esimerkiksi UNIX-järjestelmissä paluuarvo päätyykäyttäjän komentotulkille, joka voi sitten hyödyntääpaluuarvoa, tai olla hyödyntämättä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 371▼ Useissa komentotulkeissa on muuttuja $status,✶ jonka
arvo on on viimeisimmän suoritetun komennonmain-funktion paluuarvo (status tai exit-status):
proffa> ./akro New MexicoNMproffa> echo $status0
▼ akro-ohjelman paluuarvo on 0, koska EXIT_SUCCESS
on cstdlib-kirjastossa määritelty jotenkin seuraavasti:
const int EXIT_SUCCESS = 0;
▼ Vastaavasti EXIT_FAILURE on määritelty joksikinnollasta poikkeavaksi arvoksi.
▼ Tämä käytäntö johtuu siitä, että UNIX-järjestelmätulkitsee ohjelman paluuarvon 0 onnistumiseksija kaikki muut arvot ilmaisevat, että ohjelmansuorituksessa meni jotain pieleen (ohjelma päättyivirhetilanteeseen).
✶ On syytä korostaa, että kyseessä on komentotulkin muuttuja, jolla ei olemitään tekemistä C++-ohjelman muuttujien kanssa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 372▼ main:in paluuarvo on siis tapa palauttaa tietoa
ajonaikaiselle ympäristölle.
▼ Usein ohjelma voi joutua erilaisiin virhetilanteisiin,jotka aiheuttavat sen suorituksen päättymisen
➠ jokaiselle virheelle kannattaa antaa omakokonaislukukoodinsa, jonka main voi sittenvirhetilanteessa palauttaa.
▼ Komentotulkki on ohjelmointikieli itsessään jasisältää mm. oikosulkevat | | - ja &&-operaattorit,jotka toimivat yhteen main:in paluuarvojen kanssa:
– Tiedosto data.txt tulostetaan näytölle vain, jos seon olemassa (»test –f» palauttaa nollan vain, jossen parametritiedosto on olemassa):
proffa> test –f data.txt && cat data.txt
– Tulostetaan ilmoitus »Tiedostoa ei ole» vain, jos»test –f» epäonnistuu (paluuarvo on nollastapoikkeava, jos parametritiedosto ei ole olemassa):
proffa> test –f data.txt | | echo "Tiedostoa ei ole"
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 373
Modulaarisuus ja moduulit
▼ Ohjelmaa kutsutaan modulaariseksi silloin, kun
1. se on jaettu loogisesti yhteenkuuluviin osiin elimoduuleihin, joita
2. voidaan kehittää ja ylläpitää toisistaan erillään.
▼ Modulaarisuus on malli/päämäärä, jonka mukaanohjelmia tulisi suunnitella ja toteuttaa, jottalopputulos on selkeämpi ja hallitavampi.
▼ Moduuli puolestaan tarkoittaa puhujasta jatilanteesta riippuen jompaa kumpaa tai molempia:
1. Ohjelman looginen osakokonaisuus.
2. Ohjelmointikielen tarjoama työkalu, jolla modu-laarisuus saadaan esitettyä kyseisellä ohjelmointi-kielellä.✶
▼ Esimerkki valaisee käsitteet parhaiten, määritelmiinkannattaa kuitenkin palata esimerkin jälkeen.
✶ Varsinkin C- ja C++-ohjelmoijille tyypillinen näkemys.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 374
Esimerkki
Ohjelman kuvaus
▼ Tehdään ohjelma, joka osaa lukea seuraavanmuotoisen tietokannan tiedostosta ohjelmat.dat:
* :YLEn uutiset:1:20.30
ma:TV3n uutiset:3:21.00ti–su:TV3n uutiset:3:22.00
pe-ma:Piirrettyjä:BBC Prime:09.00
#En tykkää enää kauniista ja rohkeista#ma–pe:Kauniit ja rohkeat:3:17.30
ti :Kameleontti :2:19.15ti :Makupalat:2:20.00
......monen hyvän ohjelman tiedot puuttuvat tästä...
▼ Kommenttirivit ja tyhjät rivit ohjelma jättäähuomioimatta.
▼ Viikonpaivän lyhenne-kentän arvo jokin seuraavista:
ma, ti, ke, to, pe, la tai su
tarkoittaa kyseistä viikonpäivää.
päivä1 – päivä2
tarkoittaa mitä tahansa päivää annettujenpäivien välissä päätepisteet mukaanlukien.
* tarkoittaa mitä tahansa viikonpäivää.
▼ Muut kentät saavat olla mitä vain ➠ ohjelma eitee niille sen kummempia virhetarkasteluita.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 376▼ Ohjelma toimii seuraavan kaltaisesti, kun oletetaan
että viikonpäivä on sunnuntai:proffa> ./tvohjelmat20.30 YLEn uutiset (1)22.00 TV3n uutiset (3)09.00 Piirrettyjä (BBC Prime)14.45 Kukkulan kuningas (3)15.30 Simpsonit (3)19.15 Kova laki (2)20.55 People’s Century (BBC Prime)proffa>
Algoritmi hyvin yleisellä tasolla
▼ Ylimmän tason algoritmi näyttäisi seuraavalta:
Algoritmi: Tulosta TV-ohjelmat
1. ohjelmatiedot← lue ohjelmatietokanta
2. viikonpäivä ← selvitä tämänhetkinen
viikonpäivä
3. tulosta ohjelmatiedoista kyseisen viikonpäivän
ohjelmien tiedot
▼ Yksityiskohtaistamisen varaa olisi vaikka kuinka,mutta tuo palvelee tämän esimerkin tarkoitustariittävän hyvin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 377▼ Esitetystä algoritmista löytää pienellä miettimisellä
selkeitä loogisia osakokonaisuuksia:
1. ohjelmatietokannan lukeminen ja jäsentely,
2. tietotyyppi, johon ohjelmatiedot talletetaan jakyseisellä tyypillä operointi,
3. viikonpäivän esittävä tietotyyppi ja tämänhet-kisen viikonpäivän selvittäminen sekä
4. toiminnot jotka kontrolloivat edellisten suoritus-järjestystä (siis pääohjelma).
▼ Osakokonaisuudet ovat kuitenkin sen verran suuria,että niiden toteuttaminen vaatii enemmän kuinyhden funktion ➠ ne muodostuvat useammanaliohjelman/tietotyypin yhteistyön tuloksena.
▼ Löydetyt 4 osakokonaisuutta muodostavat tulevanohjelman moduulijaon eli ne ovat sen moduulit.
▼ Nähtäväksi jää enää, kuinka moduulit esitetäänC++-kielellä, siis ohjelman toteutus.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 378Ohjelman toteutus
// Moduuli: tvohjelmat.cc
// Pääohjelmamoduuli.
#include <cstdlib>
#include <string>
using namespace std;
#include "viikonpaivat.hh"
#include "tietokanta.hh"
#include "ohjelmatiedot.hh"
const string TIETOKANTA = "ohjelmat.dat" ;
int main( ) {Ohjelmatiedot tiedot;
if ( !LueTietokanta(TIETOKANTA, tiedot) ) {return EXIT_FAILURE;
}
Paiva viikonpaiva = MikaViikonpaivaTanaan( ) ;
tiedot.tulosta_paivan_tiedot(viikonpaiva);
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 379// Rajapinta: viikonpaivat.hh
// Viikonpäivien käsittelyn hoitavan
// moduulin julkinen rajapinta.
#ifndef VIIKONPAIVAT_HH
#define VIIKONPAIVAT_HH
#include <string>
using namespace std;
// Sunnuntain arvo on 0 johtuen anglosakseja
// myötäilevästä kirjastofunktiosta localtime( ).
enum Paiva {SU, MA, TI, KE, TO, PE, LA,
VIRHEELLINEN_VIIKONPAIVA};
Paiva MikaViikonpaivaTanaan( );
Paiva LyhenneViikonpaivaksi(string lyhenne);
#endif
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 380// Moduuli: viikonpaivat.cc
▼ Lähdekoodimoduuleja voidaan periaatteessakääntää yksi kerrallaan ja yhdistää näin saadutobjektitiedostot suoritettavaksi ohjelmaksi ➠tästä lisää myöhemmin.
▼ Tässä vaiheessa riittää kuitenkin, että pystytäänkääntämään kaikki moduulit kerralla:
kirjoitettuna yhdelle rivillä ilman (selkeyden vuoksilisättyjä) rivinvaihtoja ja sisennyksiä.
▼ Tärkeää on huomata, että kääntäjälle syötetään
vain .cc-tiedostot, ei koskaan .hh-tiedostoja.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 393
Siitä opittua
▼ Abstraktin tietotyypin esittely voi sisältää yksityisessärajapinnassaan (private-osa) myös tietotyyppejä ➠ainoastaan jäsenfunktiot voivat käyttää niitä.
▼ Luettelotyypin alkioita voi käyttää kokonaisluku-
vakioina, mikä onkin suositeltu tapa, jos halutaanluokan sisäisiä vakioita.
▼ Vaikkei esimerkissä näin ollutkaan, niin tosiasiassatietotyyppejä ja const-vakioita voidaan määritellämyös julkisessa rajapinnassa (public-osa): tämä onjoskus hyödyllinen ominaisuus.
▼ Olennaisin uutuus esimerkissä oli C++:n tapamoduulien toteuttamiseen:
1. Moduulin julkisen rajapinnan esittely otsikkotie-dostossa (.hh-tiedosto).
2. Moduulin yksityinen rajapinta ja julkisen raja-pinnan toteutus .cc-tiedostossa.
ja muut niihin liittyvät käytännön yksityiskohdat.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 394
Moduuleista yleisesti
▼ Moduulit ovat ohjelman loogisia osakokonaisuuksia
➠ joukko funktioita, tietotyyppejä ja tietoa, jotkayhdessä toteuttavat jonkin ohjelman osan.
▼ Moduuleihin liittyvät samat käsitteet kuin abstrak-teihin tietotyyppeihin:
Julkinen rajapinta
Ne moduulin osat (funktiot, tietotyypit ja tieto),joita voidaan käyttää moduulin ulkopuolella.
Yksityinen rajapinta
Ne moduulin osat, joita vain moduuli itsevoi käyttää hyväkseen ➠ käytännössä siistoteutusyksityiskohdat.
▼ Moduulit ovat siis työkalu abstraktioiden luomiseen
➠ taas uusi apuväline, joka auttaa hallitsemaanohjelmalla ratkaistavaan ongelmaan liittyväämonimutkaisuutta abstraktioiden avulla.
▼ Moduuleiden tarkoitusta kuvaa muinainen totuus:hajoita ja hallitse.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 395▼ Modulaarisuus täytyy ottaa huomioon jo ohjelman
– Määrätään löydettyjen moduulien julkisetrajapinnat: mitä palveluita moduulin täytyy tarjota,jotta kaikki sen edustaman osakokonaisuudentoiminnot saadaan hoidettua.
▼ Kun rajapinta on lyöty suunnitteluvaiheessa lukkoon,niin periaatteessa eri moduuleita voidaan kehittäärinnan ➠ moduulin julkisen rajapinnan käyttäjiäei kiinnosta toteutusyksityiskohdat (yksityinenrajapinta), niin kauan kuin julkinen rajapinta tarjoaasovitut palvelut.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 396
Moduulien toteuttaminen C++:lla
▼ C++-kielessä moduuli koostuu kahdesta osasta:
esittely- eli otsikko- eli .hh-tiedosto✶
– Julkisen rajapinnan funktioiden ja tietotyyppienesittelyt ja mahdollisesti const-vakioidenmäärittelyjä.
– Ei saa sisältää funktioiden ja muuttujienmäärittelyjä.
määrittely- eli toteutus- eli .cc-tiedosto✶
– Julkisen rajapinnan funktioiden määrittelyt.
– Yksityisen rajapinnan funktioiden ja tietotyyp-pien määrittelyt.
▼ Moduulin esittely- ja määrittelytiedosto on syytänimetä samoin ➠ ainoastaan pääte on erilainen.
▼ Tutkitaan kumpaakin yksityiskohtaisemmin.
✶ Päätteet .hh ja .cc vaihtelevat riippuen systeemistä ja käytetystäkääntäjästä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 397
Esittely- eli .hh-tiedostot
▼ Moduulin xyz esittelytiedostolla xyz.hh on yleensäseuraavan kaltainen rakenne:
// Tiedoston alussa kommentit, jotka kertovat tiedoston// nimen, tarkoituksen, versionumeron, tekijän jne.
// .hh–tiedostossa tarvittavien käyttäjän// omien moduulien includet#include "... "...
/ / julkisen rajapinnan tietotyyppien esittelytclass ... {
...};enum ... { ... };
...
/ / julkisen rajapinnan const–vakioiden määrittelytconst int XYZ_VIRHE = –1;...
/ / julkisen rajapinnan funktioiden esittelytvoid xyz_tulosta( ... );
...
#endif ▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 398▼ Esitetty rakenne ja kohtien järjestys riippuu tietysti
tilanteesta.
▼ Esimerkiksi .hh-tiedosto moduulille, joka toteuttaaabstraktin tietotyypin, sisältää tyypillisesti vainosan esitellyistä kohdista (vrt. esimerkkiohjelmanohjelmatiedot.hh).
▼ Jollei erityistä syytä ole, niin kannattaa pitäytyäannetussa järjestyksessä ➠ esittelytiedostoillayhdenmukainen rakenne.
▼ Huomaa, että pääohjelmamoduulilla ei tavallisestiole esittelytiedostoa.
▼ C++-ohjelmissa #-merkillä alkavat rivit ovat käskyjänk. esiprosessorille, joka käy tiedoston läpi ennenkuin se syötetään kääntäjälle.
▼ Esiprosessorin käskyt ovat yksinkertaisia: se suorittaalähinnä tekstin korvausta ja hyvin yksinkertaistamuuttujien✶ käsittelyä.
✶ Esiprosessorin omia muuttujia (makroja), niillä ei ole mitään tekemistäC++-ohjelman muuttujien kanssa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 399▼ Kurssin kannalta hyödyllisin esiprosessorin käsky on
#include, joka lukee perässään annetun tiedostonsisällön osaksi ohjelmakoodia ➠ sen avulla luetaanmoduulien julkisten rajapintojen esittelytiedot osaksikäsiteltävää lähdekooditiedostoa.
▼ Jos tiedoston nimi on hakasulkeissa
#include <esittelytiedosto>
niin esittelytiedosto haetaan jostain systeemin vakio-paikasta ➠ standardikirjastojen esittelytiedostot.
▼ Huomaa, että standardikirjastojen esittelytiedostojennimissä ei ole .hh-päätettä.
▼ Jos nimi taas on lainausmerkeissä
#include "esittelytiedosto.hh"
niin esittelytiedosto.hh luetaan samasta hakemistosta,jossa käsiteltävä lähdekooditiedosto on ➠ käyttäjänomien moduulien esittelytiedostot.
▼ #ifndef-, #define- ja #endif-rivit pitävät huolta siitä,ettei hämminkiä synny, vaikka sama esittelytiedostoluetaan osaksi samaa lähdekooditiedostoa useamminkuin kerran.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 400▼ Tuollaistahan saattaa tapahtua kulissien takana lähes
huomaamatta:
tvohjelmat.cc
viikonpaivat.hh viikonpaivat.hh
viikonpaivat.hh
tietokanta.hh
ohjelmatiedot.hh
ohjelmatiedot.hh
▼ Esimerkkiohjelmassa viikonpaivat.hh luetaan siisosaksi tvohjelmat.cc-tiedostoa kolmesti.
▼ Jos tähän ei jotenkin varauduttaisi, niin kääntäjävalittaisi että viikonpaivat.hh:ssa määriteltytietotyyppi Paiva on määritelty useammin kuinkerran.
▼ C++-esittelytiedostoissa tämä hoidetaan idiomilla:
/ / Käytetään "using namespace std" –käskyä uskollisesti/ / vaikkei sitä varsinaisesti ole tarkoitus käyttää näin.using namespace std;
// .hh–tiedostossa tarvittavien käyttäjän omien// moduulien includet, mukana lähes poikkeuksetta// moduulin oma esittelytiedosto.#include "xyz.hh"#include "... "
▼ Järjestys ei taaskaan ole kiveen kirjoitettu ja saattaatilanteesta riippuen vaihdella.
▼ Usein jotain kohtia myös puuttuu.▼ Jollei erityistä syytä ole, niin kannattaa taas pitäytyä
esitetyssä järjestyksessä. ▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 404▼ Moduulin määrittelytiedostoihin liittyy vain
muutama uusi asia:
1. Jos moduuli käyttää hyväkseen muita käyttäjänomia moduuleita, niiden julkisen rajapinnanesittelevä .hh-tiedosto pitää muistaa lukea osaksilähdekoodia #include "... "-komennolla.
2. Jos moduulin omassa esittelytiedostossa onesitelty tietotyyppejä tai const-vakioita, on sekinluettava osaksi lähdekoodia ➠ tietotyypit ja vakiotkäytettävissä myös määrittelytiedostossa.
3. Yksityisen rajapinnan funktiot esitellään jamääritellään namespace { ... } -lohkojen sisällä
➠ niiden näkyvyysalue rajautuu kyseisenlähdekooditiedoston/moduulin sisään, eli niitä eivoi kutsua muista moduuleista.
4. Edellinen ei päde luokan yksityisen rajapinnanjäsenfunktioihin, jotka määritellään kuten muutkinjäsenfunktiot ilman namespace -lohkoja.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 405
Mistä moduulit tulevat?
▼ Ohjelman jako moduuleihin tehdään jo määrittely-ja suunnitteluvaiheessa.
▼ Periaatteessa moduulit löytää miettimällä, mitäloogisia osakokonaisuuksia ohjelmassa tulee olla,jotta se toimisi halutulla tavalla.
▼ Esimerkiksi tvohjelmat jakautui aluksi selkeästikolmeen osaan:
1. pääohjelma,
2. tietokannan käsittely ja
3. tietorakenne, jonne aikataulutiedot talletetaan jajosta niitä etsitään.
▼ Kun näitä kutakin analysoi lisää, niin pian oivalsiviikonpäivien kummittelevan mukana niissä kaikissa.
▼ Koska viikonpäiville suoritettavat operaatiottuntuivat monimutkaisilta, se kannatti toteuttaaomana moduulinaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 406▼ Moduulien etsinnässä on siis päämääränä jakaa
ohjelma loogisiin osiin, joista kukin
– tekee oman osuutensa kokonaisuudesta ja– on riittävän yksinkertainen.
▼ Jos ongelma on iso, kannattaa moduulit etsiäpilkkomalla ongelmaa aina vaan pienempiin osiin(top-down) ➠ lopulta päädytään niin pieniinpaloihin, että ne pysyvät hanskassa.
▼ Oikea moduulijako ei ole yksikäsitteinen ➠ riippuutilanteesta ja tekijästä.
▼ Joitain hyvin yleisiä suuntaviivoja kokonaisuuksista,jotka kannattaa toteuttaa omana moduulinaan:
OHJ-1150 Ohjelmointi II 407▼ Usein selkeän ja toimivan julkisen rajapinnan
löytäminen on huomattavasti vaikeampaa kuinmoduulijaon aikaansaaminen.
▼ Rajapinnan etsimisessä on lähdettävä liikkeellepohtimalla
– mitä palveluita moduulin tulee toisille tarjota ➠rajapinnan funktiot
– mitä tietoa moduuli tarvitsee pystyäkseen palveluntarjoamaan ➠ rajapinnan funktioiden parametritja rajapinnan tietotyypit
– mitä hyödyllistä moduuli mahdollisesti tuottaatoisten käyttöön ➠ rajapinnan funktioidenpaluuarvot ja rajapinnan tietotyypit
▼ Modulaarisuuden onnistunut hyväksikäyttö vaatiihuolellista ennakkosuunnittelua.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 408
Modulaarisuuden hyödyt
▼ Mahdollistavat moduulin sisäisen toteutuksenmuuttamisen rajapinnan pysyessä ennallaan.
▼ Ohjelmiston loogisesti yhteenkuuluvat osat voidaankoota samaan pakettiin, mikä selkeyttää ohjelmaa jahelpottaa sen testaamista ja ylläpitoa.
▼ Moduulit ovat erinomainen työkalu suurienkokonaisuuksien hallintaan.
▼ Moduuleita voidaan kehittää rinnakkain sen jälkeen,kun julkinen rajapinta on lyöty lukkoon.
▼ Moduuleita voidaan kääntää erikseen, mikänopeuttaa kehitystyötä ja kuluttaa vähemmänresursseja ➠ muutoksen teon jälkeen vain muuttu-neet moduulit tarvitsee kääntää uudelleen.
▼ Usein moduuleita voidaan uudelleenkäyttää jokokokonaan tai ainakin osittain.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 409
Automatisoitu käännös ja make
▼ Ennen kuin tutustutaan make-komentoon,palautetaan mieleen sivulta 401 vaiheet, joillaC++-lähdekoodista saadaan suoritettava ohjelma.
▼ Yksittäisen lähdekoodimoduulin käännöksentuloksena syntyy nk. objektitiedosto (.o-tiedosto).
▼ UNIX:ssa objektitiedoston nimi saadaan lähdekoodi-tiedoston nimestä muuttamalla .cc .o :ksi.
▼ .o-tiedosto sisältää alkuperäisen lähdekoodin kään-nettynä konekielelle, apuinformaatiota linkkerille jne.
▼ Linkkeri yhdistää sitten objektitiedostot ja kirjastotsuoritettavaksi ohjelmaksi.
▼ Yleensä käännöskomento (esimerkiksi s. 392)proffa> ohj2c++ –o tvohjelmat
huolehtii kulissien takana automaattisesti kaikistavaiheista ja tuhoaa turhat(?) .o-tiedostot linkityksenvalmistuttua.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 410▼ Tämä ei kuitenkaan ole taloudellista, jos ohjelma
on suuri (paljon lähdekoodimoduuleja) ➠ ainakun ohjelma käännetään, jokainen moduuli pitäämuuntaa objektitiedostoksi, vaikka sitä ei olisiedellisen käännöksen jälkeen muutettukaan.
▼ Parempi ratkaisu olisi kääntää kaikki .cc-tiedostot.o-tiedostoiksi, linkittää ne suoritettavaksi ohjelmaksija olla tuhoamatta .o-tiedostoja.
▼ Seuraavan kerran kun ohjelma on tarpeen kääntää,objektitiedostoiksi tarvitsee muuntaa vain nelähdekoodimoduulit, joiden sisältöä on muutettusitten edellisen käännöksen ➠ valmis ohjelmasaadaan linkitettyä näistä uusista ja tallessa olevistavanhoista .o-tiedostoista.
▼ ohj2c++:lla lähdekooditiedosto saadaan muutettuaobjektitiedostoksi lisäoptiolla –c, esimerkiksi:
jossa koko komento on kirjoitettu yhdelle riville.
▼ Tämä kaikki liittyy make-komentoon siinä suhteessa,että maken avulla paloissa/osissa tapahtuvakäännösprosessi voidaan automatisoida.
▼ make:lle kuvataan erillisellä Makefile-tiedostolla,minkälaisia riippuvuussuhteita eri lähdekooditiedos-tojen välillä vallitsee.
▼ Kuvatuista suhteista ja tiedostojen muutosajoistamake osaa päätellä, mitkä tiedostot on käännettävä,linkitettävä tai jollain muulla tavoin päivitettävä ajantasalle.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 412▼ Tutkitaan esimerkki make:n toiminnasta tekemällä
OHJ-1150 Ohjelmointi II 413▼ Jos nyt haluttaisiin saada suoritettava ohjelma
tvohjelmat, niin se tapahtuisi komennolla:proffa> make tvohjelmat
tai koska tvohjelman riippuvuudet on määriteltyMakefile:ssä ensimmäisenä, pelkkä make riittäisi:
proffa> make
▼ Makefile:n tärkein osa ovat säännöt, jotka kuvaavatmitkä tiedostot riippuvat toisistaan ja miten:
kohde: riippuvuudet
T päivityskomento
▼ Jos mitä tahansa tiedostoista riippuvuudet on muu-tettu sen jälkeen, kun tiedostoa kohde on viimeksimuutettu, niin make suorittaa päivityskomennot.
▼ Päivityskomennon on siis tarkoitus saattaa kohde ajantasalle riippuvuudet-tiedostoja prosessoimalla.
▼ Riippuvuuksien kuvaukset muodostavat puumaisenrakenteen ➠ ennen kuin yksittäisen säännönpäivityskomentoa suoritetaan, make tarkistaarekursiivisesti onko olemassa sääntöjä, joillariippuvuudet itsessään päivitetään ajan tasalle.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 414▼ Makefile:n nimen on oltava Makefile tai makefile.
▼ Kaikki teksti rivillä #-merkistä rivin loppuun onkommenttia, eikä siitä välitetä.
▼ Seuraavalle riville jatkuvat rivit ilmaistaan \-merkillärivin viimeisenä merkkinä.
▼ Makefile:ssä voi määritellä myös makroja (käytän-nössä lähes sama asia kuin muuttuja):
muuttujan_nimi = arvo
joiden arvo saadaan kirjoittamalla
$(muuttujan_nimi)
▼ Sääntöön voi liittyä useampikin päivityskomento.
▼ make on työkalu tilanteisiin, joissa useat tiedostotriippuvat jotenkin toisistaan ➠ sitä voi käyttäämuuhunkin kuin C++-ohjelmien kääntämiseen.
▼ Esimerkki-Makefile oli sangen yksinkertainen ➠make tarjoaa »tehokäyttäjälle» paljon monimutkai-sempiakin ominaisuuksia.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 415
Versionhallinta ja rcs
▼ Versionhallinnan avulla pidetään kirjaa ohjelmistonmoduulien eri versioista eli niiden kehittymisestä.
▼ Versionhallinta tarjoaa tavallisesti ainakin:
– mahdollisuuden tallettaa moduulista useita kehi-tysversioita, joihin kaikkiin päästään tarvittaessakäsiksi myöhemmin,
– mahdollisuuden kommentoida version muutokset,uudet ominaisuudet ja syyt, miksi versio tehtiin,
– automaattisen versionumeroinnin,
– tavan kontrolloida sitä, ketkä saavat muuttaamoduulia ja
– poissulkemisongelman ratkaisun: kaksi taiuseampia henkilöitä ei vahingossa muuta samaamoduulia yhtä aikaa tai että se tapahtuu hallitusti.
▼ Ideana on, että kaikki merkittävät muutoksetlähdekoodimoduuleissa talletetaan versiohallintaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 416▼ Tutustutaan seuraavaksi (ilmaiseen) versionhallinta-
järjestelmään RCS (Revision Control System).
▼ RCS toimii siisteimmin, jos heti alussa tekee projektintyöhakemistoon alihakemiston nimeltä RCS:
proffa> cd projektin_hakemisto
proffa> mkdir RCS
▼ Tämän jälkeen kaikista projektiin kuuluvistamoduuleista, niiden esittelytiedostoista, Makefile:stäjne. täytyy tallettaa alkutilanne RCS:ään komennollaci (lyhenne sanoista check in).
▼ Tutkitaan nyt (ja jatkossa) esimerkkinä yhdenmoduulin tvohjelmat.cc käyttäytymistä:
proffa> ci tvohjelmat.ccRCS/tvohjelmat.cc,v <–– tvohjelmat.ccenter description, terminated with single ’.’ or ...NOTE: This is NOT the log message!>> TV-Ohjelmat ohjelmiston paamoduuli>> .initial revision: 1.1done
▼ Jos ohjelmistoon lisätään myöhemmin uusiamoduuleita, edeltävä operaatio on tehtävä jokaiselle.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 417▼ Edellisen ci:n seurauksena tvohjelmat.cc on
kadonnut työhakemistosta ja näin kuuluukin olla ➠RCS-hakemistoon on ilmestynyt versionhallintatieto-kanta tvohjelmat.cc,v.
▼ Jos moduulin sisältöä nyt halutaan tutkia, saadaanviimeisin versio haettua RCS:stä komennollaco (lyhenne sanoista check out):
proffa> co tvohjelmat.ccRCS/tvohjelmat.cc,v ––> tvohjelmat.ccrevision 1.1done
▼ tvohjelmat.cc ilmestyi takaisin työhakemistoon,mutta siihen ei ole kirjoitusoikeuksia ➠ sen voiesimerkiksi kääntää, mutta sitä ei voi muuttaa.
▼ Editointia varten moduuli on lukittava, eli saatettavasellaiseen tilaan, etteivät muut projektin jäsenet voimuuttaa sitä samanaikaisesti (co :n optio –l):
proffa> co –l tvohjelmat.ccRCS/tvohjelmat.cc,v ––> tvohjelmat.ccrevision 1.1 (locked)done
▼ Nyt tvohjelmat.cc:tä voi myös muuttaa normaalisti.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 418▼ Kun moduuliin on tehty kaikki halutut muutokset
(tai vähintään kerran päivässä), siitä on talletettavauudempi versio RCS:ään komennolla ci:
proffa> ci tvohjelmat.ccRCS/tvohjelmat.cc,v <–– tvohjelmat.ccnew revision: 1.2; previous revision: 1.1enter log message, terminated with single ’.’ or ...>> Korjailtu koodin sisennyksiä siistimmiksi>> ja lisäilty välilyöntejä samasta syystä.>> .done
▼ tvohjelmat.cc katosi jälleen työhakemistosta, muttase on tallessa versionhallintatietokannassa.
▼ Huomaa, että ci pyytää aina kommentteja, joilla voiselittää versioon tehtyjä muutoksia ja niiden syitä.
▼ Kommenttien syöttäminen päätetään antamallariviksi pelkkä piste.
▼ Jos moduulin lukitsemisen jälkeen huomaa, etteimuutoksiin olekaan tarvetta, niin ci:in voi siltisuorittaa ➠ RCS ei tee muutoksia versionhallintaan:
proffa> ci tvohjelmat.ccRCS/tvohjelmat.cc,v <–– tvohjelmat.ccfile is unchanged; reverting to previous revision 1.1done
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 419▼ Jos taas moduuliin on tehty muutoksia edellisen
lukitsemisen jälkeen, mutta muutokset ovatvirheellisiä/huonoja ja ne halutaan heittää mene-mään, niin silloin auttaa rcs –u :
▼ Koska tehtyjen muutosten unohtaminen on vaaral-lista, toimii rcs –u siten, että se vain avaa lukituksenmutta ei tuhoa moduulia työhakemistosta automaat-tisesti ➠ käyttäjän on tehtävä se itse rm-komennolla:
proffa> rm tvohjelmat.cc
▼ Tämä on ainoa tilanne, jossa versionhallinnassaolevia tiedostoja kannattaa tuhota rm:llä ➠ muutenmokaa jossain vaiheessa ja itkua piisaa.
▼ Jos on tarve poistaa työhakemistosta versionhallinnanpiiriin kuuluvia tiedostoja, komento on rcsclean,joka ei suostu poistamaan lukittuja tiedostoja.
▼ Komento rlog kertoo moduulista koko versiohisto-rian, myös ci:llä asetetut kommentit.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 420▼ Komento rcsdiff kertoo, mitä muutoksia lukittuun
moduuliin on tehty co –l:n jälkeen.
▼ RCS:llä on vielä yksi hyödyllinen ominaisuus ➠kun co-komento hakee moduulia versionhal-linnasta, se korvaa samalla tiettyjä avainsanojaversiohallintainformaatiolla.
▼ Hyödyllisimpiä näistä ovat:
Avainsana Korvaava teksti
$Revision$ versionumero$Date$ päivämäärä jolloin versio talletettiin ci:llä$Id$ yhden rivin olennainen informaatio versiosta$Log$ koko versiohistoria nokkelasti siten, että sen
saa kommentteina ohjelman osaksi
▼ Ymmärrettävistä syistä tämä oli vain pintavilkaisuRCS:n ominaisuuksiin ➠ lisätietoa saa esimerkiksiUNIX-manuaalisivuilta:
proffa> man ciproffa> man coproffa> man rcs
ja niin edelleen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 421▼ Kuvallinen havainnollistus RCS-komentojen käytöstä:
en
kyllä
Luontici tiedosto
Haku luettavaksico tiedosto
Haku editoitavaksico – l tiedosto
Versiohistoriarlog tiedosto
Tehdyt muutoksetrcsdiff tiedosto
Hylkää muutoksetrcs – u tiedosto
Talleta muutoksetci tiedosto
Poista tiedostorcsclean tiedosto
Poista tiedostorm –i tiedosto
versiotietokantaolemassa
versiotietokantapäivitetty
hyväksytkömuutokset?
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 422
Ajonaikaiset virheet ja gdb
▼ Ajonaikaisiksi virheiksi kutsutaan virheitä, joitakääntäjä ei havaitse ➠ ilmenevät vasta valmistaohjelmaa suoritettaessa.
▼ Tyypillisesti ajonaikaisia virheitä on kahdenlaisia:
1. loogiset virheet (ohjelma toimii väärin) ja2. virheet, jotka keskeyttävät ohjelman suorituksen.
▼ Debuggeriksi kutsutaan ohjelmaa, jonka avullavoidaan yrittää paikantaa ajonaikaisia virheitä.
▼ Debuggeri ei ole korvike huolelliselle suunnittelulleja koodaukselle, vaan pikemminkin viimeinen apu.
▼ Debuggeri mahdollistaa ohjelman kontrolloidunsuorittamisen:– muuttujien arvojen selvittäminen,– ohjelman suoritus käsky kerrallaan,– funktioiden kutsuminen jne.eli ohjelman tilan tutkimisen suorituksen aikana.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 423▼ Jos ohjelma kaatuu kesken suorituksen, niin myös
kaatumisen syyn selvittely (UNIX:issa) syntyväncore-tiedoston avulla on mahdollista.
▼ Tutustutaan lyhyesti gdb-debuggeriin.
▼ Toimiakseen gdb vaatii suoritettavalta ohjelmaltatiettyjä lisäominaisuuksia ➠ lähdekoodimoduulit onkäännettävä objektimoduuleiksi käyttäen kääntäjänlisäoptiota –g joko yhdessä:
▼ Tuloksena saatua ohjelmaa tvohjelmat voidaankäyttää myös ilman gdb:tä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 424▼ gdb käynnistetään antamalla sille komentorivillä
tutkittavan ohjelman nimi:proffa> gdb tvohjelmat
tai jos ohjelma on suoritettaessa kaatunut ja tiedostocore on olemassa, sitä voidaan tutkia komennolla:
proffa> gdb tvohjelmat core
▼ Kun gdb käynnistyy se tulostaa kehotteen ja sille voialkaa syöttämään komentoja:
GNU gdb Red Hat Linux (6.3.0.0-1.143.el4rh)Copyright 2004 Free Software Foundation, Inc.···
This GDB was configured as "i386-redhat-linux-gnu".(gdb)
▼ Tutustutaan luettelonomaisesti gdb:n perusko-mentoihin ➠ varsinainen tutustuminen ja käytönopiskelu jää lukijan oman harrastuksen varaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 425▼ Hyödyllisiä komentoja selityksineen:
run (r)Käynnistään ohjelman suorituksen pääohjelman alusta.
break kohta (b kohta)Asettaa keskeytyspisteen haluttuun kohtaan: kun suoritussaavuttaa kohdan, ohjelma pysähtyy. Kohta voi ollarivinumero, tiedostonnimi : rivinumero tai funktion nimi.
info breakpoints (i b)Listaa käytössä olevat keskeytyspisteet.
delete numero (d numero)Poistaa keskeytyspisteen numero.
whereKertoo missä kohdassa suoritus keskeytyi. Toimii myös core-tiedoston kanssa ohjelman kaatumiskohdan selvittämiseksi.
step (s) ja next (n)Jatkavat pysähtyneen ohjelman suorittamista lähdekoodirivikerrallaan. Muuten samat, mutta next suorittaa funktiokutsutsiirtymättä askeltamaan niiden runkoa.
continue (c)Jatkaa normaalia suoritusta pysähtymiskohdasta eteenpäin.
print lauseke (p lauseke)Lausekkeen (myös muuttujan) arvon tulostaminen.
help komento (h komento)Selitys gdb:n komennon toiminnasta.
quit (q)Lopettaa gdb:n ja palaa komentotulkkiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 426▼ Lisäksi gdb:ssä on sisäänrakennettu komentohistoria,
jonka sisällä on mahdollista liikkua nuolinäppäimilläja editoida vanhoja komentoja.
▼ E näppäimen painallus toistaa viimeisimmänsyötetyn komennon, mikäli se on mielekästä.
▼ T näppäimellä voi laajentaa funktioiden jamuuttujien nimiä, kunhan kirjoittaa nimestä ensinyksikäsitteisen alun.
▼ Debuggeri on hyvä työkalu loogisten virheidenmetsästämiseen, mutta ei kuitenkaan niin suoravii-vainen ja helppo kuin voisi kuvitella.
▼ Ei siis kannata aliarvioida vanhoja konsteja:
– koodin huolellinen lukeminen ja analysointi,
– sopivat testisyötteet ohjelmalle ja
– sopivasti sijoitellut testitulostukset ohjelmansuorituksen aikana.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 427
Rekursio
▼ Rekursio tarkoittaa jonkin asian määrittelemistäitsensä avulla.
▼ Esimerkiksi matematiikasta tuttu luonnollisen luvunkertoma:
n! = n · (n− 1) · (n− 2) · . . . · 3 · 2 · 1kun lisäksi on määrätty, että 0! = 1.
▼ Nollaa suuremman n:n kertoma on siis n:n ja kaikkiensitä pienempien positiivisten kokonaislukujen tulo.
▼ Esimerkiksi 5! = 5 · 4 · 3 · 2 · 1 = 120.
▼ Kertoma voidaan määritellä myös rekursiivisesti:
n! =
{
1 mikäli n on 0
n · (n− 1) ! mikäli n > 0
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 428▼ Soveltamalla rekursiivista määritelmää 5!:n laskemi-
seksi saadaan:
5! = 5 · 4!
= 5 ·SSw
4̂ · 3!
= 5 · 4 ·SSw
3̂ · 2!
= 5 · 4 · 3 ·SSw
2̂ · 1!
= 5 · 4 · 3 · 2 ·SSw
1̂ · 0!
= 5 · 4 · 3 · 2 · 1 ·?
1
= 120
▼ Rekursion idea on pyrkiä esittämään ongelmanratkaisu alkuperäisen ongelman kanssa samanmuo-
toisina, mutta ratkaisultaan yksinkertaisempina
osaongelmina.
▼ Saatuihin osaongelmiin voidaan sitten soveltaarekursiivista sääntöä uudelleen, kunnes lopultapäädytään niin yksinkertaiseen tapaukseen, että senratkaisu nähdään suoraan.
▼ Rekursion päämäärä on: hajoita ja hallitse.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 429
Rekursio ohjelmoinnissa
▼ Kun puhutaan rekursiosta ohjelmoinnin yhteydessä,niin yleensä aihe liittyy funktioihin.✶
▼ Rekursiivinen funktio on määritelty itsensä avulla
➠ se kutsuu itseään.
▼ Toteutetaan edellä esitelty kertoman laskeminenrekursiivisella C++-funktiolla:
unsigned int kertoma(unsigned int n) {if (n == 0) {
return 1;} else {
return n * kertoma(n – 1);}
}
joka siis laskee ja palauttaa parametrinsa n kertoman.
▼ Periaatteessa funktion voi kirjoittaa suoraan kertomanrekursiivisen määritelmän pohjalta (s.427).
▼ Mutta hyödyllisempää olisi ymmärtää, miksi funktiotoimii ja vieläpä oikein.
✶ Tietorakenteetkin voivat olla rekursiivisia, mutta siitä myöhemmin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 430▼ Oletetaan, että kertoma-funktiota kutsutaan näin:
#include <iostream>
using namespace std;
int main( ) {cout << kertoma(5) << endl;
}
ja tutkitaan vaihe-vaiheelta, kuinka suoritus etenee.
▼ Kun main:in suoritus saavuttaa kohdan, jossakertoma-funktiota kutsutaan, niin havainnollistetaantilannetta seuraavasti:
main
→ cout << kertoma(5) << endl;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 431▼ Pääohjelman funktiokutsun seurauksena päädytään
suorittamaan kertoma-funktion runkoa:
main
kertoma
// parametrin n arvo on 5
if (n == 0) {return 1;
} else {→ return n * kertoma(n – 1);}
▼ Koska n:n arvo on 5, suoritetaan else-haara japäädytään tekemään rekursiivinen kutsu todellisenparametrin arvolla 4.
▼ Kuvassa peitettynä oleva main-laatikko kuvaa sitä,että main-funktion suoritus on kesken ➠ vastakun sen peittävä kertoma-funktio suorittaa return-käskyn, jatkuu main:in suoritus siitä kohdasta, jossakertomaa kutsuttiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 432▼ Itse asiassa suoritus etenee tällä rekursiokierroksella
täysin samoin kuin edelliselläkin, paitsi ettämuodollisen parametrin n arvo on nyt 4:
main
kertoma
kertoma
// parametrin n arvo on 4
if (n == 0) {return 1;
} else {→ return n * kertoma(n – 1);}
▼ Suoritus ajautuu siis else-haaraan ja rekursio eteneeaina vain syvemmälle.
▼ Huomaa, kuinka taustalle alkaa kertyä myöskeskeneräisiä kertoma-funktion suorituksia.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 433▼ Näin ohjelma etenee, kunnes lopulta saavutaan
tilanteeseen, jossa parametrin n arvo on 0:
main
kertoma
kertoma
kertoma
kertoma
kertoma
kertoma
// parametrin n arvo on 0
if (n == 0) {→ return 1;} else {
return n * kertoma(n – 1);}
▼ Enää ei tapahdukaan rekursiivista kutsua, vaankertoma palauttaa suoraan arvon 1 ja keskeneräistenfunktiokutsujen suma alkaa purkautua.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 434▼ Toiseksi viimeiselle kertoma:n kutsulle palautuu
siis viimeiseltä rekursiiviselta kutsulta arvo 1, jokakerrotaan muodollisen parametrin n arvolla 1:
main
kertoma
kertoma
kertoma
kertoma
kertoma
// parametrin n arvo on 1
if (n == 0) {return 1;
} else {→ return n * kertoma(n – 1)
︸ ︷︷ ︸
1 ∗ 1
;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 435▼ Seuraavaksi vuorossa oleva keskeneräinen kertoma-
funktio jatkuu samaan tapaan:
main
kertoma
kertoma
kertoma
kertoma
// parametrin n arvo on 2
if (n == 0) {return 1;
} else {→ return n * kertoma(n – 1)
︸ ︷︷ ︸
2 ∗ 1
;}
▼ Huomaa että jokaisella funktiolla on oma paramet-rimuuttuja n, jonka arvo toki on säilynyt ennallaan,kun rekursiivisen funktion kutsusta palataan takaisinsuorittamaan kutsuneen funktion runkoa.
▼ Sama pätee kaikkiin paikallisiin muuttujiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 436▼ Näin suoritus etenee ja saavuttaa lopulta rekursiossa
ylimmän kertoma-funktion:
main
kertoma
// parametrin n arvo on 5
if (n == 0) {return 1;
} else {→ return n * kertoma(n – 1)
︸ ︷︷ ︸
5 ∗ 24
;}
▼ Joka palauttaa pääohjelmalle 5:n kertoman arvon:
main
→ cout <<
120︷ ︸︸ ︷
kertoma(5) << endl;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 437▼ Rekursiossa ei ole ohjelmointikielen kannalta mitään
erikoista ➠ rekursiiviset funktiot ovat funktioita siinämissä kaikki muutkin funktiot.
▼ Rekursion vaikeaselkoisuus piilee siinä, että se vaatiiohjelmoijalta ei-luontaista tapaa hahmottaa ongelmaja sen ratkaisu.
▼ Rekursiivisten funktioiden suunnittelua ja ymmärtä-mistä helpottaa, kun pitää mielessään että:
– Funktio on rekursiivinen, jos se kutsuu itseään(suoraan tai epäsuoraan).
– Suorituksen kuluessa rekursiivisesta funktiosta on»käynnissä» yhtä monta instanssia, kuin mikä onkeskeneräisten rekursiivisten kutsujen lukumäärä.
– Jokaisella instanssilla on omat arvonsa muodollisilleparametreille ja paikallisille muuttujille.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 438▼ Rekursiivisella funktiolla on kaksi olennaista
ominaisuutta, jotka on syytä muistaa:
1. Sillä on oltava lopetusehto (tai ehtoja), jokatunnistaa ongelman triviaalitapaukset ja reagoiniihin ilman uuden rekursiivisen kutsun tarvetta.
2. Jokaisen rekursiivisen kutsun on yksinkertaistettavaratkaistavaa ongelmaa niin, että lopulta päädytääntriviaalitapaukseen.
▼ Esimerkin kertoma-funktiossa näitä kohtia edustivat:
1. if-rakenteen ehto n == 0 ➠ triviaalitapaus onnollan kertoma: sen arvo tiedetään suoraan.
2. Jokaisella rekursiokierroksella selvitettiin yhtäpienemmän luvun kertomaa: kutsuparametripieneni yhdellä.
▼ Jos jomman kumman unohtaa, niin funktiota kutsut-taessa koko ohjelma kaatuu (UNIXissa core dump).
▼ Tämä johtuu siitä, että funktio päätyy kutsumaanitseään loputtomiin (päättymätön rekursio) ➠aiempien kutsukertojen muuttujat✶ kuluttavatmuistia, kunnes se yksinkertaisesti loppuu.
✶ Ja muu aiempiin rekursiokierroksiin liittyvä informaatio.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 439▼ Rekursiolla saa aikaan toistoa.
▼ Periaatteessa mikä tahansa toistorakenne (silmukka)voidaan korvata rekursiolla.
▼ Parhaiten rekursio sopii kuitenkin sellaisten ongel-mien ratkaisuun, jotka ovat luonteeltaan rekursiivisia
➠ niitä voidaan »luonnollisesti» yksinkertaistaa siten,että jäljelle jäävät osaongelmat ovat alkuperäisenongelman yksinkertaistettuja tapauksia.
▼ Yleensä rekursiivinen ratkaisu on lyhyempi jasiistimpi kuin vastaava silmukkarakenteella toteutetturatkaisu.
▼ Toisaalta, yleensä rekursiivinen ratkaisu on hitaampija kuluttaa enemmän muistia kuin silmukkarakenne.
▼ Rekursiiviset funktiot voidaan ominaisuuksiensaperusteella jaotella ryhmiin, jotka kannattaayleissivistyksen nimissä katsoa läpi.
▼ Suoraksi rekursioksi kutsutaan tapausta, jossafunktio kutsuu itseään omassa rungossaan.
▼ Esimerkin kertoma on suoraan rekursiivinen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 440▼ Epäsuora rekursio eli rinnakkaisrekursio tarkoittaa
tilannetta, jossa funktio func_A kutsuu funk-tiota func_B, joka taas kutsuu func_A:ta jne:
func_A func_B
▼ Sotkuun voi osallistua useampiakin funktioita ➠syntyvä »kutsusilmukka» voi olla pidempi.
▼ Epäsuoran rekursion käytössä kannattaa ollapidättyväinen, koska se on usein hankalastihahmotettava ➠ ohjelmasta tulee vaikeatajuinen.
▼ Häntärekursioksi (tail recursion) kutsutaantapausta, jossa rekursiivisen kutsun paluuarvostatulee ilman lisäoperaatioita kutsuvan instanssinpaluuarvo.
▼ Esimerkin kertoma-funktio ei ole häntärekursiivinen
➠ rekursiivisen kutsun paluuarvo pitää ensin kertoan:llä, jotta saadaan kutsujan paluuarvo:
return n * kertoma(n – 1);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 441▼ kertoma voitaisiin toteuttaa häntärekursiivisena:
unsigned inthantakertoma(unsigned int tulos, unsigned int n) {
if (n == 0) {return tulos;
} else {return hantakertoma(tulos * n, n – 1);
}}
▼ jota pitäisi kutsua siten, että ensimmäiseksiparametriksi annetaan 1:
cout << hantakertoma(1, n) << endl;
▼ Häntärekursiivisille funktioille on tyypillistä, ettälopputulosta kerätään ylimääräiseen parametriin(tulos), johon kertynyt arvo voidaan sitten suoraanpalauttaa lopetusehdon täyttyessä.
▼ Tuosta seuraa, että ylimääräisen parametrinalkuarvon tulee olla triviaalitapauksen lopputulos.
▼ Häntärekursio on tärkeä käsite, koska se voidaanmuuttaa mekaanisesti silmukkarakenteeksi ➠saavutetaan rekursion hyödyt mutta päästään eroonsen huonoista puolista.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 442
Muisti
▼ Kaikki tietokoneen käsittelemä tieto talletetaankoneen (keskus)muistiin.✶
▼ Muistia voi ajatella joukkona numeroituja lokeroita:
· · ·0 1 2 3 4 5 6 7 8 9 · · · n
joista jokaiseen prosessori voi tallettaa pienenmäärän tietoa.
▼ Yhtä lokeroa kutsutaan muistipaikaksi ja senjärjestysnumeroa (muisti)osoitteeksi.
▼ Muistipaikkaan mahtuu tietoa talteen yksi tavu
(byte) joka puolestaan koostuu 8 bitistä.
▼ Yleensä muistia käsitellään kuitenkin tavun moni-kerran kokoisina paloina.
✶ Ei ihan koko totuus, mutta ainakin kaikki lähtötiedot ovat jossain vaiheessatalletettuina keskusmuistiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 443▼ Kun tiedetään muistiosoite, niin siellä tallessa olevaan
informaatioon päästään käsiksi (haku ja talletus).
▼ Yhteen muistipaikkaan voidaan siis tallettaatietoalkio, joka on esitettävissä 8 bitillä (esim. char)
➠ jos on tarve tallentaa tietoa, joka esitetäänsuuremmalla bittimäärällä, niin käytetään riittävämäärä peräkkäisiä muistipaikkoja.
▼ Esimerkiksi jos int-tietotyypin alkiot ovat 32-bittisiä,voitaisiin yksi kokonaisluku (siis sen binääriesitys)tallettaa neljään peräkkäiseen muistipaikkaan:
32-bittiä︷ ︸︸ ︷
11101· · ·101 · · ·0 1 2 3 4 5 6 7 8 9
▼ Vastaavasti 64-bittinen double voitaisiin tallettaa:
OHJ-1150 Ohjelmointi II 444▼ Esimerkeistä huomataan, että samoihin muistipaik-
koihin voidaan tallettaa eri aikoina eri tyyppistäinformaatiota.
▼ Koska tietokoneen uumenissa kaikki tieto esitetäänbitteinä, niin pelkästään muistipaikan sisältöä tutki-malla ei voi päätellä, minkä tyyppistä informaatiotasinne on talletettu.
▼ Tuosta päädytään vanhaan totuuteen: kaikellatiedolla pitää olla tyyppi, jotta sitä osattaisiin käsitelläoikein.
▼ Ohjelmointikielessä muuttujan määrittely on tapaantaa symbolinen nimi jonkin muistipaikan sisällölle
➠ kääntäjä valitsee muuttujalle talletuspaikanmuistissa ja pitää kirjaa tyyppi-informaatiosta.
int main( ) {4 int i;5 int* ip = nullptr; / / nullptr selitetty s. 450
6 i = 5;7 ip = &i;8 *ip = 42;
9 cout << i << "," << *ip << ","10 << ip << "," << &ip << endl;
}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 448▼ Kun ohjelma suoritetaan hypoteettisella tietoko-
neella, ilmestyy näytölle:proffa> ./osoitintesti42,42,0x000002,0x000008
▼ Tutkitaan lähdekoodirivi kerrallaan, mitä suorituksenaikana tapahtuu:
5 Luodaan kokonaislukumuuttuja i: varataan sille tilaamuistista jostain vapaasta kohdasta (0x000002).
6 Luodaan osoitin kokonaislukuun ip: varataan sille tilaamuistista jostain vapaasta kohdasta (0x000008).
7 Sijoitetaan i:hin arvo 5, siis talletetaan 5:n binääriesitysosoitteesta 0x000002 alkaviin muistipaikkoihin.
8 Selvitetään muuttujan i osoite (0x000002) &-operaattorillaja talletetaan se muuttujaan ip eli osoitteesta 0x000008alkaviin muistipaikkoihin.
9 Sijoitetaan ip:hen talletetusta osoitteesta (0x000002)alkaviin muistipaikkoihin kokonaisluvun 42 binääriesitys(samat muistipaikat joissa muuttuja i on tallessa, siis i:narvo muuttuu).
10 ja 11Tulostetaan mielenkiintoiset arvot. Koska osoitin ip onmuuttuja, sen sijainti muistissa saadaan &-operaattorilla.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 449▼ Kuvallisesti esimerkin muistinkäyttö näyttäisi
▼ Esimerkissä on oletettu osoittimen olevan kooltaan4 tavua eli 32-bittiä.
▼ Esimerkistä havaitaan myös, että lausekkeessakäytettynä *ip käyttäytyy kuin int-tyyppinenmuuttuja ➠ sille evaluoituu arvo muistipaikasta,johon ip osoittaa ja siihen voidaan suorittaa sijoitus=-operaattorilla, jolloin arvo tallettuu kyseiseenmuistipaikkaan.
▼ Sama pätee muunkin tyyppisiin osoittimiin.
▼ Osoittimen arvo kertoo, missä jokin tieto sijaitsee.
▼ *-operaattorilla osoitimesta saadaan selville varsi-
nainen osoitettu tieto.
▼ Sanomattakin(?) on selvää, että osoittimeen voitallentaa vain sellaisen muuttujan osoitteen, jonkatyyppi on sama kuin osoittimen kohdetyyppi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 450▼ Esimerkin rivillä 6 ip-osoittimeen alustetaan arvo
nullptr, tämä on erittäin tärkeä pieni yksityiskohta.
▼ Tälläista osoitinta kutsutaan null- tai nil-pointteriksi.
▼ Kaikkiin osoitinmuuttujiin voidaan tallettaa nullptr
(tai kokonaislukuliteraali 0) ➠ osoittimelle saadaannäin asetettua arvo, joka ei osoita minnekään.➀
▼ Osoittimen yhtä- ja erisuuruutta voi myös ainavertailla nullptr-arvon kanssa ja sitä voi käyttääosoitintyyppisenä todellisena parametrina.
▼ Jos ohjelman suorituksen aikana yritetään »seurata»➁
null-osoitinta, on tuloksena aina ajonaikainen virheja ohjelman päättyminen (core).
▼ Null-osoitinta kannattaa (aina) käyttää osoitinmuut-tujien alustusarvona ja virhepaluuarvona funktioilta,jotka palauttavat osoittimen.
➀ Vanhemmissa kääntäjissä vain literaalinen kokonaisluku nolla (0) kelpaanull-osoittinliteraaliksi. Tämä kannattaa pitää mielessä, jos joutuu joskustilanteeseen, jossa on käytettävä kääntäjää, joka ei tunnista varattua sanaanullptr. Myös cstddef-kirjaston vakio NULL on silloin mahdollinenkorvaava vaihtoehto.
➁ Eli tutkia tietoa, johon osoitin osoittaa.Eli kohdistaa osoittimeen unäärinen *-operaattori tai –> -operaattori.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 451
Osoittimet struct- ja
class-tietotyyppeihin
▼ Dynaamisten tietorakenteiden yhteydessä joudutaanusein käsittelemään osoittimia tietueisiin ja/tailuokkiin.
▼ Tällaisia osoittimia luodaan aivan tavalliseen tapaan:
struct Opiskelija {string nimi;...
};...Opiskelija* optr = nullptr;
ja
class Polynomi {public:
double evaluoi(double x) const;...
private:...
};...Polynomi* polptr = nullptr;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 452
▼ Sen jälkeen kun osoittimeen on sijoitettu muisti-osoite, jossa oikeasti on jotain tietoa tallessa, niinkenttiin ja jäsenfunktioihin päästään osoittimenvälityksellä käsiksi:
string opiskelijan_nimi = (*optr).nimi;
ja
double tulos = (*polptr) .evaluoi(2.0);
▼ Sulutus johtuu siitä, että . -operaattorin presedenssion korkeampi kuin unaarisen *-operaattorin
➠ jatkuva sulkujen kirjoittaminen alkaa pianottaa päähän, minkä vuoksi sille on olemassalyhennysmerkintä:
string opiskelijan_nimi = optr –>nimi;
ja
double tulos = polptr –>evaluoi(2.0);
▼ Operaattori –> siis valitsee kentän tai jäsenfunktion(myös jäsenmuuttujan) seuraamalla ensin osoitintatiedon varsinaiseen sijaintipaikkaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 453
Taulukot ja osoittimet
▼ Luodaan kolmen alkion kokonaislukutaulukko iv jaint-osoitin ip:
int iv[3];int* ip = nullptr;
▼ Kääntäjä sijoittaa taulukon muistiin yhtenä pötkönä
▼ C++:ssa siis taulukoiden ja osoittimien välillä onyhteys, niitä kumpaakin voidaan käyttää samoillaoperaattoreilla.
▼ Kun lisäksi muistaa, että kääntäjä varaa tauluk-komuuttujan aina yhtenä pötkönä muistista, voiominaisuutta käyttää dynaamisten taulukoidenluonnissa ja käsittelyssä.
▼ Tärkeä ero taulukon ja osoittimen välillä on se,että taulukkomuuttujan määrittelyyn sisältyy ainaautomaattinen muistinvaraus, osoitinmuuttuja
ohjelmoijan pitää alustaa itse.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 456
Muuttujan näkyvyysalue ja elinikä
▼ Muuttujan näkyvyysalue (scope, visibility) on seosa ohjelmaa, jossa muuttujaa voidaan käyttää:
– Lohkon sisällä (funktion tai kontrollirakenteenaaltosulkeiden sisällä) määriteltyä paikallista
muuttujaa voi käyttää määrittelykohdastaankyseisen lohkon loppuun saakka.
– Luokan jäsenmuuttujia voidaan käyttää senjäsenfunktioissa.
– Moduulin yksityistä muuttujaa voidaan käyttääyhden lähdekooditiedoston (.cc) sisällä.
– Globaalia muuttujaa (määritelty kaikkienfunktioiden ja luokkien ulkopuolella) voi käyttääkaikkialla ohjelmassa.
▼ Edellisessä sana »käyttää» tarkoittaa, että muuttujannimen voi kirjoittaa koodiin, eikä kääntäjä anna siitävirheilmoitusta »tuntematon muuttuja» tms.
▼ Ohjelmassa voi olla useita keskenään samannimisiämuuttujia, kunhan niitä ei ole määritelty samannäkyvyysalueen sisällä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 457▼ On tärkeätä ymmärtää, että näkyvyysalue määrää
vain, missä muuttujan nimi on käytettävissä.
▼ Aiemmasta on tuttua, että muuttujalle varataanmäärittelyvaiheessa muistia, johon muuttujan arvofyysisesti talletetaan laitteistotasolla.
▼ Muuttujan elinikä määrää, kuinka kauan muuttujaon olemassa ➠ kuinka sen tarvitsema muistipidetään varattuna ➠ kuinka kauan sen arvo säilyy.
▼ On houkuttelevaa tehdä virhepäätelmä, ettänäkyvyysalue ja elinikä tarkoittavat samaa, koska niinsattuu olemaan normaaleille paikallisille muuttujille:
void fun( ) {int i; // näkyvyysalue ja elinikä (muistinvaraus) alkaa
...// kun funktion loppu saavutetaan, niin näkyvyys-// alue päättyy ja muisti vapautetaan
}
▼ Eli i:n näkyvyysalue ja elinikä ovat samat.
▼ Tällaisia muuttujia kutsuaan joskus automaattisiksi
muuttujiksi, koska ne automaattisesti luodaannäkyvyysalueensa alussa ja tuhotaan sen lopussa.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 458▼ Automaattisille muuttujille varataan muisti aina
uudelleen, kun niiden määrittely »suoritetaan».
▼ Näin ei kuitenkaan välttämättä ole, elinikä voi olla:
– Automaattinen kuten edellä esitettiin.
– Staattinen (static) jolloin elinikä on koko ohjelmansuoritusaika (globaalit ja staattiset muuttujat sekämoduulin yksityiset muuttujat).
– Dynaaminen (dynamic) jolloin ohjelmoija määrääeliniän eksplisiittisillä käskyillä.
▼ Dynaamisiin muuttujiin paneudutaan jatkossaurakalla, tutkitaan tässä pieni hahmotelmaesimerkkistaattisesta paikallisesta muuttujasta:
if (ensimmainen_kerta) {// Tee alustustoimet, jotka halutaan suorittaa
// vain funktion ensimmäisellä kutsukerralla.
ensimmainen_kerta = false;}...
}▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 459
▼ Staattiset muuttujat määritellään lisämääreen static
avulla.
▼ Paikalliset staattiset muuttujat noudattavat normaa-leja näkyvyysaluesääntöjä.
▼ Niiden elinikä on kuitenkin koko ohjelman suori-tusaika ➠ niille varattua muistia ei vapautetamäärittelylohkon lopussa.
▼ Kun näkyvyysalueen sisälle palataan myöhemmin,staattiset muuttujat »muistavat» arvonsa edelliseltäkerralta.
▼ Staattisen muuttujan alustus tapahtuu vain
kerran käännösaikana ➠ ajatuksellisesti sen voitulkita tapahtuvaksi, kun muuttujan määrittelyrivi»suoritetaan» ensimmäisen kerran.
▼ Myöhemmillä kerroilla muuttujan arvona on siihenedellisellä kerralla sijoitettu arvo.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 460
Dynaaminen muistinhallinta
▼ Joustavien tietorakenteiden toteutus edellyttää, ettäohjelma/ohjelmoija huolehtii itse muistinhallinnasta:– Tietorakenteen koko✶ voidaan määrätä vasta
suorituksen aikana ja koko voi vaihdella.
– Ohjelmoija voi eksplisiittisesti määrätä tieto-alkioiden eliniän.
▼ Edelliset ehdot täyttäviä tietorakenteita kutsutaandynaamisiksi tietorakenteiksi (dynamic data
structures).
▼ Dynaamiset rakenteet ovat entuudestaan tuttujenstaattisten rakenteiden vastakohta ➠ staattistenrakenteiden– koko on oltava tiedossa käännösaikana eikä se
ohjelman suorituksen kuluessa muutu ja
– elinikä määräytyy muuttujan määrittelyhetkellä.
✶ Siis sen käyttöön varatun muistin määrä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 461▼ C++-kielen työkalut dynaamisen muistinhallinnan
toteuttamiseen ovat operaattorit new ja delete:new tietotyyppi ;
Varaa riittävästi muistia, jotta sinne voidaan tallettaatietotyypin alkio. Jos tietotyypillä on rakentaja, jokaei tarvitse parametreja,✶ niin sitä kutsutaan varatundynaamisen muuttujan alustamiseksi. Muussa tapauksessamuuttujaa ei alusteta. Arvoksi evaluoituu osoitin varattuunmuuttujaan. Esimerkiksi:
int* iptr = new int;Murtoluku* mlptr = new Murtoluku;
new tietotyyppi( rakentajan_parametrit ) ;Kuten edellä, mutta muuttuja alustetaan rakentajalla, jonkaparametrit täsmäävät✶ rakentajan parametrien kanssa:
int* iptr = new int(42);Murtoluku* mlptr = new Murtoluku(2, 3);
Vapautettavan muuttujan on oltava new:llä varattu, muutenpahuus voittaa.
✶ Kuormitukset ja oletusparametrit huomioidaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 462▼ new:stä dynaamisen muuttujan elinikä siis alkaa ja
delete:en se päättyy.
▼ Koska C++ »samaistaa» taulukot ja osoittimet, onnew:stä ja delete:stä olemassa versiot, joilla voidaanvarata ja vapauttaa dynaaminen taulukko:new tietotyyppi [ alkioiden_määrä ] ;
Varaa dynaamisen taulukon, jossa on alkioiden määrä
alkiota, jotka ovat tyypiltään tietotyyppiä. Alkiot alustetaanparametrittomalla rakentajalla,✶ jos sellainen on. Muutoinalkoita ei alusteta. Esimerkiksi:
Vapautettavan taulukon on tuttuun tapaan oltava jokonew[ ]:llä varattu tai null.
▼ Jotta taulukko-new:n ja -delete:n voisi ymmärtää,kannattaa kerrata sivut 452–455.
✶ Kuormitukset ja oletusparametrit huomioidaan.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 463▼ Dynaamisesti varatun muistin käsittelyyn sisältyy
kaksi tärkeää periaatetta:1. Muistinvarauksen onnistuminen on aina
tarkistettava.
2. Ohjelman on vapautettava kaikki dynaaminenmuisti, jota se ei enää tarvitse.
▼ Jos muistinvaraus epäonnistuu, eikä ohjelmoijavaraudu siihen, on new-operaattorin normaalitoi-minto ohjelman kaataminen (UNIX:ssa core dump).
▼ Jos varattua muistia ei jossain vaiheessa vapautetamutta sitä kuitenkin varataan jatkuvasti lisää, niinjossain vaiheessa vapaa muisti loppuu.
▼ Itse asiassa muistin vapauttamatta jättäminen eiitsessään ole virhe, jos ohjelma tosiaankin käyttääkaikkea varaamaansa muistia.
▼ Ongelma syntyy silloin, kun osoitin dynaamisestivarattuun muuttujaan »hukataan» ➠ muistia ei voidavapauttaa, koska delete tarvitsee vapautettavanmuuttujan osoitteen ➠ tällaista hukkamuistinsyntymekanismia kutsutaan muistivuodoksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 464▼ Myöhemmin, kun yritetään havainnollistaa dynaa-
misia rakenteita, osoittautuu seuraava visuaalinenesitystapa hyödylliseksi.
▼ Ajatellaan yksinkertaista ohjelmanpätkää:struct Data {
int luku;int* iptr;
} ;
int i = 7;Data data;data.luku = 13;data.iptr = &i;Data* dataptr = &data;
▼ Kun nuo rivit on suoritettu, näyttää muistitilannejotenkin seuraavalta:
OHJ-1150 Ohjelmointi II 465▼ Sen sijaan että esitettäisiin tilanne edellisen kaltaisella
sekavalla lokerikolla, merkitään muuttujia laatikoillaja osoittimia nuolilla seuraavaan tapaan:
7
13
dataptr
data
i
luku
iptr
sillä eihän muistiosoitteiden absoluuttisilla arvoillaole sinällään merkitystä ➠ tärkeintä on nähdä, kukaosoittaa minnekin.
▼ Edellisenkaltaisissa kaavioissa null-osoittimelle:int* ip = nullptr ;
käytetään yleensä merkintää:
ip
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 466
Dynaamiset tietorakenteet
▼ Yksinkertaisissa dynaamisissa tietorakenteissa ontiettyjä yhtäläisyyksiä ➠ ne koostuvat tietue- tailuokkatyyppisistä alkoista, jotka linkittyvät toisiinsaosoittimien avulla.
▼ Lisäksi käytössä on osoitin, joka kertoo missävarsinaisen linkkiketjun ensimmäinen alkio on.
▼ Esimerkiksi jos halutaan tallettaa dynaamiseentietorakenteeseen kokonaislukuja, määriteltäisiinseuraavankaltainen tietue ja osoitinmuuttuja:
struct Alkio {int luku;Alkio* seuraava;
} ;
Alkio* ensimmainen = nullptr ;
jolloin syntyvä rakenne näyttää muutaman luvun(4kpl) lisäämisen jälkeen seuraavalta:
ensimmainen
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 467▼ Alkio-tietue ja osoitin ensimmäiseen alkioon kätke-
tään abstraktin tietotyypin yksityiseen rajapintaan ➠kaikki operaatiot julkisen rajapinnan kautta.
▼ Syntyvää rakennetta kutsutaan linkitetyksi/ketju-
tetuksi listaksi (linked list) tai tarkemmin yhteen
suuntaan linkitetyksi listaksi (singly linked list).
▼ Myös monimutkaisemmat rakenteet ovat tokimahdollisia: kahteen suuntaan linkitetyt listat, puut,verkot jne ➠ vain ratkaistava ongelma ja mielikuvitusasettavat rajat.
▼ On aika tutkia ensimmäinen kokonainen esimerkki.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 468
Esimerkki: dynaaminen pino
▼ Yleisesti pino (stack) on tietorakenne, johon voidaankohdistaa kolmenlaisia operaatioita:
push
lisää uuden alkion pinon päällimmäiseksi.
pop
poistaa pinon päällimmäisen alkion ja palauttaa sen.
empty
kertoo onko pino tyhjä vai ei.
▼ Push- ja pop-operaatiot kohdistuvat pinon pääl-limmäiseen alkioon ➠ syvemmällä pinossa oleviinalkioihin päästään käsiksi vain suorittamalla useitaperäkkäisiä pop-operaatioita.
▼ Alkiot saadaan pinosta pois käänteisessä järjestyksessäverrattuna sisäänmenojärjestykseen ➠ pino onlast in–first out -rakenne (LIFO).
▼ Pino on linkitetyistä tietorakenteista helpointoteuttaa, koska kaikki listaa muokkaavat operaatiotkohdistuvat listan alkuun.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 469▼ Esimerkiksi reaalilukupinon (pino johon voidaan
/ / Dynaamisella rakenteella on oltava rakentaja,// joka alustaa paallimmainen–osoittimen null:iksi.Pino( );
/ / Paluuarvo true, jos pinossa ei ole alkioita.bool empty( ) const;
/ / Työntää luvun pinon päälimmäiseksi alkioksi./ / Paluuarvo true jos onnistuu, false, jos ei// (muistia ei saada varattua).bool push(double luku);
// Poistaa pinon päällimmäisen alkion ja palauttaa// sen muuttujaparametrissa luku. Paluuarvo true// jos onnistuu, false jos pino oli tyhjä.bool pop(double& luku);
// Dynaamisella tietorakenteella on oltava purkaja,// joka vapauttaa dynaamisesti varatun muistin,// kun sitä ei enää tarvita.~Pino( );
private:struct Alkio {
double data;Alkio* seuraava;
};
Alkio* paallimmainen;};#endif
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 470▼ Pino:n toteutus pino.cc näyttäisi tältä:
▼ Esimerkkiä ei ole tässä tarkoitus selitellä monisanai-sesti, koska ideana on käydä pino-tyypin toteutus jatoiminta luennolla läpi askel-askeleelta.✶
▼ Pinon toteutukseksi on valittu linkitetty lista, joka onkätketty abstraktin tietotyypin sisään.
▼ Ainoa jäsenmuuttuja paallimmainen kertoo, missälistan ensimmäinen alkio sijaitsee.
▼ Jos lista on tyhjä, paallimmainen on null-osoitin.
▼ Viimeisen alkion seuraava-kenttä on aina null.
▼ Uuden alkion lisäys (push) ja alkion poisto (pop)kohdistuu aina listan alkuun.
▼ new-operaattorin epäonnistumisen testaustalukuunottamatta muu koodi on olennaisilta osiltaanosoittimien päivittelyä oikeassa järjestyksessä, niinettä listan laillinen rakenne säilyy eikä dynaamisestivarattuja muistilohkoja hukata.
✶ b there || b �
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 474▼ Rakentaja alustaa pinon tyhjäksi eli sijoittaa null:in
osoittimeen paallimmainen.
▼ C++:ssa purkaja (s.326) on jäsenfunktio, jonka
– nimi on sama kuin abstraktin tietotyypin nimi,jonka eteen on lisätty tilde (~) ja jolla
– ei ole paluuarvon tyyppiä.
▼ Purkajaa kutsutaan kulissien takana automaattisesti,
kun abstraktia tietotyyppiä olevan muuttujanelinikä päättyy ➠ kun pinoa ei enää tarvita,sen mahdollisesti varaama dynaaminen muistivapautetaan ~Pino-funktion avulla.
▼ Huomaa kuinka ~Pino asettaa paallimmainen-osoittimen arvoksi null:in ➠ ei jäänneviitteitä eliosoittimia, jotka osoittavat jo vapautettuun muistiin
➠ jatkossa ei voida vahingossa käyttää vapautettuadynaamista muutujaa.
▼ ~Pino-jäsenfunktio huolehtii periaatteen 2 (s.463)toteutumisesta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 475
new:n onnistumisen tarkistaminen
▼ Jos dynaamisen muistin varaus new:llä epäonnistuu,niin oletusarvoisesti C++ käyttää sen ilmaisemiseenmekanismia, jota kutsutaan poikkeukseksi.
▼ Hyväksytään toistaiseksi totuutena esimerkissä ollutidea, mutta palataan asiaan yksityiskohtaisemminmyöhemmin poikkeuksien yhteydessä:
tyyppi* osoitin_dynaamiseen_muuttujaan ;
try {osoitin_dynaamiseen_muuttujaan = new tyyppi ;
} catch ( ... ) {// Virhetilanteen käsittely: mitä tehdään// jos muistia ei saanut varattua....
}
// Normaalitilanteen käsittely:// muisti saatiin varattua....
▼ Tämä rakenne toteuttaa periaatteen 1 (s.463).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 476
Poikkeukset
▼ Tutustutaan lyhyesti C++-kielen poikkeusmekanismiinja sen yksinkertaisimpiin käyttötarkoituksiin:
OHJ-1150 Ohjelmointi II 478▼ Poikkeuksien avulla voidaan delegoida virhe-
tilanteiden käsittely jonnekin muualle kohdasta, jossavirhe varsinaisesti havaittiin.
▼ Poikkeuksien avulla voidaan myös välittää tietoavirheen tapahtumiskohdasta/laadusta virhetilanteenkäsittelijälle.
▼ Välitettävä tieto voi olla mitä tahansa tyyppiä,mutta useimmiten käytetään abstraktia tietotyyppiä,sillä sen avulla on helppo esittää ja käsitellämonimutkaistakin virheinformaatiota.
▼ Koodi, jonka suoritus saattaa aiheuttaa poikkeuksen,laitetaan try-lohkon sisään.
▼ Poikkeus voidaan ottaa kiinni (eli käsitellä) try–catch-rakenteen avulla.
▼ Jos/kun poikkeus on heitetty, järjestelmä etsiifunktiokutsujen hierarkiassa viimeisimmän try-catch-rakenteen, jossa catch-komennolla otetaan kiinniheitetyn tyyppinen poikkeus.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 479▼ Suoritus jatkuu löytyneen catch-haaran perässä
olevan lohkon sisällä.
▼ Käsittelemätön poikkeus ➠ ohjelma kaatuu.
▼ Joskus on tarpeen ottaa kiinni poikkeus riippumattasen tyypistä ➠ catch-osan »muodollisen parametrin»paikalle voi kirjoittaa kolme pistettä:
▼ Tyypin out_of_range poikkeus heitetään ainakinstandardikirjaston vector- ja string-tyyppienat( ) -funktioissa, jos indeksi on liian suuri.
✶ Vaikka sitä ei kurssin tiedoilla voikaan kattavasti perustella.▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 481▼ Poikkeuksen heittäminen siirtää ohjelman suorituksen
mahdollisesti hyvinkin kauas heittokohdasta ➠ohjelman selkeys saattaa kärsiä.
▼ Usein sopivasti määritellyt funktioiden paluuarvot janiiden tutkiminen virhetilanteiden tunnistamiseksion vähintään yhtä hyvä ja lähes aina selkeämpi tapa.
▼ Poikkeukset ovat kuitenkin käyttökelpoinenmekanismi, jos:
– virheidenkäsittely halutaan tehdä keskitetystiyhdessä paikassa,
– virhe tapahtuu jossain syvällä funktiokutsuhie-rarkiassa, jolloin tiedon välittäminen tapahtuneestapaluuarvojen avulla virhetilanteen käsittelykohtaanolisi hankalaa,
– ohjelmassa käytetään kirjastoa, joka reagoivirhetilanteisiin heittämällä poikkeuksen tai
▼ Käytä poikkeuksia harkiten jos ollenkaan (ainakaanOhjelmointi II-kurssin tietotaidoilla).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 482
Pino toteutettuna dynaamisena
taulukkona
▼ Tässä vaiheessa on hyvä varmistaa, että ymmärtääpinon ja listan tarkoittavan eri asiaa.
▼ Lista on toteutusmekanismi, joka perustuu linkki-kenttien käyttöön pidettäessä kirjaa seuraaja-alkionsijainnista.
▼ Listan avulla voidaan toteuttaa pino, mutta listanavulla voidaan toteuttaa muunkinlaisia rakenteita.
▼ Pino puolestaan on toteutustavasta riippumattasellainen abstraktio, jossa ainoastaan viimeisintärakenteeseen lisättyä alkiota päästään suoraankäsittelemään (LIFO).
▼ Jotta tämä varmasti tulee selväksi, toteutetaandynaaminen pino taulukkona listan sijaan.
▼ Samalla tämä toimii esimerkkinä taulukko-new[ ]:nja taulukko-delete[ ] :n käytöstä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 483▼ Moduulin esittelytiedosto pino.hh
#ifndef PINO_HH#define PINO_HH
// using namespace std; ei pakollinen, koska mitään// kirjastoja ei tässä esittelytiedostossa käytetä.
class Pino {public:
/ / Sama abstraktio kuin alkuperäisessä esimerkissä// ➠ julkinen rajapinta on identtinen.
OHJ-1150 Ohjelmointi II 487▼ Koska C++:ssa on taulukoiden ja osoittimien välillä
tunnettu yhteys, voidaan dynaamisesti varattuataulukkoa käsitellä osoittimena sen ensimmäiseenalkioon.
▼ Uuden pinotyypin julkinen rajapinta on sama kuinalkuperäisen ➠ alkuperäinen voidaan tarvittaessakorvata uudella tai päinvastoin.
▼ Uusi toteutus yrittää optimoida jatkuvan muistinva-rauksen tarvetta varaamalla kerralla tilaa useammallealkiolle ➠ osaa varatusta muistista ei välttämättäkoskaan käytetä ➠ tuhlausta(?)
▼ Toisaalta alkuperäinen listatoteutuskin käyttääylimääräistä muistia linkkikenttien ylläpitämiseen.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 488
Tietorakenteen kopiointi:
alustus ja sijoitus
▼ Kertaus: alustus tarkoittaa alkuarvon asettamistamuuttujalle sen määrittelyvaiheessa.
▼ Taulukoita lukuunottamatta muuttujia voi alustaaC++:ssa toisella samantyyppisellä arvolla:
int i(5); // Huomaa kaksi eri syntaksia alustamiselle.int j = 6; // Molemmat tarkoittavat samaa.Opiskelija o1;· · ·Opiskelija o2(o1);Opiskelija o3 = o2;
▼ Kannattaa huomata, että alustus tapahtuu myössilloin, kun arvoparametrit alustetaan todellisistaparametreista ➠ muodollisen parametrin arvoksialustetaan todellisen parametrin arvo.
▼ Muuttujaan voi myös sijoittaa toisen samantyyppisenarvon =-operaattorilla:
int i;i = 5;Opiskelija o1, o2;· · ·o2 = o1;
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 489▼ Alustuksen ja sijoituksen oletussemantiikka on, että
kaikki sijoitettavat tiedot kopioidaan tavu-tavultamuistissa lähdemuuttujasta kohdemuuttujaan.
▼ Jos kopioitavana on tietue tai luokka, kukin kenttä taijäsenmuuttuja kopioidaan tavu-tavulta muuttujastatoiseen.
▼ Tällainen kopiointi on ohjelmoijan kannaltapääsääntöisesti järkevä ja looginen toimintatapa.
▼ Jos kuitenkin kopiotava tietoalkio on osoitin,seurauksena on lähes aina ongelmia, erityisesti joskyseessä on osoitin dynaamisesti varattuun muistiin.
▼ Tämä johtuu siitä, että vain osoittimen arvokopioituu, ei osoittimen osoittama tieto (matala-
kopio, shallow copy) ➠ Tämä on C++:ssa alustuksenja sijoituksen oletuskäyttäytyminen.
▼ Havainnollistetaan ongelmaa pienellä koodinpätkällä:
Pino p1;p1.push(1.0);p1.push(2.1);Pino p2(p1);
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 490▼ Tai aivan sama tilanne, vaikka koodi olisi:
▼ Molemmat koodit menevät käännöksestä läpi, muttalähempi tarkastelu tuo ilmi vakavan ongelman.
▼ Pino-luokka koostuu osoitinjäsenmuuttujastapaallimmainen, joka osoittaa linkitetyn listanensimmäiseen alkioon.
▼ Kun tämä osoitin kopioidaan tavu-tavulta muuttu-jasta p1 muuttujaan p2, saadaan kaksi pinoa, jotkamolemmat käsittelevät samaa dynaamista listaa:
p1 p2
2.1 1.0
paallimmainenpaallimmainen
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 491▼ Syntyneessä tilanteessa esimerkiksi operaatio:
p2.pop(luku);
sotkee tilanteen pahasti:
p1 p2
1.0
paallimmainenpaallimmainen
▼ Pinon p1 paallimmainen-osoitin jää nyt osoittamaanvapautettuun dynaamiseen alkioon (jäänneviite).
▼ Ongelman ratkaisuun on kaksi mahdollisuutta:
– Estetään kopioituminen täysin (siis kielletäänalustus ja sijoitus samantyyppisestä alkiosta).
– Määritellään nk. kopiorakentaja ja sijoitus-operaattori kyseessä olevalle tietotyypille siten,että kopiointi ei tapahdu C++:n oletussemantiikanmukaisesti, vaan tyypin kannalta järkevästi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 492▼ Kopioitumisen esto on helppo toteuttaa:
▼ Lisäämällä kopiorakentaja- ja sijoitusoperaattori-funktioiden esittely luokan private-osaan jajättämällä funktiot määrittelemättä, saadaan ainakäännösvirhe, jos rakennetta yritetään tavalla taitoisella kopioida.
▼ Kopiorakentajafunktio on muodoltaan ainaseuraavan näköinen:
Tyyppinimi(const Tyyppinimi& alustusarvo);
▼ Sijoitusoperaattori taas on muotoa:Tyyppinimi& operator=(const Tyyppinimi& sijoitettava) ;
▼ Funktion nimenä oleva operator= on C++:n tapaesittää, että kyseessä ei ole normaalifunktio, vaanettä ollaan kertomassa, kuinka = -operaattori toimiikyseisellä luokalle.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 493▼ Sijoitusoperaattorin paluuarvon tyypin on oltava
viite käsiteltävään tietotyyppiin, koska se vastaaC++-kielen oman =-operaattorin toimintaa ➠ tätäei ole kriittistä ymmärtää, kunhan muistaa tehdäpaluuarvon tyypistä viitteen.
▼ Jos ei ole erityistä syytä sallia rakenteen kopiointia,edellä esitelty tapa on suositeltava ➠ Estä aina
jatkossa kopioituminen, paitsi jos todella haluat
sallia sen tietotyypillesi.
▼ Uudessa C++-kielen versiossa oletuskopiorakentajanja -sijoitusoperaattorin voi estää myös selkeämmin: ✶
✶ Tämä on uuden c++-standardin tuoma ominaisuus, joka toimiiOhjelmointi II-kurssilla käytetyssä ohj2c++-kääntäjässä. Jos osallistutjatkossa muille Ohjelmistotekniikan laitoksen järjestämille kursseille, joissaopetuskielenä on c++, siellä saattaa olla käytössä vanhempi versio
kääntäjästä, eikä tämä ominaisuus toimi. Älä siis tule liian riippuvaiseksiominaisuuden olemassaolosta.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 494▼ Joskus on tarve toteuttaa kopiorakentaja ja
sijoitusoperaattori, jotka kopioivat rakenteen toiseenilman ongelmia.
▼ Tällöin ohjelmoijan on esiteltävä kopiorakentajaja sijoitusoperaattori luokan public-osassa jamääriteltävä ne siten, että kopiointi tapahtuuhalutulla tavalla.
▼ Muutetaan dynaamisena taulukkona toteutettuapinoa siten, että alustus ja sijoitus toisesta pinostatoimivat oikein.
▼ Lisätään kopiorakentajan ja sijoituksen esittelyt Pino-luokan julkiseen rajapintaan pino.hh-tiedostossa:
// tarpeeksi uutta tilaa.delete[ ] taulukko;tilaa_kaikkiaan = sijoitettava.tilaa_kaikkiaan;taulukko = new double[tilaa_kaikkiaan];
}
// Jatkuu seuraavalla sivulla...▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 496// ... jatkoa edelliseltä sivulta
// Kopioidaan kaikki alkiot lähdetaulukosta
// kohdetaulukkoon.for ( int i = 0; i < alkioita; ++i ) {
taulukko[i] = sijoitettava.taulukko[i];}
}
return *this;}
▼ Olennaista edellä on se, että dynaamisesti varatustamuistista tehdään oma dynaaminen kopio kopioinninkohteelle (syväkopio, deep copy).
▼ Jos pinon toteutus olisi ollut linkitetty lista, olisikopionti tietysti hiukan monimutkaisempaa, koskakoko listasta pitäisi tehdä kopio alkio-alkiolta.
▼ Kopiorakentaja on yleensä melko suoraviivainen,koska se vain kopioi tiedot paikasta toiseen.
▼ Sijoitusoperaattori on tavallisesti kopiorakentajaamonimutkaisempi, koska sijoituksen kohde onolemassa oleva muuttuja ➠ operaation otettavakantaa siihen, mitä vanhalle tiedolle tehdään(muistin vapauttaminen yms.)
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 497▼ Sijoitusoperaattorin toteutukseen liittyy yksi
erityispiirre, jota kannattaa korostaa: mitä tehdä, josohjelmoija jostain syystä sijoittaa muuttujan arvonitseensä:
Pino p;· · ·p = p;
▼ Tämä on laillinen operaatio, mutta sijoitusoperaat-torin kannalta sikäli erikoinen, että sen ei (yleensä)tarvitse tehdä mitään.
▼ «Itseensäsijoitus» huomataan sijoitusoperaattori-funktiossa siten, että vertaillaan osoitinmuuttujanthis arvoa lähtöarvomuuttujan osoitteeseen:
if ( this != &sijoitettava ) {// Mitä tehdään jos sijoitettava ja// sijoituksen kohde ovat eri muuttujia.· · ·
}
▼ Muuttuja this on automaattisesti määriteltynäkaikien luokkien kaikissa jäsenfunktioissa ja seosoittaa siihen muuttujaan, jonka välitykselläjäsenfunktiota kutsuttiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 498▼ Esimerkiksi jos p, p1 ja p2 ovat tyypiltään luokkia:
// Seuraavassa print-funktion kutsussa this// osoittaa muuttujaan p.p.print( );
// Nyt this osoittaa print-funktiossa muuttujaan p1.p1.print( ) ;
// operator= -funktiossa this osoittaa// muuttujaan p1.p1 = p2;
▼ Jälkimmäinen esimerkki ei ehkä ole itsestäänselvä,mutta se juontaa juurensa siitä, että luokkatyyppisillämuuttujilla seuraavat kaksi tarkoittavat samaa:
p1 = p2; // Tämä on vain lyhyempip1.operator=(p2); // tapa kirjoittaa tämä.
▼ Funktion operator= paluuarvon on aina oltava *this.
▼ Samaa operatorX(. . . )-ideaa voi käyttää muidenkinkielen operaattorien kuormittamiseen luokkatyypeille
▼ Syntyvän rakenne on tuttu yhteen suuntaan linkitettylista, mutta uusina piirteinä:
– Lisättävä alkio menee aina automaattisestiaakkosjärjestyksensä mukaiselle paikalle ➠listan alkiot ovat nimen mukaisessa kasvavassaaakkosjärjestyksessä.
– Puhelinluettelosta voidaan hakea ja poistaa infor-maatiota käyttäen henkilön nimeä hakuavaimena.
– Koko puhelinluettelo voidaan tulostaa näytölle.
▼ Sama henkilö voi myös olla luettelossa useamminkuin kerran ➠ haku ja poisto kohdistuvat kerrallakaikkiin kyseisen henkilön tietoihin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 500▼ Puhelinluettelo-tietotyypin julkinen rajapinta
(puhelinluettelo.hh):
#ifndef PUHELINLUETTELO_HH
#define PUHELINLUETTELO_HH
#include <string>
class Puhelinluettelo {public:
Puhelinluettelo( ) ;
bool lisaa(string nimi, string numero);bool poista(string nimi);
Henkilo* seuraava = vapautettava–>seuraava;delete vapautettava;vapautettava = seuraava;
}ensimmainen = nullptr;
} ▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 510
▼ Esimerkin olennaisimmat kohdat on kommentoitukoodissa.
▼ Yleisellä tasolla ohjelman oli tarkoitus havainnollistaasitä, että linkkikenttiä modifioiviin operaatioihinsisältyy erikoistapauksia, jotka on otettava huomioon.
▼ Erikoistapaukset johtuvat siitä, että usein osa taikaikki operaatioista:– ensimmäisen alkion lisääminen tyhjään listaan,– listan ainoan jäljellä olevan alkion poisto,– listan alkuun lisääminen tai listan ensimmäisen
alkion poisto,– alkion lisääminen tai poistaminen listan keskellä ja– alkion lisääminen tai poistaminen listan lopussajoudutaan toteuttamaan hiukan eri tavoin.
▼ Aina kaikki edellämainitut kohdat eivät vaadi erillistäkäsittelyä ja joskus useampia kohtia voidaan yhdistää,mutta listaoperaatioita toteuttaessaan kannattaakaikki kohdat käydä mielessään läpi ➠ muutenjoskus joku unohtuu ja homma menee happamaksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 511
Listat ja rekursio
▼ Listat ja hyvin monet muut linkitetyt tietorakenteetovat rakenteeltaan rekursiivisia.
▼ Esimerkiksi yhteen suuntaan linkitetty lista koostuu
– listan kärkialkiosta ja
– listan hännästä (siis lopuista alkioista), jotkaselvästi muodostavat yhtä alkiota lyhyemmän listan.
▼ Kun lista määritellään em. rekursiivisena rakenteena,ajautuu väistämättä pohtimaan mahdollisuuttatoteuttaa listaoperaatiot rekursiivisina funktioinajotenkin seuraavasti:
1. Käsittele listan kärkialkio.
2. Käsittele rekursiivisella funktiokutsulla loput alkiot.
▼ Tämä onkin usein lyhyin, elegantein ja selkein tapatoteuttaa operaatiot linkitetyille tietorakenteille.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 512▼ Johtuen tavasta jolla linkitetyt rakenteet toteutetaan
(kätketty abstraktin tietotyypin yksityiseen raja-pintaan), ajaudutaan yleensä seuraavaan ratkaisuun:
– Julkisen rajapinnan jäsenfunktio näyttää käyt-täjälle aivan samalta kuin iteratiivisen version(silmukkarakenteeseen perustuvassa) tapauksessa.
– Käytännössä se on kuitenkin vain helppo liittymäyksityiseen jäsenfunktioon, joka on rekursiivinen jasaa alkuparametrinaan osoittimen linkkirakenteenensimmäiseen alkioon.
▼ Yksityisen rekursiivisen funktion paluuarvo riippuutilanteesta, kuten esimerkistä pian huomataan.
▼ Toteutetaan esimerkin vuoksi koko Puhelinluettelo-tietotyyppi rekursion avulla.
▼ Pääohjelma säilyy luonnollisesti ennallaan, sillä eihänjulkinen rajapinta muutu, vain toteutus sen takana.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 513▼ Uusi versio puhelinluettelo.hh:sta:
#ifndef PUHELINLUETTELO_HH
#define PUHELINLUETTELO_HH
#include <string>
class Puhelinluettelo {public:Puhelinluettelo( );bool lisaa(string nimi, string num);bool poista(string nimi);void tulosta( ) const;void nayta_numero(string nimi) const;~Puhelinluettelo( );
// Etsitään uuden alkion tuleva seuraaja–alkioNode* follower = first;while (follower–>data < number) {
follower = follower–>next;}
// ja lisätään uusi alkio sen eteennew_node–>next = follower;new_node–>prev = follower–>prev;follower–>prev–>next = new_node;follower–>prev = new_node;
}
return true;}
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 525bool Set::remove(int number) {
▼ Kahteen suuntaan linkitetty lista on hyödyllinen, jostietoa on tarkoitus käydä läpi molempiin suuntiin.
▼ Käytännössä tutkittu koodi ei käyttänyt hyödyksikahteen suuntaan linkitetyn listan iloja, se olivalitettavasti vain esimerkki esimerkin vuoksi.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 527
Binäärihakupuu
▼ Binääripuu on juurisolmusta »kasvava» linkitettytietorakenne, jossa jokainen solmu sisältää talletet-tavan tiedon lisäksi osoittimen vasempaan ja oikeaanhaaraan:
0
1
2
3
4 5
6
7
8
9
osoitin juurisolmuun
juurisolmu
lehdet
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 528▼ Solmuja, jotka eivät enää haaraudu, kutsutaan
lehdiksi (kuvassa harmaalla pohjalla).
▼ Binäärihakupuu on vastaava rakenne sillä lisäeh-dolla, että puun jokaiselle solmulle pitää paikkansa:– Kaikki solmun vasempaan haaraan talletettu
informaatio ≤ solmuun talletettu informaatio.– Kaikki solmun oikeaan haaraan talletettu infor-
maatio > solmuun talletettu informaatio.
▼ Esimerkiksi seuraava on laillinen binäärihakupuu:
1
2
3
4
5
6
7
osoitin juurisolmuun
juurisolmu
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 529▼ Tasapainoiseksi sanotaan sellaista binääripuuta,
jossa jokaisen solmun vasemman ja oikean haarankorkeusero on enintään yksi.
▼ Jos binäärihakupuu on tasapainoinen tai lähestasapainoinen, on tiedon etsintä siitä erittäintehokasta.
▼ Tämä on helppo ymmärtää, kun miettii binääripuustarekursiivisesti hakuavaimen olemassaoloa tutkivaaalgoritmia:
bool OnkoPuussa(Solmu* solmu, int hakuavain) {if (solmu == nullptr) {
return false;} else if (solmu–>data == hakuavain) {
▼ Huomaa, että kyseessä on ulkoasultaan C++-koodinnäköinen algoritmi, ei jäsenfunktio tms.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 530▼ Koska tasapainoisen binääripuun syvyys
(pisin matka juuresta lehteen) on suurusluokkaalog2 puun alkioiden lukumäärä ➠ rekursio päättyysuunnilleen noin monen kutsun jälkeen ➠ jokolöydetään hakuavain tai tiedetään, että se ei olepuussa.
▼ On olemassa lisäys- ja poistoalgoritmeja, jotkatakaavat, että muutoksen jälkeen puu on tasapaino-tettu.
▼ Algoritmit ovat kuitenkin jonkin verran monimut-kaisia.
▼ Iloinen uutinen on se, että mikäli hakuavaimetlisätään puuhun satunnaisessa järjestyksessä,tuloksena saadaan puu, joka on yleensä melkotasapainoinen.
▼ Tuohon sisältyy kuitenkin riskejä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 531▼ Edellä esitetty etsintäalgoritmi on todellisuudessa
vastaava kuin lukujoukkotyypin member-funktio
➠ katsotaan tämän idean pohjalta lopuksi,kuinka voitaisiin toteuttaa joukkoon lisäys, mikälijoukkotyyppi olisi toteutettu binäärihakupuuna.
▼ Oletetaan että joukko on määritelty seuraavasti:
class Joukko {public:
Joukko( );...bool lisaa( int luku);...
private:struct Solmu {
int data;Solmu* vasen;Solmu* oikea;
};
Solmu* juuri;};
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 532▼ Toteutus olisi seuraavan kaltainen:
▼ Kyseessä on taas C++-koodin näköinen algoritmi,ei jäsenfunktio tms.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 535
Geneerisyys
▼ Vaikka sivuilla 468–472 esitelty pinorakenne onkinhieno ja toimiva, niin se voisi olla parempikin.
▼ Nykyinen toteus mahdollistaa vain reaalilukupinojenkäsittelyn ohjelmassa.
▼ Jos tulee tarve luoda pino, johon talletettaisiinvaikkapa string-tyyppisiä arvoja, niin tässä vaiheessaainoa tapa olisi kopioida alkuperäinen pinomoduulija muuttaa sitä sopivilta osin ➠ tuollaisessa käsityössäei ole järkeä ja se on myös virhealtista puuhaa.
▼ Joissain ohjelmointikielissä on mahdollista luoda jakäyttää nk. geneerisiä (generic) eli yleiskäyttöisiä
tietorakenteita ja algoritmeja/funktioita.
▼ Geneerisyys on kansanomaisesti sitä, että kerrankoodattua tietorakennetta tai funktiota voidaanilman muutoksia käyttää useilla eri tietotyypeilläoperointiin.
▼ Tutustutaan seuraavaksi (pinnallisesti) C++:ngeneerisyysominaisuuksiin.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 536
Esimerkki: geneerinen pino
▼ Testipääohjelma (pinomain.cc):
#include <iostream>
#include <cstdlib>
#include <string>
using namespace std;
#include "genpino.hh"
int main( ) {Pino<double> lukupino;
for (int i = 0; i < 10; i++) {if ( !lukupino.push(double(i) ) ) {
▼ Jossa luokan rakenteen määrittely on aivan normaali,paitsi että ne tietotyypit joita ei haluta kiinnittää,korvataan muodollisella tyypillä (siis määrittelyngeneeriset kohdat).
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 542▼ Vastaavasti geneeristen jäsenfunktioiden määrittely:
▼ Jossa paluuarvon tyypissä, muodollisissa parametreissa
ja funktion rungossa voi tarvittavissa kohdissa esiintyämuodollinen tyyppi.
▼ Saatu geneerinen tietotyyppi toimii koodissa aivannormaalisti sillä lisäyksellä, että käytön yhteydessäannetaan kulmasuluissa (<>) tieto siitä, mitätietotyyppiä käytetään muodollisen tyypin paikalla.
▼ Geneeristen tietorakenteiden ja funktioiden toimintaperustuu siihen, että kääntäjä pitää kirjaa kaikistageneerisistä määrittelyistä (malleista) ja luo niistätarvittavat erityistapaukset automaatisesti.
▼ Koko template-mekanismi on pohjimmiltaan vainälykäs korvausmenetelmä, jossa kääntäjä tarvittaessakopioi ja kääntää mallin lähdekoodin ilman, ettäohjelmoijan tarvitsee tehdä käsityötä.
▼ ▼ ▼ ▼ ▼ ▼ ▼
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
OHJ-1150 Ohjelmointi II 545▼ Geneerisyyden pointti on tietysti se, että jos on
kerran huolella koodattu ja testattu tietorakennetai algoritmi, niin sitä on helppo käyttää aina vainuudelleen eri tyyppisellä informaatiolla:
1. vältetään turha rutiininomainen käsityö ja
2. siinä helposti syntyvät huolimattomuusvirheet.
▼ Geneerinen ohjelmointi on sangen monimutkaista,vaikka sitä ei tutkituista simppeleistä esimerkeistävälttämättä huomaakaan ➠ älä harrasta sitä ennen
kuin olet perehtynyt aiheeseen syvällisemmin
kuin tällä kurssilla on mahdollista.
▼ Huomaa, että template-mekanismi ei mahdollista»sekarakenteita», siis esimerkiksi sellaista pinoa, johonvoisi tallentaa useamman tyyppistä informaatiota.
▼ Geneerinen ohjelmointi ei varsinaisesti kuulu tämänkurssin aihepiiriin.