Lukijalle
Tämä moniste on tarkoitettu oheislukemistoksi Ohjelmointi- kurssille.  Vaikka monisteen yksi teema onkin C++- kieli, ei C++- kieli ole monisteen päätarkoitus.  Päätarkoituksena on esitellä ohjelmointia.  Esitystavaksi on valittu yhden ohjelman suunnitteleminen ja toteuttaminen alusta lähes loppuun saakka.  Tämä Top-Down - metodi tuottaa varsin suuren kokonaisuuden, jonka hahmottaminen saattaa aluksi tuntua vaikealta.  
Kunhan oppii kuitenkin katsomaan kokonaisuuksiin yksityiskohtien sijasta, asia helpottuu.  Yksityiskohtia harjoitellaan monisteen esimerkeissä (Bottom-Up), joista suuri osa liittyy monisteen malliohjelmaan, mutta jotka silti voidaan käsittää mallista riippumattomina palasina.
Monisteen liitteeksi on saatavana myös toinen moniste, Listaukset, johon monisteen ohjelmat on kerätty eri kehitysvaiheissaan.  Ohjelmat on saatavissa myös elektronisesti, jotta niiden toimintaa voidaan kokeilla kunkin vaiheen jälkeen.
C++- kieltä ja sen ominaisuuksia on monisteessa sen verran, että lukijalla on juuri ja juuri erittäin pienet mahdollisuudet selvitä ilman muuta kirjallisuutta.  
Lukijan kannattaakin ilman muuta hankkia ja seurata tämän monisteen rinnalla jotakin varsinaista C++- ohjelmointikirjaa.  Esimerkinä mainittakoon Päivi Hietasen C++ ja Olio- ohjelmointi, Teknolit, 1996  tai itse kielen kehittäjän teos:. Bjarne Stroustrup: The C++ Programming Language, Second Edition - Addison-Wesley, 1991.  Olio- ohjelmoinnista on kansantajuinen teos: Timothy A. Budd: An Introduction to Object-Oriented Programming, Second Edition - Addison Wesley, 1997.  Hietasen teoksen joihinkin esimerkkeihin kannattaa suhtautua kuitenkin varauksellisesti.  Usein myös ohjelmointiympäristön mukana olevasta OnLine- avustuksesta (Help) saa tarvittavaa lisätietoa.
Monisteen esimerkkiohjelmat löytyvät elektronisessa muodossa:
FTP: 	kone:	tarzan.math.jyu.fi                    
	polku: 	/pub/pc/kurssit/cpp/moniste/esim
	käyttäjätunnus:	ftp
	salasana:	 oma käyttäjätunnus 
Mikroluokka: 	hakemisto:	 N:\KURSSIT\CPP\MONISTE\ESIM
WWW:	URL:	 http://www.math.jyu.fi/~vesal/kurssit/cpp/moniste/esim

Edellä mainittuun polkuun lisätään vielä ohjelman yhteydessä mainittu polku.
Monisteessa on lukuisia esimerkkitehtäviä, joiden tekeminen on oppimisen kannalta lähes välttämätöntä.  Vaikka lukija saattaa muuta kuvitellakin, on monisteen vaikeimmat tehtävät monisteen alussa.  Mikäli loppupuolen tehtävät tuntuvat vaikeilta, ei monisteen alkuosa olekaan hallinnassa.  Siksi kehotankin lukijaa aina vaikeuksia kohdatessaan palaamaan monisteen alkuosaan; siitä ei monikaan voi sanoa, ettei asioita ymmärtäisi.
Lopuksi kiitokset kaikille työtovereilleni monisteen kriittisestä lukemisesta.  Erityisesti Tapani Tarvainen on auttanut suunnattomasti C- kieleen tutustumistani ja Jonne Itkonen vastaavasti tutustumista C++- kieleen ja olio- ohjelmointiin.
Alkuperäinen versio allekirjoitettu Palokassa 28.12.1991
Monisteen 3. korjattuun painokseen on korjattu edellisissä monisteissa olleita painovirheitä sekä lisätty lyhyt C- kielen "referenssi".  Lisäksi kunkin esimerkkiohjelman alkuun on laitettu kommentti siitä, mistä tiedostosta lukija löytää esimerkin.  Myös hakemistoa on parannettu vahventamalla määrittelysivun sivunumero.
Palokassa 28.12.1992
Monisteen 4. korjattuun painokseen on jopa vaihdettu monisteen nimi: Ohjelmointi++, kuvaamaan paremmin olio- ohjelmoinnin ja C++:n saamaa asemaa.  Tätä kirjoittaessani moniste ei ole vielä kokonaan valmis ja kaikkia siihen tulevia muutoksia en vielä tässä pysty luettelemaan.
Joka tapauksessa olen monisteeseen lisännyt tekstiä -  valitettavasti nimenomaan ohjelmointikieleen liittyvää -  jota vuosien varrella olen huomannut opiskelijoiden jäävän kaipaamaan.  Lisäksi kunkin luvun alkuun on lisätty suppea luettelo luvun pääteemoista ja luvussa esiintyvistä kielen piirteistä sekä niiden syntaksista.  Tämä syntaksilista on helppolukuinen "lasten" syntaksi, varsinainen tarkka ja virallinen syntaksi pitää katsoa kielen määrityksistä.
Ohjelmalistauksiin on lisätty syntax-highlight, eli kielen sanat on korostettu ja näin lukijan toivottavasti on helpompi löytää mitkä termit on itse valittavissa ja mitkä täytyy kirjoittaa juuri kuvatulla tavalla.  Myös joitakin vinkkejä on lisätty. Pedagogisesti on vaikea päättää saako esittää virheellisiä tai huonoja ohjelmia lainkaan, mutta vanha viidakon sananlasku sanoo että "Viisas oppii virheistä, tavallinen kansa omista virheistä ja tyhmä ei niistäkään".  Siis mahdollisuus "viisaillekin" ja nämä virheelliset ohjelmat on merkitty surullisella naamalla: .  Näin aivan jokaisen ei tarvitse rämpiä jokaista sudenkuoppaa pohjia myöten ominpäin.
Olio- ohjelmointi on kuvattu esimerkkien avulla ja varsinainen oliosuunnittelu - joka on erittäin tärkeää -  on jätetty erittäin vähälle.  Suosittelenkin lukijalle jonkin oliosuunnitteluun liittyvän kurssin käymistä tai kirjan lukemista.
Myös tätä kurssia edeltävä kurssi Ohjelmoinnin alkeet  on kokenut muutoksia ja vaikka kurssi meneekin nykyisin entistä pitemmälle, ei tästä monisteesta ole kuitenkaan poistettu kaikkea päällekkäisyyttä Ohjelmoinnin alkeet - monisteen kanssa.  Toimikoon nämä päällekkäisyyden kertauksena ja kohtina, joissa luennoilla voidaan asia sivuttaa nopeammin.  Joka tapauksessa lukijan kannattanee pitää myös Ohjelmoinnin alkeet - moniste tämän monisteen rinnalla.
Palokassa 5.1.1997
	Vesa Lappalainen

1.	Johdanto
Tämän monisteen tarkoituksena on toimia tukimateriaalina opeteltaessa sekä algoritmisen että olio- ohjelmoinnin alkeita.  Aluksi meidän tulee ymmärtää mitä kaikkea ohjelmointi pitää sisällään.  Aivan liian usein ohjelmointi yhdistetään päätteen äärellä tapahtuvaan jonkin tietyn ohjelmointikielen koodin naputtamiseen.  Tämä on ehkä ohjelmoinnin näkyvin osa, mutta myös toisaalta mekaanisin ja helpoin osa.
Ohjelmointi voidaan jakaa esimerkiksi seuraaviin vaiheisiin:
•	tehtävän saaminen
•	tehtävän tarkentaminen ja tarvittavien toimintojen hahmottaminen
•	ohjelman toimintojen ja tietorakenteiden suunnittelu, oliosuunnittelu
•	yksityiskohtaisten olioiden ja algoritmien suunnittelu
•	OHJELMOINTITYÖKALUN VALINTA
•	algoritmien/luokkien metodien tarkentaminen valitulle työkalulle
•	ohjelmakoodin kirjoittaminen
•	ohjelman testaus
•	ohjelman käyttöönotto
•	ohjelman ylläpito

Huomattakoon, että edellisessä listassa varsinaisesti tietokoneella tehtävä työ on vain aivan listan viimeisissä kohdissa.  Tietenkin nykyisin suunnittelun alkuvaiheessakin tarvittava dokumentointi ja ideoiden sekä vaihtoehtojen kirjaaminen tehdään käyttäen tekstinkäsittelyohjelmia.  Varsinaisesta koodauksesta ei kuitenkaan alkuvaiheessa ole kysymys.
Ohjelman kehityksen eri vaiheissa saatetaan tarvittaessa palata takaisin alkumäärityksiin.  Kuitenkin ohjelman valittujen toimintojen muuttaminen oman laiskuuden tai osaamattomuuden takia ei ole suotavaa.  Ei saa lähteä ompelemaan kissalle takkia ja huomata, että kangas riittikin lopulta vain rahapussiin.
1.1	Ohjelman suunnittelu
Usein ohjelmointikursseilla unohdetaan itse ohjelmointi ja keskitytään valitun työkalun -  ohjelmointikielen -  esittelyyn.  Ajanpuutteen takia tämä onkin osin ymmärrettävää.  Kuulijat kuitenkin hämääntyvät eivätkä ymmärrä luennoitsijan tekevän edelläkuvatun listan kaltaista suunnittelutyötä myös kunkin pienen malliesimerkin kohdalla.  Kokenut ohjelmoija saattaa pystyä hahmottamaan ongelman ratkaisun ja tarvittavat erikoistapaukset päässään silloin, kun on kysy erittäin lyhyistä malliesimerkeistä.  Jossain vaiheessa ohjelmoinnin oppimista suunnittelu ja koodin kirjoittaminen tuntuvat sulautuvan yhteen.  
Opiskelun alkuvaiheessa on kuitenkin syytä keskittyä nimenomaan ongelman analysointiin ja ohjelman suunnitteluun.  Tässä paras apu on usein terve maalaisjärki.  Mitä vähemmän ymmärtää itse ohjelmointikielistä, sitä vähemmän kielet rajoittavat luovaa ajattelua.
Usein ohjelman suunnittelu voidaan aloittaa jopa käyttöohjeen kirjoittamisella!  Tällöin tulee tutkituksi ohjelmalta vaaditut ominaisuudet ja toimintojen loogisuus sekä helppokäyttöisyys!  Nykytyökaluilla voidaan myös rakentaa suhteellisen helposti ensin ohjelman käyttöliittymä ilman oikeita toimintoja.  Tätä "protoa" voidaan sitten tutkia yhdessä asiakkaan kanssa ja päättää toimintojen loogisuudesta ja riittävyydestä.
1.2	Työkalun valinta
Kun ohjelmaan on suunniteltu halutut toimenpiteet ja päätetty mitä tietorakenteita tarvitaan, on edessä työkalun valinta.  Nykypäivänä ei ole itsestään selvää, että valitaan työkaluksi jokin perinteinen ohjelmointikieli.  Vastakkain pitää asettaa erilaiset sovelluskehittimet, valmisohjelmat kuten tietokannat ja taulukkolaskennat, ehkä jopa tavallinen tekstinkäsittely sekä ohjelmointikielet.  Matemaattisissa ongelmissa jokin symbolisen tai numeerisen laskennan paketti saattaa olla soveltuva.  
Ratkaisu voi koostua myös useiden eri ohjelmien toimintojen yhdistelemisestä: CAD - ohjelmalla piirretään/digitoidaan kartan  pohjakuva, tietokantaohjelmalla pidetään kirjaa paikoista ja pienellä C- kielisellä ohjelmalla suoritetaan ne osat, joita CAD- ohjelmalla tai tietokantaohjelmalla ei voida suorittaa.
Joskus työkaluksi valitaan prototyyppiä varten jokin sovelluskehitin tai tietokantaohjelmisto.  Kun halutut toiminnot on perusteellisesti testattu ja tuotetta tarvitsee edelleen kehittää, voidaan ohjelmointi toteuttaa uudelleen vaikkapa C- kielellä.  Prototyyppi on rinnalla toimivana ja uudessa ohjelmassa käytetään samoja tietoja ja toimintoja.
1.3	Koodaus
Mikäli työkalun valinnassa päädytään olio/lausekieleen (esim. C++), ei pyörää kannata keksiä uudelleen.  Nelikulmioon nähden kolmikulmiossa on yksi poksaus vähemmän kierroksella, mutta kyllä silti ympyrä on paras.  Siis käytetään toisten kirjoittamia valmiita olioita ja/tai aliohjelmapaketteja "likaisessa" työssä.
Aina tietenkin puuttuu joitakin alemman tason palasia.  Nämä tietysti koodataan JA TESTATAAN ERILLISINÄ ennen varsinaiseen ohjelmaan liittämistä.
Siis itse koodaus on pienten aputyökalujen etsimistä, tekemistä, testaamista ja dokumentointia.  Lopullinen koodaus on näiden aputyökaluista muodostuvan palapelin yhteenliittäminen.  
Jo koodausvaiheessa kannattaa miettiä ongelman yleisiä ominaisuuksia.  Jos ollaan kirjoittamassa telinevoimistelun pistelaskua naisten sarjaan, niin koodissa ei mitenkään tulisi estää ohjelman käyttöä myös miesten sarjassa.  Siis telineiden nimet ja määrät pitäisi olla helposti muutettavissa.
Koodausta voidaan tehdä joko BOTTOM- UP periaatteella, jolloin ensin rakennetaan työkalut (=olioluokat/aliohjelmat) jotka sitten kasataan yhteen.  Toinen mahdollisuus on koodaus TOP- DOWN periaatteella, jolloin päätoiminnat kirjoitetaan ensin ja alatoiminnoista tehdään aluksi tyhjiä laatikoita.  Myöhemmin valmiita ja testattuja alitoimintoja liitetään tähän runkoon.  Valitulla menetelmällä ei ole vaikutusta lopputulokseen ja joskus voikin olla hyvää vaihtelua siirtyä näpertelemään pikkuasioiden kimpussa isojen kokonaisuuksien sijasta tai päinvastoin.
Missään tapauksessa ohjelma ei synny siten kuin se kirjallisuudessa näyttää olevan: alkumäärittelyt, aliohjelmat ja päämoduli.
Koodaajan on osattava hyvin käytettävä työkalu, esim. ohjelmointikieli.  Kuitenkin jonkin ohjelmointikielen hyvän osaamisen avulla on suhteellisen helppo kirjoittaa myös muun kielisiä ohjelmia.
Koodaus on pääosin tekstinkäsittelyä ja 10- sormijärjestelmä nopeuttaa koodin syntymistä oleellisesti.  Myös hyvä tekstinkäsittelytaito valmiiden palasten siirtelemisineen ja kopioimisineen helpottaa tehtävää.
1.4	Testaus
Ohjelman testaus alkaa jo suunnitteluvaiheessa.  Valitut algoritmit ja toiminnot pitää pöytätestata teoriassa ennen niiden koodaamista.  Suunnitteluvaiheessa täytyy miettiä kaikki mahdolliset erikoistapaukset ja todeta algoritmin selviävän niistäkin tai ainakin määritellä miten erikoistapauksissa menetellään.  Testitapaukset kirjataan ylös myöhempää käyttöä varten.
Koodausvaiheessa kukin yksittäinen aliohjelma testataan kaikkine mahdollisine syötteineen pienellä testiohjelmalla. Aliohjelman kommentteihin voidaan kirjata suunnitteluvaiheessa todettu testiaineisto ja testausvaiheessa ruksataan testatut toiminnot ja erikoistapaukset.
Lopullisen ohjelman toimivuus riippuu hyvin paljon siitä, miten hyvistä palasista se on kasattu.  
Testauksessa apuna on aluksi pöytätestit. Sitten käytetään pieniä testiohjelmia.  Ennen testiohjelmiin lisättiin tulostuslauseita.  Nykyisin tehokkaat debuggerit helpottavat testausta huomattavasti: ohjelman toimintaa voidaan seurata askel kerrallaan ja epäilyttävien muuttujien arvoja voidaan tarkistaa kesken suorituksen.  Voidaan myös laittaa ohjelma pysähtymään jonkin muuttujan saadessa virheellisen arvon.
Testaus on vaihe, missä hyvä koneenkäyttörutiini on suureksi avuksi.
1.5	Käyttöönotto
Valmiin ohjelman käyttöönotto tapahtuu yleensä aina liian aikaisin.  Paineet keskeneräisesti testatun ohjelman käyttämiseksi ovat suuret.  Lisäksi testaajat ja erityisesti ohjelman koodaajat ovat sokeita tavallisen käyttäjän (usein myös omilleen) virheille.
Käyttöohjeen olisi syytä olla valmis viimeistään tässä vaiheessa.
1.6	Ylläpito
Käyttöönotetusta ohjelmasta paljastuu aina virheitä tai puuttuvia toimintoja.  Virheet pitää korjata ja puuttuvat toiminnot mahdollisesti lisätä ja ollaan jälleen ohjelmansuunnittelun alkuvaiheessa.  Hyvin suunniteltuun ohjelmaan saattaa olla helppo lisätä uusia toimintoja ja vastaavasti huonosti suunnitellussa saattaa jopa tietorakenteet mennä uusiksi.
Myös ohjelman alkuperäiset kirjoittajat ovat saattaneet häipyä ja kesätyöntekijä joutuu ensitöikseen paikkaamaan toisten huonosti dokumentoitua sotkua.
1.7	Yhteenveto
Ohjelmointi ei yleensä ole yhden henkilön työtä.  Eri henkilöt voivat tehdä eri vaiheita ohjelmoinnissa.  Lähes aina tulee tilanne, missä jonkin toisen kirjoittamaa koodia joudutaan korjailemaan.  
Oli ohjelmaa tekemässä kuinka monta henkilöä tahansa (vaikka vain yksi), pitää ohjelmointi jakaa vaiheisiin.  Oikeaa ohjelmaa on mahdoton "nähdä" valmiina C- kielisinä lauseina heti tehtävän määrityksen antamisen jälkeen.  Aloitteleva ohjelmoija kuitenkin haluaisi pystyä tähän (koska hän "näkee" määrityksestä: Kirjoita ohjelma joka tulostaa "Hello world", heti myös C- kielisen toteutuksen).  Tämän takia ohjelmoinnin helpoin osa, eli koodaus koetaan ohjelmoinnin vaikeimmaksi osaksi -  suunnittelu on unohtunut!
Valitulla ohjelmointikielellä ei ole suurtakaan merkitystä ohjelmoinnin toteuttamiseen.  Jokin kieli saattaa soveltua paremmin johonkin tehtävään, mutta pääosin BASIC, Fortran, Pascal, C, LISP, Modula- 2, ADA jne. ovat samantyylisiä lausekieliä.  Samoin oliokielistä esimerkiksi C++, Java ja Delphi (Pascal) ovat hyvin lähellä toisiaan.  Kun yhden osaa, on toiseen siirtyminen jo helpompaa.  
Jos joku kuvittelee ettei hänen tarvitse koskaan ohjelmoida C/C++- kielellä, voi hän olla aivan oikeassakin.  Nykyisin kuitenkin jokaisessa tietokantaohjelmassa, taulukkolaskentaohjelmassa ja jopa tekstinkäsittelyohjelmissakin (vrt. esim TEX, joka on tosin ladontaohjelma) on omat ohjelmointikielensä.  Osaamalla jonkin ohjelmointikielen perusteet, voi saada paljon enemmän hyötyä käyttämästään valmisohjelmasta.  Ja joka väittää selviävänsä nykymaailmassa (ja sattuu lukemaan tätä monistetta) esimerkiksi ilman tekstinkäsittelyohjelmaa on suuri valehtelija!

2.	Kerhon jäsenrekisteri
Mitä tässä luvussa käsitellään?
•	tehtävän "analysointi"
•	ohjelman vaatimien aputiedostojen sisällön suunnittelu 
•	ohjelman suunnittelu ohjelman tulosteiden avulla
•	suunnitelman korjaus
•	tarvittavien algoritmien hahmottaminen 
•	relaatiotietomalli
2.1	Tehtävän tarkennus
Ohjelman suunnittelu aloitetaan aina tehtävän tarkastelulla.  Annettua tehtävää joudutaan usein huomattavasti tarkentamaan.  
Olkoon tehtävänä suunnitella kerhon jäsenrekisteri.  Onko kerho iso vai pieni?  Mitä tietoja jäsenistä talletetaan?   Mitä ominaisuuksia rekisteriltä halutaan?
Mikäli sovitaan, että kerho on kohtuullisen pieni (esim. alle 500 jäsentä), ei meidän heti alkuun tarvitse miettiä parhaita mahdollisia hakualgoritmeja eikä tiedon tiivistämistä.
Mitä tietoja jäsenistä tarvitaan?
- nimi
- sotu
- katuosoite
- postinumero
- postiosoite
- kotipuhelin
- työpuhelin
- autopuhelin
- liittymisvuosi
- tämän vuoden maksetun jäsenmaksun suuruus
- lisätietoja
jne...

Mitä ominaisuuksia rekisteriltä halutaan?
- kerholaisten lisääminen
- kerholaisten poistaminen
- tietyn kerholaisen tietojen hakeminen
- tietyn kerholaisen tietojen muuttaminen
- postitustarrat postinumerojärjestyksessä 
- nimilista nimen mukaisessa järjestyksessä
- lista jäsenmaksua maksamattomista jäsenistä
jne...
2.2	Työkalun valinta
On varsin selvää, ettei tätä nimenomaista tehtävää kannattaisi nykypäivänä lähteä itse ohjelmoimaan, vaan turvauduttaisiin tietokantaohjelmaan.  Joissakin erikoistapauksissa saatetaan vaatia ominaisuuksia, joita tietokantaohjelmasta ei saada. Tällöin työkaluksi valittaisiin lausekieli ja tietokantaohjelmiston  aliohjelmakirjasto, joka hoitelee varsinaiset tietokannan ylläpitoon yms. liittyvät toimenpiteet.  
Edellinen analyysi on kuitenkin tehtävä työkalusta riippumatta!  Esimerkin vuoksi jatkamme tehtävän tutkimista hieman pidemmälle tavoitteena ohjelmoida jäsenrekisteri jollakin lausekielellä.
2.3	Tietorakenteet ja tiedostot
Mikäli työkalun valinnassa on päädytty johonkin lausekieleen, on jossain vaiheessa päätettävä käytettävistä tietorakenteista.  Esimerkin tapauksessa meillä on selvästikin joukko yhden henkilön tietoja.  Mikäli yhden henkilön tietoa pidetään yhtenä yksikkönä (tietueena), on koko tietorakenne taulukko henkilöiden tiedoista.  Taulukko voidaan tarvittaessa toteuttaa myös lineaarisena listana tai jopa puurakenteena.  Mikäli kyseessä on pieni rekisteri, mahtuu koko tietorakenne ohjelman ajon aikana muistiin.  
Missä tiedot talletetaan kun ohjelma ei ole käynnissä? Tietenkin levyllä tiedostona.  Minkä tyyppisenä tiedostona?  Tiedoston tyyppinä voisi olla binäärinen tiedosto alkioina henkilötietueet.  Tällaisen tiedoston käsittely hätätapauksessa on kuitenkin vaikeata.  Varmempi tapa on tallettaa tiedot tekstitiedostoksi, jota tarvittaessa voidaan käsitellä millä tahansa tekstinkäsittelyohjelmalla.  Tällöin on lisäksi usein mahdollista käsitellä tiedostoa taulukkolaskentaohjelmalla tai tietokantaohjelmalla ja näin joitakin harvinaisia toimintoja voidaan suorittaa rekisterille vaikkei niitä olisi alunperin edes älytty laittaa ohjelmaan mukaan.
Minkälainen tekstitiedosto?  Ehkäpä yhden henkilön tiedot yhdellä rivillä?  Miten yhden henkilön eri tiedot erotetaan toisistaan?  Mahdollisuuksia on lähinnä kaksi:  erotinmerkki tai tietty sarake.  Valitaan erotinmerkki.  Usein on mukavaa lisäksi laittaa joitakin huomautuksia eli kommentteja tiedostoon.  Siis talletustiedoston muoto voisi olla vaikkapa seuraava: 
kelmit.dat - ensimmäinen ehdotus tiedostoksi
; Tässä tiedostossa on KELMIT Ry:n jäsentiedot
; Kenttien järjestys tiedostossa on seuraava:
; sukunimi etunimi|sotu|katuosoite|postinumero|postiosoite
;|kotipuhelin|työpuhelin|autopuhelin|liittymisvuosi|jmaksu|lisätietoja
Ankka Aku|010245-123U|Ankkakuja 6|12345|ANKKALINNA|12-12324|||1991|50|velkaa Roopelle
Susi Sepe|020347-123T||12555|Takametsä||||1990|50|jäsen myös kelmien kerhossa
Ponteva Veli|030455-3333||12555|Takametsä||||50|1989|

Tällaisenaan tiedosto on varsin suttuinen luettavaksi.  Vaikka valitsimmekin erotinmerkin erottamaan tietoja toisistaan, voimme silti kirjoittaa vastaavat tiedot allekkain sopimalla, ettei loppuvälilyönneillä ole merkitystä.
kelmit.dat - sarakkeet linjaan
; Tässä tiedostossa on KELMIT Ry:n jäsentiedot
; Kenttien järjestys tiedostossa on seuraava:
; sukunimi etunimi |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin|työpuhelin|.
Ankka Aku          |010245-123U|Ankkakuja 6 |12345      |ANKKALINNA |12-12324   |          | 
Susi Sepe          |020347-123T|            |12555      |Takametsä  |           |          | 
Ponteva Veli       |030455-3333|            |12555      |Takametsä  |           |          | 

Nyt tiedostoa on helpompi lukea ja tyhjien kenttien jättäminen ei ole vaikeaa.  Tiedosto vie kuitenkin levyltä enemmän tilaa kuin ensimmäinen versio.  Lisäksi yhden henkilön tiedot eivät mahdu kerralla näyttöön.  Onneksi kuitenkin lähes kaikki nykyiset tekstieditorit suostuvat rullaamaan näyttöä myös sivusuunnassa.  Mikäli saman henkilön tietoja jaettaisiin eri riveille, tarvitsisi meidän valita vielä tietueen loppumerkki (nytkin se on valittu: rivinvaihto). 
2.4	Käyttöohje tai käyttöliittymä
Jatkosuunnittelu on ehkä helpointa tehdä suunnittelemalla ohjelman toimintaa käyttöohjeen tai käyttöliittymän tavoin.  Erityisesti graafisella käyttöliittymällä varustettuja ohjelmia suunnitellaan nykytyökaluilla nimenomaan "piirtämällä" käyttäjälle näkyvä käyttöliittymän osa.  Tähän osaan sitten lisätään heti tai jälkeenpäin itse toiminnallisuus.  Tällaisia työkaluja on esimerkiksi Delphi, Visual Basic ja myös muiden ohjelmointikielten resurssityökalut.
Graafisen käyttöliittymän suunnittelu ja toteutus on kuitenkin jo edistyneempää - ja erityisesti laiteriippuvaa - puuhaa, joten tällä kurssilla täytyy tyytyä "vanhanaikaiseen" keskustelevaan tekstikäyttöliittymään.  Jos koodausvaiheessa riittävästi erotetaan käyttöliittymään liittyvä koodi tietorakennetta ylläpitävästä koodista, voidaan ohjelma kohtuullisella työllä muuttaa myös graafisessa käyttöliittymässä toimivaksi.
Suunnittelussa toimitaan käyttäjän ja helppokäyttöisyyden (= myös nopea käyttö, ei aina välttämättä hiiri) ehdoilla.
Ohjelmassa on kahdenlaisia vastauksia.  Toisiin riittää painaa pelkkä yksi kirjain tai numero (kuten menut ja K/e tyyppiset valinnat).  Mikäli vastaukseen on mahdollista kirjoittaa enemmän kuin yksi merkki, pitää vastaus lopettaa [RET]- näppäimen painamisella (Return, Enter).  Jatkossa hoputteilla (prompt) on seuraavat merkitykset 
:	odotetaan pelkkää yhtä merkkiä.  Mikäli vaihtoehdot on lueteltu ja jokin niistä on isolla kirjaimella, valitaan tämä painettaessa [RET]- näppäintä.
>	odotetaan 0 -  useata merkkiä ja [RET]- näppäintä.  Mikäli hoputteen edessä on suluissa jokin arvo, tulee tämä arvo vastauksen arvoksi painettaessa pelkkää [RET]- näppäintä.  Mikäli oletusvastaus on epätyhjä ja halutaan antaa vastaukseksi tyhjä merkkijono, painetaan välilyönti ja [RET].
Ohjelman päävalintaan päästään usein vastaamalla pelkkä [RET] uuden kierroksen alussa tai painamalla q[RET] missä tahansa ohjelman kohdassa.
Kun ohjelma käynnistyy, tulostuu näyttöön:
      ###################################
      #   J Ä S E N R E K I S T E R I   #
      #     versio 9.95                 #
      #     Hannu Hanhi                 #
      ###################################

Tällä ohjelmalla ylläpidetään kerhon jäsenrekisteriä.
Anna kerhon nimi>_

Kerhon tiedot on talletettu vaikkapa tiedostoon nimi.DAT.  Näin voimme ylläpitää samalla ohjelmalla useiden eri kerhojen tietoja.   Mitäpä jos tiedostoa ei ole?  Tällöin voi syynä olla kirjoitusvirhe tai se, ettei rekisteriä ole vielä edes aloitettu!  Miten ohjelman tulee tällöin menetellä?
Tällä ohjelmalla ylläpidetään kerhon jäsenrekisteriä.
Anna kerhon nimi>KERMIT[RET]
Kerhon KERMIT tietoja ei ole!  
Luodaanko tiedot (K/e):e
Anna kerhon nimi>KELMIT[RET]
Odota hetki, luetaan tietoja...

Jäsenrekisteri
==============

Kerhossa KELMIT on 52 jäsentä.

Valitse:
   0  = lopetus
   1  = lisää uusi jäsen
   2  = etsi jäsenen tiedot
   3  = tulosteet
   4  = tietojen korjailu
   :_

Edellä on edetty siihen saakka, kunnes ohjelmassa on päädytty päävalikkoon  (main menu).  Seuraavaksi voimme lähteä tarkastelemaan eri alakohtien toimintaa.  Näissä eri erikoistapaukset on otettava huomioon:
2.4.1	Lisää uusi jäsen
1. Uuden jäsenen lisäys
=======================
Jäseniä on nyt 52.
Anna uusi nimi muodossa sukunimi etunimi etunimi
Jäsenen nimi      ()            >Ankka Aku[RET]
Rekisterissä on jo jäsen Aku Ankka!  Mikäli haluat muuttaa
hänen tietojaan, valitse tietojen korjailu päävalikosta!
Jäsenen nimi      ()            >ANKKA TUPU[RET]
Sotu              ()            >012356-1257[RET]
Sotu mieletön tai tarkistusmerkki väärin (N)!
Sotu              (012356-125N) >010356-125J[RET]
Katuosoite        ()            >Ankkakuja 6[RET]
Postinumero       (12345)       >[RET]
Postiosoite       (ANKKALINNA)  >[RET]
Kotipuhelin       ()            >12-12324[RET]
Työpuhelin        ()            >[RET]
Autopuhelin       ()            >[RET]
Liittymisvuosi    (91)          >91[RET]
Jäsenmaksu mk     (50)          >10[RET]
Lisätietoja       ()            >Aku Ankan veljenpoika[RET]

Lisätäänkö 
  Ankka Tupu 010356-125J
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 10 mk.
  Aku Ankan veljenpoika
:k

Jäseniä on nyt 53.

Anna uusi nimi muodossa sukunimi etunimi etunimi
Jäsenen nimi      ()            >[RET]

Jäsenrekisteri
==============

Kerhossa KELMIT on 53 jäsentä.

Valitse:
   0  = lopetus
   1  = lisää uusi jäsen
   2  = etsi jäsenen tiedot
...

Edellä on suluissa esitetty oletusarvo, joka tulee kentän arvoksi, mikäli painetaan pelkkä [RET].  Mikäli oletusarvon tilalle halutaan vaikkapa tyhjä merkkijono, vastataan välilyönti ja [RET].  Näin  voidaan nopeuttaa tiettyjen samojen asioiden syöttöä.  Oletusarvo voidaan antaa joko ohjelmasta väkisin (kuten tyhjä sotuksi) tai se voidaan ottaa edellisen syötön perusteella.
2.4.2	Etsi jäsenen tiedot
Tietoa voidaan hakea usealla eri tavalla.  Voidaan haluta etsiä nimellä, osoitteella tai jopa puhelinnumerolla.  Myös lisätietokentästä voidaan haluta etsiä tiettyä sanaa.
Siis aluksi pitää valita minkä kentän mukaan haetaan.  Kun kenttä on selvitetty, voi olla mielekästä voida käyttää myös jokerimerkkejä (esim ? ja *, vrt. MS-DOS. 
2. Etsi jäsenen tiedot
======================

  Nykyinen henkilö:

  Ankka Lupu 010356-127L
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 10 mk.
  Aku Ankan veljenpoika, sudenpentu

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.) :?

  1 = nimi
  2 = sotu 
  3 = katuosoite     
  4 = postinumero
  5 = postiosoite
  6 = kotipuhelin
  7 = työpuhelin     
  8 = autopuhelin    
  9 = liittymisvuosi 
  A = jäsenmaksu mk  
  B = lisätietoja    

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.) :1
Jäsenen nimi      (Ankka Lupu)  >*ANKKA*[RET]
Tähän täsmää 3 jäsentä:

  Ankka Aku 010245-123U
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 50 mk.
  velkaa Roopelle

Lisää (K/e):[RET]

  Ankka Lupu 010356-127L
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 10 mk.
  Aku Ankan veljenpoika, sudenpentu

Lisää: (K/e):e

2. Etsi jäsenen tiedot
======================

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.) :[RET]

Korjailua ja muuta varten on mukaavaa, että viimeksi löydetyn henkilön tiedot jäävät muistiin.  Näin esimerkiksi korjailu tai poisto voidaan tehdä suoraan tälle henkilölle.
2.4.3	Tulosteet
Tulosteita varten voisi tulla menu siitä, mitä tulostetaan.  Lisäksi voisi olla kysymys siitä, millä ehdoilla tulostus tehdään (jäsenmaksu maksamatta jne.) sekä tulostusjärjestys.  Tulostusjärjestys on tärkeä, sillä esimerkiksi postitusta varten lehdet yms. pitää lajitella postin antamien ohjeiden mukaisesti postinumeroittain nippuihin, jotka sitten menevät tietylle postialueelle.  Jäsenrekisteriä varten taas aakkosjärjestetty lista on kätevin.  
Tulostuskohdan suunnittelu jätetään lukijalle harjoitustehtäväksi.  Tulosteiden ulkonäkö kannattaa kuitenkin suunnitella tarkasti, koska tämä on muille kerholaisille näkyvin osa ohjelmastamme!
Tehtävä 2.1	Tulosteet
Suunnittele tulostusmenu ja kunkin kohdan alta mahdollisesti saatavat kysymykset sekä tulosteiden ulkonäkö.
2.4.4	Tietojen korjailu
4.  Tietojen korjailu
=====================

  Korjailtava henkilö:

  Ankka Lupu 010356-127L
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 10 mk.
  Aku Ankan veljenpoika, sudenpentu

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.) 
tai poisto (P) tai korjailu (K) :1

Jäsenen nimi      (Ankka Lupu)  >*aku*[RET]
Tähän täsmää 1 jäsentä:

  Ankka Aku 010245-123U
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 50 mk.
  velkaa Roopelle

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.)
tai poisto (P) tai korjailu (K) :p

Haluatko todellakin poistaa jäsenen Ankka Aku (k/E):e

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.) 
tai poisto (P) tai korjailu (K) :k

Jäsenen nimi      (Ankka Aku)   >[RET]
Sotu              (010245-123U) >[RET]
Katuosoite        (Ankkakuja 6) >Ankkakuja 13[RET]
Postinumero       (12345)       >[RET]
Postiosoite       (ANKKALINNA)  >[RET]
Kotipuhelin       (12-12324)    >12-12325[RET]
Työpuhelin        ()            >12-33333[RET]
Autopuhelin       ()            >[RET]
Liittymisvuosi    (91)          >[RET]
Jäsenmaksu mk     (50)          >[RET]
Lisätietoja       (velkaa Roopelle) >[RET]

  Ankka Aku 010245-123U
  Ankkakuja 13 12345 ANKKALINNA
  k: 12-12325 t: 12-33333 a:
  Liittynyt -91.  Jäsenmaksu 50 mk.
  velkaa Roopelle

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.) 
tai poisto (P) tai korjailu (K) :[RET]

Huomattakoon, että edellä LUPU ANKKA jäi vielä asumaan osoitteeseen Ankkakuja 6.  Tietysti ohjelma voitaisiin tehdä myös siten, että joidenkin tiettyjen arvojen muuttaminen muuttaisi haluttaessa myös muita (esim. kirjoitettu sama osoite).
2.4.5	Lopetus
Ohjelman lopetuksessa tulee huolehtia siitä, että ohjelman aikana mahdollisesti rekisteriin tehdyt muutokset tulevat talletetuksi.   Tämä voidaan tehdä automaattisesti tai talletus voidaan varmistaa käyttäjältä.  Automaattisen talletuksen tapauksessa alkuperäinen tiedosto on ehkä syytä tallettaa eri nimelle.  
      ###################################
      #   J Ä S E N R E K I S T E R I   #
      #     versio 9.95                 #
      #     Hannu Hanhi                 #
      ###################################

Tiedot talletettu tiedostoon KELMIT.DAT
Vanhat tiedot tiedostossa    KELMIT.BAK

  KIITOS KÄYTÖSTÄ JA TERVETULOA UUDELLEEN

2.5	Käyttöohjeen testaus
Kirjoitettu käyttöohje kannattaa antaa jollekin luotettavalle henkilölle "apinatestiin".  Onko toimintoja riittävästi?  Keksitäänkö erikoistapauksia, joista saattaisi seurata hankaluuksia?  
Miksi jäsenen tietojen etsintä ja korjailu ovat eri paikoissa?  Turvallisuussyistä!
2.5.1	Testaus
Annetaanpa käyttöohje vaikkapa kerhon sihteerin testattavaksi.  Hänelle on juuri vuodenvaihteessa tullut iso kasa pankkisiirtokuitteja uuden vuoden jäsenmaksujen maksamisesta.  Niinpä seuraakin ehkä keskustelu:
Sihteeri:		Miten jäsenmaksut korjataan.  Ensin kaikki viimevuotiset pitää poistaa.  Jaa ehkä jos joku on ainaisjäsen, niin hänen merkintäänsä ei poisteta.  No muilta kuitenkin.  Sitten pitäisi nämä kuitit saada naputeltua sinne!
Ohjelmoija:	No poisto ensiksi.  Valitse 4 eli korjailu.  Sitten valitse 1 Jäsenen nimi ja vastaa ensimmäisen jäsenen nimi.  Kun se löytyy, niin paina K niinkuin korjailu ja sitten vain RET kunnes olet jäsenmaksun kohdalla ja sitten välilyönti ja RET jäsenmaksuun.  Sitten taas 1 Jäsenen nimi jne.  Helppoa!
Sihteeri:		Tuohan vie AINAKIN vuoden!!!  MINÄ KÄYTÄN EDELLEEN VANHAA käsikirjanpitoani.  Tästä vaan kumilla vanha pois ja uusi tieto tilalle...
Ohjelmoija:	Öh, tuota mutta kun minä...
2.5.2	Korjaus
Siis parasta mennä takaisin miettimään.  Onneksi itse ohjelmaa ei ole ehditty kirjoittamaan.  Jäsenmaksut? 
Miten jäsenmaksukentän käyttö?   Ehkä tämä kenttä pitää säilyttää tiedoksi siitä, paljonko henkilön pitäisi maksaa jäsenmaksua.  Tarvitsemme siis uuden kentän:
Maksettu maksu    (10)          >

Ainais-  ja kunniajäsenet hoidetaan siten, että heidän jäsenmaksukseen, joka pitäisi maksaa, annetaan vaikkapa 0 mk.
Lisäksi meille ilmiselvät asiat eivät aina olleetkaan sihteerille selviä.  Ohjelmaamme täytynee lisätä myös avustus.
Lisätään kaksi uutta valintaa päävalikkoon:
...
Valitse:
   ?  = avustus
   0  = lopetus
   1  = lisää uusi jäsen
   2  = etsi jäsenen tiedot
   3  = tulosteet
   4  = tietojen korjailu
   5  = päivitä jäsenmaksuja
   :5

5. Päivitä jäsenmaksuja
=======================

Valitse:
   ?  = avustus
   0  = takaisin päävalintaan
   1  = poista kaikki edellisen vuoden maksut
   2  = kysy maksettu maksu nimen mukaan
   :1

Poistetaan kaikki edellisen vuoden jäsenmaksut!
Poistetaan siis kaikki maksetut jäsenmaksut (K/e):K
Odota hetki...  Jäsenmaksut poistettu!

Valitse:
   ?  = avustus
   0  = takaisin päävalintaan
   1  = poista kaikki edellisen vuoden maksut
   2  = kysy maksettu maksu nimen mukaan
   :2

Muutetaan maksettuja jäsenmaksuja!
==================================

Jos haluat kaikki jäsenet, anna *
Kysely loppuu, mikäli annat maksuksi  q
Jäsenen nimi      ()            >*[RET]

  Ankka Aku 010245-123U
  Ankkakuja 13 12345 ANKKALINNA
  k: 12-12325 t: 12-33333 a:
  Liittynyt -91.  Jäsenmaksu 50 mk.  Maksettu mk.
  velkaa Roopelle
Maksettu maksu mk ()            >[RET]

  Ankka Lupu 010356-127L
  c/o Aku Ankka (Ankkakuja 13 12345 ANKKALINNA)
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 10 mk.  Maksettu mk.
  Aku Ankan veljenpoika, sudenpentu
Maksettu maksu    ()            >10[RET]
... Jatkuu näin kunnes kaikki käyty läpi ...
... Tai vastataan ...
Maksettu maksu mk ()            >q[RET]

Jäsenen nimi      ()            >*aku*[RET]
  Ankka Aku 010245-123U
  Ankkakuja 13 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 50 mk.  Maksettu mk.
  velkaa Roopelle
Maksettu maksu    ()            >40[RET]
Pitäisi maksaa 50 mk ja on maksanut 40 mk.  
Onko oikein (K/e):e
Maksettu maksu mk ()            >50[RET]

Jäsenen nimi      ()            >[RET]

Valitse:
   ?  = avustus
   0  = takaisin päävalintaan
   1  = poista kaikki edellisen vuoden maksut
   2  = kysy maksettu maksu nimen mukaan
   :0
...

2.5.3	Muita korjauksia
Käyttäjällä saattaa tulla vaikeuksia myös kesken jonkin kysymyksen.  Tällöin voisi olla hyvä, että käyttäjä voi painaa ?- merkkiä ja saada avustusta siitä, mitä tähän kohti pitäisi syöttää (sisältöriippuva avustus, context sensitive help).
Jäsenmaksun päivittämisessä jokerimerkin käyttö tuntui varsin kätevältä tavalta saada joko yksi tai useampi henkilö (tai vaikkapa kaikki) päivitettäväksi.
Samaa ajatusta voitaisiin täydentää myös muuhun päivittämiseen.  Muutetaankin korjailussa esiintynyt kysymys
Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.)
tai poisto (P) tai korjailu (K) :

muotoon
Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.),
poisto (P), korjailu (K), seuraava (+), edellinen (-):

silloin, kun hakuehtoon täsmääviä on löytynyt useita.  Tekstit seuraava ja edellinen voidaan varmaankin jättää pois, jos seuraavaa tai edellistä ei ole.
Kenttälistaan hakuehdossa voitaisiin lisätä lisäkohta, jossa kaikille kentille voidaan antaa ehto (ja/tai): 
  1 = nimi
...
  9 = liittymisvuosi 
  A = jäsenmaksu mk  
  B = maksettu maksu mk
  C = lisätietoja    
  & = JA ehto kaikille kentille
  | = TAI ehto kaikille kentille

Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.):&

Kirjoita niihin kenttiin ehto, joiden mukaan haluat etsiä.
== tarkoittaa, että kentän TÄYTYY olla tyhjä.
Jäsenen nimi      ()            >*ankka*[RET]
Sotu              ()            >[RET]
Katuosoite        ()            >[RET]
Postinumero       ()            >[RET]
Postiosoite       ()            >[RET]
Kotipuhelin       ()            >[RET]
Työpuhelin        ()            >[RET]
Autopuhelin       ()            >[RET]
Liittymisvuosi    ()            >[RET]
Jäsenmaksu mk     ()            >[RET]
Maksettu maksu mk ()            >==[RET]
Lisätietoja       ()            >[RET]

Tähän täsmää 1 jäsentä:

  Ankka Tupu 010356-125J
  Ankkakuja 6 12345 ANKKALINNA
  k: 12-12324 t: a:
  Liittynyt -91.  Jäsenmaksu 10 mk. Maksettu mk.
  Aku Ankan veljenpoika
...

Edellä siis etsittiin kaikkia niitä Ankkoja, joilla maksettu maksu on tyhjä.  Näin sihteeri voisi aina tutkia kenellä maksut on maksamatta (tässä tapauksessa erityisesti Ankoista).  Hakuehtoihin voitaisiin vielä liittää epäyhtälöt:
<    <=    >   >=    ==   !=

Siis hakuehto voisi olla esimerkiksi
Jäsenen nimi      ()            >!=*ankka*[RET]
Sotu              ()            >[RET]
...
Jäsenmaksu mk     ()            ><30[RET]
Maksettu maksu mk ()            >==[RET]
Lisätietoja       ()            >[RET]

Eli etsitään niitä jäseniä, joiden nimi EI OLE *ankka* ja joiden jäsenmaksu on alle 30 sekä maksettu maksu on tyhjä.
Samalla tietojen etsimisessä kysymys
Lisää (K/e):[RET]

voitaisiin muuttaa selaukseksi:
Valitse kenttä jonka mukaan etsitään (?=kenttälista uudel.),
seuraava (+), edellinen (-):

Kerhon nimi saattaa olla varsin pitkä.  Sen antaminen aina ohjelman käynnistämisen yhteydessä voi olla työlästä.   Siksi käynnistämisessä kannattaakin antaa vain lyhenne, jolla tiedosto on talletettu.  Varsinainen nimi täytyy tallettaa jonnekin muualle.  Minne?
Nimi voitaisiin tallettaa vaikkapa jäsenrekisteritiedoston ensimmäiselle riville:
kelmit.dat - kerhon nimikin talteen
Kelmien kerho ry
; Kenttien järjestys tiedostossa on seuraava:
; sukunimi etunimi |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin|työpuhelin|
Ankka Aku          |010245-123U|Ankkakuja 6 |12345      |ANKKALINNA |12-12324   |          | 
Susi Sepe          |020347-123T|            |12555      |Takametsä  |           |          | 
Ponteva Veli       |030455-3333|            |12555      |Takametsä  |           |          | 

Kerhon nimi kysytään käyttäjältä uutta tiedostoa luotaessa.  
Jos halutaan vielä suurempaa yhteensopivuutta valmiiden tietokantaohjelmien kanssa, voidaan kerhon nimi tallettaa erilliseen tiedostoon muiden kerhoon liittyvien lisätietojen kanssa.  Esimerkiksi jos jäsenet on tiedostossa kelmit.dat, voisi lisätiedot olla tiedostossa kelmit.opt.  Tällöin myös kommentit (;) kannattaa jättää pois tiedostosta ja tiedoston ensimmäinen rivi on kenttien nimiä kuvaava rivi.
kelmit.dat - yhteensopivuus muihin ohjlemiin
nimi               |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin|työpuhelin|
Ankka Aku          |010245-123U|Ankkakuja 6 |12345      |ANKKALINNA |12-12324   |          | 
Susi Sepe          |020347-123T|            |12555      |Takametsä  |           |          | 
Ponteva Veli       |030455-3333|            |12555      |Takametsä  |           |          | 

kelmit.opt - yleiset tiedot tänne
[Tiedot]
nimi=Kelmien kerho ry
maxjaseniä=100

2.5.4	Uusi testaus
Koska käyttöohjeemme on tietenkin kirjoitettu tekstinkäsittelyohjelmalla (tai ohjelmaeditorilla), on muutokset helppo tehdä.  Menemme uudelleen sihteerin luokse uudistetun käyttöohjeen kanssa ja pyydämme jos hän armeliaasti vielä kerran suostuisi katsomaan sitä.  
Paranneltavaa saattaa löytyä lisää nyt kun kiinnostus ehkä herää.  Korjaamme selvät kohdat mutta joistakin asioista täytyy neuvotella ja ainakin tinkiä ettei niitä ehkä toteuteta ohjelman ensimmäiseen versioon.
Yksi selvä ongelma ilmenee.  Entä jos lisäyksessä todella on kaksi samannimistä jäsentä?  Homma täytynee korjata seuraavasti:
Jäsenen nimi      ()            >Ankka Aku[RET]
Rekisterissä on jo jäsen Aku Ankka!  Mikäli haluat muuttaa
hänen tietojaan, valitse tietojen korjailu päävalikosta!
Lisätäänkö sama nimi (l) vai kysytäänkö toinen nimi (T):T
Jäsenen nimi      ()            >ANKKA TUPU[RET]

Jatkamme testauskierrosta kunnes potentiaaliset käyttäjät on saatu tyytyväiseksi.
2.6	Tarvittavien algoritmien hahmottaminen
Nyt olemme selvillä ohjelman toiminnasta.  Edellisestä käyttöohjeesta voimme etsiä mitä työkaluja (aliohjelmia) tarvitsemme ohjelman toteutuksessa.  Ainakin seuraavat tulevat helposti mieleen:
2.6.1	Ylemmän tason aliohjelmat
1.	tiedoston lukeminen
2.	tiedoston tallettaminen
3.	henkilön tietojen kysyminen päätteeltä
4.	tiedoston lajittelu haluttuun järjestykseen
5.	tiedon etsiminen tiedostosta tietyllä hakuehdolla
6.	uuden henkilön lisääminen tiedostoon
7.	henkilön poistaminen tiedostosta
2.6.2	Alemman tason aliohjelmat
Mikäli tutkimme yo. palasia tarkemmin, tarvitsemme ehkä seuraavia pienempiä ohjelman palasia (apualiohjelmia):
1.	yhden merkin vastauksen lukeminen mahdollisen oletusarvon kanssa
2.	merkkijonon lukeminen päätteeltä siten, että sille voidaan jättää oletusarvo
3.	pitkän merkkijonon pilkkominen osamerkkijonoihin annetun merkin kohdalta
4.	loppuvälilyöntien poistaminen merkkijonosta
5.	isojen ja pienien kirjainten muuttaminen merkkijonossa Esim:
AKU ANKKA  ->  Aku Ankka
aku ankka  ->  Aku Ankka
aKU ANkKa  ->  AKU ANKKA
6.	sotun oikeellisuuden tarkistus
7.	ovatko merkkijonot "*aku*" ja "AKU ANKKA" samoja?
2.6.3	Ohjelman yhteiset osat
Kannattaa myös etsiä onko ohjelmassa samanlaisina toistuvia osia.  Edellä meillä selvästikin tiedon haku on samanlainen sekä etsimis-  että korjailukohdassa.  Samoin henkilön tietojen luku on samanlainen sekä lisäämisessä että korjailussa.  Yhden henkilön tietojen tulostaminen näytölle esiintyy useammassa kohdassa.
Mikäli löytyy likipitäen samanlaisuuksia, kannattaa harkita voidaanko ne käyttämisen yksinkertaistamiseksi ja/tai ohjelmoinnin helpottamiseksi muuttaa samanlaisiksi.  Mikäli voidaan, korjataan äkkiä käyttöohjetta tältä osin.
2.7	Ikkunoinnit ja muut hienostelut
Ohjelma voitaisiin suunnitella myös nykyaikaisen ikkunoidusti toimivan käyttöliittymän mukaiseksi.  Kuten edellä todettiin, tämä on kuitenkin ohjelmoinnin oppimisen tässä vaiheessa liian työlästä ja tähän paneudutaan vasta myöhemmillä kursseilla.
Tässäkin ohjelmassa korjailua voitaisiin parantaa siten, että meillä olisi käytössä aliohjelma, jolle korjailtava merkkijono vietäisiin parametrina.  Palautuksena tulisi korjattu merkkijono ja korjailun aikana toimisivat nuolinäppäimet yms. hienoudet.  Tosin suurtakaan muutosta ohjelmaan ei tarvitse tehdä mikäli em. aliohjelman tilalla käytämme aluksi vain merkkijonon lukemiseen kykenevää aliohjelmaa.  Myöhemmin tätä voitaisiin parantaa.
Menut voisivat olla nykytyyliin alasvetovalikoita, mutta aluksi meille riittää vallan hyvin käyttöohjeessa esitetyn kaltainen "näyttö tyhjäksi ja uusi menu ruutuun" - tyyli.  Myös hiirtä voitaisiin käyttää, mutta jälleen ohjelmointityö kasvaisi vastaavasti.  
Hakuehdot voisivat olla monipuolisempia ja niille voitaisiin yrittää keksiä jokin hienompi menetelmä.  Esimerkiksi tarvitsisi hakea seuraavilla ehdoilla
nimi on "*ankka*" tai jäsenmaksu on "<50"
postiosoite "ankka*" ja lisätiedoissa "*sudenpentu*"

Tätä varten hakemiseen voitaisiin kehitellä vaikkapa seuraavanlainen kieli:
Hakuehto >(nimi=*ankka*) || (jmaksu<50)[RET]
...
Hakuehto >(postiosoite=ankka*) && (lisätiedot=*sudenpentu*)[RET]

Saattaa tulla myös tarve lisätä uusia kenttiä henkilön tietoihin.   Tämä on hallittavissa huolellisella ohjelman suunnittelulla, jossa käytettyjen kenttien määrä ja nimet esiintyisivät vain yhdessä paikassa ohjelmaa.
Kun näitä haluttuja lisäominaisuuksia silmäillään, ei ole ihme että on kehitetty tietokantaohjelmia; eli halutut ominaisuudet tarvitaan lähes jokaisessa vastaavassa sovelluksessa.  Hieman muuttamalla oman ohjelmamme toimintaa, voisimme saada siitäkin yleiskäyttöisen tietokantaohjelman, mutta jätettäköön tämäkin lukijalle harjoitustehtäväksi.
Etsimisissä voisi olla oletuksena lisätä * kummallekin puolelle etsittävää jonoa, jolloin kun nimeen vastataan
aku

täydennetään tämä muotoon
*AKU*

ja näin löydetään Ankka Aku.
2.8	Koodaus ohjelmointikielelle
Seuraava vaihe olisi suunnitelman koodaaminen valitulle ohjelmointikielelle.  Voisimme kirjoittaa aluksi löytämiämme alimman tason aliohjelmia (BOTTOM-UP- suunnittelu) ja testata ne toimiviksi.  Voisimme myös kirjoittaa pääohjelman ja tyhjiä aliohjelmia testataksemme ohjelman rungon (TOP-DOWN).  Menujen alavalinnat voitaisiin laittaa vain sanomaan:
TOIMINTAA EI OLE VIELÄ TOTEUTETTU!

Emme kuitenkaan osaa vielä riittävästi ohjelmointikieltä, jotta voisimme aloittaa koodauksen.  Huomattakoon, ettei ylläolevassa suunnitelmassa ole missään kohti vedottu käytettävään ohjelmointikieleen.  Palaamme myöhemmin takaisin ohjelman osien koodaamiseen.
2.9	Varautuminen tulevaan, eli relaatiotietomalli
Vaikka sihteerimme ei juuri nyt huomannutkaan, saattaa hän tulevaisuudessa esimerkiksi kysyä miten rekisterillä pidettäisiin yllä tietoja jäsenten harrastuksista.  Mietitäänpä?
Ensin miten harrastukset muuttaisivat tiedostomuotoamme?
2.9.1	Kaikki samassa tietueessa
Eräs mahdollisuus olisi lisätä kunkin rivin loppuun jollakin erotinmerkillä harrastukset:
kelmit.dat - harrasteet samalle riville
Kelmien kerho ry
; Kenttien järjestys tiedostossa on seuraava:
; sukunimi etunimi |sotu       |…|harrastukset
Ankka Aku          |010245-123U|…|kalastus,laiskottelu,työn pakoilu 
Susi Sepe          |020347-123T|…|possujen jahtaaminen,kelmien kerho
Ponteva Veli       |030455-3333|…|susiansojen rakentaminen

Ratkaisu toimisi tietyissä erityistapauksissa.  Ongelmia tulisi esimerkiksi jos pitäisi kuhunkin harrastukseen liittää esimerkiksi harrastuksen aloitusvuosi, viikottain harrastukseen käytetty tuntimäärä jne.
2.9.2	Erimalliset tietueet
Edellinen ongelma ratkeaisi esimerkiksi laittamalla henkilön tietojen rivin perään jollakin tavalla eroavia rivejä, joilla harrastuksen on lueteltu:
kelmit.dat - harrasteet omalle riville
Kelmien kerho ry
; Kenttien järjestys tiedostossa on seuraava:
; sukunimi etunimi |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin|työpuhelin|
Ankka Aku          |010245-123U|Ankkakuja 6 |12345      |ANKKALINNA |12-12324   |          | 
- kalastus                 | 1955 | 20
- laiskottelu              | 1950 | 20
- työn pakoilu             | 1952 | 40
Susi Sepe          |020347-123T|            |12555      |Takametsä  |           |          | 
- possujen jahtaaminen     | 1954 | 20
- kelmien kerho            | 1962 |  2
Ponteva Veli       |030455-3333|            |12555      |Takametsä  |           |          | 
- susiansojen rakentaminen | 1956 | 15

Ratkaisu olisi aivan hyvä ja tämän ratkaisun valitsemiseksi meidän ei tarvitsisi tehdä mitään muutoksia tiedostomuotoomme vielä tässä vaiheessa.
Huono puoli on kuitenkin se, että tämän muotoisen tiedoston siirrettävyys muihin järjestelmiin on varsin huono.
2.9.3	Relaatiomalli
Suurin osa tämän hetken valmiista järjestelmistä käyttää relaatiotietokantamallia.  Tämä tarkoittaa sitä, että koko tietokanta koostuu pienistä tauluista, jossa kukin rivi (=tietue) on samaa muotoa.  Eri taulujen välillä tiedot yhdistetään yksikäsitteisten avainkenttien avulla.  Meidän esimerkissämme kelmit.dat olisi yksi tällaisen taulu ja sosiaaliturvatunnus kelpaisi yhdistäväksi avaimeksi (relaatioksi).
Kuitenkin sosiaaliturvatunnus on varsin pitkä kirjoittaa ja välttämättä sitä ei saada kaikilta.  Jos tällainen pelko on olemassa, täytyy avain luoda itse.  Itse ohjelman käyttäjän ei tarvitse tietää mitään tästä uudesta muutoksesta, vaan ohjelma voi itse generoida avaimet ja käyttää niitä sisäisesti.
Valitaan vaikkapa juoksevasti generoituva numero. Jos jäseniä poistetaan jää ko. jäsenen numero vapaaksi eikä sitä yritetäkään enää käyttää.  Uuden jäsenen numero olisi sitten aina suurin jäsenen numero +1.
kelmit.dat - relaatiokannan päätaulu
Kelmien kerho ry
; Kenttien järjestys tiedostossa on seuraava:
;id|sukunimi etunimi   |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin|työpuhelin|
1  |Ankka Aku          |010245-123U|Ankkakuja 6 |12345      |ANKKALINNA |12-12324   |          | 
2  |Susi Sepe          |020347-123T|            |12555      |Takametsä  |           |          | 
4  |Ponteva Veli       |030455-3333|            |12555      |Takametsä  |           |          | 

Harrastukset kirjoitetaan toiseen tiedostoon, jossa tunnusnumerolla ilmaistaan kuka harrastaa mitäkin harrastusta.
harrastu.dat - harrasteet relaation avulla
id|harrastus                |aloit |viikossa
1 |kalastus                 | 1955 | 20
1 |laiskottelu              | 1950 | 20
1 |työn pakoilu             | 1952 | 40
2 |possujen jahtaaminen     | 1954 | 20
2 |kelmien kerho            | 1962 |  2
4 |susiansojen rakentaminen | 1956 | 15

Nyt esimerkiksi kysymykseen "Mitä Sepe Susi harrastaa" saataisiin vastus etsimällä ensin Sepe Suden tunnus (2) tiedostosta kelmit.dat.  Sitten etsittäisiin ja tulostettaisiin kaikki rivit joissa tunnus on 2 tiedostosta harrastu.dat.
Myös vastaus kysymykseen "Ketkä harrastavat laiskottelua" löytyisi suhteellisen helposti.
Tämä ratkaisu vaatii muutoksen tiedostomuotoomme jo suunnitelman tässä vaiheessa, mutta toisaalta mikäli ratkaisu valitaan, voidaan sen ansiosta lisätä jatkossa vastaavia "monimutkaisia" kenttiä rajattomasti tekemällä kullekin oma "taulu".
Valitsemmekin siis tämän ratkaisun, eli annamme kullekin jäsenelle tunnusnumeron heti alusta pitäen.  Itse ohjelman käyttösuunnitelmaan ei tässä vaiheessa tarvita muutoksia.
Tehtävä 2.2	Ketkä harrastavat?
Kirjoita algoritmi joka relaatiomallin tapauksessa vastaa kysymykseen "Ketkä harrastavat harrastusta X".
Tehtävä 2.3	Mikä on tilaa säästävin talletusmuoto
Laske mikä edellä esitetyistä kolmesta vaihtoehdosta on tilaa säästävin kun rivinvaihtomerkin lasketaan vievän yhden merkin verran tilaa ja välilyönnit "unohdetaan". 

3.	Yksinkertainen tulkkiohjelma
3.1	Tehtävän tarkennus 
Olkoon meillä tehtävänä tehdä satunnaisen matkaajan käyttöön soveltuva tulkkiohjelma, joka kääntää sana kerrallaan.  Mitään valmiita lauseita ja taivutusmuotoja hallitsevaa ohjelmaa emme edes yritä tehdä.  Tavoitteena on siis sanakirjan yksinkertaistettu elektroninen versio.
Ohjelman yksi tavoite täytyisi olla sanaston helppo täydentäminen.  Sanasto on jälleen luonnollisinta säilyttää tiedostossa.  Mikäli sanasto on normaali tekstitiedosto, on sen täydentäminen tekstinkäsittelyohjelmalla helppoa. Nykyisin JOKAISEN on osattava käyttää jotakin tekstinkäsittelyohjelmaa!
Tehtäväksemme jää siis sanastotiedoston muodon suunnittelu sekä itse ohjelman toimintojen suunnittelu.
3.2	Tietorakenteet ja tiedostot
Sanasto voisi näyttää vaikkapa seuraavalta:
Suomi     Ruotsi    Englanti      
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
minä      jag       i
sinä      du        you
hän       han       he

Tässä tuleekin nyt ongelmia.  Tarvitaan synonyymejä.  Esimerkiksi "hän" täytyisi voida sanoa "he" tai "she" suvun mukaan.  Miten esittäisimme synonyymit? Tarvitaan ehkä myös kommentteja!  Ratkaisu voisi olla seuraava:

Suomi        | Ruotsi         | Englanti      
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
minä         | jag            | i
sinä         | du             | you (yks.)
hän          | han (mask.)    | he  (mask.)
hän          | hon (fem.)     | she (fem.)
me           | vi             | we          
te           | ni             | you (mon.)
he           | dom            | they
hernekeitto  | ärtsoppa       | pea soup
sukellusvene | ubåt           | submarine
suklaa       | chokolade      | chocolate
sukka        | socka (lyhyt)  | sock (lyhyt)
sukka        | strumpa (pitkä)| stocking (pitkä) 

Itse ohjelman on sitten huolehdittava siitä, että synonyymien tapauksessa myös synonyymit tulostetaan.
Ohjelma lukee käynnistyessään koko tiedoston.  Tiedoston 1. riviltä selviävät käytettyjen kielten nimet.  Nimessä iso kirjain tarkoittaa kirjainta, jolla kielen nimi voidaan lyhentää.
Tehtävä 3.4	Kielen lisääminen
Lisää sanastoon Savon kieli! 
3.3	Käyttöohje 
Ohjelman suunnittelu on tässäkin tapauksessa parasta tehdä käyttöohjeen avulla.  Suunnittelemme aluksi kuitenkin ohjelmalta halutut toiminnot.
Satunnaisen matkaajan tarpeet ovat varmaankin seuraavat:
•	Hän istuu ruokapöydässä Ruotsissa ja lukee menua.  Pitäisi saada selville mitä "ärtsoppa" tarkoittaa.
•	Hän haluaa "hernekeittoa".  Mitä se on ruotsiksi?
•	Hän näkee sukellusveneen Tukholman rannikolla ja haluaa kertoa tästä vieressään seisovalle merisotilaalle.
•	Ruotsalainen merisotilas ei hetkauta korvaansa "UBÅT- UBÅT" - huudoille.  Takana seisoo englantilainen kenraali ja satunnainen matkaaja haluaisi tiedottaa näkemästään myös hänelle. 

Suomalaisen ollessa Ruotsissa tarvitaan käännöksiä ruotsi- suomi ja suomi- ruotsi.   Siis molemmat kielet tulee voida valita.
Hätätilassa pitää suhteellisen helposti päästä käsiksi myös muunkielisiin sanoihin.
Periaatteessa, mikäli kielet on jo valittu, riittäisi käyttö tyyliin
Sana >ärtsoppa[RET]
ruotsi: ärtsoppa = suomi: hernekeitto

Entäpä mikäli kielinä olisi suomi ja englanti:
Sana >he[RET]
suomi   : he         = englanti: they
englanti: he (mask.) = suomi   : hän

Tämä voisi olla aivan hyvä ratkaisu.  Miten kielen vaihto kävisi kätevästi (nopeasti)?
Sana >sukellusvene/s- e[RET]
suomi: sukellusvene = englanti: submarine

Voisi olla tarpeen myös saada käännös samanaikaisesti usealla eri kielellä:
Sana >sukellusvene/s- re[RET]
suomi: sukellusvene = ruotsi  : ubåt
                    = englanti: submarine

Sovimme siis, että sanan perään kirjoitetaan kauttaviivan jälkeen mistä kielestä käännetään ja mihin.  Mikäli kumpiakin kieliä on vain yksi, käännös voi olla kumpaankin suuntaan.  Mikäli toista kieltä on useita kappaleita, tehdään käännös vain kuten on pyydetty.  Mikäli toista kieltä ei anneta lainkaan, tarkoitetaan kaikkia mahdollisia kieliä.
Kerran annetut käännöskielet säilyvät, kunnes käännöskielet annetaan uudelleen.  
Siis ohjelman toiminta voisi olla vaikkapa seuraavanlainen:
Terve! Olen Satunnaiselle matkaajalle suunniteltu Tulkki!
Sanastoni on tiedostossa SANASTO.DAT.  Mikäli haluat lisätä
sanastoa, käytä tekstinkäsittelyohjelmaa sanaston
päivittämiseksi.

Avustusta saat vastaamalla ? kun sinulta kysytään sanaa.
Tulkattavina kielinä on nyt suomi- ruotsi.
Sana>?[RET]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Avustus:

Kirjoita sanakysymyksen perään sana jonka haluat kääntää.
Mikäli painat pelkän [RET], ohjelman toiminta loppuu.

Mikäli haluat vaihtaa kieliä, kirjoita sanan perään
kauttaviiva ja kielten nimien lyhennekirjaimet, joita 
haluat kääntää.  Mikäli haluat käännöksen samalla 
usealle eri kielelle/kieleltä, kirjoita kaikki haluamasi 
kielet.  Mikäli haluat käännöksen kaikille mahdollisille
kielille, kirjoita vain lähtökieli.

Tunnen kielet Suomi Ruotsi Englanti.
Esimerkki: 
Sana>kissa/s- e
suomi: kissa = englanti: cat
Sana>car
englanti: car = suomi: auto
Sanassa voi käyttää myös jokerimerkkejä * ja ?
Esimerkki:
Sana>suk*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

Sana>hän/s- e[RET]
suomi: hän = englanti: he  (mask.)
           = englanti: she (fem.)
Sana>he[RET]
suomi   : he         = englanti: they
englanti: he (mask.) = suomi   : hän

Sana>hän/s- [RET]
suomi: hän = englanti: he  (mask.)
           = ruotsi  : han (mask.)
           = englanti: she (fem.)
           = ruotsi  : hon (fem.)

Sana>suk*/s- r[RET]
suomi: sukellusvene = ruotsi : ubåt
       sukka        = ruotsi : strumpa (pitkä)
       sukka        = ruotsi : socka (lyhyt) 
       suklaa       = ruotsi : chokolade

Sana>käyrätorviorkesteri[RET]
Ei löydy sanastosta!
Sana>[RET]
Kiitos käytöstä! 

.Satunnainen matkaaja voi olla huono/hidas konekirjoittaja.  Siksi valitsimme hänelle lisäksi mahdollisuuden antaa sanoja jokerimerkkien avulla.  Näin hänen ei välttämättä tarvitse osata kirjoittaa koko sanaa vaan hän voi lyhentää sen haluamastaan kohdasta.  Tätä sanotaan käyttäjäystävällisyydeksi
Mikäli kieliä ei anneta lainkaan,  sanaa etsittäisiin kaikista mahdollisista kielistä.  Tällaisen option tarve olisi silloin, kun ei ole harmaintakaan aavistusta siitä, millä kielellä sana on kirjoitettu.
Sana >h*/- [RET]
suomi   : hän         = ruotsi  : han (mask.)
                      = englanti: he (mask.)
ruotsi  : han (mask.) = suomi   : hän
                      = englanti: he (mask.)
englanti: he (mask.)  = suomi   : hän
                      = ruotsi  : han (mask.)
suomi   : hän         = ruotsi  : hon (fem.)
                      = englanti: she (fem.)
ruotsi  : hon (fem.)  = suomi   : hän 
                      = englanti: she (fem.)
suomi   : he          = ruotsi  : dom
                      = englanti: they
suomi   : hernekeitto = ruotsi  : ärtsoppa
                      = englanti: pea soup
Sana >h*/- s[RET]
ruotsi  : han (mask.) = suomi   : hän
englanti: he (mask.)  = suomi   : hän
ruotsi  : hon (fem.)  = suomi   : hän 

3.4	Algoritmien hahmottaminen 
3.4.1	Ylemmän tason aliohjelmat 
Voimme jälleen havaita seuraavat suuremmat ohjelman kokonaisuudet:
1.	tiedoston lukeminen
1.	sanan lukeminen ja kielten erottaminen
1.	sanan etsiminen sanastosta sekä synonyymien tarkistus
Miten sanaa etsitään sanastosta?  Mikäli sanasto ei ole järjestetty (voidaan myöhemmin järjestää mikäli sanasto kasvaa) riittää varmaankin raaka peräkkäishaku.  
Miten löydetään sanaa vastaavat muunkieliset tulkinnat?  Ne ovat löytyneen sanan kanssa samalla rivillä.
Käännöksenä on esim. "s- e" (suomi- englanti) ja annetaan sana he.  Mitä tehdään?  Etsitään suomi - sarakkeesta sanan "he" - rivi ja tulostetaan rivin englanti - sarakkeessa oleva sana.  Etsitään englanti - sarakkeesta "he" - rivi ja tulostetaan rivin suomi - sarakkeessa oleva sana.
3.4.2	Alemman tason aliohjelmat 
Yllättäen alemman tason aliohjelmissa tarvitaan samoja ominaisuuksia kuin jäsenrekisteriäkin tehtäessä:
1.	merkkijonon lukeminen päätteeltä siten, että sille voidaan jättää oletusarvo (laitetaan oletusarvoksi tyhjä)
1.	pitkän merkkijonon pilkkominen osamerkkijonoihin annetun merkin kohdalta
1.	loppuvälilyöntien poistaminen merkkijonosta 
1.	isojen ja pienien kirjainten muuttaminen merkkijonossa.  Esim:
AKU ANKKA     - >  Aku Ankka
aku ankka     - >  Aku Ankka
aKU ANkKa     - >  AKU ANKKA
1.	ovatko merkkijonot "*aku*" ja "AKU ANKKA" samoja?
Tehtävä 3.5	Aliohjelmien käyttö
1.	Mikä edellisistä sopii sanan perässä olevan kommentin poistoon (suluissa oleva sana)?
1.	Voidaanko jotain edellistä soveltaa sanan ja kielten erottamiseen?  
1.	Entä kun käännös (esim. s- e) on löytynyt, niin voidaanko jotain edelläolevista soveltaa mistä-  ja mihin-  kielten erottamiseen?

Mikäli käännösetsiminen osataan tehdä esim. s- sarakkeesta ja tulos ottaa e- sarakkeesta ohjeella "s- e", niin miten sama saataisiin tehtyä päinvastoin?  Ohjeella "e- s"?.  Siis mikäli käännösohjeeksi valittaisiin "s- e", niin se ehkä kannattaisi muuttaa muotoon "se- es".  Näin kahden kielen tapausta ei tarvitsisi käsitellä minään erikoistapauksena.  Samalla ajatuksella ohje "- " muutettaisiin muotoon "sre- sre" (eli kaikkiin tunnettuihin kieliin).  Siis koko kääntäminen muodostuisi siten, että annettua sanaa etsitään ohjeessa vasemmalla annetuista sarakkeista ja mikäli se jostakin niistä löytyy, tulostettaisiin oikealla annetut sarakkeet paitsi se sarake josta sana löytyi!
Tehtävä 3.6	Erikoistapauksia
Miten muutetaan seuraavat käännöskielet:
	- s
	r- 
Tehtävä 3.7	Algoritmi muuttamiselle
Kirjoita selkeät säännöt (=taulukko), miten käännösohje muutetaan kussakin erikoistapauksessa.
syöttömuoto     muunnettu muoto
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
s- e             se- es
e- s             es- se
s- r             sr- rs
s-               s- sre
...
-                sre- sre
... 

Eräs ongelma on se, miten tulos muotoillaan siistiksi, eli löydetään pisimmät mahdolliset sanat ja kielet jotta vastinsanat saadaan tulostettua siististi allekkain.
3.5	Koodaus ohjelmointikielelle 
Koodaus jätetään jälleen oppimisen myöhempään vaiheeseen.  Taaskaan suunnittelun alkuvaihe ei sisältänyt (eikä tarvinnut sisältää) mitään tietoa siitä, millä ohjelmointikielellä ohjelma toteutettaisiin.

4.	Algoritmin suunnittelu
Mitä tässä luvussa käsitellään?
•	mikä on algoritmi
•	vertailu ja lajittelu
•	algoritmin kompleksisuus
•	alkion etsiminen joukosta

Kun ohjelman suunnittelu on edennyt siihen pisteeseen, että tarvitaan yksityiskohtaisia algoritmeja, meneekin jo monella sormi suuhun.
Vaikeudet johtuvat taas liian hankalasta ajattelutavasta ja siitä, että algoritmi yritetään nähdä osana koko ohjelmaa.  Tästä ajattelutavasta on luovuttava ja osattava määritellä tarvittava algoritmi omana kokonaisuutenaan, jota suunniteltaessa sitten unohdetaan kaikki muu.
4.1	Algoritmi
Algoritmi on se joukko toimenpiteitä, joilla annettu tehtävä saadaan suoritettua.  Mieti esimerkiksi miten selostat kaverillesi ohjeet juna- asemalta opiskelu- boxiisi.
Voit tietysti antaa ohjeet myös muodossa "Tule osoitteeseen Ohjelmoijankuja 17 B 5".  Tämäkin on varsin hyvä algoritmi.  Kaverin vain oletetaan nyt osaavan enemmän.  Kaverin oletetaan osaavan etsiä katuluettelosta kadun paikka ja keksivän itse menetelmän tulla asemalta sinne.
Toisaalta kaverisi saattaa hypätä taksiin ja sanoa kuskille osoitteen.  Tämä on hyvä ja helppo algoritmi, mutta ehkä liian kallis opintovelkaiselle opiskelijalle.  Mikäli algoritmia tarvitaan useasti, voidaan sitä myöhemmin parantaa tyyliin:
-  kävele asemalta sinne ja sinne
-  hyppää bussiin se ja se
-  jne

Tarkennettu algoritmi voisi olla myös seuraavanlainen:
Valitse seuraavista:
1.  Kello 7- 20:
     -  kävele kirkkopuistoon
     -  nouse bussiin no 3 joka lähtee 15 yli ja 15 vaille
2.  Sinulla on rahaa tai saat kimpan:
     -  ota taksi
3.  Ei rahaa tai haluat ulkoilla:
     -  kävele

Edellä eri kohdat eivät ole toisiaan poissulkevia.  Kello voi olla 9 ja rahaakin voi olla, mutta siitä huolimatta halutaan kävellä.  Hyvässä algoritmissa ei saa olla tällaisia epätäsmällisyyksiä, vaan ohjelmoijan tulee etukäteen jo päättää mitä missäkin tapauksessa tehdään.  Esimerkiksi:
1.  Jos haluat ulkoilla, niin 
     -  kävele.
2.  Muuten jos kello 7- 20:
     -  kävele kirkkopuistoon
     -  nouse bussiin no 3 joka lähtee 15 yli ja 15 vaille
3.  Muuten jos sinulla on rahaa tai saat kimpan:
     -  ota taksi
4.  Muuten
     -  kävele

Tässäkin algoritmissa jää vielä kaverillekin tehtävää:  Miten kävellään?  Miten astutaan bussiin jne..
No tätä ei kaverille ehkä enää selostetakaan.  Lapsille nämä asiat on aikanaan opetettu ja myöhemmin ne kuitataan yhdellä tai kahdella sanalla.  Sama pätee ohjelmoinnissakin.  Kerran tehtyä ei joka kerran pureksita uudelleen (vrt. aliohjelma)! 
Tehtävä 4.8	Kävelyohjeet
Yritä kirjoittaa ohjeet siitä miten kävellään.

Kirjoita kaverillesi kävelyohjeet (missä käännytään, ei miten kävellään) rautatieasemalta asunnollesi.

4.2	Lajittelu 
Kerhon jäsenrekisteriä suunniteltaessa tulee jossakin kohtaa vastaan tilanne, jossa nimet tai osoitteet pitää pystyä lajittelemaan jollakin tavalla.
4.2.1	Nimien ja numeroiden vertaus
Jos osaamme lajitella numeroita, niin osaammeko lajitella nimiä?  Vastaus on KYLLÄ.  Mikä numeroiden lajittelussa on oleellista?  Oleellista on tietää onko numero A pienempi kuin numero B.  Miten tämä sitten soveltuu nimille?  Jos osaamme päättää onko nimi A aakkosissa ennenkuin nimi B, on ongelma ratkaistu.
Verrataanpa erilaisia nimiä:
A: Kassinen Katto
B: Ankka Aku

B on ensin aakkosissa.  Miksi?  Koska B:n ensimmäinen kirjain (A) on ennen nimen A ensimmäistä kirjainta (K).
A: Kassinen Katto
B: Karhukopla 701107

Nytkin B on ensin.  Siis miten vertaamme kahta nimeä?
Vertaamme nimiä merkki kerrallaan kunnes vastaan tulee eri- 
suuret kirjaimet.  Kumpi erisuurista kirjaimista on aakko- 
sissa ennen, määrää sen kumpi nimistä on aakkosissa ennen. 

Siinä meillä on algoritmi joka on varsin selvä.  Jos algoritmi haluttaisiin vielä kirjoittaa "lausekieliseen"- muotoon, niin se olisi suurinpiirtein seuraavanlainen:
1. siirry kummankin nimen ensimmäiseen kirjaimeen
2. jos kummankin nimen viimeinen merkki on ohitettu, niin nimet ovat samat
3. jos toisessa nimessä viimeinen merkki on ohitettu, niin se on ennen 
   aakkosissa
4. verrataan vuorossa olevia kirjaimia kummastakin nimestä
 -  jos samat, niin siirrytään seuraaviin kirjaimiin ja jatketaan kohdasta 
   2.
 -  jos erisuuret, niin se ensin aakkosissa, jonka kirjain on ensin

Tähän vielä pieni "viilaus enemmän strukturoidummaksi", niin meillä olisikin valmis (ali)ohjelma nimien vertaamiseksi.
4.2.2	Algoritmin sanallinen versio on kuvaavampi!
Vaikka esitimmekin algoritmin "lausekielisenä" kohdittain numeroituna, ei koskaan pidä unohtaa sitä ennen ollutta sanallista versiota, joka on selkeämpi kuvaus siitä ideasta, mitä tehdään!
Siis kirjoita aina ensin sanallinen kuvaava kuvaus algoritmista ja vasta sitten sen yksityiskohtainen "lausekielinen" versio!
4.2.3	Numeroiden järjestäminen 
Näin ollen on aivan yksi lysti opettelemmeko järjestämään nimiä vai numeroita.  Siksi paneudummekin seuraavassa numeroiden järjestämiseen.  Kuulostaako vaikealta?
Otapa käteesi korttipakka ja ota sieltä esiin vaikkapa vain kaikki padat.  Nyt sinulla on joukko "numeroita" (A=14, K=13, Q=12, J=11), yhteensä 13 kappaletta.  Sekoita kortit!  Yritä järjestää kortit suuruusjärjestykseen siten, ettet tarvitse pöytätilaa kuin yhden kortin verran, loput kortit pidät kädessäsi.
Millaisen algoritmin saat?  Ehkäpä seuraavan (insertion sort):
Pöydällä on lajiteltujen kasa.  Aluksi tietysti tyhjä.  Ota
kädestäsi seuraava kortti ja laita pöydällä olevaan kasaan 
omalle paikalleen.  Jatka kunnes kädessä ei enää kortteja. 

"Lausekielisenä":
1. ota kädessä olevan kasan päällimmäinen kortti
2. sijoita se pöydällä olevaan kasaan paikalleen
3. mikäli kortteja vielä jäljellä, niin jatka kohdasta 1. 

Algoritmisi voi olla myös seuraava (selection sort):
Etsitään aina pienin kortti ja laitetaan se pöydälle olevan 
kasan päällimmäiseksi.  Jatketaan kunnes kädessä olevat 
kortit on loppu. 

Eli "lausekielisenä":
1.  etsi kädessäsi olevista korteista pienin
2.  laita se pöydällä olevan pinon päällimmäiseksi
3.  mikäli vielä kortteja jäljellä, niin jatka kohdasta 1. 

Tehtävä 4.9	Muita lajittelualgoritmeja
Mitä muita mahdollisia "lajittelumenetelmiä" keksit?

Siinä eräitä ratkaisuja tähän "hirveän vaikeaan" ongelmaan.  Ratkaisuissa on tiettyjä huonoja puolia, mutta ne ovat todella yksinkertaisia ja jokaisen itse keksittävissä.  
Tehtävä 4.10	Algoritmin kompleksisuus
Mikäli kahden kortin vertaaminen lasketaan yhdeksi "operaatioksi", niin kuinka monta "operaatiota" joudumme tekemään, jotta pakka on lajiteltu?

Edellisen tehtävän vastausta sanotaan algoritmin kompleksisuudeksi.
Tehtävä 4.11	Lajittelujärjestys
Edellinen algoritmi (selection sort) toimi siten, että kortit jäivät pöydälle suurin päällimmäiseksi.  Miten algoritmia pitää muuttaa, jotta pienin saataisiin päällimmäiseksi?

Ei siis ole suurtakaan väliä pitääkö lajitella nouseva vai laskeva järjestys!
4.2.4	Kuplalajittelu
Kokeillaanpa vielä erästä algoritmia:  Sotke kortit kädessäsi uudelleen.
Bubble sort:
Vertaa aina kahta peräkkäistä korttia keskenään.  Mikäli ne
ovat väärässä järjestyksessä, vaihda ne keskenään.  Kun koko
pakka on käyty lävitse, aloita alusta ja jatka kunnes yhtään 
kertaa ei tarvitse vaihtaa peräkkäisiä kortteja. 

Tehtävä 4.12	Kuplalajittelu
Tuleeko pakka järjestykseen tällä algoritmilla?  Voidaanko algoritmia nopeuttaa mitenkään?  Kirjoita algoritmista "lausekielinen"- versio.

4.2.5	Lajittelu avaimen mukaan
Kirjoita nyt joukko pahvilappuja, joissa kussakin on henkilön nimi, osoite ja puhelinnumero. 
      
Sekoita laput ja kokeile toimiiko edelliset algoritmit mikäli laput järjestetään nimien mukaan.  Ai tyhmä ehdotus!  Tässä se onkin ohjelmoinnin vaikeus.  Asiat ovat yksinkertaisia!  Eiväthän ne osoitteet siellä lajittelua sotke.  
Mikäli laput järjestetään nimen mukaan, sanotaan nimen olevan lajitteluavaimena.  Lajitteluavaimeksi voitaisiin valita myös osoite tai puhelinnumero. Mikäli kahdella henkilöllä olisi sama nimi, voitaisiin nämä kaksi järjestää osoitteen perusteella.  Tällöin lajitteluavain muodostuisi merkkijonosta johon olisi yhdistettynä nimi ja osoite.
4.2.6	Algoritmin parantaminen
Kaikki edelliset algoritmit ovat kompleksisuudeltaan normaalitapauksessa samanlaisia.
Tehtävä 4.13	Loppuminen erikoistapauksessa
Mikä edellisistä algoritmeista loppuu nopeasti, mikäli kortit jo olivat järjestyksessä?

Ohjelman toimintaan saattamisen kannalta olisi riittävää löytää jokin toimiva algoritmi.  Myöhemmin, mikäli ohjelman toiminta todetaan hitaaksi ko. algoritmin kohdalta, voidaan algoritmia yrittää tehostaa.  Lajittelussa tehostus saattaisi olla vaikkapa QuickSort (mukana mm. C- kielen standardikirjastossa).
Tehtävä 4.14	QuickSortin kompleksisuus
Jos algoritmin kompleksisuus on esimerkiksi 2n2+n, sanotaan että kompleksisuus on O(n2), eli usein kiinnostaa vain kompleksisuuden suurin "potenssi".  QuickSortin keskimääräinen kompleksisuus on O(n log2n).  On olemassa myös erikoistapauksissa toimivia lajitteluja, joissa kompleksisuus on O(n).  Piirrä kuva jossa on Selection Sortin, QuickSortin ja lineaarisen lajittelun käyttämä "aika" piirrettynä lajiteltavien alkioiden (n=10,100,1000,10000,1000000) funktiona.
Tehtävä 4.15	Lisäys oikealle paikalleen vaiko lisäys loppuun ja lajittelu?
Tutki kumpiko on työmäärältään edullisempaa jos järjestettyyn taulukkoon tulee lisättäväksi suuri määrä uusia alkiota 
1) 	lisätä alkio aina taulukkoon oikealle paikalleen
2) 	lisätä alkio aina taulukon loppuun ja kun kaikki alkiot on lisätty, niin lajitella taulukko

4.3	Algoritmin tarkentaminen 
Edellisissä lajittelualgoritmeissa oli vielä muutamia aukkopaikkoja!  Etsi pienin?  Laita oikealle paikalleen?
4.3.1	Pienimmän etsiminen 
Miten kädessä olevista korteista voidaan etsiä pienin.  Yksi mahdollisuus on kuljettaa "pieninehdokasta" läpi koko pakan.  Mikäli matkan varrelta löytyy parempi ehdokas, otetaan tämä tilalle.  Edellä mainittu kuplalajittelu korjattuna perustuu nimenomaan tähän ideaan.
Entä jos kädessä olevien korttien järjestystä ei haluta muuttaa?  Voisimme menetellä esimerkiksi seuraavasti (alkuarvaus ja arvauksen korjaaminen):
0.  vedä kädessä olevan pakan ylin kortti hieman esille
    ota ensimmäinen kortti tutkittavaksi
1.  vertaa tutkittavaa korttia ja esiinvedettyä korttia
2.  mikäli tutkittava on pienempi, vedä se esiin ja työnnä
    edellinen takaisin
3.  siirry tutkimaan seuraavaa korttia ja jatka kohdasta 1.
    kunnes olet tutkinut koko pakan

4.3.2	Paikalleen sijoittaminen
Miten kortti sijoitetaan paikalleen jo lajiteltuun kasaan? Esimerkiksi seuraavasti:
0. laita uusi kortti päällimmäiseksi lajiteltuun kasaan
1. vertaa uutta ja seuraavaa
2. mikäli väärässä järjestyksessä, niin vaihda ne keskenään
   ja jatka kohdasta 1. 

4.4	Haku järjestetystä joukosta 
Usein tulee vastaan myös tilanne, jossa tietyn henkilön tiedot pitäisi hakea esimerkiksi nimen mukaan.  Mikäli valittu tietorakenne on järjestetty nimen mukaan, voidaan hakemisessa käyttää vaikkapa puolitushakua.
Nimen hakeminen ei taas poikenne kortin etsimisestä järjestetystä korttipakasta vai mitä?
4.4.1	Suora haku 
 Kun kortit ovat järjestämättä, niin miten löydät haluamasi kortin?
Ota seuraava kortti.  Mikäli etsittävä niin lopeta, muuten ota taas seuraava. 

Algoritmi on OK 13 kortille, mutta kokeilepa Äystön etsimistä puhelinluettelosta tällä algoritmilla (muista lukea jokainen nimi ennen Äystöä)!
4.4.2	Puolitushaku
Mikäli 13 korttiasi on järjestyksessä ja sinun pitäisi mahdollisimman vähällä pläräämisellä löytää vaikkapa pata 4, niin miten voisit menetellä?
1. laita pakka pöydälle kuvapuolet ylöspäin
2. laita pakka puoliksi
3. laita molemmat pakat pöydälle kuvapuolet ylöspäin
4. kummassako kasassa etsittävä on?
5. heitä se pakka pois jossa etsittävä ei ole
6. jos etsittävä ei päällimmäinen, niin jatka kohdasta 1. 

Vaikuttaa tyhmältä 13 kortille, mutta kokeilepa 1000 kortilla!  Tai kokeile nyt etsiä ÄYSTÖÄ puhelinluettelosta tällä algoritmilla.
Tehtävä 4.16	Puolitushaku
Kirjoita puolitushausta kunnon "lausekielinen versio" kun meillä on sivunumeroitu kirja, jonka kullakin sivulla on täsmälleen yhden henkilön tiedot.  Sivunumeroita kirjassa on N- kappaletta. Aloitat sivuista S1=0 ja S2=N+1.  Miten jatkat mikäli pitää etsiä nimi NIMI?
Tehtävä 4.17	Puolitushaun kompleksisuus
Mikä on puolitushaun kompleksisuus?
4.5	Yhteenveto 
Tätä on ohjelmointi!  Kykyä (ja rohkeutta) sanoa selvät asiat täsmällisesti.  Jossain vaiheessa vaihdamme vain täsmällisyyden astetta ja "lausekielen"- sijasta siirrymme käyttämään oikeata lausekieltä, esim. C- kieltä.  Nämä omatekoiset algoritmit kannattaa kuitenkin säilyttää ja kirjata näkyviin todellisen ohjelman kommentteihin.  Arviot algoritmin nopeudesta kannattaa myös laittaa kommentteihin, jotta jälkeenpäin on helpompi etsiä jo tekovaiheessa hitaaksi epäiltyjä kohtia.  Miksi jättää seuraavalle lukijalle sama tehtävä ihmeteltäväksi, jos olemme sen toteutuksen jo jonnekin kirjanneet.
Algoritmit kannattaa testata huolellisesti jossain tutussa ympäristössä.  Hyvin moni ohjelmointiongelma vektoreiden (=taulukko, =kasa kortteja, =ruutupaperi, =sivunumeroitu kirja jne...) kanssa samaistuu johonkin jokapäiväiseen ilmiöön.  Kuten etsiminen puhelinluettelosta, korttipakan järjestäminen jne.  Yritä etsiä näitä yhteyksiä ja kokeile ensin ratkaista ongelma tällä tavoin.  Siirrä ratkaisu sitten "lausekielelle" ja lopulta ohjelmointikielelle.
Äläkä yritä liikaa, vaan jaa aina ongelma pienempiin osiin, kunnes tulee vastaan sen kokoisia osaongelmia, jotka osataan ratkaista!  Tällaista osaongelman ratkaisijaa sanotaan ohjelmointikielessä aliohjelmaksi.   
Kun osaongelma on ratkaistu, unohda se miten sen ratkaisija toimii ja käsittele ratkaisijaa vain yhtenä yksinkertaisena toimenpiteenä (vrt. aikaisempi kävelyesimerkki).  Tämä on myös eräs ohjelmoinnin "vaikeus".  Kirjoittaja haluaa nähdä kaikkien osien toiminnan yhtäaikaisesti.  Tämä on kuitenkin mahdotonta.  Siis kun jokin osa tekee hommansa, niin tehköön se sen miten tahansa.  
Huono on johtaja joka kyttää koko ajan alaisiaan, eikä luota siihen, että nämä tekevät heille annetun tehtävän.  Tässä mielessä ohjelmointia voisi verrata yrityksen johtamiseen:  Johtaja jakaa koko yrityksen pyörittämisessä tarvittavia tehtäviä alaisilleen (aliohjelmille).  Nämä saattavat edelleen jakaa joitakin osatehtäviä omille alaisilleen (aliohjelma kutsuu toista aliohjelmaa) jne.  Johtaja (=ohjelmoija ja pääohjelma) kokoaa alaisten tekemän työn toimivaksi kokonaisuudeksi ja firma tuottaa.
Tehtävä 4.18	Kumin paikkaus
Kirjoita algoritmi polkupyörän kumin paikkaamiseksi.
Tehtävä 4.19	Sunnuntai- ilta
Kirjoita algoritmi sunnuntai- illan viettoa varten (muista että ohjelmoinnin demot on maanantaina).
Tehtävä 4.20	Onkiminen
Kirjoita algoritmi 10 ei- alimittaisen kalan onkimiseksi mato- ongella.
Tehtävä 4.21	Järjestyksen kääntäminen päinvastaiseksi
Kirjoita algoritmi pöydälle levitetyn 13 kortin kääntämiseksi päinvastaiseen järjestykseen.

5.	Algoritmeissa tarvittavia rakenteita
Mitä tässä luvussa käsitellään?
•	silmukat ja valintalauseet
•	totuustaulut
•	pöytätesti
•	muuttujat
•	taulukot
•	osoittimet

Vaikka jatkossa keskitymmekin oliopohjaiseen ohjelmointiin, tarvitaan yksittäisen olion metodin toteutuksessa algoritmeja.  Riippumatta käytettävästä ohjelmointikielestä, tarvitaan algoritmeissa aina tiettyjä samantyyppisiä rakenteita.
Käsittelemme seuraavassa tyypilliset rakenteet nopeasti lävitse.  Tarvitsisimme asioille enemmänkin aikaa, mutta otamme asiat tarkemmin esille käyttämämme ohjelmointikielen opiskelun yhteydessä.  Lukijan on kuitenkin asioita tarkennettaessa syytä muistaa, ettei rakenteet ole mitenkään sidottu ohjelmointikieleen.  Vaikka ne näyttäisivät kielestä täysin puuttuvankin (esim. assembler), voidaan ne kuitenkin lähes aina toteuttaa.
5.1	Ehtolauseet 
Triviaaleja algoritmeja lukuunottamatta algoritminen suoritus tarvitsee ehdollisia toteutuksia:
Jos kello yli puolenyön ota taksi
muuten mene linja- autolla

Ehtolauseita voi ehtoon tulla useampiakin ja tällöin on syytä olla tarkkana sen kanssa, mihin ehtoon mahdollinen muuten- osa liittyy:
Jos kello 00.00- 07.00
  Jos sinulla on rahaa niin ota taksi
  muuten kävele
muuten mene linja- autolla

Kuva 5.1 Ehtolauseet

Tehtävä 5.22	Ajanlisäys
Jos sinulla on muuttujassa t tunnit ja muuttujassa m minuutit, niin kirjoita algoritmi miten lisäät n minuuttia kellonaikaan t:m.
Tehtävä 5.23	Postimaksu
Kirjoita algoritmi g- painoisen kirjeen postimaksun määräämiseksi (saat keksiä hinnaston itse).
5.2	Valintalauseet 
Usein ehtoja kasaantuu niin paljon, että peräkkäiset ja sisäkkäiset ehtolauseet muodostavat varsin sekavan kokonaisuuden.  Tällöin voi olla helpompi käyttää valintalausetta:
Auto on/oli rekisterinumeron 1. kirjaimen mukaan rekisteröity seuraavassa läänissä: 
  X     Keski- Suomen lääni
  K     Kuopion lääni
  M     Mikkelin lääni
  A,U   Uudenmaan lääni

Kuva 5.2 swicth- valintalause

Tehtävä 5.24	Korvaaminen ehtolauseilla
Esitä auton rekisteröintipaikan riippuvuus rekisterin ensimmäisestä kirjaimesta sisäkkäisten ehtolauseiden avulla.
5.3	Silmukat 
Hyvin usein algoritmi tarvitsee toistoa: Esimerkiksi ohjeet (vuokaavio) hiekkarannalla toimimiseksi jos nenä näyttää merelle päin:

Kuva 5.3  do- silmukka ja do-while- silmukka
Ehtolause voi olla joko silmukan alussa: mahdollisuus ettei silmukan runkoa tehdä yhtään kertaa.  Ehto voi olla myös silmukan jälkeen, jolloin silmukan runko tehdään vähintään yhden kerran.  Joissakin kielissä on lisäksi mahdollisuus silmukan rungon keskeltä poistuminen.
Silmukoihin liittyy aina ohjelmoinnin eräs klassisimmista vaaroista:  päättymätön silmukka!  Tämän takia silmukoita tulee käsitellä todella huolella.  Eräs oleellinen asia on aina muistaa suorittaa silmukan rungossa jokin silmukan lopetusehtoon vaikuttava toimenpide.  Mitä tapahtuu muuten?
Myös silmukan suorituskertojen lukumäärän kanssa tulee olla tarkkana.  Silmukka tulee helposti suoritettua yhden kerran liikaa tai yhden kerran liian vähän.
Tehtävä 5.25	Uiminen
Mitä eroa on kahdella edellä esitetyllä "uimaan- meno" - algoritmilla?  Mitä ehtoja algoritmiin voisi vielä lisätä?
Tehtävä 5.26	Ynnää luvut 1- 100
Kirjoita algoritmi lukujen 1- 100 yhteenlaskemiseksi sekä do- while-  että while - silmukan avulla.

5.4	Muuttujat 
Algoritmeissa tarvitaan usein muuttujia.  
kellonaika
rahan määrä

5.4.1	Yksinkertaiset muuttujat
Yksinkertaisessa tapauksessa muuttuja voi olla yksinkertaista tyyppiä kuten kellonaika (jos ilmaistu minuutteina), rahasumma jne.
Yksinkertainen luvun jaollisuuden testausalgoritmi voisi olla vaikkapa seuraavanlainen: 
Jaetaan tutkittavaa lukua jakajilla 2,3,5,7...luku/2.
Jos jokin jako menee tasan, niin ei alkuluku:

0. Laita jakaja:=2, kasvatus:=1, 
   Jos luku=2 lopeta, alkuluku
1. Jaa luku jakajalla.  Meneekö jako tasan?
   -  jos menee, on luku jaollinen jakajalla, lopeta 
2. Kasvata jakajaa kasvatus arvolla (jakaja:=jakaja+kasvatus)
3. Kasvatus:=2; (koska parillisilla ei kannata enää jakaa)
4. Onko jakaja<luku/2?
   -  jos on, niin jatka kohdasta 1
   -  muuten lopeta, luku on alkuluku
Tehtävä 5.27	Vuokaavio
Piirrä jaollisuuden testausalgoritmista vuokaavio.

5.4.2	Pöytätesti 
Hyvin usein algoritmi kannattaa pöytätestata.  Pöytätesti alkaa kirjoittamalla sarakkeiksi kaikki algoritmissa esiintyvät muuttujat.  Muuttujiksi voidaan kirjoittaa myös algoritmissa esiintyviä ehtoja.  Tällainen muuttuja voi saada arvon kyllä tai ei.  Pöytätestin riveiksi kirjoitetaan algoritmin eteneminen vaiheittain.  Sarakkeisiin muuttujille kirjoitetaan uusia arvoja vain niiden muuttuessa.
Testataan esimerkiksi edellisen esimerkin algoritmi:
askel	Luku	Jakaja	Kasvatus	Luku/Jakaja	Jako tasan?	Jakaja<Luku/2?	Tulostus	
0	25	2	1					
1				12.500	ei			
2		3						
3			2					
4						3<12.5		
1				8.333	ei			
2		5						
3			2					
4						5<12.5		
1				5.000	kyllä		Jaollinen 5:llä	

askel	Luku	Jakaja	Kasvatus	Luku/Jakaja	Jako tasan?	Jakaja<Luku/2?	Tulostus	
0	23	2	1					
1				11.500	ei			
2		3						
3			2					
4						3<11.5		
1				7.667	ei			
2		5						
3			2					
4						5<11.5		
1				4.600	ei			
2		7						
3			2					
4						7<11.5		
1				3.286	ei			
2		9						
3			2					
4						9<11.5		
1				2.556	ei			
2		11						
3			2					
4						11<11.5		
1				2.091	ei			
2		13						
3			2					
4				           	 	13>11.5	Alkuluku	

Usein pöytätesti antaa hyviä vinkkejä myös algoritmin jatkokehittelylle.  
Tehtävä 5.28	Algoritmin parantaminen
Tarvitsisimmeko algoritmin kohtaa 4 lainkaan?  Voitaisiinko algoritmin lopetus hoitaa muuten?
Tehtävä 5.29	Pöytätesti
Pöytätestaa edellinen algoritmi kun syöttönä on luku 121.
Pöytätestaa molemmat Ynnää luvut 1- 100 - algoritmisi versiona Ynnää luvut 1- 6.

5.4.3	Yksiulotteiset taulukot 
 Tutkikaamme aikaisempia korttipakkaesimerkkejämme!  Nyt tietorakenteeksi ei enää riitäkään pelkkä yksi muuttuja.  Mikäli pakasta on otettu esiin pelkät padat, tarvitsisimme 13 muuttujaa.  Näiden kunkin nimeäminen erikseen olisi varsin työlästä.  
Tarvitsemme siis jonkin muun tietorakenteen.  Mahdollisuuksia on useita: listat, jonot, pinot ja taulukot.  Ohjelmoinnin alkuvaiheessa taulukot ovat tietorakenteista helpoimpia, joten keskitymme niihin aluksi.
Varataan pöydältä tilaa leveyssuunnassa 13 kortille.  Varattua tilaa voimme nimittää taulukoksi tai vektoriksi.  Taulukon yksi alkio on yhdelle kortille varattu paikka.  Taulukon yhden alkion sisältö on se kortti, joka on siinä paikassa.  
Mikäli numeroimme varatut paikat vaikkapa 0:sta alkaen vasemmalta oikealle, on meidän korteillamme osoitteet 0- 12:
0	1	2	3	4	5	6	7	8	9	10	11	12	
													
7	3	K	2	5	9	4	6	Q	10	J	A	8	

Nyt voimme käsitellä yksittäisiä kortteja aivan kuin ne olisivat yksittäisiä muuttujia.  Viittaamme tiettyyn korttipaikkaan (taulukon alkioon) sen indeksillä (olkoon taulukon nimi kortit):
paikassa kortit[5] meillä on pata 9
paikassa kortit[8] meillä on pata akka

Minkälaisia algoritmeja tulee vastaan taulukoita käsiteltäessä?  Esim 9:n siirtäminen taulukon viimeiseksi vaatisi 4:en siirtämistä paikkaan 5. 6:en siirtämistä paikkaan 6, Q:n siirtämistä paikkaan 7 jne.  Näin loppuun saataisiin raivatuksi paikka 9:lle.
Lajittelun ilman valtaisaa korttien siirtelyä voisimme hoitaa seuraavasti:
0. laita alku paikkaan 0
1. etsi alku paikasta lähtien pienin kortti
2. vaihda pienin ja paikassa alku oleva kortti
3. alku:=alku+1
4. mikäli alku<suurin indeksi, niin jatka 1

Sovitaan, että ässä=1.  Nyt pienimmän kortin etsimisalgoritmi voisi olla seuraava:
0.  Alkuarvaus:  pien.paikka:=alku,  tutki:=alku
1.  Jos kortit[tutki] < kortit[pien.paikka] 
      niin pien.paikka:=tutki
2.  tutki:=tutki+1
3.  Jos tutki<=suurin indeksi, niin jatka 1. 

Voisimme vielä pöytätestata algoritmin:
	pien.		Kortit	[tutki]<	tutki<	
askel	paikka	tutki	0	1	2	3	4	5	6	7	8	9	10	11	12	[pp]	suur.ind	
0	0	0	7	3	K	2	5	9	4	6	Q	10	J	A	8			
1			t													7<7 ei		
2&3		1															juu	
1	1			t												3<7 juu		
2&3		2															juu	
1					t											K<3 ei		
2&3		3															juu	
1	3					t										2<3 juu		
2&3		4															juu	
1							t									5<2 ei		
2&3		5															juu	
1								t								9<2 ei		
2&3		6															juu	
1									t							4<2 ei		
2&3		7															juu	
1										t						6<2 ei		
2&3		8															juu	
1											t					Q<2 ei		
2&3		9															juu	
1												t				10<2 ei		
2&3		10															juu	
1													t			J<2 ei		
2&3		11															juu	
1	11													t		A<2 juu		
2&3		12															juu	
1															t	8<A ei		
2&3		13															ei	

Tehtävä 5.30	Lajittelun testaus
Oletetaan, että pienimmän etsimisalgoritmi toimii.  Pöytätestaa edellä esitelty lajittelualgoritmi edellisen pöytätestin mukaisella korttien järjestyksellä.

Onko tämä insertion sort?  Missä on lajiteltujen kasa ja missä lajittelemattomien?
Tehtävä 5.31	Korttien poisto
Kirjoita algoritmi kuvakorttien poistamiseksi taulukosta käyttäen indeksejä.

5.4.4	Osoittimet 
 Edellisessä pöytätestissä merkitsimme pienen merkin niiden korttien kohdalle, joita kunakin hetkenä tutkimme.  Kun tutkimme esimerkiksi paikoissa 3 ja 10 olevia kortteja (P2 ja PJ) voisimme sanoa, että muuttuja pien.paikka osoitti korttiin pata 2 ja muuttuja tutki korttiin pata jätkä.  Näin ollen voisimme oikeastaan sanoa, että (indeksi)muuttujat pien.paikka ja tutki ovat osoittimia korttipakkaan.  Niiden osoittamassa paikassa (indeksit 3 ja 10) on tietyt kortit (P2 ja PJ).
Lajittelualgoritmi voitaisiin lausua esimerkiksi:
0. levitä kortit rinnakkain pöydälle
   osoita vasemman käden etusormella ensimmäiseen korttiin
1. etsi vasemman käden osoittamasta kohdasta alkaen oikealle
   pienin kortti ja osoita sitä oikean käden etusormella
2. vaihda etusormien kohdalla olevat kortit keskenään
3. siirrä vasemman käden etusormea yksi kortti oikealle päin
4. 	mikäli vasen sormi vielä kortin päällä, jatka kohdasta 1. 

													
7	3	K	2	5	9	4	6	Q	10	J	A	8	

Osoittimen ja indeksin ero on siinä, että osoittimen tapauksessa emme yleensä koskaan ole kiinnostuneita itse osoittimen arvosta (osoitteesta, siitä indeksistä missä kohdin olemme menossa), vaan osoittimen osoittaman paikan sisällöstä (sormen kohdalla olevan kortin arvo tai ei korttia).  Indeksejä käsitellessämme tutkimme monesti myös itse indeksin arvoa (tutki=3 - >kortit[tutki]=P2).
Osoitin voi tarvittaessa osoittaa myös itse taulukon ulkopuolelle.  Mikäli kirjoittaisimme pöydälle numeroita, voisimme osoittaa sormella yhtä hyvin pöydälle kirjoitettuja numeroita (älkää hyvät ihmiset nyt töhrätkö pöytää!) kuin pöydälle levitettyjä kortteja (taulukon alkioita).
Siis indeksit liittyvät kiinteästi taulukoihin ja osoittimet voivat liittyä mihin tahansa tietorakenteisiin alkaen yksinkertaisesta muuttujasta päätyen monimutkaisen lista-   tai puurakenteen alkioon.
Tehtävä 5.32	Korttien poisto osoittimia käyttäen
Kirjoita algoritmi kuvakorttien poistamiseksi taulukosta käyttäen osoittimia.
Pöytätestaa algoritmi.

5.4.5	Moniulotteiset taulukot 
Yksiulotteista taulukkoa voidaan verrata rivitaloon tai ruutupaperin yhteen riviin.  Kaksiulotteinen taulukko on vastaavasti kuten kapea kerrostalo tai koko ruutupaperin yksi sivu.  Tarvitsemme vastaavasti useampia osoitteita (indeksejä) osoittamaan millä rivillä ja missä sarakkeessa liikumme.
Alla on esimerkki 5x7 taulukosta (=pata, =Risti, =ruutu, =hertta):
	0	1	2	3	4	5	6	
0								
	7				A			
1								
		K		5				
2								
			A					
3								
	7	2	2	9	6	3	7	
4								
			2				J	

Jos taulukon nimi on peli, niin paikassa 3,1 on kortti pata 2:
peli[3][1] = 2

Tehtävä 5.33	Kaksiulotteisen taulukon indeksit
Kirjoita kaikkien esimerkissä olevien korttien osoitteet em. muodossa.

Kaksiulotteista taulukkoa nimitetään usein matriisiksi.  
Usein taulukoiden indeksit ilmoitetaan eri järjestyksessä kuin koordinaatiston (x,y)- koordinaatit.  Tämä johtuu siitä ajattelutavasta, että taulukon rivi sinänsä voidaan kuvitella yhdeksi alkioksi (rivityypiksi) ja tällöin ilmaisu
peli[3] 

tarkoittaa koko riviä (7, 2, 2, 9, 6, 3, 7), jonka indeksi on kolme. Mikäli tämän perään laitetaan vielä [1], niin tarkoitetaan ko. tietorakenteen alkiota jonka indeksi on yksi (2).
Tarvittaessa moniulotteiset taulukot voidaan muodostaa yksiulotteisenkin taulukon avulla.  Esimerkin taulukko voitaisiin muodostaa yksiulotteisesta taulukosta siten, että yksiulotteisen taulukon 7 ensimmäistä alkiota kuvaisivat matriisin 0:ta riviä, 7 seuraavaa matriisin ensimmäistä riviä jne.  
Siis mikäli yksiulotteisen taulukon nimi olisi pakka, niin voisimme käyttää samaistuksia:
peli[3][1] = pakka[7*3+1]
peli[j][i] = pakka[7*j+i] 

Olemme siis numeroineet kaksiulotteisen taulukon alkiot juoksevasti.  Voimmehan tehdä näin myös kerrostalon huoneistoille tai teatterin istumapaikoille.
Taulukot voivat olla myös useampiulotteisia, esimerkiksi 3x4x5 taulukko:
   0        1        2        3       4 
  ┌──┐     ┌──┐     ┌──┐     ┌──┐    ┌──┐2 
0 │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐  │ ┌┴─┐1 
  └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐└─┤ ┌┴─┐0 
    └─┤  │   └─┤PJ│   └─┤  │   └─┤  │  └─┤  │ 
      └──┘     └──┘     └──┘     └──┘    └──┘ 
  ┌──┐     ┌──┐     ┌──┐     ┌──┐     ┌──┐     
1 │ ┌┴─┐   │ ┌┴─┐   │R5┴─┐   │ ┌┴─┐   │ ┌┴─┐   
  └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤HA┴─┐ └─┤ ┌┴─┐ 
    └─┤  │   └─┤  │   └─┤  │   └─┤  │   └─┤  │ 
      └──┘     └──┘     └──┘     └──┘     └──┘ 
  ┌──┐     ┌──┐     ┌──┐     ┌──┐     ┌──┐      
2 │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐    
  └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐  
    └─┤  │   └─┤  │   └─┤  │   └─┤  │   └─┤  │  
      └──┘     └──┘     └──┘     └──┘     └──┘  
  ┌──┐     ┌──┐     ┌──┐     ┌──┐     ┌──┐      
3 │P7┴─┐   │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐   │ ┌┴─┐    
  └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐  
    └─┤  │   └─┤  │   └─┤  │   └─┤  │   └─┤  │  
      └──┘     └──┘     └──┘     └──┘     └──┘  

isopeli[0][0][1]=PJ
isopeli[2][1][2]=R5
isopeli[1][1][3]=HA
isopeli[2][3][0]=P7
 
Tehtävä 5.34	Sijoitus 3- ulotteiseen taulukkoon
Esitä 5 muuta sijoitusta taulukkoon.
Tehtävä 5.35	3- ulotteinen taulukko 1- ulotteiseksi
Esitä kaava miten edellä oleva 3- ulotteinen taulukko voitaisiin esittää yksiulotteisella taulukolla.
 
Aikaisempi satunnaisen matkaajan sanastomme on oikeastaan myös kolmiulotteinen taulukko:
     0        1        2
0  minä      jag       i
1  sinä      du        you
2  hän       han       he

Se on kaksiulotteinen taulukko sanoista.  Mitä sitten yksi sana on?  Se on yksiulotteinen taulukko kirjaimista!
    0        1        2        3       4 
   ┌──┐     ┌──┐     ┌──┐     ┌──┐    ┌──┐2      
 0 │h┌┴─┐   │ä┌┴─┐   │n┌┴─┐   │ ┌┴─┐  │ ┌┴─┐1    
   └─┤s┌┴─┐ └─┤i┌┴─┐ └─┤n┌┴─┐ └─┤ä┌┴─┐└─┤ ┌┴─┐0  
     └─┤m │   └─┤i │   └─┤n │   └─┤ä │  └─┤  │   
       └──┘     └──┘     └──┘     └──┘    └──┘   
   ┌──┐     ┌──┐     ┌──┐     ┌──┐    ┌──┐2      
 1 │h┌┴─┐   │a┌┴─┐   │n┌┴─┐   │ ┌┴─┐  │ ┌┴─┐1    
   └─┤d┌┴─┐ └─┤u┌┴─┐ └─┤ ┌┴─┐ └─┤ ┌┴─┐└─┤ ┌┴─┐0  
     └─┤j │   └─┤a │   └─┤g │   └─┤  │  └─┤  │   
       └──┘     └──┘     └──┘     └──┘    └──┘   
   ┌──┐     ┌──┐     ┌──┐     ┌──┐    ┌──┐2      
 2 │h┌┴─┐   │e┌┴─┐   │ ┌┴─┐   │ ┌┴─┐  │ ┌┴─┐1    
   └─┤y┌┴─┐ └─┤o┌┴─┐ └─┤u┌┴─┐ └─┤ ┌┴─┐└─┤ ┌┴─┐0  
     └─┤i │   └─┤  │   └─┤  │   └─┤  │  └─┤  │   
       └──┘     └──┘     └──┘     └──┘    └──┘   

Siis "you" sanan indeksi on [1][2] ja sen kirjaimen "y" indeksi on [0].  Siis kaiken kaikkiaan "you"- sanan "y"- kirjaimen indeksi olisi [1][2][0].
Taulukko voitaisiin järjestää 3- ulotteiseksi myös toisinkin.  Esimerkiksi yhdessä "tasossa" olisi yksi kieli jne.
Tehtävä 5.36	3- ulotteinen taulukko
Esitä edellisessä esimerkissä kaikkien kirjainten indeksit.  
Millaisella yhden kirjaimen sijoituksella muuttaisit sanan "han" sanaksi "hon"?
Tehtävä 5.37	4- ulotteinen taulukko
Mitenkä tavallinen kirja voitaisiin kuvitella 3- ulotteiseksi taulukoksi? 
Miten kirja voitaisiin kuvitella 4- ulotteiseksi taulukoksi?
Piirrä edellisiin perustuen esimerkki 4- ulotteisesta taulukosta ja anna muutama esimerkkisijoitus.

Osoitinmuuttuja osoittaisi myös moniulotteisessa taulukossa yhteen alkioon kerrallaan.  Esimerkiksi osoittamalla "you"- sanan "y"- kirjaimeen.
Moniulotteisen ja yksiulotteisen taulukon väliset muunnokset ovat tärkeitä, koska tietokoneen muisti (1997) on lähes aina yksiulotteinen.  Siis loogisesti moniulotteiset taulukot joudutaan lopulta aina toteuttamaan yksiulotteisina.  Onneksi useat kielet sisältävät moniulotteiset taulukot tietotyyppinä ja kääntäjät tekevät sitten muunnoksen.  Tästä huolimatta esimerkiksi C- kielessä joudutaan usein muuttamaan moniulotteisia taulukoita yksiulotteisiksi.
5.4.6	Sekarakenteet
Taulukko voi olla myös taulukko osoittimista.  Esimerkiksi sanastomme tapauksessa kaikki sanat voisivat olla yhdessä "möykyssä":
0         1         2         3 
0123456789012345678901234567890123
minä jag i sinä du you hän han he 

Itse sanasto voisi sitten olla taulukko osoittimia sanojen alkupaikkoihin: 
	0	1	2	
0	00	05	09	
1	11	16	19	
2	23	27	31	

Siis taulukon paikasta sanasto[1][0] löytyy osoitin.  Tämän osoittimen arvo on tässä esimerkissä 11.  Siis osoitin viittaa sanan "sinä"- alkuun.  Tässä 2-ulotteinen taulukko osoittimista 1 ulotteiseen merkkitaulukkoon
// C++:lla
char *sanasto[3][3];

Tehtävä 5.38	Sanojen muuttaminen
Mitä ongelmia edellä olisi, mikäli yhdenkin sanan pituutta kasvatettaisiin?

Voitaisiinko edellä käyttää samoja sanoja uudestaan ja jos niin miten?

5.5	Osoittimista ja indekseistä 
Osoitinmuuttujaa voitaisiin kuvitella myös seuraavasti:  Olkoon meillä osoitekirja (osoitteet) jossa on sivuja:
sivu 0:		sivu 1:		sivu 2:	
Kassinen Katto
Katto
3452		Susi Sepe
Takametsä
-		Ankka Aku
Ankkalinna
1234	

Meidän osoitekirjamme on tavallaan taulukko osoittimista (tässä tapauksessa osoitteita, älä sotke termejä!).  Taulukon osoitteet paikassa 1 (sivu 1) on osoite "Sepe Sudelle".  Mitä tapahtuu mikäli kirjoitamme kokonaan uuden henkilön osoitteen sivulla 1 olevan osoitteen päälle (sijoitetaan uusi arvo osoitinmuuttujalle osoitteet[1])?
sivu 1:	
Batman
Gotham City
999	

Mitä tapahtuu "Sepe Sudelle"?  Tuskinpa sijoitus osoitekirjassamme siirtää "Sepe Sutta" yhtään mihinkään "Perämetsästä", tai tekee edes häntä murheelliseksi!  Tämä on eräs tyypillinen virhekäsitys osoitinmuuttujia käytettäessä.  Osoitinmuuttujaan sijoittaminen ei muuta tietenkään itse tiedon sisältöä.  Mutta sijoittaminen siihen paikkaan johon osoitinmuuttuja osoittaa (esimerkissämme "Sepe Suden" asuntoon) muuttaa tietenkin myös itse tiedon sisältöä.
// C++:lla
sivu[1] = uusi_osoite;    // ei vaikuta Sepe Suteen
*sivu[1] = uusi_henkilo;  // laittaa uuden henkilön Sepen osoitteeseen
                          // = "tähdätään osoitetta pitkin"

Vastaavasti jos meillä on indeksimuuttuja nimeltä sivu, niin sijoitus muuttujalle
sivu=2 

ei muuta mitenkään itse sivun sisältöä.  Vasta sijoitus
osoitteet[sivu]= 

muuttaisi sivun 2 sisältöä.
5.6	Aliohjelmat 
Aliohjelmat on tarkempi kuvaus tietylle asialle.  Tämä kuvaus esitetään jossakin toisessa kohdassa ja sitä ei suinkaan tarvitse joka kerta lukea uudelleen.
Keittokirjassa lukee:
pyöritä lihapullataikinasta pyöreitä pullia
paista pullat kauniin ruskeiksi 
 
Miten pullat paistetaan kauniin ruskeiksi.  Tämä olisi edellisen algoritmin aliohjelma.  Kokenut kokki ei välitä aina (eikä edes suostuisi) joka reseptin kanssa lukea itse paistamisohjetta.   Aloittelija tätä saattaisi tarvita, jottei naapuri hälyttäisi palokuntaa paikalle liian savunmuodostuksen takia.  Siis toivottavasti keittokirjasta jostakin kohti löytyisi myös itse paistamisohje.
Aliohjelmille on tyypillistä, että ne saattavat suoriutua vastaavista tehtävistä eri muuttujillekin.  Näitä kutsukerroista riippuvia muuttujia sanotaan parametreiksi.  
Esim. lihapullan paisto- ohje saattaa semmoisenaan kelvata myös tavalliselle pullalle:
paista("paistettava","c")
  Korvaa seuraavassa sana "paistettava" sillä mitä olet
  paistamassa ja "c" sillä lämpötilalla, joka keittokirjan
  ohjeessa ko. paistettavan kohdalla on:

0. laita "paistettavat" pellille
1. lämmitä uuni "c"- asteeseen
2. laita pelti uuniin
...
9. mikäli "paistettavat" ovat mustia mene ostamaan 
   kaupasta valmiita
... 

Tehtävä 5.39	Lihapullan paistaminen
Täydennä edellinen paistamisalgoritmi.  Onko parametreja tarpeeksi?

5.7	Vaihtoehtojen tutkiminen totuustaulun avulla
Usein helpostakin tehtävästä seuraa monia eri vaihtoehtoja, joiden täydellinen hallitseminen pelkästään päässä saattaa olla ylivoimaista.  Tällöin avuksi tulee totuus-  ja päätöstaulut.  Päätöstaulu on totuustaulun pidemmälle viety versio. 
Tutkikaamme aluksi muutamaa esimerkkiä jotka eivät suoranaisesti liity ohjelmointiin, mutta joissa esiintyy täsmälleen vastaava tilanne:
5.7.1	Kaikkien vaihtoehtojen kirjaaminen
Uusien opiskelijoiden lähtötasotesti 1991 (n. 20% oli osannut vastata oikein tähän tehtävään):
Tehtävä 3.4:	Seisot tienhaarassa tuntemattomassa maassa. Toinen teistä vie pääkaupunkiin.  Maan asukkaat joko valehtelevat aina tai puhuvat aina totta.  Toisen tien alussa seisoskelee yksi heistä.  Esität hänelle kyllä vai ei - kysymyksen ja saat vastauksen.  Mitä kahta seuraavista neljästä kysymyksestä olisit voinut käyttää ollaksesi varma siitä, kumpi tie vie pääkaupunkiin -  riippumatta siitä valehteleeko asukas vai ei?
1.	Viekö tämä tie pääkaupunkiin?
2.	Olisitko sanonut, että tämä tie vie pääkaupunkiin, jos olisin kysynyt sinulta sitä?
3.	Onko niin, että tämä joko on tie pääkaupunkiin, tai sitten sinä puhut totta (mutta ei molemmin tavoin)?
4.	Onko totta, että tämä tie vie pääkaupunkiin ja sen lisäksi sinä puhut totta?

Erotelkaamme eri kysymykset:
1 =	A	Viekö pääkaupunkiin?   
	B	Puhutko totta? (mielenkiinnon vuoksi)
2 =	C	Olisitko sanonut että vie jos A? 
3 <- 	D	Tie pääk. XOR puhut totta?
4 <- 	E	Tie pääk. AND puhut totta?

Nyt voimme kirjoittaa eri vaihtoehtojen mukaan seuraavan totuustaulun (V=vastaus kun valehteleminen otetaan huomioon):
Tie vie pää-	Puhuu	1		2		3	 		4		
kaupunkiin	totta	A	B	C	D		V	E		V	
E	E	K	K	E	E		K	E		K	
E	K	E	K	E	K		K	E		E	
K	E	E	K	K	K		E	E		K	
K	K	K	K	K	E		E	K		K	

Siis  2 ja 3 vastauksissa on järkevä korrelaatio siihen viekö tie pääkaupunkiin vai ei.
5.7.2	Vaihtoehtojen lukumäärä
Mistä tiedämme milloin kaikki vaihtoehdot on kirjoitettu?  Mikäli systeemiin vaikuttavia asioita on n kappaletta ja kukin on kyllä/ei tyyppinen (0/1), niin vaihtoehdot on helpointa saada aikaan kirjoittamalla kaikki n- bittiset binääriluvut järjestyksessä (esimerkissämme n=2) ja suorittamalla sitten tarvittavat samaistukset (esim E=0 ja K=1).  Vaihtoehtoja on tällöin 2n.
00   - > E  E
01   - > E  K
10   - > K  E
11   - > K  K

Tehtävä 5.40	Kombinaatioiden lukumäärä
Olkoon meillä tehtävä, jossa yksi muuttuja voi saada arvot K,E,tyhjä ja toinen muuttuja arvot 5 ja 10.  Kirjoita kaikki ko. muuttujien kombinaatiot.

Mikäli meillä on vaihtoehtoja n kappaletta ja kukin voi saada ki eri arvoa, niin montako eri kombinaatiota saamme aikaiseksi?

5.7.3	Useita vaihtoehtoja samalla muuttujalla
Ottakaamme toinen vastaava tehtävä:
Tehtävä 4.3:  	Pekka valehtelee maanantaisin, tiistaisin ja keskiviikkoisin; muina viikonpäivinä hän puhuu totta.  Paavo valehtelee torstaisin, perjantaisin ja lauantaisin; muina viikonpäivinä hän puhuu totta.  Eräänä päivänä Pekka sanoi: "Eilen valehtelin!"  Paavo vastasi: "Niin minäkin!"  Mikä viikonpäivä oli?

Minä päivinä kaverukset saattaisivat sanoa ko. lausuman?  Näitä päiviä on tietysti ne, jolloin joko eilen valehdeltiin ja tänään puhutaan totta TAI eilen puhuttiin totta ja tänään valehdellaan. (XOR)

päivä	Pekka	valehteli eilen	Paavo	valehteli
eilen		
sunnuntai   				k 	sanoo		
maanantai   	V		sanoo					
tiistai     	V	k				
keskiviikko 	V	k				
torstai     		k 	sanoo	V		sanoo	<==	
perjantai   			V	k		
lauantai    			V	k		
sunnuntai   				k 	sanoo		

Totuustaulun tavoitteena on siis kerätä kaikki mahdolliset vaihtoehdot ohjelmoijan silmien eteen, ja näin kaikki mahdollisuudet voidaan analysoida ja käsitellä.
Tehtävä 5.41	BAL=kyllä?
Eräällä saarella asuu luonnonkansa.  Puolet kansan asukkaista aina valehtelevat ja toinen puoli puhuu aina totta.  Lisäksi heidän kielensä on tuntematon.  On saatu selville, että "BAL" ja "DA" tarkoittavat "kyllä" ja "ei", muttei sitä kumpiko tarkoittaa kumpaa.  He ymmärtävät suomea mutta vastaavat aina omalla kielellään.  Vastaasi tulee yksi saaren asukas.
a)  Mitä saat selville kysymyksellä "Tarkoittaako BAL KYLLÄ"?
b) Millä kysymyksellä saat selville mikä sana on kyllä?
Tehtävä 5.42	Kuka valehtelee?
Jälleen maahan jossa asukkaat joko valehtelevat tai puhuvat aina totta.  Tapaat kolme asukasta A:n, B:n ja C:n.  He sanovat sinulla
	A: Me kaikki kolme valehtelemme!	B: Tasan yksi meistä puhuu totta!
Mitä ovat A, B ja C?

Entä jos asukkaat sanovat:
	A: Me kaikki kolme valehtelemme!	B: Tasan yksi meistä valehtelee!

Entä jos:
	A: Minä valehtelen mutta B ei!

Entä jos:
	A: B valehtelee!			B: A ja C ovat samaa tyyppiä!

Vielä yksi:  
	A sanoo: B ja C ovat samaa tyyppiä.	C:ltä kysytään: Ovatko A ja B samaa tyyppiä?
Mitä C vastaa?

5.7.4	Loogiset operaatiot
Ehtoja usein yhdistellään loogisten operaatioiden avulla:
Mikäli kello 7- 20 ja et halua ulkoilla
-  mene bussilla
...
Mikäli sinulla on rahaa tai saat kimpan
-  ota taksi

Yksittäinen ehto antaa tulokseksi tosi (T=true) tai epätosi (F=false).  Ehtojen tulosta voidaan usein myös kuvata 1 tai 0.  Ehtojen yhdistämistä loogisilla operaatioilla kuvaa seuraava totuustaulu (myös C++:n loogiset operaattorit merkitty):
							ja			tai		ehd. tai		ei			
							AND			OR			XOR			NOT			
	p			q	 	p	&&	q	p	||	q	p	^	q		!	p	 ^ toimii jos p ja q boolean	
F		0	F		0	F		0	F		0	F		0	T		1		
F		0	T		1	F		0	T		1	T		1	T		1		
T		1	F		0	F		0	T		1	T		1	F		0		
T		1	T		1	T		1	T		1	F		0	F		0		

Huomattakoon edellä, että AND operaatio toimii kuten kertolasku ja OR operaatio kuten yhteenlasku (mikäli määritellään 1+1=1).  Siis loogisia operaattoreita voidaan käyttää kuten normaaleja algebrallisia operaattoreita ja niillä operoiminen vastaa tavallista algebraa.  Loogisten operaatioiden algebraa nimitetään Boolen - algebraksi. 
Ehtojen sieventämisessä käytettäviä kaavoja voidaan todistaa oikeaksi totuustaulujen avulla.  Todistetaan esimerkiksi de Morganin kaava (vrt. joukko- oppi, 1=true, 0=false):
NOT (p AND q) = (NOT p) OR (NOT q)

Jaetaan ensin väittämä pienempiin osiin:
NOT   e1      =   e2    OR   e3

		e1	e2	e3				
p	q	p AND q	NOT p	NOT q	NOT e1	e2 OR e3		
0	0	0	1	1	1	1		
0	1	0	1	0	1	1		
1	0	0	0	1	1	1		
1	1	1	0	0	0	0		

Koska kaksi viimeistä saraketta ovat samat ja kaikki muuttujien p ja q arvot on käsitelty, on laki todistettu!
Tehtävä 5.43	de Morganin kaava
Todista oikeaksi myös toinen de Morganin kaava:

NOT (p OR q) = (NOT p) AND (NOT q) 
Tehtävä 5.44	Osittelulaki
Yhteenlaskun ja kertolaskun välillähän pätee osittelulaki:
	p * (q + r) = (p * q) + (p * r)
Samaistamalla * <=> AND ja + <=> OR todetaan loogisille operaatioillekin osittelulaki:
	p AND (q OR r) = (p AND q) OR (p AND r)
Todista oikeaksi toinen osittelulaki (toimiiko vast. yhteenlaskulla ja kertolaskulla):
	p OR (q AND r) = (p OR q) AND (p OR r)

			e1	e2	e3				
p	q	r	q AND r	p OR q	p OR r	p OR e1	e2 AND e3		
0	0	0							
0	0	1							
0	1	0							
0	1	1							
1	0	0							
1	0	1							
1	1	0							
1	1	1							

Huomaa, että totuustauluun tulee nyt 8 riviä (koska kolme muuttujaa)!
Tehtävä 5.45	Ehtojen sieventäminen
Käytä de Morganin kaavoja tai osittelulakia seuraavien ehtojen sieventämiseen:
a)	ei ole totta että hinta alle 5 mk ja paino yli 10 kg
b)	NOT (kello<=7 OR rahaa>50 mk)
c)	((hinta < 5) tai (rahaa>10)) ja ((hinta < 5) tai (kello>9)) 

5.8	Muistele tätä
Mikäli edellä esitetyt asiat tuntuvat ymmärrettäviltä, niin ohjelmoinnissa ei tule olemaan mitään vaikeuksia.  Jos vastaavat asiat tuntuvat vaikeilta ohjelmoinnin kohdalla, kannattaa palata takaisin tähän lukuun ja yrittää samaistaa asioita ohjelmointikieleen.  
Taulukoiden samaistaminen ruutupaperiin, korttipakkaan tai muuhun tuttuun asiaan auttaa asian käsittelyä.  Osoitinmuuttuja on yksinkertaisesti jokin (vaikkapa sormi) joka osoittaa johonkin (vaikkapa yhteen kirjaimeen).
Silmukat ja ehtolauseet ovat hyvin luonnollisia asioita.  
Aliohjelmat ovat vain tietyn asian tarkempi kuvaus.  Tarvittaessa tiettyä asiaa ei ongelmaa tarvitse heti ratkaista, vaan voidaan määritellä aliohjelma, joka hoitaa homman ja kirjoitetaan itse aliohjelman määrittely joskus myöhemmin.
Tehtävä 5.46	Merkkijonot
C- kielessä merkkijonot tullaan esittämään taulukoina kirjaimista.  Merkkijonon loppu ilmaistaan kirjaimella NUL.  Siis esimerkiksi Kissa olisi seuraavan näköinen

0	1	2	3	4	5		
K	i	s	s	a	NUL		

Kirjoita seuraavat algoritmit.  Erityisesti kirjoita ensin algoritmin sanallinen versio
1.	Välilyöntien poistaminen jonon alusta.
2.	Välilyöntien poistaminen jonon lopusta.
3.	Ylimääräisten (2 tai useampia) välilyöntien poistaminen jonosta.
4.	Kaikkien ylimääräisten (alku- ,loppu-  ja monikertaiset) välilyöntien poistaminen.
5.	Jonon muuttaminen siten, että kunkin sanan 1. kirjain on iso kirjain.
6.	Tietyn merkin esiintymien laskeminen jonosta.
7.	Esiintyykö merkkijono toisessa merkkijonossa (kissatarha, sata - > esiintyy; kissatarha, satu - > ei esiinny).
Tehtävä 5.47	Päivämäärät
Kirjoita seuraavat algoritmit: 
1.	Onko vuosi karkausvuosi vai ei. (Huom! 1900 ei, 2000 on)
2.	Montako karkausvuotta on kahden vuosiluvun välillä.
3.	Jos 1.1 vuonna 1 oli maanantai, niin mikä viikonpäivä on 1.1 vuonna x?  (Oletetaan että kalenteri olisi ollut aina samanlainen kuin nytkin.  Vihje!  Tutki almanakkaa peräkkäisiltä vuosilta.)
4.	Onko päivämäärä pp.kk.vv oikeata muotoa?

6.	Esimerkkejä eri kielistä
Mitä tässä luvussa käsitellään?
•	katsotaan mitä syntaktista eroa on eri ohjelmointikielillä

Jossakin vaiheessa ohjelmoinnin opiskelua tullaan siihen, että ohjelma pitäisi toteuttaa jollakin olemassa olevalla ohjelmointikielellä (on tosin ohjelmointikursseja, joilla käytetään keksittyä ohjelmointikieltä).
Seuraavassa esitämme ohjelman jonka ainoa tehtävä on tulostaa teksti: 
	Terve!  Olen ??- kielellä kirjoitettu ohjelma.
Itse ohjelman suunnittelu on tällä kertaa varsin triviaali ja tehtävä ei tarvitse varsinaista tarkennustakaan, kaikki on sanottu tehtävän määrityksessä.  Siis valitaan vain käytetyn kielen tulostuslause.
6.1	Esimerkkiohjelmat
Jotta lukija ymmärtäisi, ettei eri kielten välillä ole kuin pieni ero, esitämme ohjelman useilla eri kielillä.  Ohjelman lopun jälkeen olevan poikkiviivan alapuolella on mahdollisesti esitetty miten ohjelmaa voitaisiin kokeilla MS- DOS- koneessa:
6.1.1	Pascal
{ Pascal- kieli } 
PROGRAM olen(OUTPUT);
BEGIN
  WRITELN('Terve!  Olen Pascal- kielellä kirjoitettu ohjelma.');
END.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-  käynnistä vaikkapa Turbo Pascal
TP OLEN.PAS
-  kirjoita ohjelma
-  paina [Ctrl- F9]

6.1.2	C
/* C- kieli */ 
#include <stdio.h>
int main(void)
{
  printf("Terve! Olen C- kielellä kirjoitettu ohjelma.\n");
  return 0;
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-  käynnistä vaikkapa Turbo C
TC OLEN.C
-  kirjoita ohjelma
-  paina [Ctrl- F9] 

6.1.3	C++ 
// C++ - kieli
#include <iostream.h>
int main(void)
{
  cout << "Terve!  Olen C++ - kielellä kirjoitettu ohjelma.\n";
  return 0;
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-  käynnistä vaikkapa Turbo C++
TC OLEN.CPP
-  kirjoita ohjelma
-  paina [Ctrl- F9]
-  huomattakoon, että myös OLEN.C kelpaisi sellaisenaan C++ ohjelmaksi

6.1.4	Java
// Java -kieli
public class HelloWorld {
   public static void main(String[] args) {
      System.out.println("Terve, Olen Java-kielella kirjoitettu ohjelma.\n");
   }
}

6.1.5	Fortran 
C Fortran- kieli
      PRINT*,'Terve! Olen Fortran- kielellä kirjoitettu ohjelma.'
      STOP
      END

6.1.6	ADA 
- -  ADA- kieli
with TEXT_IO; use TEXT_IO;
procedure ADA_MALLI is
  pragma MAIN;
begin
  PUT("Terve! Olen ADA- kielellä kirjoitettu ohjelma."); NEW_LINE;
end ADA_MALLI;

6.1.7	BASIC 
REM BASIC- kieli
  PRINT "Terve! Olen BASIC- kielellä kirjoitettu ohjelma."
END
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-  käynnistä vaikkapa Quick Basic
QBASIC OLEN.BAS
-  kirjoita ohjelma
-  paina [Alt- R][Return] 

6.1.8	APL 
 APL- kieli
  'Terve! Olen APL- kielellä kirjoitettu ohjelma.'

6.1.9	Modula- 2 
(* Modula- 2 - kieli *)
MODULE olen;
FROM InOut IMPORT WriteString, WriteLn;
BEGIN
  WriteString("Terve! Olen Modula- 2 - kielellä kirjoitettu ohjelma.");
  WriteLn;
END olen.

6.1.10	Lisp 
; AutoLisp
(prin1 "Terve! Olen AutoLisp- kielellä kirjoitettu ohjelma.")
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-  Kirjoita rivi AutoCadissä (rivi tosin tulostuu 2x) 

6.1.11	FORTH 
 ( FORTH- kieli ) 
: Olen 
  " Terve! Olen FORTH- kielellä kirjoitettu ohjelma." TYPE CR
;

6.1.12	Assembler 
; 8086 assembler
DOSSEG
.MODEL TINY
.STACK
.DATA
viesti DB 'Terve! Olen 8086- assemblerilla kirjoitettu ohjelma.',0DH,0AH,'$'
.code
olen PROC NEAR
  MOV  AX,@@DATA
  MOV  DS,AX            ; Viestin segmentti DS:ään
  MOV  DX,OFFSET viesti ; Viestin offset osoite DX:ään
  MOV  AH,09H           ; Funktiokutsu 9 = tulosta merkkijono DS:DX
  INT  21H              ; Käyttöjärjestelmän kutsu
  MOV  AX,4C00H         ; Funktiokutsu 4C = ohjelman lopetus
  INT  21H              ; Käyttöjärjestelmän kutsu
olen ENDP
  END olen
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-  kirjoita ohjelma jollakin ASCII- editorilla nimelle OLEN.ASM
-  anna käyttöjärjestelmässä komennot (oletetaan että TASM on polussa):
TASM OLEN
TLINK OLEN
OLEN

Siis erot eri kielten välillä ovat hyvin kosmeettisia (Pascalin BEGIN on C:ssä { jne.).  Jollakin kielellä asia pystyttiin esittämään hyvin lyhyesti ja jossakin tarvitaan enemmän määrittelyjä.  Ainoastaan assembler- versio on sellaisenaan epäselvä, suoritettavia lauseita on täytynyt kommentoida enemmän.
6.2	Käytettävän kielen valinta
Kullakin ohjelmointikielellä on omat etunsa.  Pascal on hyvin tyypitetty kieli ja sillä ei ole aloittelijankaan niin helppo tehdä eräitä tyypillisiä ohjelmointivirheitä kuin muilla kielillä.  Standardi Pascal on kuitenkin hyvin suppea ja siitä puuttuu esim. merkkijonojen käsittely.  
Turbo Pascalin laajennukset tekevät kielestä erinomaisen ja nopean kääntäjän jaUNIT- kirjastojen ansiosta se on todella miellyttävä käyttää.  Nykyisin lisäksi Delphi- sovelluskehittimen kielenä on juuri Turbo Pascal (Borland Pascal).  Delphi on eräs merkittävimmistä ja helppokäyttöisimmistä työkaluista Windows- ohjelmointiin. Ohjelmat toimivat kuitenkin vain Windows - mikroissa.
BASIC- kieli on yleensä suppea kieli ja siksi helppo oppia.  Lukuisten eri murteiden takia ohjelmien siirtäminen ympäristöstä toiseen on lähes mahdotonta.  Microsoftin Visual Basic on kuitenkin Windows- ympäristössä nostanut Basicin jopa ohjelmankehittäjien työkaluksi.
Fortran on luonnontieteellisissä sovelluksissa eniten käytetty kieli ja siihen on saatavissa laajat aliohjelmakirjastot useisiin numeriikan ongelmiin.  Mikroissa kääntäjät ovat kuitenkin hitaita.  Fortran- 77 standardi on eräs parhaista standardeista, jota seuraamalla ohjelma toimii lähes koneessa kuin koneessa.
ADA on Pascalin kaltainen vielä tarkemmin tyypitetty kieli, jossa on joitakin olio- ominaisuuksia.  Se on Yhdysvaltain puolustusministeriön tukema kieli, joten se lienee tulevaisuuden kieliä kunhan kääntäjien hinnat laskevat ja mikrojen kääntäjät tulevat nopeammiksi ja pienemmiksi tai mikrot kasvavat.  Sopii erityisesti raskaiden reaaliaikatoteutusten kirjoittamiseen (esim. ohjusjärjestelmät).
C- kieli on välimuoto Pascalista ja konekielestä.  Ohjelmoijalle sallitaan hyvin suuria vapauksia, mutta toisaalta käytössä on hyvät tietotyypit ja rakenteet.  Hyvä kieli osaavan ohjelmoijan käsissä, mutta aloittelija saattaa saada aikaan katastrofin. ANSI- C on suhteellisen hyvin standardoitu ja sitä seuraamalla on mahdollista saada ohjelma toimimaan pienin muutoksin myös toisessakin laiteympäristössä.  Lisäksi ANSI- C:n tuoma funktioiden prototyypitys ja muutkin tyyppitarkistukset poistavat suuren osan ohjelmointivirheiden mahdollisuuksista, eivät kuitenkaan kaikkia. UNIX - käyttöjärjestelmän leviämisen myötä C on kohonnut erääksi kaikkein käytetyimmistä kielistä.
C++ on C- kielen päälle kehitetty olio- pohjainen ohjelmointikieli.  Aikaisemmin C- kielellä oli niin suuri merkitys, että se kannatti ehkä opetella aluksi.  Nykyisin jokainen merkittävä C- kääntäjä on myös C++- kääntäjä.  Oliopohjaisen ohjelmoinnin kannalta on parempi mitä aikaisemmin olio- ohjelmointi opetellaan.  Valitettavasti C++ ei ole hypridikielenä paras mahdollinen ohjelmoinnin opetteluun.  Kuitenkin paremmin opetteluun soveltuvat kielet ovat usein "leikkikieliä", kuten alkuperäinen Pascalkin oli.  Delphi olisi mahtavan graafisen kirjastonsa ja kehitysympäristönsä ansiosta loistava opettelutyökalu, valitettavasti vaan lehti- ilmoituksissa harvoin haetaan Delphi- osaajia!  Kohtuullisena kompromissina  C++:kin  voidaan valita opettelukieleksi, kunhan ei heti yritetä opetella kaikkia kielen kommervenkkejä.  Jokin työkalu olio- ohjelmoinnin opiskeluun tarvitaan, sillä erityisesti tapahtumaohjattujen järjestelmien (kuten graafiset käyttöliittymät) ohjelmoinnissa oliopohjaisilla kielillä on hyvin merkittävä asema.
Monisteen loppuosassa käytämme C++- kieltä esimerkkikielenä sen siirrettävyyden takia. 
Java on verkkoympäristössä tapahtuvaan ohjelmointiin kehitetty välikielellä käännettävään tulkkaamiseen perustuva oliokieli.  Javaa on sanottu C++:aa yksinkertaisemmaksi, mutta kuitenkin Java kirjat ovat yhtä paksuja kuin C++ kirjatkin.  Nykyisin onkin monesti niin, ettei itse kieli ole ongelma, vaan sille kehitettyjen aliohjelmakirjastojen opettelu ja käyttö.  Javan suurimpana etuna on sen lähes kaikissa koneissa toimiva graafinen kirjasto.  Java työkalut kehittyvät kovaa vauhtia ja saattaakin olla että jo seuraava ohjelmointikurssi pidetäänkin Javan pohjalta.  Tällä kertaa vielä emme Javaa valitse, koska emme halua olla juoksemassa pelkkien muotioikkujen perässä.  Tosin C++ opiskelu ei mitenkään haittaa mahdollista myöhempää Javaan siirtyvää ohjelmoijaa, pikemminkin päinvastoin.
Vaikkei valittu ohjelmointikieli olekaan kurssin tärkein asia, lukijan on kuitenkin hyvin tarkkaan ymmärrettävä kielestä esitettävät seikat, koska liika omapäinen "harhailu" saattaa kielen puuttuvien tarkistusten takia käydä kohtalokkaaksi.  Joudumme ottamaan käyttöön esimerkiksi osoittimet jo heti opiskelun alkuvaiheessa.  

7.	C ja C++ - kielten alkeita
Mitä tässä luvussa käsitellään?
•	C- kielisen ohjelman peruskäsitteet
•	C++- kielisen ohjelman peruskäsitteet
•	kääntämisen ja linkittämisen merkitys
•	vakiotyyliset makrot (#define)
•	C++:n vakiot  (const)

Syntaksi:
Seuraavassa lauseke on mikä tahansa jonkin tyypin tuottava ohjelman osa, esim:
            1+2   sin(x)+9  sormia  henkilon_nimi
kommentti, C      /* vapaata tekstiä, vaikka monta riviäkin */
kommentti, C++    // loppurivi vapaata tesktiä
sisällyttäminen:  #include <tiedoston_nimi>  tai  #include "tiedoston_nimi"
makro:            #define tunnisteXkorvattava_teksti // X mikä tahansa
                                                     // ei A-Z,a-z,_,0-9
                                                     // X tulee mukaan korv.tekst
vakio, C++:       const tyyppi nimi = arvo;
tulostus, C:      printf(format,lauseke, lauseke);   // 0-n x ,lauseke
tulostus, C++:    cout << lauseke << lauseke;        // 1-n x  <<lauseke

Ohjelman toteuttamista varten täytyy valita jokin todellinen ohjelmointikieli.  Lopullisesta ohjelmasta ei valintaa toivottavasti huomaa.  Valitsemme käyttökielen tällä kurssilla puhtaasti "markkinaperustein": käytetyin ja työelämässä vielä tällä hetkellä kysytyin - C++.
Tässä luvussa käsittelemme rinnakkain C ja C++ - kielistä ohjelmaa.  Seuraavissa luvuissa käsitellään pelkästään C++:aa, kuitenkin siten että jos ohjelman tarkeninen on .C, niin ohjelma toimii samalla myös C- kielisenä ohjelmana.
7.1	C/C++
Mikä ero on C ja C++ - kielillä?  Hyvin pieniä poikkeuksia lukuunottamatta jokainen C- kielinen ohjelma on myös C++ - kielinen ohjelma.  Voitaisiin sanoa että C++ on C:n oliopohjainen laajennus.  Tosin C++:ssa on myös lisäyksiä, joilla ei sinänsä ole mitään tekemistä olio- ohjelmoinnin kanssa.  Koska C++ ei ole "puhdas" oliokieli, sanotaan sitä  hypridikieleksi.  Sitä, että C++:lla voi kirjoittaa myös ei- olio- ohjelmia, pitävät monet olio- ohjelmoinnin asiantuntijat pahana.  Toisaalta maailmassa on valtava määrä C- kielen osaajia, joille on näin luotu "pehmeä" lasku olio- ohjelmontiin ilman että heidän aikaisemmin kirjoittamansa koodi tulisi kerralla arvottomaksi.  Ilman muuta C- kielen "painolasti" haittaa tietyllä tavalla C++- kielen kehittymistä, mutta reaalimaailmassa on tyydyttävä kompromisseihin.
Kumpi sitten kannattaa opetella ensiksi?  Puristit sanovat että jokin "leikkikieli", jotkut että C ja "oliogurut" sanovat että ilman muuta jokin oliokieli.  Otamme siis tällä kurssilla kultaisen(?) keskitien ja opettelemme C++:sta eräänlaisen "lasten" version, jossa jatkossa käytämme hyväksi myös kielen olio- ominaisuuksia.
7.1.1	Hello World! C- kielellä
Aloitamme C- kielen opiskelun klassisella esimerkillä:  Tulostetaan teksti "Hello world!" näyttöön:
c-alk\hello.c - ensimmäinen C-ohjelma
/* Ohjelma tulostaa tekstin Hello world! */
#include <stdio.h>
int main(void)
{
  printf("Hello world!\n");
  return 0;
}

7.1.2	Hello World! C++ - kielellä
Huomattakoon että edellinen hello.c on sellaisenaan aivan hyvä C++ - ohjelma, mikäli nimi muutettaisiin hello.cpp:ksi.  Kirjoitamme kuitenkin uuden version, jossa on käytetty hyväksi C++:an uusia ominaisuuksia: 
c-alk\hello.cpp - ensimmäinen C++ ohjelma
// Ohjelma tulostaa tekstin Hello world!
#include <iostream.h>
int main(void)
{
  cout << "Hello world!" << endl;
  return 0;
}

Tehtävä 7.48	Nimi ja osoite
Kirjoita puhdas C- ohjelma ja iostreamia käyttävä C++- ohjelma, joka tulostaa:
Terve!  
Olen Matti Meikäläinen 25 vuotta.
Asun Kortepohjassa.
Puhelinnumeroni on 603333. 

7.2	Tekstitiedostosta toimivaksi konekieliseksi versioksi
7.2.1	Kirjoittaminen 
Ohjelmakoodi kirjoitetaan millä tahansa tekstieditorilla tekstitiedostoon vaikkapa nimelle HELLO.C.  Yleensä tiedoston tarkennin määrää ohjelman tyypin. 
7.2.2	Kääntäminen 
Valmis tekstitiedosto käännetään ko. kielen kääntäjällä.  Käännöksestä muodostuu objektitiedosto, joka on jo lähellä lopullisen ohjelman konekielistä versiota.  Objektitiedostosta puuttuu kuitenkin mm. kirjastorutiinit.  Kirjastorutiinien kutsujen kohdalla on "tyhjät" kutsut.
7.2.3	Linkittäminen 
Linkittäjällä (kielestä riippumaton ohjelma) liitetään kirjastorutiinit käännettyyn objektitiedostoon. Linkittäjä korvaa tyhjät kutsut varsinaisilla kirjastorutiinien osoitteilla kunhan saa selville mihin kohti muistia kirjastorutiinit sijoittuvat. Näin saadaan valmis ajokelpoinen konekielinen versio alkuperäisestä ohjelmasta.
7.2.4	Ohjelman ajaminen 
Käännetty ohjelma ajetaan käyttöjärjestelmästä riippuen yleensä kirjoittamalla ohjelman alkuperäinen nimi.  Tällöin käyttöjärjestelmän lataaja- ohjelma lataa ohjelman konekielisen version muistiin ja siirtää prosessorin ohjelmalaskurin ohjelman ensimmäisenä suoritettavaksi tarkoitettuun käskyyn.  Vielä tässäkin vaiheessa osa aliohjelmakutsujen osoitteista voidaan muuttaa vastaamaan sitä todellista osoitetta, johon aliohjelma muistiin ladattaessa sijoittui.  Tämän jälkeen vastuu koneen käyttäytymisestä on ohjelmalla.  Onnistunut ohjelma päättyy aina ennemmin tai myöhemmin käyttöjärjestelmän kutsuun, jossa ohjelma pyydetään poistamaan muistista.

Kuva 7.1  Ohjelman kääntäminen ja linkittäminen
7.2.5	Varoitus
Alkuperäisellä editorilla kirjoitetulla ohjelmakoodilla ei ole tavallista kirjettä kummempaa virkaa ennenkuin teksti annetaan kääntäjä- ohjelman tutkittavaksi.  Käännöksen jälkeen alkuperäinen teksti voidaan vaikka hävittää!  Siis me kirjoitamme tekstiä, joka ehkä (toivottavasti) muistuttaa C- kielen syntaksin mukaista ohjelmaa.  Vasta käännös ja linkkaus tekevät todella toimivan ohjelman.
7.2.6	Integroitu ympäristö
On olemassa ohjelmankehitysympäristöjä, joissa editori, kääntäjä ja linkkeri (sekä mahdollisesti debuggeri, virheenjäljitin) on yhdistetty käyttäjän kannalta yhdeksi toimivaksi kokonaisuudeksi.  Esimerkkinä Microsoftin Visual- C++ ja Borlandin Turbo- C++, Borland- C++ tai Borland- C++ Builder .
Esimerkiksi Borlandin ympäristöissä ohjelma kirjoitetaan tekstinä ja kun ohjelmakoodi on valmis, saadaan koodi käännettyä, linkitettyä ja ladattua ajoa varten vain painamalla [Ctrl- F9].
7.3	Ohjelman yksityiskohtainen tarkastelu
Seuraavaksi tutkimme ohjelmaa lause kerrallaan:
7.3.1	Kommentti
/* Ohjelma tulostaa tekstin Hello world! */

Ohjelman  alussa on kommentoitu mitä ohjelma tekee.  Yleensä ohjelmakoodit on hyvä varustaa kuvauksella siitä, mitä ohjelma tekee, kuka ohjelman on tehnyt, milloin ja miksi.  Milloin ohjelmaa on viimeksi muutettu, kuka ja miten.
Lisäksi jokainen vähänkin ei- triviaali lause tai lauseryhmä kommentoidaan.  Kommenttien tarkoituksena on kuvata ohjelmakoodia lukevalle lukijalle se mistä on kyse.
Kommentti alkaa /* - merkkiyhdistelmällä ja päättyy */ - merkkiyhdistelmään.  Kommentteja voidaan sijoittaa C- koodissa mihin tahansa mihin voitaisiin pistää myös välilyönti.  Rivin loppuminen ei sinänsä lopeta kommenttia.  Kommentin sisällä SAA esiintyä / ja * - merkkejä yhdessä tai erikseen, muttei lopettavaa yhdistelmää */.
Yleinen virhe on unohtaa kommentin loppusulku pois.  Mikäli esimerkissämme puuttuisi kommentin loppusulku, olisi koko loppuohjelma kommenttia ja mitään ohjelmaa ei siis olisikaan.  Mikäli kääntäjä antaa vyöryn ihmeellisiä virheilmoituksia, kannattaa aina ensin tarkistaa kommenttisulkujen täsmäävyys.  Tosin tähän auttaa nykyisten ohjelmointiympäristöjen värikoodien käyttö eri ohjelman osille, eli esimerkiksi kommentit näkyvät eri värisinä ja puuttuva komenttisulku paljastuu välittömästi.
// Ohjelma tulostaa tekstin Hello world!

C++:ssa kommentti voidaan ilmaista myös // - merkkiyhdistelmällä, jolloin rivinloppu lopettaa kommentin.  Siirrettävyyden takia niissä ohjelman osissa, jotka ovat puhtaasti C- kieltä, voitaisiin käyttää /* */ - kommentointia ja muualla // - kommentointia.
7.3.2	Kirjastofunktioiden esittely
#include <stdio.h>

#include <iostream.h>

Tarvitsemme  ohjelmassamme tulostusfunktiota printf.  Tämä funktio löytyy C- kielen kirjastosta ja se on esitelty otsikkotiedostossa stdio.h.  Kääntäjää varten meidän täytyy esitellä millaisia parametreja funktiolle voidaan välittää.  Kutakin kirjastoa varten on esittelytiedostot ("header"- tiedostot, yleensä nimetään .h), joissa kirjastofunktioiden parametrilistat on esitelty..  Tässä ohjelmassa stdio.h - tiedostosta käytetään vain printf:n esittelevää riviä ja voitaisiin myös kirjoittaa #include - rivin tilalle printf:n esittely:
int printf(const char __format, ...);

mutta oikean muodon muistaminen voisi olla vaikeampaa.
C++:an tulostusvirta cout löytyy kirjastosta iostream.h.   cout - olion määrittely iostream.h - tiedostossa on niin monimutkainen ettei sitä käytännössä voisi itse edes kirjoittaa!
#include 	on C- kielen esikääntäjän (pre- prosessor) käsky, joka ilmoittaa että perässä olevan niminen tiedosto on luettava ja käsiteltävä koodin sekaan tässä kohti käännöstä.
< >	- merkit tiedoston nimen ympärillä ilmoittavat, että ko. tiedostoa etsitään C:n systeemin mukaisesta INCLUDE- hakemistosta.  Mikäli nimi suljettaisiin "- merkeillä, etsittäisiin tiedostoa myös käyttäjän kotihakemistosta.  Näin voidaan tehdä omia tehtäväkohtaisia kirjastoja.
7.3.3	Päämodulin esittely 
int main(void) 

Seuraavaksi esitellään ohjelman pääohjelma ("oikea" ohjelma koostuu isosta kasasta aliohjelmia ja yhdestä pääohjelmasta, jonka nimi on main).  
int	tarkoittaa, että pääohjelmamme palauttaa kokonaisluvun kutsuvalle ohjelmalle - käyttöjärjestelmälle.  Palautettavan luvun arvo on tyypillisesti 0 mikäli kaikki menee ohjelman aikana niinkuin pitääkin ja muilla numeroilla ilmaistaan erilaisia virhetilanteita.  MS- DOSissa tätä arvoa voidaan tutkia ERRORLEVEL- muuttujalla.
main	tarkoittaa pääohjelman nimeä.  Tämä TÄYTYY aina olla main.
(void)	ilmoittaa, että funktio jota kirjoitamme ei tarvitse yhtään parametria (eng. void = mitätön).  Myöhemmin huomaamme että myös pääohjelmalla voi olla parametrejä.
7.3.4	Lausesulut 
{ }	C:ssä isompi joukko lauseita kootaan yhdeksi lauseeksi sulkemalla lauseet aaltosulkuihin.  Funktion täytyy aina sisältää aaltosulkupari, vaikka siinä olisi vain 0 tai 1 suoritettavaa lausetta.
7.3.5	Tulostuslause 
  printf("Hello world!\n")

printf("?")	tulostaa ajonaikana sen tekstin, joka on lainausmerkkien välissä.  Myöhemmin opimme, että funktion kutsussa voi olla myös useampia parametreja ja voidaan käyttää myös muuttujia.
\n	on C:n erikoismerkki, joka kääntyy merkkijonon sisällä käyttöjärjestelmän rivinvaihtomerkiksi.  Tällaisen esiintyminen merkkijonossa aiheuttaa tulostuksen siirtymisen uuden rivin alkuun.  Muista käyttää!
cout << "Hello world!" << endl;

cout	on C++:n yksi tulostustietovirtaluokan (output stream class) ostream esiintymä, eli olio jolle (coutin tapauksessa) siirretyt merkit tulostuvat näyttöön (Console OUTput) .
<<	on operaattori, jolla C++:ssa on useita merkityksiä.  Tässä tapauksessa kun operaattorin vasempana operandina on tietovirtaolio, on kyseessä tietovirtaan siirtämisoperattoori (inserter).  Käytämme tästä jatkossa nimitystä tulostusoperaattori.  Koska operaattorikin on vain aliohjelma, voisi <<- operaattorin varsinainen kutsumuoto olla esimerkiksi
operator<<(cout, "Hello world!");
operator<<(cout, endl);

	Koska operaattori palauttaa cout- olion, voidaan kutsu kirjoittaa myös (samoin kuin 1+2 palauttaa kokonaisluvun) ketjumuotoon
operator<<( operator<<(cout, "Hello world!"), endl);
// lyhennetty muoto
(cout << "Hello world!") << endl;

	joka ilman sulkuja on ohjelmassa hello.cpp esitetty muoto.  Operaattorin muoto voi olla myös:
cout.operator<<("Hello world!");
cout.operator<<(endl);
// josta ketjutettuna:
(cout.operator<<("Hello world!")).operator<<(endl);
// lyhennetty muoto:
(cout << "Hello world!") << endl;

Vertaa vastaavaan ketjuttamiseen + - operaattorin kanssa:
int i,j;
i = 1 + 2;  j = i + 4;
// voidaan kirjoittaa myös:
j =  ( 1 + 2 ) + 4;

endl	on tietovirran muotoilija (manipulator), joka vaihtaa tulostuksen uudelle riville ja tyhjentää tulostuspuskurin.  Rivinvaihto voitaisiin tehdä myös jonolla "\n", mutta tulostuspuskuri on muistettava myös tyhjentää.  endl olisi hyvä olla vähintään viimeisessä tulostettavassa lauseessa ennen pysähtymistä.  Joissakin koneissa ohjelmat voivat toimia myös ilman endl:ää, mutta jos halutaan standardin mukaista koodia, joka toimii KAIKISSA koneissa, on sitä syytä käyttää.
Kumpiko tulostuslause sitten on parempi?  Jos kirjoitetaan C- koodia, on tietysti käytettävä printf:ää, mutta C++:n tapauksessa molemmat käyvät.  cout on parempi sen vuoksi, että siihen liittyvään tulostusoperaattorin voidaan lisätä uusia tietotyyppejä tulostettavaksi (kuormittaa, operator overloading).  Toisaalta tulostuksen muotoilu on helpompaa printf:n kanssa.  Määrityksissä sanotaan että molempia voi käyttää, muttei samalla tulostusrivillä.  Laajennettavuuden takia valitaan coutaina kuin se vain on mahdollista.
7.3.6	Lauseen loppumerkki ; 
;	puolipiste lopettaa lauseen.  Puolipiste voidaan sijoittaa mihin tahansa lopetettavaan lauseeseen nähden.  Sen eteen voidaan jättää välilyöntejä tai jopa tyhjiä rivejä.  Sen pitää kuitenkin esiintyä ennen uuden lauseen alkua.   Näin C- kieli ei ole rivisidonnainen, vaan C- kielinen lause voi jakaantua usealle eri riville tai samalla rivillä voi olla useita C- kielisiä lauseita.
Puolipisteen unohtaminen on tyypillinen syntaksivirhe.  Ylimääräiset puolipisteet aiheuttavat tyhjiä lauseita, joista tosin ei ole mitään haittaa:  “Tyhjän tekemiseen ei kauan mene” -  sanoo tyhjän toimittaja.
7.3.7	Funktion arvon palautus
  return 0

return	jokainen C- funktio tulisi lopettaa return- lauseeseen.  Mikäli funktio on esitelty muun kuin void- tyyppiseksi, pitää kertoa myös arvo, joka palautetaan.  Funktio voi tarvittaessa sisältää myös useita eri return- lauseita.  Heti kun kohdataan ensimmäinen return- lause, poistutaan funktiosta.  void- tyyppisessä funktiossa return- lause ei ole pakollinen.
7.3.8	Isot ja pienet kirjaimet
Isoilla ja pienillä kirjaimilla on C- kielessä eri merkitys.  Siis EI VOIDA KIRJOITTAA:
Int MAIN(Void)              /* VÄÄRIN! */	

7.3.9	White spaces, tyhjä 
Välilyöntejä, tabulointimerkkejä, rivinvaihtoja ja sivunvaihtoja nimitetään yleisesti yhteisellä nimellä "white space".  Käännettäessä kommentit muutetaan yhdeksi välilyönniksi, joten myös kommenteista voitaisiin käyttää nimitystä "white space".  Jatkossa käytämme nimitystä tyhjä tai tyhjä merkki, kun tarkoitamme "white space".
C- koodi voi sisältää tyhjiä merkkejä missä tahansa, kunhan niitä ei kirjoiteta keskelle sanaa tai tekstiä määrittelevän ""- parin ollessa auki. ""- parin sisällä tyhjätkin merkit ovat merkityksellisiä.  Tyhjillä merkeillä ei saa myöskään sotkea esikääntäjälle tarkoitettuja #- direktiivi - rivejä, näiden pitää muodostaa täsmälleen yksi rivi.
Lainausmerkkeihin suljettu jono voidaan tarvittaessa katkaista tyhjillä merkillä sulkemalla ja avaamalla lainausmerkit.  Esimerkiksi
"Kissa"
"istuu"
- >  
"Kissaistuu"

Tarvittaessa C- ohjelman riviä voidaan jatkaa uudelle riville kirjoittamalla \- merkki edellisen rivin loppuun ja sen jälkeen välittömästi rivinvaihto.
Siis kääntäjän kannalta malliohjelmamme voitaisiin kirjoittaa myös seuraavillakin tavoilla:
#include\	
<stdio.h>
int 
main 
(
void
)
{
printf
(
"Hel\
lo "
"w" /* kommentti keskellä jonoa */ "or"
"ld!"
"\n"
)
;
return
0
;
}

#include <stdio.h>	
int main(void){
    printf("Hello "
           "world!\n"
                                               );return 0;}

#include <stdio.h>	
int main(void){printf("Hello world!\n");return 0;}

Yleinen tyyli on kuitenkin jakaa koodia riveihin ja sisentää lohkoja muutamalla pykälällä.  Kunnes lukija on varma omasta tyylistään, kannattaa matkia tässä monisteessa (ei kuitenkaan edellisiä esimerkkejä) esitettyä kirjoitustapaa ohjelmille.
7.4	Makro- direktiivi ja vakiot 
#include oli tiedote esikääntäjälle siitä, että koodin sekaan täytyy lukea välillä jokin toinen teksti.
Makro- direktiivi #define on tiedote siitä, että tällä määreellä myöhemmin tekstissä esiintyvät sanat täytyy korvata toisella sanalla/sanoilla/merkeillä.
Yksinkertaisten makrojen käytön hyöty on siinä, että usein ohjelmassa esiintyvät sanat/vakionumerot voidaan koota helposti hallittavaksi ohjelman alkuun tai jopa omaan määrittelytiedostoonsa.
Siis #define- direktiivi on puhdas tekstinkäsittelyotus, joka toimii suurinpiirtein seuraavasti: 
c-alk\makrot.c - esimerkki mitä #define korvaa
#define TERVE "Hello "

tarkoittaisi esikääntäjälle:
vaihda kaikki TERVE- sanat merkkijonoiksi "Hello "

eli seuraavat vaihdettaisiin:
p=TERVE+1                /* =>  p="Hello "+1             */
"Kissa"TERVE"istuu"      /* =>  "Kissa""Terve ""istuu"   */
TERVE MIEHEEN            /* =>  "Hello" MIEHEEN          */

muttei seuraavia
TERVEYDEKSI                   /* Ei pelkkä TERVE-sana    */
TERVE_MIEHEEN                 /* Ei pelkkä sana          */
"OLEN OLLUT TERVE 2 PÄIVÄÄ"   /* Lainausmerkeissä        */
Terve MIEHEEN                 /* Eri tavalla kirjoitettu */

Huomattakoon, ettei lainausmerkkien sisällä oleviin sanoihin kajota lainkaan!
Määriteltävä sana voidaan kirjoittaa isoja ja/tai pieniä kirjaimia käyttäen, mutta yleiseen C- tyyliin kuuluu kirjoittaa #define - määritellyt sanat isoilla kirjaimilla.
Vaihdettava sana loppuu ja korvaava jono alkaa ensimmäisestä ei- kirjaimesta tai numerosta, eli
#define X=2;                /* VAARALLINEN!   X  => =2;  */	
int i=X;                    /* => int i==2;;             */

7.4.1	Vakiomerkkijonot 
Esikääntäjän makro- ominaisuutta hyväksikäyttäen voimme määritellä ohjelmaamme vakioita; eli arvoja jotka esiintyvät ohjelmassa täsmälleen yhden kerran.  Näin ohjelmastamme saadaan helpommin muuteltava.  Esimerkiksi seuraava ohjelma tulostaisi myös tekstin "Hello world!": 
c-alk\hello2.c - tervehdys vakioksi
/* Ohjelma tulostaa tekstin Hello world! */
#include <stdio.h>
#define TERVE   "Hello "
#define MAAILMA "world"
int main(void)
{
  printf(TERVE MAAILMA"!\n");
  return 0;
}

Miksikö?  Koska esikääntäjä muuttaisi lauseen 
  printf(TERVE MAAILMA"!\n");
muotoon
  printf("Hello " "world""!\n");
Kun tästä lisäksi poistetaan ylimääräiset "white space"- merkit saadaan:
  printf("Hello world!\n");

Tehtävä 7.49	Terve maailma!
Kirjoita edellisestä ohjelmasta suomenkielellä tulostava versio (= suomenna ohjelma).
Tehtävä 7.50	Nimi ja osoite vakioksi
Kirjoita aikaisemmasta "Matti Meikäläinen asuu Kortepohjassa" - ohjelmasta versio, jossa nimi, osoite ja puhelin on esitelty #define- direktiivillä.

7.4.2	Vakiolukuarvot 
Vakiomäärittelyä voitaisiin käyttää esimerkiksi kokonaislukuvakioiden määrittelemiseen: 
c-alk\kuutio.c - monikulmion tiedot vakioksi
/* Ohjelma tulostaa tietoja monitahokkaasta */
#include <stdio.h>
#define TAHOKAS     "Kuutiossa"
#define KARKIA      8
#define SIVUTASOJA  6
#define SARMIA     12
int main(void)
{
  printf("%20s on %2d kärkeä,\n"     ,TAHOKAS,KARKIA);
  printf("%20s    %2d sivutasoa ja\n"," "    ,SIVUTASOJA);
  printf("%20s    %2d särmää.\n"     ," "    ,SARMIA);
  return 0;
}

Tehtävä 7.51	Tetraedri
Muuta edellistä ohjelmaa siten, että tulostetaan samat asiat tetraedristä.
Tehtävä 7.52	printf ja %
Mitä arvelet %2d:n ja %20s:n merkitsevän edellisessä esimerkissä, kun ohjelma tulostaa seuraavan tekstin:
0        1         2         3         4
1234567890123456789012345678901234567890
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
           Kuutiossa on  8 kärkeä,
                         6 sivutasoa ja
                        12 särmää. 

7.4.3	Muita makro- "temppuja"
Koska C- kielen makro on todellakin vain tekstinkorvaus, muuttaa esikääntäjä seuraavan tekstin 
c-alk\makroja.c - "turhia" makrotemppuja
#include <stdio.h>
#define ALKU  int main(void) {
#define LOPPU return 0; }
#define mk *100
#define km *1000
ALKU
  double hinta_penneina,matka_m;
  hinta_penneina = 5 mk;
  matka_m        = 3.5 km;
LOPPU
muotoon 
… kaikki stdio.h:ssa oleva koodi makrot käsiteltynä …
int main(void) {
double hinta_penneina,matka_m;
hinta_penneina = 5 *100;
matka_m        = 3.5 *1000;
return 0; }

Tosin tällaisia makrotemppuiluja kannattaa välttää ellei siitä saa suunnatonta ohjelman ylläpidollista hyötyä.  Tarvittaessa pelkkä esikäännös voidaan tehdä vaikka Borland C++:lla:
C:\OMAT\OHJELMOI\VESA>CPP makroja.c[RET]
tulee tiedosto  makroja.i

7.4.4	Esikääntäjän toiminnasta
Esiprosessori kerää jokaisen löytämänsä #define - alkuisen rivin omaan sisäiseen listaansa.  Jos tulee vastaan #undef jollekin listassa mainitulle sanalle, poistetaan sana listasta.  Esikääntäjä lukee varsinaisia rivejä (ei #- alkuisia) token ("sananen") kerrallaan.  Tämä token on pienin esikääntäjän ymmärtämä yksikkö (joka on erotettu ympäristöstä ei- muuttujaan sallituilla kirjaimilla).  Esimerkiksi rivillä
a;bd(kissa) 

on 3 tokenia: a, bd ja kissa.
Kun esikääntäjä käsittelee yhtä tokenia, etsii se sitä sisäisestä listastaan.  Jos token löytyy listasta, korvataan se vastaavalla merkkijoukolla ja listan ko. token merkitään käytetyksi.  Mahdollinen korvaus muodostaa 0 -  n uutta tokenia.  Nämä kaikki käsitellään rekursiivisesti esikääntäjän listan kanssa kunnes yhtään korvausta ei voida tehdä (token ei löydy listasta tai kaikki listan alkiot on merkitty käytetyiksi).  Näin tämä yksi token on saatu muutetuksi.  Tämän jälkeen listan kaikki alkiot vapautetaan ja siirrytään rivin seuraavaan mahdolliseen tokeniin.
Miksi listaan merkitään sanoja käytetyksi?  Miten kävisi muuten (kävi vanhoilla C- esikääntäjillä) seuraavan ohjelmanpätkän kanssa:
#define K T T
#define T K K
K
T

Lyhyesti: esikääntäjä ei korvaa sisäkkäisiä #definejä heti, vaan sitten kun ne esiintyvät.  Näin useissa normaalitapauksissa (kuten ei edelläkään) ei ole väliä sillä, missä järjestyksessä #define- rivit kirjoitetaan.
Tehtävä 7.53	Sisäkkäiset makrot
Miten edellisestä K T T, esimerkissä korvautuu K?  Entä miten T?

7.5	C++:n vakiot
Koska makrojen käyttöön liittyy tiettyjä tyyppitarkistuksiin/oikeellisuuskirjoitukseen liittyviä riskejä, kannattaa C++:ssa mieluummin käyttää tyypitettyjä vakioita (seuraavan esimerkin const- rivit toimivat tosin C- kielessäkin):
c-alk\hello2.cpp - tervehdys "vakioksi"
// Ohjelma tulostaa tekstin Hello world! 
#include <iostream.h>
const char *TERVE = "Hello ";
const char *MAAILMA = "world";

int main(void)
{
  cout << TERVE << MAAILMA << "!" << endl;
  return 0;
}

Valitettavasti on tilanteita, joissa makroja on edelleen lähes pakko käyttää.  Esimerkiksi merkkijonojen yhdistäminen TERVE MAAILMA tyyliin ei onnistu.  Myöhemmin opimme joitakin muitakin tilanteita, joissa makrot ovat "välttämättömiä".  Erityisesti lukuvakioiden määrittelyssä const- vakiot ovat omimmillaan:
c-alk\kuutio.cpp - monikulmion tiedot vakioksi
// Ohjelma tulostaa tietoja monitahokkaasta
#include <iostream.h>
#include <iomanip.h>
const char *TAHOKAS   = "Kuutiossa";
const int KARKIA      =  8;
const int SIVUTASOJA  =  6;
const int SARMIA      = 12;
int main(void)
{
  cout<<setw(20)<<TAHOKAS<<" on "<<setw(2)<<KARKIA    <<" kärkeä,\n";
  cout<<setw(20)<<" "    <<"    "<<setw(2)<<SIVUTASOJA<<" sivutasoa,\n";
  cout<<setw(20)<<" "    <<"    "<<setw(2)<<SARMIA    <<" särmää" << endl;
  return 0;
}

Tehtävä 7.54	Vakiot  #define
Päättele seuraavasta esimerkistä miksi const- vakiot ovat parempia kuin #define - makrot.

c-alk\const.cpp - vakion ja #definen erot
// Ohjelmalla tutkitaan mikä ero on const ja #define -vakioiden välillä
#include <iostream.h>
const int sormia = 1 + 4;
#define VARPAITA 1 + 4	
int main(void)
{
  int syht = 2 * sormia;
  int vyht = 2 * VARPAITA;
  cout << "Sormia = " << syht << " ja varpaita = " << vyht << endl;
  return 0;
}

Selitä mistä virhe johtuu?  Miten virhe saadaan tässä tapauksessa poistettua?

8.	C++- kielen muuttujista ja aliohjelmista
Mitä tässä luvussa käsitellään?
•	muuttujat
•	malliohjelma jossa tarvitaan välttämättä muuttujia
•	osoiteoperaattori, &
•	osoitin muuttujat ja epäsuora osoitus, *
•	aliohjelmat, eli funktiot
•	erilaiset aliohjelmien kutsumekanismit
•	referenssimuuttujat eli viitemuuttujat, &

Syntaksi:
Seuraavassa muut = muuttujan nimi, koostuu A-Z,a-z,0-9,_, ei ala 0-9
muut.esittely:    tyyppi muut = alkuarvo;            // 0-1 x =alkuarvo
sijoitus:         muut = lauseke;
lukeminen, C:     scanf(format, osoite, osoite, ...) // 0-n x osoite
lukeminen, C++    cin >> olio >> olio;               // 1-n x olio
aliohj.esittely:  tyyppi aliohj_nimi(tyypi muut, tyyppi muut);  // 0-n x muut
aliohj.kutsu      muut = aliohj_nimi(arvo, arvo);    // 0-1 x muut=, 0-n x arvo
muut.osoite:      &muut
osoitinmuut.esit: tyyppi *pMuut_nimi
epäsuora osoitus: *osoite
viitemuut.esit.   tyyppi &rMuut_nimi

Jatkossa puhumme C- kielestä kun tarkoitamme ominaisuuksia, jotka ovat sekä varsinaisessa C- kielessa että C++- kielessä.  Jos puhumme C++- kielen ominaisuuksista, tarkoitamme ominaisuuksia joita ei ole C- kielessä.   Aloittelevan lukijan kannalta tällä jaottelulla ei niinkään ole merkitystä.  Merkitystä on ainoastaan silloin, jos joutuu kirjoittamaan ohjelmaa, jonka on mentävä läpi puhtaasta C- kääntäjästä. Tällainen tilanne voi tulla vastaan esimerkiksi sulautettuja järjestelmiä ohjelmoitaessa (=järjestelmät joissa tietokone on "näkymättömässä" osassa, kodinelektroniikka, "kännykät", ohjauslaitteet).  Näihin ei vielä aina ole saatavilla C++- kääntäjää.
8.1	Mittakaavaohjelman suunnittelu
Satunnainen matkaaja ajelee tällä kertaa kotimaassa.  Autoillessa hänellä on käytössä Suomen tiekartan GT - karttalehtiä, joiden mittakaava on 1:200000.  Viivottimella hän mittaa kartalta milleinä matkan, jonka hän aikoo ajaa.  Ilman matikkapäätä laskut eivät kuitenkaan suju.  Siis hän tarvitsee ohjelman, jolla matkat saadaan muutettua kilometreiksi.  
Millainen ohjelman toiminta voisi olla?  Vaikkapa seuraavanlainen:
C:\OMA\MATKAAJA>matka[RET]
Lasken 1:200000 kartalta millimetreinä mitatun matkan 
kilometreinä luonnossa.
Anna matka millimetreinä>35[RET]
Matka on luonnossa 7.0 km.
C:\OMA\MATKAAJA>matka[RET]
Lasken 1:200000 kartalta millimetreinä mitatun matkan 
kilometreinä luonnossa.
Anna matka millimetreinä>352[RET]
Matka on luonnossa 70.4 km.
C:\OMA\MATKAAJA>

Edellisessä toteutuksessa on vielä runsaasti huonoja puolia.  Mikäli samalla haluttaisiin laskea useita matkoja, niin olisi kätevämpää kysellä matkoja kunnes kyllästytään laskemaan.  Lisäksi olisi ehkä kiva käyttää muitakin mittakaavoja kuin 1:200000.  Muutettava matka voitaisiin tarvittaessa antaa jopa ohjelman kutsussa.  Voimme lisätä nämä asiat ohjelmaan myöhemmin, kunhan kykymme siihen riittävät.  Toteutamme nyt kuitenkin ensin mainitun ohjelman.
8.2	Muuttujat 
Ohjelmamme  poikkeaa aikaisemmista esimerkeistä siinä, että nyt ohjelman sisällä tarvitaan muuttuvaa tietoa: matka millimetreinä.  Tällaiset muuttuvat tiedot talletetaan ohjelmointikielissä muuttujiin.  Muuttuja on koneen muistialueesta varattu tarvittavan kokoinen "muistimöhkäle", johon viitataan käytännössä muuttujan nimellä.  
Kone viittaa muistipaikkaan muistipaikan osoitteella.  Kääntäjäohjelman tehtävä on muuttaa muuttujien nimiä muistipaikkojen osoitteiksi.  Kääntäjälle täytyy kuitenkin kertoa aluksi minkäkokoisia 'möhkäleitä' halutaan käyttää.  Esimerkiksi kokonaisluku voidaan tallettaa pienempään tilaan kuin reaaliluku.  Mikäli haluaisimme varata vaikkapa muuttujan, jonka nimi olisi matka_mm kokonaisluvuksi, kirjoittaisimme seuraavan C- kielisen lauseen (muuttujan esittely): 
int matka_mm; /* yksinkertaisen tarkkuuden kokonaisluku */

Pascal - kielen osaajille huomautettakoon, että Pascalissahan esittely oli päinvastoin:
VAR matka_mm: INTEGER;

Tulos, eli matka kilometreinä voitaisiin laskea muuttujaan matka_km.  Tämän muuttujan on kuitenkin oltava reaalilukutyyppinen (ks. esimerkkiajo), koska tulos voi sisältää  myös desimaaliosan: 
double matka_km; /* kaksinkertaisen tarkkuuden reaaliluku */

On olemassa myös yksinkertaisen tarkkuuden reaaliluku float, mutta emme tarvitse sitä tällä kurssilla.  Samoin kokonaisluvusta voidaan tehdä etumerkillinen, etumerkitön, "lyhyt" tai "kaksi kertaa isompi":  
signed int matka_km;       /* Sama kuin int matka_km  */
unsigned int sormia;       /* Aina positiivinen       */
short int varpaita;        /* Ei koskaan kovin montaa */
long int valtion_velka_Mmk;/* Tarvitaan ISO arvoalue  */

int- tyyppiä ei edellä olisi pakko kirjoittaa:
signed matka_km;       /* Sama kuin int matka_km  */
unsigned sormia;       /* Aina positiivinen       */
short varpaita;        /* Ei koskaan kovin montaa */
long valtion_velka_Mmk;/* Tarvitaan ISO arvoalue  */

Muuttujan määritys voisi olla myös
const volatile unsigned long int sadasosia; 

Tulemme kuitenkin aluksi varsin pitkään toimeen pelkästään seuraavilla tyypeillä ja niiden osoittimilla (Turbo C++:n arvoalueet):
int      -  kokonaisluvut - 32 768 -   32 767, 16-bit, tai
           - -2 147 483 648 - 2 147 483 647, 32-bit systeemit
double   -  reaaliluvut n. 15 desim. - > 1.7e308
char     -  kirjaimet, kokonaislukuina - 128 -   127 (tai 0-255)

8.2.1	Matkan laskeminen 
Ohjelman  käyttämä mittakaava kannattaa sijoittaa ehkä vakioksi, tällöin ainakin ohjelman muuttaminen on helpompaa.  Samoin vakioksi kannattaa sijoittaa tieto siitä, paljonko yksi km on millimetreinä (1 km = 1000 m, 1 m = 1000 mm).  Ohjelmastamme tulee tällöin esimerkiksi seuraavan näköinen:
c-muut\matka.cpp - mittakaavamuunnos 1:200000 kartalta
// matka.cpp
// Ohjelmalla lasketaan mittakaavamuunnoksia 1:200000 kartalta
// Vesa Lappalainen 18.9.1991
#include <iostream.h>

const double MITTAKAAVA = 200000.0;
const double MM_KM      = 1000.0*1000.0;

int main(void)
{
  int    matka_mm;
  double matka_km;

  // Ohjeet
  cout << "Lasken 1:" << MITTAKAAVA 
       << " kartalta millimetreinä mitatun matkan\n";
  cout << "kilometreinä luonnossa.\n";

  // Syöttöpyyntö ja vastauksen lukeminen
  cout << "Anna matka millimetreinä>";
  cin >> matka_mm;

  // Datan käsittely
  matka_km = matka_mm*MITTAKAAVA/MM_KM;

  // Tulostus
  cout << "Matka on luonnossa "<< matka_km << " km." << endl;

  return 0;
}

Lukija huomatkoon, että muuttujien ja vakioiden nimet on pyritty valitsemaan siten, ettei niitä tarvitse paljoa selitellä.  Tästä huolimatta isommissa ohjelmissa on tapana kommentoida muuttujan esittelyn viereen muuttujan käyttötarkoitus.  Mekin pyrimme tähän myöhemmin.
Tehtävä 8.55	Vakion korvaaminen
Korvaa vakioiden esittely #define makroilla.  Mitä vaaroja liittyy makrojen käyttöön tässä tapauksessa.

8.2.2	Muuttujan nimeäminen 
Muuttujien nimissä on sallittuja kaikki kirjaimet (a- z, A- Z) sekä numerot (0- 9) sekä alleviivausviiva (_).  Muuttujan nimi ei kuitenkaan saa alkaa numerolla.  Muuttujia saa esitellä (declare) useita samalla kertaa, kunhan muuttujien nimet erotetaan toisistaan pilkulla.
Muuttujan nimi ei myöskään saa olla mikään varatuista sanoista (reserwed word): 
C-kielen varatut sanat:
auto   	break       	case      	char
const   	continue    	default   	do
double      	else        	enum     	extern
float     	for         	goto	if
int       	long        	register	return
short    	signed      	sizeof	static
struct    	switch      	typedef	union
unsigned   	void        	volatile	while
C++:ssa on lisäksi varattuja sanoja:
asm	bool	catch	class	
const_cast	delete	dynamic_cast	explicit 
false	friend	inline	mutable	
namespace	new	operator	private	
protected 	public	__rtti	static_cast	
template	this	throw	true	
try	typeid	typename 	reinterpret_cast
using	virtual	wchar_t

Tehtävä 8.56	Varatut sanat
Merkitse edelliseen taulukkoon kunkin varatun sanan viereen se, missä kohti monistetta ko. sana on selitetty.

Tehtävä 8.57	Muuttujan nimeäminen
Mitkä seuraavista ovat oikeita muuttujan esittelyjä ja mitkä niistä ovat hyviä:

int    o;
int    9_kissaa;
int    _9_kissaa;
double pitkä_matka;
int    i, j, kissojen_maara;
int    auto, pyora, juna; 

8.2.3	Muuttujalle sijoittaminen = 
Muuttujalle voidaan antaa ohjelman aikana uusia arvoja käyttäen joko sijoitusoperaattoria = tai aliohjelmakutsua, joka muuttaa muuttujan arvoa osoittimen välityksellä (tai ++,- - ,+=,- =,*= jne. - operaattoreilla).
Sijoitusmerkin = vasemmalle puolelle tulee muuttujan nimi ja oikealle puolelle mikä tahansa lauseke, joka tuottaa halutun tyyppisen tuloksen (arvon).  Lausekkeessa voidaan käyttää mm. operaattoreita +,- ,*,/ ja funktiokutsuja.  Lausekkeen suoritusjärjestykseen voidaan vaikuttaa suluilla (ja):
kengan_koko  = 42;
pi           = 3.14159265358979323846;
// usein käytetään math.h:n M_PI vakiota 
pi           = M_PI;
pinta_ala    = leveys * pituus;
ympyran_ala  = pi*r*r;
hypotenuusa  = vastainen_kateetti/sin(kulma);
matka_km     = matka_mm*MITTAKAAVA/MM_KM; 

Seuraava sijoitus on tietenkin mieletön:
r*r = 5.0; /* MIELETÖN USEIMMISSA OHJELMOINTI KIELISSA! */	

Eli sijoituksessa tulee vasemmalla olla sen muistipaikan nimi, johon sijoitetaan ja oikealla arvo joka sijoitetaan.
Huom! C- kielessä = merkki EI ole yhtäsuuruusmerkki, vaan nimenomaan sijoitusmerkki.  Yhtäsuuruusmerkki on = =.
Tehtävä 8.58	Muuttujien esittely
Esittele edellisissä sijoitus - esimerkeissä tarvittavat muuttujat.

8.2.4	Muuttujan esittely ja alkuarvon sijoittaminen 
Muuttujan esittelyn (declaration) yhteydessä muuttujalle voidaan antaa myös alkuarvo (alustus, definition).  Muuttujien alustaminen tietyllä arvolla on tärkeää, koska alustamattoman muuttujan arvo saattaa olla hyvinkin satunnainen.  Alustamattoman muuttujan käyttö onkin jälleen eräs tyypillinen ohjelmointivirhe.
int    kengan_koko = 32, takin_koko = 52;
double pi = 3.14159265358979323846, r = 5.0; 

8.3	Muuttujan arvon lukeminen päätteeltä
8.3.1	scanf ja muuttujan osoite & 
Muuttujalle voidaan sijoittaa uusi arvo myös C- kielen scanf- funktiolla.  Funktion kutsussa on kaksi osaa:  ohje-  eli format - osa ja lista muuttujien osoitteista, joihin arvot sijoitetaan.  Matka olisi voitu lukea myös kutsulla:
  scanf("%d",&matka_mm); 
 
Tässä "%d" tarkoittaa, että kutsun listaosassa vastaavassa paikassa oleva muuttuja on tyyppiä d eli desimaalinen (annetaan 10- järjestelmässä) kokonaisluku.
&matka_mm tarkoittaa muuttujan matka_mm osoitetta.  Funktiolle ei voida viedä parametrina itse muuttujan arvoa, koska funktio ei tämän perusteella tiedä mihin se luetun arvon sijoittaisi.  Tämän takia välitämmekin muuttujan osoitteen, eli sen paikan muistissa, missä muuttujan arvo sijaitsee. Näin aliohjelma tietää sen mihin paikkaan arvo tulee sijoittaa.  
Varoitus! Yleinen virhe on unohtaa &- merkki pois scanf- funktion kutsusta.  
Joissakin ohjelmointikielissä (kuten Pascal tai Fortran ja osittain myös C++:ssa) ei osoitemerkkiä kirjoiteta, koska kääntäjä kääntää vastaavat kutsut siten, että parametrina viedäänkin osoite.  C- kielessä on mahdollista välittää vain arvoja parametrina.
  int pituus,leveys;
  printf("Anna kentän pituus ja leveys metreinä >");
  scanf("%d %d",&pituus,&leveys); 

Koska kyseessä on funktio, niin se myös palauttaa jonkin arvon.  Tässä tapauksessa palautetaan kokonaislukuarvo, joka kertoo montako onnistunutta sijoitusta pystyttiin tekemään.
c-muut\ala.c - tietojen kysyminen kunnes oikein
/* ala.c */
/* Ohjelmalla luetaan kentän leveys ja pituus sekä tulostetaan
   näiden perusteella kentän ala.  Tietoja kysytään kunnes
   molemmat tulevat oikein annetuksi.

   Vesa Lappalainen 18.9.1991
*/
#include <stdio.h>
int main(void)
{
  int pituus,leveys;
  do {
    printf("Anna kentän pituus ja leveys metreinä >");
    fflush(stdin); /* Poistetaan mahd. ed. kier. ylim. merkit, epästd. */
  } while ( scanf("%d %d",&pituus,&leveys) < 2 );
  printf("Ala on %d m2.\n",pituus*leveys);
  return 0;
}

format- osassa voidaan pakottaa syöttämään tiettyjä merkkejä syöttötekstin väliin: 
scanf("%d,%d",&pituus,&leveys) 

Tällöin syöttöjä saataisiin seuraavasti:
Syöttö:	syöttöjen lkm	pituus	leveys		
23,34	2	23	34		
    23,  45	2	23	45		
23 34	1	23	alkup.		
23 ,34	1	23	alkup.		
,34	0	alkup.	alkup.		
	odottaa lisää				
34	1	34	alkup.		
kissa	0	alkup.	alkup.		

Näin voitaisiin lukea esimerkiksi mittakaava:
  printf("Anna mittakaava muodossa 1:2000 >");
  scanf("1:%lf",&mittakaava); 

tai jopa:
  printf("Anna mittakaava muodossa 1:2000 >");
  scanf("%lf:%lf",&kerroin,&mittakaava); 

Mikäli syöttö on formaattiin nähden väärää muotoa, jäävät ne muuttujat alkuperäiseen arvoonsa, jotka ovat virheellisen syöttökohdan jälkeen.  Esimerkiksi 
  scanf("1:%lf",&mittakaava); 

palauttaa 0 ja jättää mittakaava - muuttujan arvon koskemattomaksi, mikäli syöttö alkaa millä tahansa muulla kuin 1:.
Tehtävä 8.59	Mittakaavan kysyminen
Muuta matka- ohjelmaa siten, että myös mittakaava kysytään käyttäjältä.  Mikäli mittakaavaan vastataan pelkkä [RET] pitää mittakaavaksi tulla 1:200000.

8.3.2	Tiedon lukeminen C++:ssa, cin
C++:ssa lukeminen voidaan suorittaa C:n scanf:n lisäksi tietovirtoja käyttäen:
cin >> matka_mm;

Valitettavasti samanlaista syötön pakottamista kuin scanf:llä ala.c - ohjelmassa oli, ei ole helppo saavuttaa.  Käytännössä tällä ei ole merkitystä, koska "oikeassa" ohjelmassa syöttö tulee lähes aina ensin merkkijonoon, josta syöttötietoa sitten aletaan käsittelemään.  Pilkun syöttäminen lukujen väliin (jos se on oleellista) voitaisiin tutkia seuraavasti:
c-muut\ala.cpp - tiedon kysyminen kunnes pilkku väliin
#include <iostream.h>
int main(void)
{
  int pituus,leveys;
  char c;
  do {  // Ohjelma on helppo saad ikuiseen silmukkaan!
    cout << "Anna kentän pituus ja leveys metreinä >";
    cin >> pituus >> c >> leveys;
  } while ( c != ',' );
  cout << "Ala on " << pituus*leveys << " m2." << endl;
  return 0;
}

Hyvä puoli tietovirtojen käytössä on se, ettei helposti unohtuvia osoitemerkkejä (&) tarvita.  Ohjelma ei kuitenkaan toimi idioottivarmasti, mutta kuten edellä todettiin, niin lukemalla syöttörivi ensin merkkijonoon saataisiin parempi lopputulos.  Tähän tekniikkaan paneudutaan vähän myöhemmin.
8.4	Osoittimet 
8.4.1	Miksi osoittimet?
C- kielessä osoittimet piti opetella heti ohjelmoinnin alussa, jos halusi tehdä minkäänlaisia järkeviä aliohjelmia.  C++:ssa ongelmaa voidaan kiertää viitemuuttujien (references) avulla.  Mutta vaikka emme osoittimia ihan välttämättä parametrin välityksen takia enää tarvitsisikaan, opettelemme ne seuraavaksi.  Käytännössä niitä kuitenkin tarvitaan, koska maailmassa on valtavasti valmista C- koodia, jonka hyödyntämiseksi osoittimia joutuu joka tapauksessa käyttämään.  Lisäksi olio- ohjelmointi pääsee C++:ssa täysiin oikeuksiinsa vasta olio- osoittimien ja polymorfismin myötä.  Ja kuten aiemmin todettiin, myös taulukoiden läpikäynnissä osoittimet ovat käteviä.
Edellä  sanottiin, että &matka_mm on osoite muuttujaan matka_mm.  Tätä voitaisiin kuvata seuraavasti:

Kuva 8.1 Muistipaikan osoite
Reaali-  ja kokonaislukujen lisäksi voidaan määritellä myös muuttujia, jotka voivat saada osoite- arvoja.  Tällaisia muuttujia nimitetään osoittimiksi (eng. pointer, vrt. sormet ja korttien lajittelu).  Osoite- arvon saava muuttuja siis osoittaa johonkin muistipaikkaan, eli muuttujaan.  Muuttujiin voidaan sijoittaa arvoja paitsi suoraan, niin myös epäsuorasti osoittimien avulla.
8.4.2	Muuttujan arvon epäsuora muuttaminen
Tutkitaanpa vaikkapa seuraavaa ohjelmaa:
c-muut\kissaoso.cpp - epäsuora osoitus
// kissaoso.cpp
// Mitä ohjelma tulostaa?
#include <iostream.h>
int main(void)
{
  int kissoja,koiria;
  int *pElaimia;   // Osoitinmuuttuja kokonaislukuun. (p = pointer)

  pElaimia  = &kissoja;
  *pElaimia = 5;

  pElaimia  = &koiria;
  *pElaimia = 3;

  cout << "Kissoja on " << kissoja << " ja koiria " << koiria << "." << endl;

  return 0;
}

Osoitinmuuttuja määritellään laittamalla * muuttujan nimen eteen esiteltäessä muuttujaa.  Sijoituksessa   
osoitin =                // tässä tapauksessa pElaimia =

pitää oikealla puolella olla osoite- tyyppiä oleva lauseke (esim. &kissoja).
Vastaavasti muoto *osoitin ("tähdätään osoitinta pitkin") tarkoittaa sen muistipaikan sisältöä, johon muuttuja osoitin osoittaa.  Olkoon meillä muisti jakaantunut käännöksen jälkeen seuraavasti:

Kuva 8.2 Epäsuora osoitus
Osoitinmuuttujia tarvitaan erityisesti aliohjelmien ja taulukoiden yhteydessä.  Tämä esimerkki on itse asiassa varsin huono!  Nimenomaan tällaista moninimisyyttä (aliasing), eli sama muuttuja voi muuttua useata eri kautta, tulisi välttää, koska se on omiaan tekemään ohjelmista epäluotettavia ja vaikeasti ylläpidettäviä.  Järkevämmän osoite- esimerkin otamme heti kun saamme aliohjelmat kunnolla käyttöön.
Tehtävä 8.60	Sijoitus osoittimeen
Jos edellä voitaisiin sijoittaa
pElaimia = 100; 
niin mitä tapahtuisi sijoituksella
*pElaimia = 10; 
Entäpä jos olisi sijoitukset
pElaimia = 106;
*pElaimia = 104;
*pElaimia = 108; 

Varoitus! Edellisestä tehtävästä huolimatta ÄLÄ KOSKAAN mene itse keksimään arvoja, joita sijoitat osoitinmuuttujalle.  Tämä on 99.999% varma tapa saada kone kaatumaan.  Käytännössä osoittimien arvot (osoitteet) ovat edellä kuvattua esimerkkiä monimutkaisempia.
8.4.3	* ja & 
Osoittimen sisällön ottaminen
*p

on sallittu silloin, kun p on esitelty osoittimeksi johonkin muuhun tyyppiin kuin void tyyppiin.  Siis 
c-muut\ososij.c - miten osoitinta saa käyttää
/* ososij.c */
int *p,i;        ...  i = *p;  /* OK jos on ollut sijoitus p = */
double *p,d;     ...  d = *p;  /* OK jos on ollut sijoitus p = */
void *p; int i;  ...  i = *p;  /* VÄÄRIN */

Jos otetaan muuttujan osoite
&i

saadaan osoitin, joka on samaa tyyppiä kuin muuttuja.  Siis
int i,*p;        p = &i;  /* OK */
double *p,d;     p = &d;  /* OK */
void *p; int i;  p = &i;  /* OK, koska void osoittimelle saa
                                 sijoittaa minkä tahansa osoittimen */

Seuraavat ovat oikein, mutta niitä täytyy varoa:
double *p; int i; 
  p = &i;  /* VAARALLINEN */
  i = *p;  /* OK, paikassa p oleva double muuttuu int */

Jos i ja j on esitelty vaikkapa int i,j;, ei seuraavassa ole mieltä:
int i,j;
j = *i; /* VÄÄRIN, koska i ei ole osoitin! */
Tehtävä 8.61	*& 
Tästä  tehtävästä huolimatta ei alla olevan kaltaisia sotkuja tule käyttää!
Olkoon muuttujien esittelyt kuten ohjelmassa kissaoso.c.  Missä seuraavista tapauksissa tulee sijoitetuksi 5 muuttujalle kissa (*&i tarkoittaa *(&i)):

/* ososotku.c */
/* a */  *&kissoja = 5; 
/* b */  &*kissoja = 5;
/* c */  *&*&*&*&kissoja = 5;
/* d */  &*pElaimia = &kissoja; *&*pElaimia = 5;
/* e */  *&pElaimia = kissoja; *pElaimia = 5; 

8.4.4	NULL- osoitin 
Yleensä osoittimelle ei saa sijoittaa mitään vakioarvoa.  Kuitenkin eräs vakioarvo, NULL, muodostaa poikkeuksen.  Jokaisen kunnollisen ohjelman tulisi aina ennen osoittimen käyttöä tarkistaa ettei osoittimen arvo ole NULL.
NULL on varattu tarkoittamaan, ettei osoittimella ole laillista osoitteena toimivaa arvoa.  Yleensä vakio NULL on 0, mutta tähän ei saa liiaksi luottaa.  Kuitenkin taataan, että jos p on NULL- osoitin, niin ehtolause
if ( p ) ...
/* on sama kuin */
if ( p != NULL ) ... 

Erityisesti monet C- kirjaston funktioista palauttavat NULL arvoja, mikäli hommat eivät menneet niinkuin piti (vrt. malloc, fopen jne.).
8.5	Aliohjelmat (funktiot)
Eräs ohjelmoinnin tärkeimmistä rakenteista on aliohjelma. C- kielessä kaikkia eri tyyppisiä aliohjelmia nimitetään funktioiksi; joissakin muissa kielissä eri tyyppejä erotetaan eri nimille.  Aliohjelmaa käytetään seuraavissa tapauksissa:
1.	Haluttu tehtävä on valmiiksi jonkun toisen kirjoittamana aliohjelmana esimerkiksi standardikirjastossa. 
2.	Haluttua tehtävää suoritetaan usein liki samanlaisena joko samassa ohjelmassa tai jossain toisessa ohjelmassa.
3.	Haluttu tehtävä muodostaa selvän kokonaisuuden, jonka toiminta on ilmaistavissa muutamalla sanalla riittävän selkeästi (= aliohjelman nimi).
4.	Haluttua tehtävää ei juuri sillä hetkellä osata tai viitsitä ohjelmoida.  Tällöin määritellään millainen aliohjelma tarvitaan ja kirjoitetaan tarvittavaan kohtaan pelkkä aliohjelman kutsu.  Itse aliohjelma voidaan aluksi toteuttaa varsin triviaalina ja korjata myöhemmin tekemään sen varsinainen tehtävä.
5.	Rakenne saadaan selkeämmän näköiseksi.
8.5.1	Parametriton aliohjelma 
Aliohjelma esitellään vastaavasti kuin pääohjelmakin.  Esimerkiksi satunnaisen matkaajan mittakaavaohjelmassa (tässä puhdas C- kielinen versio) voisimme kirjoittaa käyttöohjeet omaksi aliohjelmakseen:
c-muut\matka_a1.c - ohjeet aliohjelmaksi
#include <stdio.h>

#define MITTAKAAVA 200000.0
#define MM_KM      (1000.0*1000.0)

void ohjeet(void)
{
  printf("Lasken 1:%3.0lf kartalta millimetreinä mitatun matkan\n",MITTAKAAVA);
  printf("kilometreinä luonnossa.\n");
}

int main(void)
{
  int    matka_mm;
  double matka_km;

  ohjeet();

  printf("Anna matka millimetreinä>");
  scanf("%d",&matka_mm);

  matka_km = matka_mm*MITTAKAAVA/MM_KM;

  printf("Matka on luonnossa %1.1lf km.\n",matka_km);

  return 0;
}

Tämän etu on siinä, että saimme pääohjelman selkeämmän näköiseksi.
8.5.2	Funktiot ja parametrit 
Voisimme jatkaa pääohjelman selkeyttämistä.  Tavoite voisi olla aluksi vaikkapa kirjoittaa pääohjelma muotoon:
  ohjeet();
  kysy_matka(&matka_mm);
  matka_km = mittakaava_muunnos(matka_mm);
  tulosta_matka(matka_km);
  return 0; 

Tällainen pääohjelma tuskin tarvitsisi paljoakaan kommentteja.
Edellä on käytetty neljän eri tyypin aliohjelmia (funktioita)
1.	ohjeet();-  parametriton aliohjelma
2.	kysy_matka(&matka_mm); -  aliohjelma joka palauttaa tuloksen parametrissaan (vrt. esim scanf).
3.	mittakaava_muunnos(matka_mm); -  funktio, joka palauttaa tuloksen nimessään
4.	tulosta_matka(matka_km); -  aliohjelma, jolle vain viedään arvo, mutta mitään arvoa ei palauteta
Valmis ohjelma, jossa myös aliohjelmat on esitelty, näyttäisi seuraavalta (rivien numerointi on myöhemmin esitettävää pöytätestiä varten):
c-muut\matka_a2.cpp - erilaisia funktioita 
// matka_a2.cpp
// Ohjelmalla lasketaan mittakaavamuunnoksia 1:200000 kartalta
// Vesa Lappalainen 4.1.1997
#include <iostream.h>

const double MITTAKAAVA = 200000.0;
const double MM_KM      = 1000.0*1000.0;

void ohjeet(void)
{
  cout << "Lasken 1:" << MITTAKAAVA 
       << " kartalta millimetreinä mitatun matkan\n";
  cout << "kilometreinä luonnossa.\n";
}

void kysy_matka(int *pMatka_mm)
{
  int mm;
  cout << "Anna matka millimetreinä>";
  cin >> mm;
  *pMatka_mm = mm;
}

double mittakaava_muunnos(int matka_mm)
{
  return matka_mm*MITTAKAAVA/MM_KM;
}

void tulosta_matka(double matka_km)
{
  cout << "Matka on luonnossa "<< matka_km << " km." << endl;
}

int main(void)
{
  int    matka_mm;
  double matka_km;

  ohjeet();
  kysy_matka(&matka_mm);
  matka_km = mittakaava_muunnos(matka_mm);
  tulosta_matka(matka_km);
  return 0;
}

Edellä olevasta huomataan, että aliohjelmat jotka eivät palauta mitään arvoa nimessään, esitellään void- tyyppisiksi.
mittakaava_muunnos on reaaliluvun palauttava funktio, joten se esitellään double - tyyppiseksi.
Seuraavaksi pöytätestaamme ohjelmamme toiminnan: 
	main	kysy_matka	mi..muunnos	tulosta		
lause	matka_mm	matka_km	pMatka_mm	mm	matka_mm	tulos	matka_km	tulostus	
40 ohjeet()	??	??							
9-13 cout<< 								Lasken 1:200000	
41 kysy_mat			&matka_mm						
16-18 int m				??					
19 cout <<								Anna matka ...	
20 cin>>mm				352					
21 *pMatka	352		<==o						
42 matka_km					352				
26 return						70.4			
42 matka_km		70.4							
43 tulosta							70.4		
29-31 cout<								Matka on luo..	
44 return 0;									

Mikäli kukin aliohjelma olisi testattu erikseen, riittäisi meille pelkkä pääohjelman testi: 
	main		
lause	matka_mm	matka_km	tulostus	
40 ohjeet()	??	??	Lasken 1:200000	
41 kysy_mat	352		Anna matka ..	
42 matka_km		70.4		
43 tulosta			Matka on luo..	
44 return 0;				

Tämä on testaustapa, johon tulisi pyrkiä.  Isossa ohjelmassa ei ole enää mitään järkeä testata sitä jokainen aliohjelma kerrallaan.  Koodiin liitettyjen aliohjelmien tulee olla testattuja kukin erillisinä ja lopullinen testi on vain viimeisimmän mallin mukainen!
Tehtävä 8.62	matka_a2.c
Kirjoita matka_a2.cpp:stä C- versio (tietovirrat => printf/scanf)

8.5.3	Parametrin nimi kutsussa ja esittelyssä 
Huomattakoon, ettei parametrien nimillä aliohjelmien esittelyissä ja kutsuissa ole mitään tekemistä keskenään.  Nimi voi olla joko sama tai eri nimi.  Parametrien idea on nimenomaan siinä, että samaa aliohjelmaa voidaan kutsua eri muuttujien tai mahdollisesti vakioiden tai lausekkeiden arvoilla.  Esimerkiksi nyt kirjoitettua tulosta_matka aliohjelmaa voitaisiin kutsua myös seuraavasti:
c-muut\matka_a3.cpp - erilaisia tapoja kutsua funktiota
// matka_a3.cpp
#include <iostream.h>

void tulosta_matka(double matka_km)
{
  cout << "Matka on luonnossa "<< matka_km << " km." << endl;
}

int main(void)
{
  double d = 50.2;
  tulosta_matka(d);         // eri niminen muuttuja
  tulosta_matka(30.7);      // vakio 
  tulosta_matka(d+20.8);    // lauseke
  tulosta_matka(2*d-30.0);  // lauseke 
  return 0;
}

Edellä aliohjelman kutsut voidaan tulkita seuraaviksi sijoituksiksi aliohjelman tulosta_matka muuttujaan matka_km:
matka_km = d;
matka_km = 30.7;
matka_km = d+20.8;
matka_km = 2*d- 30.0

Aliohjelma jouduttiin edellä vielä kirjoittamaan uudestaan (käytännössä kopioimaan edellisestä ohjelmasta), mutta myöhemmin opimme miten aliohjelmia voidaan kirjastoida standardikirjastojen tapaan (ks. moduuleihin jako), jolloin kerran kirjoitettua aliohjelmaa ei enää koskaan tarvitse kirjoittaa uudestaan (eikä kopioida).
8.5.4	Osoitteen välittäminen 
Aliohjelma  kysy_matka joutuu palauttamaan kysytyn matkan arvon kutsuvalle ohjelmalle.  Tämän takia aliohjelmalle ei viedä matkan arvoa parametrina, vaan sen paikan osoite, johon aliohjelman tulos täytyy laittaa:
kysy_matka(&matka_mm) 

Vastaavasti parametri esitellään osoitin- tyyppiseksi aliohjelman esittelyssä:
void kysy_matka(int *pMatka_mm) 

Jatkossa pitää muistaa, että tämän aliohjelman pMatka_mm on osoite sinne, minne tulos pitää laittaa.  Nimeämmekin tässä opiskelun alkuvaiheessa osoitinmuuttujat aina alkamaan pienellä p- kirjaimella (p=pointer).  Tällaista nimeämistapaa, jossa muuttujan alkukirjaimilla kuvataan muuttujan tyyppi, sanotaan unkarilaiseksi nimeämistavaksi.  Jotkut ohjelmoinnin opettajat vastustavat nimeämistapaa, mutta suuria Windows- ohjelmia lukiessa täytyy todeta että on todella mukava tietää mitä tyyppiä mikäkin muuttuja on.
Aliohjelmassa on aluksi yksinkertaisuuden vuoksi esitelty aliohjelman paikallinen muuttuja mm, johon päätteeltä saatava arvo ensin luetaan.   Jotta myös pääohjelma saisi tietää tästä, pitää suorittaa epäsuora osoitus ("tähdätä osoitinta pitkin"):
*pMatka_mm = mm;

"Tosiohjelmoija" ei tällaista apumuuttujaa kirjoittaisi, vaan lyhentäisi aliohjelman suoraan muotoon:
void kysy_matka(int *pMatka_mm)
{
  cout << "Anna matka millimetreinä>";
  cin >> *pMatka_mm;
}
Vinkki

"Tähtäämisoperaation" muistaa ehkä helpoimmin, jos ajattelee että muuttujan nimi onkin *pMatka_mm  eikä  pMatka_mm.
Lyhennetty C- versio samasta aliohjelmasta olisi:
void kysy_matka(int *pMatka_mm)
{
  printf("Anna matka millimetreinä>");
  scanf("%d",pMatka_mm);
}

Aikaisemmin olemme tottuneet kirjoittamaan scanf- funktioon 
scanf("%d",&matka_mm); 

Nyt kuitenkin pMatka_mm on valmiiksi osoite, jolloin &- merkki täytyy jättää pois kutsusta.  Toisaalta voisimme ajatella, että muuttujan nimi onkin *pMatka_mm (koska se on niin esitelty) ja tällöin scanf:n kutsu olisi
scanf("%d",&*pMatka_mm); /* ==scanf("%d",pMatka_mm); */

On siis erittäin tärkeää ymmärtää milloin on kyse osoitteesta ja milloin arvosta.
8.5.5	Viitemuuttujat (referenssimuuttujat, &)
C++ tarjoaa vielä oman uuden tavan välittää muuttuvia parametreja:  viitemuuttujat, eli referenssimuuttujat (reference).  Varsinainen mekanismi on täsmälleen sama kuin parametrin välittäminen osoitteiden avullakin.  Vain syntaksi on erilainen - aloittelijalle jopa helpompi käyttää:
Aliohjelma olisi muotoa:
c-muut\matka_ar.cpp - parametri referenssinä
void kysy_matka(int &rMatka_mm) 
{
  int mm;
  cout << "Anna matka millimetreinä>";
  cin >> mm;
  rMatka_mm = mm;
}

tai lyhyemmässä muodossa:
void kysy_matka(int &rMatka_mm)
{
  cout << "Anna matka millimetreinä>";
  cin >> rMatka_mm;
}

Tässä esittelyllä
int &rMatka_mm

esitellään referenssimuuttuja rMatka_mm, eli muuttuja joka referoi johonkin toiseen muuttujaan aina kun siihen viitataan.  Näin sijoitus 
rMatka_mm = mm;

tarkoittaakin, että tulos sijoitetaan kutsuneen ohjelman vastaavalle muuttujalle.  
Huonona (tai jonkin mielestä hyvänä) puolena viitemuuttujista voitaisiin pitää sitä, että itse kutsu pääohjelmasta täytyy nyt olla muodossa:
kysy_matka(matka_mm);                 // HUOM!

Miksi pitäisin tätä huonona?  Siksi, ettei kutsusta nyt näe suoran aikooko aliohjelma muuttaa muuttujan matka_mm arvoa vaiko ei.  
Hyvänä puolena on taas se, että aliohjelma voidaan muuttaa viitteitä käyttäväksi muuttamatta itse kutsuvaa ohjelmaa.  Tämä tulee kyseeseen sitten kun välitämme parametreinä "isoja" oliota, jolloin on edullisempaa vain viitata olioon, kuin kuljettaa mukana koko olio.
Vielä yksi huono (tai joidenkin mielestä hyvä) puoli viitemuuttujissa on se, ettei niiden viittaamaa paikkaa voida muuttaa muuta kuin alustuksessa (esim. aliohjelman kutsun yhteydessä).  Näin osoittimia tarvitaan vielä tilanteissa, joissa tietorakenteita pitää käydä läpi.
8.5.6	& - merkillä monta eri merkitystä
Vielä eräs inhottava puoli on se, että samalle merkille & (eikä tämä ole edes ainoa kuormitettu symboli) on otettu useita eri merkityksiä riippuen siitä missä yhteydessä merkki esiintyy:
1.	Osoiteoperaattori: Jos & on jo olemassa olevan muuttujan edessä, otetaan muuttujan osoite. - scanf("%d",&matka_mm);
2.	Referenssin esittely:  Jos & on muuttujan esittelyn yhteydessä, esitellään viitemuuttuja (referenssi) - int &rMatka_mm
3.	Bittitason AND- operaattori:  Jos & esiintyy kahden lausekkeen välissä, on kyseessä bittitason JA- operaattori: parillinen = luku & 0xfffe
4.	Looginen AND- operaattori:  Jos & esiintyy 2-kertaisena kahden lausekkeen välissä, on kyseessä looginen JA- operaattori: 
if ( kello < 23 && 0 < rahaa ) ...
Jos selviämme tästä &- sekamelskasta, selviämme lopuistakin ongelmista C++:n kanssa.
Yleensä AND operaatiot eivät aiheuta sekaannusta, mutta jokin muistisääntö tarvitaan siihen milloin kirjoitetaan * ja milloin &.  Olkoon se vaikka:
Vinkki

Tähdet  taivaalla
1.	Jos kyseessä on osoiteparametrin välitys tulee * ylös, koska tähdet ovat taivaalla ja niitä osoitetaan, tällöin &- merkit tulevat alas!
2.	Jos kyseessä on parametrin välitys referenssin avulla, ei tarvitse tähtäillä, joten tähtiä ei tule ja jokainen merkki siirtyy pykälän ylöspäin, eli & merkit ylös ja alhaalle ei jää mitään!
3.	Tavallisessa arvoparametrin välityksessä ei tarvita mitään ihmemerkkejä, mutta ei saada ihmeitä aikaankaan!
Kertaamme "problematiikkaa" vähän myöhemmin, kun katsomme tarkemmin mitä aliohjelmakutsu oikein tarkoittaa.
8.5.7	Nimessään arvon palauttavat funktiot 
Funktion arvo palautetaan return - lauseessa.  Jokaisessa ei- void - tyyppiseksi esitellyssä funktiossa tulee olla vähintään yksi return - lause.  void- tyyppisessäkin voi olla return- lause.  Tarvittaessa return- lauseita voi olla useampiakin:
int suurempi(int a, int b)
{
  if ( a >= b ) return a;
  return b;
}

Kun return - lause tulee vastaan, lopetetaan HETI funktion suoritus.  Tällöin myöhemmin olevilla lauseilla ei ole mitään merkitystä.  Näin ollen useat return- lauseet ovat mielekkäitä vain ehdollisissa rakenteissa.  Siis seuraavassa ei olisi mitään mieltä:
int hopo(int a) 	
{
  int i;
  return 5;  /* Palauttaa aina 5!!! */
  i = 3 + a;
  return i+2;
}

return- lausetta ei saa sotkea arvojen palauttamiseen osoitteen avulla.  Esimerkiksi:
c-muut\funjaoso.c - sivuvaikutuksellinen funktio
/* funjaoso.c */
#include <stdio.h>

int tupla_plus_yksi(int *pArvo)
{
  int vanha=*pArvo;     /* Alkuperäinen arvo talteen              */
  *pArvo = *pArvo + 1;  /* Kutsun muuttuja muuttui nyt! eli j = 4 */
  return 2*vanha;       /* Arvo palautui nimessä nyt!   eli i = 6 */
}

int main(void)
{
  int i,j=3;
  i = tupla_plus_yksi(&j);
  printf("i = %d, j = %d\n",i,j);
  return 0;
} 

Tehtävä 8.63	Funktio ja osoitin
Mitä pääohjelma funjaoso tulostaisi jos aliohjelma olisikin ollut:

int tupla_plus_yksi(int *pArvo)
{
  *pArvo = *pArvo + 1;  
  return 2**pArvo;     
}
Tehtävä 8.64	Funktio ja referenssi
Kirjoita edellisestä tehtävästä molemmista tupla_plus_yksi - versioista viitemuuttujia käyttävä versio!

8.5.8	Ketjutettu kutsu
Koska funktio- aliohjelma palauttaa valmiiksi arvon, voitaisiin matka_a2.cpp:n pääohjelma kirjoittaa myös muodossa:
  ohjeet();
  kysy_matka(&matka_mm);
  tulosta_matka(mittakaava_muunnos(matka_mm));
  return 0; 

Funktioita käytetään silloin, kun aliohjelman tehtävänä on palauttaa vain yksi täsmällinen arvo.  Tyypillisiä math.h- kirjaston funktioita on esim (suluissa olevat eivät ole standardin funktioita): 
(abs)   acos   asin   atan   atan2     atof  (cabs)  ceil 
cos     cosh   exp    fabs   floor     fmod  frexp   (hypot)
(labs)  ldexp  log    log10  (matherr) modf  (poly)   pow 
(pow10) sin    sinh   sqrt   tan       tanh 

Funktioita käytetään kuten matematiikassa on totuttu:
c = sqrt(a*a+b*b) + asin((sin(alpha)+0.2)/2.0); 

kysy_matka ja kysy_mittakaava voitaisiin kirjoittaa myös funktioiksi, ja tällöin niitä voitaisiin kutsua esim. seuraavasti:
matka_km = kysy_matka()*kysy_mittakaava()/MM_KM; 

Vaarana olisi kuitenkin se, ettei voida olla aivan varmoja kumpiko funktiosta kysy_matka vai kysy_mittakaava suoritettaisiin ensin ja tämä saattaisi aiheuttaa joissakin tilanteissa yllätyksiä.
Tämän vuoksi pyrimmekin kirjoittamaan funktioiksi vain sellaiset aliohjelmat, jotka palauttavat täsmälleen yhden arvon ja jotka eivät ota muuta informaatiota ympäristöstä kuin sen mitä niille parametrina välitetään.  Eli tavoitteena on se, että funktioiden kutsuminen lausekkeen osana olisi turvallista.
Muut aliohjelmat kirjoitamme siten, että arvot palautetaan osoitteen avulla.  Hyvin yleinen C- tapa on kuitenkin palauttaa tällaisenkin aliohjelman onnistumista kuvaava arvo funktion nimessä (vrt. esim. scanf).
Tehtävä 8.65	math.h
Katso Turbo- C:n Help- toiminnon avulla kunkin math.h- kirjaston funktion parametrien määrä ja tyyppi sekä se mitä kukin todella tekee.
Tehtävä 8.66	Funktiot
Kirjoita edellä mainitut kysy_matka ja kysy_mittakaava nimessään arvon palauttavina funktioina.
Tehtävä 8.67	Ympyrän ala ja pallon tilavuus
Kirjoita funktiot, jotka palauttavat r- säteisen ympyrän pinta- alan ja r- säteisen pallon tilavuuden.

Kirjoita pääohjelma, jossa pinta- ala ja tilavuus - funktiot testataan.
Tehtävä 8.68	Pääohjelma yhtenä funktiokutsuna
Jatka edellä mainittua ketjuttamista siten, että koko pääohjelma on vain yksi lauseke (ohjeet- kutsu saa olla oma rivinsä jos haluat).  Tosin tämä on C- hakkerismia eikä mikään tavoite helposti luettavalta ohjelmalta.  Itse asiassa hyvä kääntäjä tekee automaattisesti tämän kaltaista optimointia (mitä muka voitiin säästää?).

8.5.9	Aliohjelmien testaaminen 
Kun uusi aliohjelma kirjoitetaan, kannattaa sen testaamista varten kirjoittaa hyvin lyhyt testi- pääohjelma.  
Esimerkiksi kerhon jäsenrekisterin päämenun tulostamista varten voisimme kirjoittaa aliohjelman nimeltä paamenu.  Tämä päämenu voitaisiin sitten testata vaikkapa seuraavalla testipääohjelmalla:  
c-muut\menutest.cpp - päämenun testaus
// menutest.cpp 
#include "paamenu.cpp" // HUOM! "Oikeasti" aliohjemia ei INCLUDEta 
                       // vaan paamenu.h ja tehdään projekti tai MAKEFILE! 
int main(void)
{
  paamenu(10);
  return 0;
}

Tiedostot paamenu.h ja paamenu.cpp, joissa aliohjelman prototyyppi ja itse päämenu esiteltäisiin, voisivat olla esimerkiksi:
c-muut\paamenu.h - päämenun otsikkotiedosto
/* paamenu.h */
#ifndef PAAMENU_H
#define PAAMENU_H
void paamenu(int jasenia);
#endif /* PAAMENU_H */

c-muut\paamenu.cpp - päämenun totetutus 
// paamenu.cpp 
#include <iostream.h>
#include "paamenu.h"

void paamenu(int jasenia)
{
  cout << "\n\n\n\n\n";
  cout << "Jäsenrekisteri\n";
  cout << "==============\n";
  cout << "\n";
  cout << "Kerhossa on " << jasenia << " jäsentä.\n";
  cout << "\n";
  cout << "Valitse:\n";
  cout << "   ?  = avustus\n";
  cout << "   0  = lopetus\n";
  cout << "   1  = lisää uusi jäsen\n";
  cout << "   2  = etsi jäsenen tiedot\n";
  cout << "   3  = tulosteet\n";
  cout << "   4  = tietojen korjailu\n";
  cout << "   5  = päivitä jäsenmaksuja" << endl;
  cout << "   :";
}

Huomattakoon, että aliohjelma voitaisiin kirjoittaa myös seuraavasti (miksi?):
c-muut\paamenu2.cpp - toteutus vähillä cout-kutsuilla
// paamenu2.cpp 
#include <iostream.h>
#include "paamenu.h"

void paamenu(int jasenia)
{
  cout << "\n\n\n\n\n"
  "Jäsenrekisteri\n"
  "==============\n"
  "\n"
  "Kerhossa on " << jasenia << " jäsentä.\n"
  "\n"
  "Valitse:\n"
  "   ?  = avustus\n"
  "   0  = lopetus\n"
  "   1  = lisää uusi jäsen\n"
  "   2  = etsi jäsenen tiedot\n"
  "   3  = tulosteet\n"
  "   4  = tietojen korjailu\n"
  "   5  = päivitä jäsenmaksuja" << endl <<
  "   :";
}

Voidaan kirjoittaa jopa (miksi):
c-muut\paamenu3.cpp - rivin jatkaminen 
#include <iostream.h>
#include "paamenu.h"

void paamenu(int jasenia)
{
cout << "\n\n\n\n\n\
Jäsenrekisteri\n\
==============\n\
\n\
Kerhossa on " << jasenia << " jäsentä.\n\
\n\
Valitse:\n\
   ?  = avustus\n\
   0  = lopetus\n\
   1  = lisää uusi jäsen\n\
   2  = etsi jäsenen tiedot\n\
   3  = tulosteet\n\
   4  = tietojen korjailu\n\
   5  = päivitä jäsenmaksuja" << endl << "\
   :";
}

Jatkossa kommentoimme aliohjelmia enemmän, mutta nyt olemme jättäneet kommentit pois, jotta ohjelma olisi mahdollisimman lyhyt.  
Huomattakoon, että aliohjelma on saatu kopioiduksi suoraan aikaisemmasta ohjelman suunnitelmasta lisäämällä vain kunkin rivin alkuun cout <<"ja loppuun \n";.  Tällaiset toimenpiteet voidaan automatisoida tekstinkäsittelyn avulla.
8.6	Lisää aliohjelmista
8.6.1	Useita parametreja 
Kaikissa edellisissä esimerkeissämme meillä on ollut vain 0 tai yksi parametria välitettävänä aliohjelmaan.  Käytännössä usein tarvitsemme useampia parametreja.  Esimerkiksi edellisessä paamenu- aliohjelmassa pitäisi oikeastaan tulostaa myös kerhon nimi.  Emme vielä kuitenkaan osaa käsitellä merkkijonoja, joten palaamme tähän ongelmaan myöhemmin.
Ottakaamme esimerkiksi mittakaava_muunnos - funktio.  Mikäli ohjelma haluttaisiin muuttaa siten, että myös mittakaavaa olisi mahdollista muuttaa, pitäisi myös mittakaava voida välittää muunnos- aliohjelmalle parametrina.  Kutsussa tämä voisi näyttää esim. tältä:
  matka_km = mittakaava_muunnos(10000.0,32); 

Vastaavasti funktio- esittelyssä täytyisi olla kaksi parametria:
double mittakaava_muunnos(double mittakaava,int matka_mm)
{
  return matka_mm*mittakaava/MM_KM;
}

Kun kutsu suoritetaan, välitetään aliohjelmalle parametrit siinä järjestyksessä, missä ne on esitelty.  Voitaisiin siis kuvitella aliohjelmakutsun aiheuttavan sijoitukset aliohjelman parametrimuuttujiin:
mittakaava = 10000.0;
matka_mm   = 32; 

Jos kutsu on muotoa
  matka_km = mittakaava_muunnos(MITTAKAAVA,matka_mm); 

kuvitellaan sijoitukset
mittakaava = MITTAKAAVA; /* Ohjelman vakio                */
matka_mm   = matka_mm;   /* Pääohjelman muuttuja matka_mm */

Siis vaikka kutsussa ja esittelyssä esiintyykin sama nimi, ei nimien samuudella ole muuta tekemistä kuin mahdollisesti se, että turha on väkisinkään keksiä lyhennettyjä huonoja nimiä, jos kerran on hyvä nimi keksitty kuvaamaan jotakin asiaa.
Parametreista osa, ei yhtään tai kaikki voivat olla myös osoitteita tai referenssejä.
Huom! Vaikka kaikilla aliohjelman parametreilla olisikin sama tyyppi, täytyy jokaisen parametrin tyyppi mainita silti erikseen:
double nelion_ala(double korkeus, double leveys) 

Tehtävä 8.69	Toisen asteen yhtälön juuri
Kirjoita funktio root_1(a,b,c), joka palauttaa jomman kumman toisen asteen yhtälön ax2+bx+c=0 juurista (oletetaan tällä kertaa, että a<>0 ja D = b2- 4ac >= 0.  Miksi oletetaan?).
Tehtävä 8.70	Toisen asteen polynomi, root_1
Kirjoita funktio root_1 joka palauttaa toisen asteen polynomin P(x) = ax2+bx+c arvon (muista viedä parametrina myös a,b ja c).
Tehtävä 8.71	root_1 testaus
Kirjoita pääohjelma, jolla voidaan testata root_1 -  aliohjelma (jotenkin myös se, että tulos toteuttaa yhtälön).

8.6.2	Muuttujien lokaalisuus 
Kukin aliohjelma muodostaa oman kokonaisuutensa.  Edellä olleissa esimerkeissä aliohjelmat eivät tiedä ulkomaailmasta mitään muuta, kuin sen, mitä niille tuodaan parametreina kutsun yhteydessä.  
Vastaavasti ulkomaailma ei tiedä mitään aliohjelman omista muuttujista.  Näitä aliohjelman lokaaleja muuttujia on esim. seuraavassa:
void kysy_matka(int *pMatka_mm)
{
  int mm;
  printf("Anna matka millimetreinä>");
  scanf("%d",&mm);
  *pMatka_mm = mm;
}

pMatka_mm   -  aliohjelman parametrimuuttuja (tässä tapauksessa osoitinmuuttuja).
mm          -  aliohjelman lokaali apumuuttuja matkan lukemiseksi.

Yleensäkin C- kielessä lausesulut { ja } muodostavat lohkon, jonka ulkopuolelle mikään lohkon sisällä määritelty muuttuja tai tyyppimääritys ei näy.  Näkyvyysalueesta käytetään englanninkielisessä kirjallisuudessa nimitystä scope.  Lokaaleilla muuttujilla voi olla vastaava nimi, joka on jo aiemmin esiintynyt jossakin toisessa yhteydessä.  Lohkon sisällä käytetään sitä määrittelyä, joka esiintyy lohkossa: 
c-muut\lokaali.c - lokaalien muuttujien näkyvyys
#include <stdio.h>             	
int main(void)
{
   char ch='A';
   printf("Kirjain %c",ch);
   {
     int ch = 5;
     printf(" kokonaisluku %d",ch);
     {
       double ch = 4.5;
       printf(" reaaliluku %5.2lf\n",ch);
     }
   }
   return 0;
}

Saman tunnuksen käyttäminen eri tarkoituksissa on kuitenkin kaikkea muuta kuin hyvää ohjelmointia.
Tehtävä 8.72	Eri nimet
Korjaa edellinen ohjelma siten, että kullakin erityyppisellä muuttujalla on eri nimi.
8.6.3	auto ja register 
Varattu sana auto tarkoittaa, että muuttujasta tehdään automaattinen muuttuja.  Oletuksena jokainen lokaali muuttuja, joka ei ole esitelty static - määrityksellä, on automaattinen muuttuja.  Tämän vuoksi auto- sanaa harvoin käytetään.  Muuttujan automaattisuus tarkoittaa sitä, että kun muuttujan määrittelylohkosta poistutaan, tuhotaan muuttuja samalla.  
{
  int mm;        /* sama kirjoitetaanko näin */
  auto int mm;   /* vai näin                 */

Joskus kääntäjän työn helpottamiseksi voidaan kääntäjälle ehdottaa jonkin lokaalin muuttujan sijoittamista prosessorin (CPU) rekisteriin ja näin saadaan muuttujan käyttö nopeammaksi.  Tämä tehdään varatulla sanalla register.
c-muut\register.c -  muuttujat prosessorin rekistereihin
#include <stdio.h>
int main(void)
{
  register int i;
  for (i=0; i<4; i++) printf("i=%d\n",i);
  /* Seuraava ei toimi koska rekisteristä ei saada osoitetta */
/*  printf("i:n osoite on %p\n",&i); */
  return 0;
}

8.6.4	Parametrinvälitysmekanismi
Ainoa C- kielen tuntema parametrinvälitysmekanismi on parametrien välittäminen arvoina.  Tämä tarkoittaa sitä, että aliohjelma saa käyttöönsä vain (luku)arvoja, ei muuta.  Olkoon meillä esimerkiksi ongelmana tehdä aliohjelma, jolle viedään parametreina tunnit ja minuutit sekä niihin lisättävä minuuttimäärä.  Jos ensimmäinen yritys olisi seuraava:
c-muut\aikalisa.cpp - yritys lisätä arvoja
#include <iostream.h>
void lisaa(int h, int m, int lisa_min) 	
{
  int yht_min = h*60 + m + lisa_min;
  h = yht_min / 60;
  m = yht_min % 60;
}

void tulosta(int h, int m)
{
  cout << h << ":" << m << endl;
}

int main(void)
{
  int h=12,m=15;
  lisaa(h,m,55);
  tulosta(h,m);
  return 0;
}

Tämä ei tietenkään toimisi!  Hyvä kääntäjä jopa varoittaisi että:
Warn :  aikalisa.cpp(8,2):'m' is assigned a value that is never used
Warn :  aikalisa.cpp(7,2):'h' is assigned a value that is never used

Mutta miksi ohjelma ei toimisi?  Seuraavan selityksen voi ehkä ohittaa ensimmäisellä lukukerralla.  Tutkitaanpa tarkemmin mitä aliohjelmakutsussa oikein tapahtuu.  Oikaisemme seuraavassa hieman joissakin kohdissa liian tekniikan kiertämiseksi, mutta emme kovin paljoa.  Katsotaanpa ensin miten kääntäjä kääntäisi aliohjelmakutsun (Borland C++ 5.1, 32-bittinen käännös, rekisterimuuttujat kielletty jottei optimointi tekisi konekielisestä ohjelmasta liian monimutkaista):
lisaa(h,m,55);

muistiosoite assembler         selitys
-------------------------------------------------------------------------
004010F9     push 0x37           pinoon 55
004010FB     push [ebp-0x08]     pinoon m:n arvo 
004010FE     push [ebp-0x04]     pinoon h:n arvo
00401101     call lisaa          mennään aliohjelmaan lisää
00401106     add esp,0x0c        poistetaan pinosta 12 tavua (3 x int)

Kun saavutaan aliohjelmaan lisaa, on pino siis seuraavan näköinen:
muistiosoite  sisältö          selitys
------------------------------------------------------------------------
064FDEC       00401106 <-ESP   paluuosoite kun aliohjelma on suoritettu
064FDF0       0000000C         h:n arvo, eli 12
064FDF4       0000000F         m:n arvo, eli 15
064FDF8       00000037         lisa_min, eli 55

Eli aliohjelmaan saavuttaessa aliohjelmalla on käytössään vain arvot 12,15 ja 55.  Näitä se käyttää tässä järjestyksessä omien parametriensa arvoina, eli m,h,lisa_min.
Muutetaanpa ohjelmaan parametrin välitys osoitteiden avulla:
c-muut\aikalis2.cpp - parametrin välitys osoittimilla
#include <iostream.h>

void lisaa(int *ph, int *pm, int lisa_min)
{
  int yht_min = *ph * 60 + *pm + lisa_min;
  *ph = yht_min / 60;
  *pm = yht_min % 60;
}

void tulosta(int h, int m)
{
  cout << h << ":" << m << endl;
}

int main(void)
{
  int h=12,m=15;
  lisaa(&h,&m,55);
  tulosta(h,m);
  return 0;
}

Nyt ohjelma toimii ja tulostaa 13:10 kuten pitääkin.  Eikä kääntäjäkään anna varoituksia.  Mitä nyt tapahtuu ohjelman sisällä?  Aliohjelmakutsusta seuraa:
lisaa(&h,&m,55);

muistiosoite assembler         selitys
-------------------------------------------------------------------------
0040111D     push 0x37           pinoon 55
0040111F     lea eax,[ebp-0x08]  m:osoite rekisteriin eax
00401122     push eax            ja tämä pinoon (0064FDFC) 
00401123     lea edx,[ebp-0x04]  h:n osoite rekisteriin edx
00401126     push edx            ja tämä pinoon (0064FE00)
00401127     call lisaa          mennään aliohjelmaan lisää
0040112C     add esp,0x0c        poistetaan pinosta 12 tavua (2 x int)

Pino on aliohjelmaan saavuttaessa seuraavan näköinen
muistiosoite assembler         selitys
------------------------------------------------------------------------
0064FDEC      0040112C <-ESP   paluuosoite kun aliohjelma on suoritettu
0064FDF0      0064FE00         h:n osoite,  eli ph:n arvo
0064FDF4      0064FDFC         m:n osoite,  eli pm:n arvo
0064FDF8      00000037         lisa_min, eli 55

Aliohjelman alussa olevien lauseiden 
muistiosoite assembler         selitys
-------------------------------------------------------------------------
0040107C     push ebp            pinoon talteen rekisterin ebp arvo
0040107D     mov  ebp,esp        ebp:hn pinon pinnan osoite
0040107F     push ecx            pinoon tilaa yhdelle kokonaisluvulle (min)

suorittamisen jälkeen pino näyttää seuraavalta
muistiosoite  sisältö          selitys
------------------------------------------------------------------------
0064FDE4  -04 00000000 <-ESP   aliohjelman oma tila, eli min-muuttuja
0064FDE8  +00 0064FE04 <-EBP   vanha EBP:n arvo, johon EBP nyt osoittaa
0064FDEC  +04 0040112C         paluuosoite kun aliohjelma on suoritettu
0064FDF0  +08 0064FE00         h:n osoite,  eli ph:n arvo
0064FDF4  +0C 0064FDFC         m:n osoite,  eli pm:n arvo
0064FDF8  +10 00000037         lisa_min, eli 55

Esimerkiksi minuuteille sijoitus kääntyisi seuraavasti:
*pm = yht_min % 60;

muistiosoite assembler         selitys
-------------------------------------------------------------------------
004010A1     mov  eax,[ebp-0x04] eax:ään yht_min muuttujan arvo        
004010A4     mov  ecx,0x0000003c ecx:ään 60 jakajaksi          
004010A9     cdq                 konvertoidaan eax 64 bitiksi edx:eax      
004010AA     idiv ecx            jaetaan edx:eax/ecx:llä tulos eax, jakoj. edx
004010AC     mov  eax,[ebp+0x0c] eax:ään m:n osoite (eli pm:n arvo)
004010AF     mov  [eax],edx      sinne muistipaikkaan, jonne eax asoittaa,
                                 eli 0064FDFC, eli pääohjelman m:ään kopioidaan
                                 edx, eli 10.  HUOM!  Pääohjelman m:n arvo
                                 muuttui juuri tällä hetkellä!
}                                aliohjelmasta poistuminen
004010B1     pop  ecx            pinosta pois aliohjelman omat muuttujat
004010B2     pop  ebp            alkuperäinen ebp:n arvo talteen
004010B3     ret                 ja paluu osoitteeseen joko on pinon päällä nyt
                                 eli 0040112C, eli pääohjelmaan call-lauseen
                                 jälkeen

HUOM!  Kääntäjä ei ole optimoivillakaan asetuksilla huomannut, että sekä h:n että m:n sijoitus saataisiin samasta jakolaskusta, koska toisessa tarvitaan kokonaisosaa ja toisessa jakojäännöstä, jotka molemmat saadaan samalla kertaa idiv operaatiossa.  Huonoa kääntäjän kannalta, mutta ilahduttavaa että hyvälle assembler- ohjelmoijallekin jää vielä käyttöä.
Takaisin asiaan.  Nyt siis aliohjelmalla oli pelkkien 12,15,55 arvojen sijasta käytössä osoitteet pääohjelman h:hon ja m:ään sekä arvo 55.  Näin aliohjelma pystyi muuttamaan kutsuneen ohjelman muuttujien arvoja.  Sama kuvana ennen *pm- sijoitusta:

Kuva 8.3  Sijoitus aliohjelmasta kutsuvan ohjelman muuttujaan
Tehtävä 8.73	aikalis3.cpp
Kirjoita aikalis2.cpp:stä viitemuuttujia käyttävä versio.  Sisäisesti ohjelma kääntyy täsmälleen samanlaiseksi kuin osoittimilla, eli osoittimet ja viitemuuttujat ovat sisäisesti todellakin sama asia.
Tehtävä 8.74	Muotoilu?
Kokeilepa lisätä aikaan esimerkiksi 50 min.  Mitä tulostuu?  Miten vian voisi korjata?
Tehtävä 8.75	Tiedon lukeminen
Kirjoita aliohjelma lue, joka kysyy ja lukee arvon kellonajalle, syöttö muodossa 12:15.

8.6.5	Aliohjelmien kirjoittaminen
Aliohjelmien kirjoittaminen kannattaa aina aloittaa aliohjelmakutsun kirjoittamisesta.  Näin voidaan suunnitella mitä parametreja ja missä järjestyksessä aliohjelmalle viedään.  Näinhän teimme mittakaava- ohjelmassakin.
8.6.6	Globaalit muuttujat 
 Muuttujat voidaan esitellä myös globaaleiksi.  Mikäli muuttujat esitellään kaikkien ohjelman lausesulkujen ulkopuolella, näkyvät muuttujat kaikille niille lohkoille, jotka on esitelty muuttujan esittelyn jälkeen.  Seuraava ohjelma on kaikkea muuta kuin hyvän ohjelmointitavan mukainen, mutta pöytätestaamme sen siitä huolimatta:
c-muut\alisotku.c - parametrin välitystä
/* 01 */ /* alisotku.c */
/* 02 */ /* Mitä ohjelma tulostaa?? */
/* 03 */ #include <stdio.h>
/* 04 */
/* 05 */ int a,b,c;
/* 06 */
/* 07 */ void ali_1(int *a, int b)
/* 08 */ {
/* 09 */   int d;
/* 10 */   d  = *a;
/* 11 */   c  = b + 3;
/* 12 */   b  = d -  1;
/* 13 */   *a = c -  5;
/* 14 */ }
/* 15 */
/* 16 */ void ali_2(int *a, int *b)
/* 17 */ {
/* 18 */   int c;
/* 19 */   c  = *a + *b;
/* 20 */   *a = 9 -  c;
/* 21 */   *b = 32;
/* 22 */ }
/* 23 */
/* 24 */ int main(void)
/* 25 */ {
/* 26 */   int d;
/* 27 */   a=1; b=2; c=3; d=4;
/* 28 */   ali_1(&d,c);
/* 29 */   ali_2(&b,&a);
/* 30 */   ali_1(&c,3+d);
/* 31 */   printf("%d %d %d %d\n",a,b,c,d);
/* 32 */   return 0;
/* 33 */ }

Seuraavassa g.c täytyy tulkita: globaali muuttuja c ja m.d: main- funktion muuttuja d. 
	globaalit 	main	ali_1	ali_2	laskuja	
           	   	   	   	    	*   	   	   	*   	*   	   		
   lause  	 a 	 b 	 c 	  d 	  a 	 b 	 d 	  a 	  b 	 c 		
27 a=1; b=2	 1 	 2 	 3 	  4 	    	   	   	    	    	   		
28 ali_1(&d	   	   	   	    	&m.d	 3 	 ? 	    	    	   	ali_1(&m.d,3)	
10 d = *a; 	   	   	   	    	    	   	 4 	    	    	   	= m.d = 4	
11 c = b+3;	   	   	 6 	    	    	   	   	    	    	   	= 3+3 = 6	
12 b = d-1;	   	   	   	    	    	 3 	   	    	    	   	= 4-1 = 3	
13 *a= c-5;	   	   	   	  1 	 <-o	   	   	    	    	   	m.d = g.c-5 = 6-5 = 1	
29 ali_2(&b	   	   	   	    	    	   	   	&g.b	&g.a	 ? 	ali_2(&g.b,&g.a)	
19 c =*a+*b	   	   	   	    	    	   	   	    	    	 3 	= g.b+g.a = 2+1 = 3	
20 *a= 9-c;	   	 6 	   	    	    	   	   	<-o	    	   	g.b = 9-3 = 6 	
21 *b= 32; 	32 	   	   	    	    	   	   	    	<-o	   	g.a = 32	
30 ali_1(&c	   	   	   	    	&g.c	 4 	 ? 	    	    	   	ali_1(&g.c,3+1)	
10 d = *a; 	   	   	   	    	    	   	 6 	    	    	   	= g.c = 6	
11 c = b+3;	   	   	 7 	    	    	   	   	    	    	   	= 4+3 = 7	
12 b = d-1;	   	   	   	    	    	 5 	   	    	    	   	= 6-1 = 5	
13 *a= c-5;	   	   	 2 	    	 <-o	   	   	    	    	   	g.c = c-5 = 7-5 = 2	
31 printf("	   	   	   	    	    	   	   	    	    	   	Tulostus: 32 6 2 1	
32 return 0	   	   	   	    	    	   	   	    	    	   	          ========= 	

Globaaleiden muuttujien käyttöä tulee ohjelmoinnissa välttää.  Tuskin mistään on tullut yhtä paljon ohjelmointivirheitä, kuin vahingossa muutetuista globaaleista muuttujista!
Tehtävä 8.76	Muuttujien näkyvyys
Pöytätestaa seuraava ohjelma:

c-muut\alisotk2.cpp - parametrin välitystä
/* 01 */ /* alisotk2.c */ /* Mitä ohjelma tulostaa?? */
/* 02 */ #include <stdio.h>
/* 03 */
/* 04 */ int b,c;
/* 05 */
/* 06 */ void s_1(int *a, int b)
/* 07 */ {
/* 08 */   int d;
/* 09 */   d  = *a;
/* 10 */   c  = b + 3;
/* 11 */   b  = d -  1;
/* 12 */   *a = c -  5;
/* 13 */ }
/* 14 */
/* 15 */ void a_2(int *a, int &b)
/* 16 */ {
/* 17 */   c  = *a + b;
/* 18 */   { int c; c = b;
/* 19 */   *a = 8 * c; }
/* 20 */   b = 175;
/* 21 */ }
/* 22 */
/* 23 */ int main(void)
/* 24 */ {
/* 25 */   int a,d;
/* 26 */   a=4; b=3; c=2; d=1;
/* 27 */   s_1(&b,c);
/* 28 */   a_2(&d,a);
/* 29 */   s_1(&d,3+d);
/* 30 */   printf("%d %d %d %d\n",a,b,c,d);
/* 31 */   return 0;
/* 31 */ }

9.	Kohti olio- ohjelmointia
Mitä tässä luvussa käsitellään?
•	tietueet
•	yksinkertaiset luokat
•	olioiden perusteet
•	olioterminologia
•	koostaminen
•	perintä
•	polymorfismi

Syntaksi:
luokan esittely:  class cNimi : public cIsa, public cIsa { // 0-n x public cIsa 
                  private:                              // päällä oletuksena
                     yksityiset_attribuutit             // vain itse näkee
                     yksityiset_metodit
                  protected: 
                     suojatut_attribuutit               // perilliset näkee
                     suojatut_metodit
                  public:
                     julkiset_attribuutit               // kaikki näkee
                     julkiset_metodit
                  };                                    // HUOM! puolipiste
attr              kuten muuttuja
attrib.esitt.     tyyppi attr;
metodin esitt.    kuten aliohjelman esittely
metodin esitt. 
luokan ulkop.     tyyppi cNimi::metodin_nimi(param_lista)
viit.olion metod: olio.metodin_nimi(param,param)        // 0-n x param
jos osoitin:      pOlio->metodin_nimi(param,param)
yliluokan 
metodiin viit.    yliluokka::metodin_nimi(param,param)
muodostaja        cNimi(param_lista)       // voi olla monta eri param_listoilla
hajoittaja        ~cNimi

Tähän lukuun on kasattu suuri osa olioihin liittyvää asiaa yhden esimerkin valossa.  Esimerkin yksinkertaisuuden takia se ei anna joka tilanteessa täyttä hyötyä esitetyistä ominaisuuksista.  Lisäksi asiaa voi olla yhdelle lukukerralla liikaa ja esimerkiksi perintä ja polymorfismi kannattaa ehkä jättää myöhemmälle lukukerralle.
9.1	Miksi olioita tarvitaan
Emme tässä ryhdy pohtimaan kovin syvällisiä siitä, miten olio- ohjelmointiin on päädytty.  Todettakoon kuitenkin että olio- ohjelmointi on hyvin luonnollinen jatke rakenteelliselle ohjelmoinnille heti, kun huomataan siirtää käsiteltävä data ja dataa käsittelevä koodi samaan ohjelman osaan.  Tämä toimenpide voidaan tehdä tietenkin myös perinteisillä ohjelmointikielilläkin.  Puhtaat oliokielet eivät vaan jätä edes muuta mahdollisuutta.  Lähestymme asiaa evoluutiomaisesti -  niinkuin kehitys on olioihin johtanut.  Loput ylilaulut olioista kannattaa lukea jostakin hyvästä kirjasta.
Aloitetaanpa tutkimalla aikalisa esimerkkiämme.  Pääohjelmassa esiteltiin muuttuja tunteja varten ja muuttuja minuutteja varten.  Aluksi tämä saattaa tuntua hyvin luonnolliselta ja niin se onkin, niin kauan kuin ohjelman koko pysyy pienenä.  Entäpä ohjelma jossa tarvitaan paljon kellonaikoja?
olioalk\aikalis4.cpp - useita aika "muuttujia"
... alku kuten aikalis3.cpp
int main(void) 	?
{
  int h1=12,m1=15;
  int h2=13,m2=16;
  int h3=14,m3=25;
  lisaa(h1,m1,55);
  tulosta(h1,m1);
  lisaa(h2,m2,27);
  tulosta(h2,m2);
  lisaa(h3,m3,39);
  tulosta(h3,m3);
  return 0;
}

Hyvinhän tuo vielä toimii?  Ja jos otettaisiin taulukot käyttöön, ei tarvitsisi edes numeroida muuttujia.  Entäpä jos joku tulee ja sanoo, että sekunnitkin mukaan!  Tulee paljon työtä jos on paljon aikoja.
Tehtävä 9.77	Tulostus
Mitä ohjelma aikalis4.cpp tulostaa?
9.2	Tietueet, välivaihe kohti olioita
Jo pitkään ovat ohjelmointikielet tarjonneet erään apuvälineen tietojen yhdistämiseen:  tietueet.  Kasataan asiaan liittyvät muuttujat yhteen "joukkoon" ja annetaan tälle "joukolle" uusi nimi, meidän esimerkissämme vaikkapa unkarilaisella nimeämisellä tAika -  aika tyyppi:
olioalk\aikalist.cpp - yhteiset asiat samaan tietueeseen
#include <iostream.h>
#include <iomanip.h>

struct tAika {
  int h,m;
};

void lisaa(tAika &rAika, int lisa_min)
{
  int yht_min = rAika.h * 60 + rAika.m + lisa_min;
  rAika.h = yht_min / 60;
  rAika.m = yht_min % 60;
}

void tulosta(tAika Aika)
{ // Muotoilu jotta 15:04 tulostuisi oikein eikä 15:4
  cout << setfill('0') << setw(2) << Aika.h << ":" << setw(2) << Aika.m << endl;
}

int main(void)
{
  tAika a1={12,15}, a2={13,16}, a3={14,25};
  lisaa(a1,55);   tulosta(a1);
  lisaa(a2,27);   tulosta(a2);
  lisaa(a3,39);   tulosta(a3);
  return 0;
}

9.2.1	Tietuemuuttujan esittely
Eli muutos ohjelmassa on varsin syntaktinen. Siinä missä ennen esittelimme 2 muuttuja, h1 ja m1, esitellään nyt vain yksi muuttaja a1 joka onkin aika- tyyppiä. 
tAika a1;

9.2.2	Tietueen alustaminen esittelyn yhteydessä
Jos tietuetyyppinen muuttuja halutaan alustaa esittelyn yhteydessä, voidaan se tehdä (ja vain esittelyn yhteydessä):
tAika a1={12,15};

9.2.3	Uuden tietuetyypin määrittely
Uusi tietuetyyppinen tyyppi määritellään
struct tAika {  // struct ja tyypin nimi
  int h,m;      // alkioiden tyypit ja nimet
};

// voi olla myös
struct tAika {  
  int h;
  int m;
};

Huom!  C- kielessä  määrittely täytyy tehdä typedef - lauseella:

typedef struct { 
  int h,m; 
} tAika;

Muuten kaikki tässä sanottu pätee myös C- kieleen.  C++:ssakin voitaisiin tietue määritellä em. tavalla, yhteensopivuussyistä ehkä jopa kannattaisi?
9.2.4	Viittaus tietueen alkioon 
Jos ohjelmassa tarvitsee viitata yksittäiseen tietueen alkioon, voidaan tämä tehdä ilmoittamalla tietuen nimi, piste ja tietueen alkio:
minuutit = a1.m;

9.2.5	Viittaus osoitteen avulla
Mikäli lisaa- aliohjelma olisi kirjoitettu C:mäisesti, pitäisi muistaa "tähdätä"  osoitinta pitkin:
olioalk\aikalisp.cpp -  osoittimen avulla viittaaminen tietueeseen
void lisaa(tAika *pAika, int lisa_min)
{
  int yht_min = (*pAika).h * 60 + (*pAika).m + lisa_min;
  (*pAika).h = yht_min / 60;
  (*pAika).m = yht_min % 60;
}
...
  lisaa(&a1,55);

9.2.6	Lyhennetty viittaus osoitteen avulla (->)
Muoto (*pAika).m on aika pitkähkö kirjoittaa.  Siksi sille onkin synonyymi pAika‑>m, joka on lisäksi varsin havainnollinen; tähdätään osoitinta pitkin tietueen tiettyyn alkioon :
void lisaa(tAika *pAika, int lisa_min)
{
  int yht_min = pAika->h * 60 + pAika->m + lisa_min;
  pAika->h = yht_min / 60;
  pAika->m = yht_min % 60;
}

Muista että tämä osoitemerkintä toimii VAIN silloin kun kyseessä on osoitin muuttuja.  Tavallisen tietuemuuttujan tapauksessa viitataan alkioon pisteellä.
9.2.7	Tietueen muuttaminen?
Joko nyt on helpompi lisätä sekunnit, niin ettei esimerkiksi pääohjelmassa tarvitse tehdä muutoksia?  Osittain, sillä jos tietueen viimeiseksi lisättäisiin int s, niin alustus
tAika a1={12,15};

jättäisi sekunnit alkuarvoonsa, joka tässä tapauksessa on valitettavasti tuntematon!  Muuten riittää muuttaa pelkkiä aliohjelmia!
Suurin hyöty tietueesta olikin lähinnä kasata yhteen kuuluvat asiat saman nimikkeen alle.  Tämä on kapselointia (encapsulation) väljässä mielessä.
Tehtävä 9.78	Päivämäärätyyppi
Esittele tietue, jolla kuvataan päivämäärä.  Kirjoita aliohjelma tulosta, joka tulostaa päivämäärän.
9.2.8	Funktioiden kuormittaminen (lisämäärittely, overloading)
Edellisessä tehtävässä pyydettiin kirjoittamaan aliohjelma tulosta, joka tulostaa päivämäärätyyppisen muuttujan arvon.  Onko tämä järkevää, koska meillä jo oli aliohjelma tulosta, joka tulostaa kellonajan?  Eräs C++:n uusia ominaisuuksia on mahdollisuus kuormittaa, eli määritellä lisää merkityksiä (eng. overloading) funktion nimelle.  Varsinainen kutsuttava funktio tunnistetaan nimen ja parametrilistassa olevien lausekkeiden avulla.  Funktion nimi koostuukin tavallaan nimen ja parametrilistan yhdisteestä.  Siten jos on esitelty
tAika aika={12,30};
tPvm pvm={14,1,1997};
tulosta(aika);
tulosta(pvm);

niin kumpikin tulosta- kutsu kutsuu eri aliohjelmaa.  Funktioiden kuormitus onkin varsin mukava lisä ohjelmointiin, se ei kuitenkaan ole varsinaisia olio- ohjelmoinnin piirteitä.
Tehtävä 9.79	Mitäs me tehtiin kun ei ollut kuormitusta?
Miten asiat on hoidettava C- kielessä, kun siellä funktioiden nimien kuormitus ei ole mahdollista, vaan kunkin funktion nimen tulee olla yksikäsitteinen.

9.3	Hynttyyt yhteen, eli muututaan olioksi
Itse asiassa vanhalla C- kielelläkin pystyi kirjoittamaan "olioita", kirjoittamalla tietuetyypin esittely ja sitä käyttävät aliohjelmat yhdeksi aliohjelmakirjastoksi.  Näin data ja sitä käsittelevät aliohjelmat on kapseloitu yhdeksi paketiksi.
9.3.1	Terminologiaa
Asia voidaan viedä vielä hieman pitemmälle.  Kasataankin käsittelevät aliohjelmat suoraan tietueen sisälle.  Nyt astuu kuvan mukaan olio- ohjelmoijat ja he nimittävät sitten näin syntyneitä aliohjelmia metodeiksi (method), tai C++- kirjallisuudessa jäsenfunktioiksi (member function).  Tietueen alkioita, kenttiä nimitetään sitten attribuuteiksi.
Itse tietue saakin nimen luokka (class) ja tietuetta vastaava muuttuja - luokan ilmentymä - on sitten se kuuluisa olio (object)
9.3.2	Ensimmäinen olio- esimerkki
Muutetaanpa aikalisa luokaksi ja olioksi:
olioalk\aikaolio.cpp -  tietueesta olioksi
#include <iostream.h>
#include <iomanip.h>

struct cAika {
  int h,m;
  void lisaa(int lisa_min)  {
    int yht_min = h * 60 + m + lisa_min;
    h = yht_min / 60;
    m = yht_min % 60;
  }
  void tulosta() const  {
    cout << setfill('0') << setw(2) << h << ":" << setw(2) << m << endl;
  }
};

int main(void)
{
  cAika a1={12,15}, a2={13,16}, a3={14,25};
  a1.lisaa(55);  a1.tulosta();
  a2.lisaa(27);  a2.tulosta();
  a3.lisaa(39);  a3.tulosta();
  return 0;
}

Siinäpä se!  Onko muutokset edelliseen nähden suuria?  Siis iso osa koko olio- ohjelmoinnista (ja tietotekniikasta muutenkin) on markkinahenkilöiden huuhaata ja yleistä hysteriaa "kaiken ratkaisevan" teknologian ympärillä.  No, tosin olio- ohjelmonnissa on puolia, joita emme vielä ole nähneetkään, joiden ansiosta olio- ohjelmointia voidaan pitää uutena ja ohjelmointia ja ylläpitoa helpottavana teknologiana.  Näitä ovat mm. perintä ja polymorfismi (monimuotoisuus), joihin emme valitettavasti tällä kurssilla ehdi perehtyä kovinkaan syvällisesti.
No takaisin esimerkkiimme.  Uutta on lähinnä se, että metodien (no sanotaan tästä lähtien funktioita metodeiksi) parametrilistat ovat lyhentyneet.  Itse oliota ei tarvitse enää viedä parametrina,  koska metodit ovat luokan sisäisiä ja tällöin luokkaa edustava olio kyllä tuntee itse itsensä.  Samoin on jäänyt pois metodien sisältä viite olioon - samasta syystä:
...
  void lisaa(int lisa_min)  {
    int yht_min = h * 60 + m + lisa_min;
    h = yht_min / 60;
    m = yht_min % 60;
  }
...

Metodia kutsutaan analogisesti tietueen kenttään viittaamisen kanssa, eli
a1.lisaa(55);  a1.tulosta();

Tällekin on keksitty oma nimi:  välitetään oliolle viesti "tulosta" (message passing).  Tässä kuitenkin jatkossa voi vielä lipsahtaa ja vahingossa sanomme kuitenkin, että kutsutaan metodia tulosta, vaikka ehkä pitäisi puhua viestin välittämisestä.
Metodin tulosta esittelyssä
void tulosta() const  {

oleva const tarkoittaa että metodi ei muuta itse olion tilaa (sen attribuutteja).  Metodi lisaa taas vastaavasti muuttaa olion tilaa hyvinkin paljon.
Tehtävä 9.80	const-metodi
Kokeile mitä kääntäjä ilmoittaa, jos muutat myös lisaa- metodin const- tyyppiseksi.
9.3.3	Taas terminologiaa
Kerrataanpa vielä termit edellisen esimerkin avulla:
Vinkki

Älä hämäänny termeistä
                oliotermi                                perinteinen termi
-----------------------------------------------------------------------------
cAika         - aika-luokka                                tietuetyyppi
h,m           - aika-luokan attribuutteja                  tietueen alkio
lisaa,tulosta - aika-luokan metodeja                       funktio,aliohj.
a1,a2,a3      - olioita, jotka ovat aika-luokan ilmentymiä muuttuja
a1.lisaa(55)  - viesti olioille a1: lisää 55 minuuttia     aliohjelman kutsu

9.3.4	Luokka (class) ja kapselointi
Virallisesti luokat esitellään C++:ssa class- avainsanalla struct- avainsanan sijasta.  Mikä ero sitten class- avainsanalla on?  Kumpikin käy, mutta attribuuteille ja metodeille on suojaustasot, jotka oletuksena struct- määritellyssä luokassa kaikki ovat julkisia, eli metodeja voi kutsua kuka tahansa ja erityisesti kuka tahansa voi muuttaa  attribuuttien arvoja ilman että olio tätä itse huomaa.
	Kuka voi käyttää metodia/attribuuttia				Oletuksena		
Suojaus	kaikki	aliluokat	friend- funktiot 	luokan metodit	struct
union	class	
private			x	x		x	
protected		x	x	x			
public	x	x	x	x	x		
Kuva 9.1  Suojaustasot
Jos esimerkkimme luokka esiteltäisiin:
class cAika {
  int h,m;
  void lisaa(int lisa_min)  {...}
  void tulosta() const      {...}
};

lakkaisi ohjelmamme toimimasta, koska esimerkiksi pääohjelman kutsu
a1.lisaa(55)

tulisi laittomaksi kaikkien luokan jäsenten ollessa yksityisiä (private).  Ongelmaa voisi yrittää korjata esittelemällä metodit julkisiksi:
class cAika {
  int h,m;
public:
  void lisaa(int lisa_min)  {...}
  void tulosta() const      {...}
};

Nyt kääntäjä valittaisi esimerkiksi:
Error:  aikacla.cpp(21,13):Objects of type 'cAika' cannot be initialized with { }

rivistä
cAika a1={12,15},...

Nyt vasta alkaakin olio- ohjelmoinnin hienoudet!  Aloittelijasta saattaa tuntua että mitä turhaa tehdään asioista monimutkaisempaa kun se onkaan!  Nimittäin aikaolio.cpp saattoi tuntua kohtuullisen ymmärrettävältä.  Mutta todellakin jos olisi ilman muuta sallittua sijoittaa oliolle mitä tahansa arvoja, niin mitä mieltä oltaisiin seuraavasta:
cAika a1={42,175};

Väärinkäytetyt ja virheelliset arvot muuttujilla on ollut ohjelmoinnin kiusa alusta alkaen.  Nyt meillä on mahdollisuus päästä niistä eroon kapseloinnin (jotkut sanovat kotelointi, encapsulation) ansiosta.  Eli kaikki arvojen muutokset (eli olio tapauksessa olion tilojen muutokset) voidaan suorittaa kontrolloidusta, vain olion itse siihen suostuessa.  Mutta miten sitten alustuksen tapauksessa?
9.3.5	Muodostajat (constructor)
C++:ssa on yksi erityinen metodi: muodostaja (konstruktori, rakentaja, constructor), jota kutsutaan muuttujan syntyessä.  Muodostajan tehtävä on alustaa olion tila ja luoda mahdollisesti tarvittavat dynaamiset muistialueet. Näin voidaan järjestää se, että olion tila on aina tunnettu olion syntyessä.
Joissakin oliokielissä konstruktori ilmoitetaan omalla avainsanallaan.  C++:ssa muodostaja on metodi, jolla on sama nimi kuin luokalla.  Muodostajia voi olla useitakin.  Muodostaja on aina tyypitön, siis ei edes void- tyyppiä.
olioalk\aikacla.cpp - muodastaja alustamaan tiedot
#include <iostream.h>
#include <iomanip.h>

class cAika {
  int h,m;
public:
  cAika(int ih, int im) { h = ih; m = im; }
  void lisaa(int lisa_min)  {
    int yht_min = h * 60 + m + lisa_min;
    h = yht_min / 60;
    m = yht_min % 60;
  }
  void tulosta() const  {
    cout << setfill('0') << setw(2) << h << ":" << setw(2) << m << endl;
  }
};

int main(void)
{
  cAika a1(12,15), a2(13,16), a3(14,25);
  a1.lisaa(55);    a1.tulosta();
  a2.lisaa(27);    a2.tulosta();
  a3.lisaa(39);    a3.tulosta();
  return 0;
}

Esimerkissämme muodostaja on esitelty 2-parametriseksi
cAika(int ih, int im) { h = ih; m = im; }

ja sitä kutsutaan olion esittelyn yhteydessä
cAika a1(12,15);

9.3.6	Oletusmuodostaja (default constructor)
Nyt ei kuitenkaan voida esitellä oliota ilman alkuarvoa
cAika aika;

Kääntäjä antaisi esimerkiksi virheilmoituksen:
Error:  aikacla.cpp(26,14):Could not find a match for 'cAika::cAika()'

Parametritonta muodostajaa sanotaan oletusmuodostajaksi (default constructor).  Sellainen on luokalla aina ilman muuta, jos luokalle ei ole esitelty yhtään muodostajaa.  Jos luokalle esitellään muodostaja, ei oletusmuodostaja enää tulekaan automaattisesti.  
Meidän pitäisi päättää nyt paljonko kellomme on, jos sitä ei erikseen ilmoiteta.  Olkoon kello vaikka 0:0, eli keskiyö.  Esittelemme oletusmuodostajan
olioalk\aikacla2.cpp - lisätään oletusmuodostaja
... 
class cAika {
  int h,m;
public:
  cAika()                   { h = 0;  m = 0;  }
  cAika(int ih, int im)     { h = ih; m = im; }
  void lisaa(int lisa_min)  { ... }
  void tulosta() const      { ... }
};

int main(void)
{
  cAika a1(12,15), a2(13,16), a3(14,25);
  a1.lisaa(55);    a1.tulosta();
...
  cAika aika;
  aika.tulosta();
  return 0;
}

Oletusmuodostajaa kutsutaan hieman epäloogisesti ilman sulkuja
cAika aika;

eikä
cAika aika(); 	

kuten muiden parametrittomien funktioiden kutsusta saattaisi päätellä!
9.3.7	Oletusparametrit
C++:ssa on myös ominaisuus antaa funktioiden ja metodien parametreille oletusarvoja oikealta vasemmalle päin.  Koska oletusmuodostajaksi riittää se, että muodostajaa voi kutsua ilman parametreja, voitaisiin esitellä myös:
olioalk\aikacla3.cpp - oletusparametrit
... 
public:
  cAika(int ih=0, int im=0) { h = ih; m = im; }
  void lisaa(int lisa_min)  {
...
};

Nyt oliot voitaisiin esitellä millä tahansa seuraavista tavoista; ilman parametreja, yhdellä tai kahdella parametrillä:
cAika a1, a2(13), a3(14,25);

Tulostus olisi vastaavasti:
a1.tulosta(); a2.tulosta();  a3.tulosta();
=>
00:00
13:00
14:25

Oletusparametri tarkoittaa siis sitä, että mikäli parametrilla ei ole kutsussa arvoa, käytetään funktion/metodin esittelyssä ollutta oletusarvoa.
9.3.8	Sisäinen tilan valvonta
Emme edelleenkään ole ottaneet kantaa siihen, mitä tapahtuu, jos joku yrittää alustaa oliomme mielettömillä arvoilla, esim:
cAika a1(42,175);

Toisaalta miten joku voisi muuttaa ajan arvoa muutenkin kuin lisaa- metodilla?  Teemmekin aluksi metodin aseta, jota kutsuttaisiin
a1.aseta(12,15); a2.aseta(16);

Nyt pitää kuitenkin päättää mitä tarkoittaa laiton asetus!  Jos sovimme että minuuteissa yli 59 arvot ovat aina 59 ja alle 0:n arvot ovat aina 0, voisi aseta- metodi olla kirjoitettu seuraavasti:
  void aseta(int ih,int im=0) {
    h = ih; m = im; 
    if ( m > 59 ) m = 59;
    if ( m <  0 ) m =  0;
  }

Jos taas haluaisimme, että ylimääräinen osuus siirtyisi tunteihin, voitaisiinkin tämä tehdä "kierosti" lisaa- metodin avulla
olioalk\aikacla4.cpp - sisäinen tilan valvonta asetuksessa
#include <iostream.h>
#include <iomanip.h>

class cAika {
  int h,m;
public:
  void aseta(int ih,int im=0) {
    h = ih; m = im; lisaa(0);
  }
  cAika(int ih=0, int im=0) { aseta(ih,im); }
  void lisaa(int lisa_min)  { ... }
  void tulosta() const      { ... }
};

int main(void)
{
  cAika a1, a2(13), a3(14,25);
  a1.tulosta();  a2.tulosta();  a3.tulosta();
  a1.aseta(12,15); a2.aseta(16,-15);
  a1.tulosta(); a2.tulosta();
  return 0;
}

Huomattakoon, että samalla kun tehdään aseta- metodi, kannattaa muodostajassakin kutsua sitä.  Näin olion tilaa muutetaan vain muutamassa metodissa, jotka voidaan ohjelmoida niin huolella, ettei mitään yllätyksiä pääse koskaan tapahtumaan.  Tämä rupeaa jo muistuttamaan olio- ohjelmointia!
Tehtävä 9.81	Negatiivinen minuuttiasetus
Mitä ohjelma aikacla4.cpp tulostaisi?  Miksi ohjelma toimisi halutulla tavalla?
Tehtävä 9.82	Tuntien tarkistus
Ohjelmoi myös tunneille tarkistus, missä pidetään huoli siitä, että tunnit ovat aina välillä 0-23.

9.3.9	inline- metodit ja tavalliset metodit
Olemme kaikissa edeltävissä olioesimerkeissä kirjoittaneet metodien toteutuksen myös luokan esittelyn sisään.  Javassa näin tehdään aina, mutta C++:ssa oikeastaan varsin harvoin.  Tyypillisempi tapa kirjoittaa on sellainen, missä ensin esitellään luokka ilman metodien toteutusta ja sitten metodit esitellään luokan ulkopuolella:
olioalk\aikacla5.cpp - metodit luokan ulkopuolella
#include <iostream.h>
#include <iomanip.h>

class cAika {
  int h,m;
public:
  cAika(int ih=0, int im=0);
  void aseta(int ih,int im=0);
  void lisaa(int lisa_min);
  void tulosta() const;
};

void cAika::aseta(int ih,int im)
{
  h = ih; m = im; lisaa(0);
}

cAika::cAika(int ih, int im)
{
  aseta(ih,im);
}

void cAika::lisaa(int lisa_min)
{
  int yht_min = h * 60 + m + lisa_min;
  h = yht_min / 60;
  m = yht_min % 60;
}

void cAika::tulosta() const
{
  cout << setfill('0') << setw(2) << h << ":" << setw(2) << m << endl;
}

int main(void)
{
  cAika a1, a2(13), a3(14,175);
  a1.tulosta(); a2.tulosta();  a3.tulosta();
  a1.aseta(12,15); a2.aseta(16,-15);
  a1.tulosta(); a2.tulosta();
  return 0;
}

Jos metodin toteutus on luokan ulkopuolella, pitää metodin nimen eteen liittää tieto siitä, minkä luokan metodista on kyse.  Esimerkiksi:
void cAika::lisaa(int lisa_min)

Nyt metodit ovat todellisia kutsuttavia aliohjelmia.  Tähän saakka metodit ovat olleet inline- funktioita, eli niihin ei ole tehty aliohjelmakutsua, vaan niiden koodi on sijoitettu kutsun tilalle.  Etuna on nopeampi suoritus kun aliohjelmaan siirtyminen ja paluu jäävät pois, mutta haittana suurempi ohjelman koko, erityisesti jos kutsuja on paljon ja metodit ovat suuria. 
Jos nopeussyistä jälkeenpäin jokin luokan ulkopuolelle kirjoitettu metodi haluttaisiinkin takaisin inline- metodiksi, voidaan tämä tehdä myös inline- avainsanalla:
inline void cAika::aseta(int ih,int im)
{
  h = ih; m = im; lisaa(0);
}

Siis inline- metodeja voidaan tehdä kahdella eri tavalla:
1.	Kirjoitetaan metodi luokan sisälle.  Tässä monisteessa käytetään jatkossa tätä tapaa lähinnä siitä syystä, että ohjelmalistaukset tulevat näin hieman lyhyemmiksi.
2.	Kirjoitetaan metodi luokan ulkopuolella ja kirjoitetaan metodin toteutuksen eteen inline.
Sopivalla inline- määreen käytöllä mahdollinen olio- ohjelmoinnin byrokratiasta aiheutuva hitaus voidaan poistaa.  Tosin inline kannattaa laittaa vain usein kutsuttavien lyhyiden metodien eteen.
Esimerkiksi seuraavasta ohjelmasta:
olioalk\inline.cpp - esimerkki optimoinnista
#include <iostream.h>

inline void ynnaa_yksi(int &ri)
{
  ri = ri + 1;
}

inline void ynnaa_kaksi(int &ri)
{
  ynnaa_yksi(ri);
  ynnaa_yksi(ri);
}

int main (void)
{
  int i = 10;
  ynnaa_kaksi(i);
  cout << i << endl;
  return 0;
}

riveistä
  int i = 10;
  ynnaa_kaksi(i);

hyvä kääntäjä kääntää koodin:
mov  eax,10    // Suoraan kääntäjä ei välttämättä uskalla laittaa mov eax,12
inc  eax       // koska se ei voi tietää tarvitaanko inc lauseen ominaisuutta
inc  eax       // Oikein hyvä kääntäjä ehkä laittaisikin mov eax,12

joka on täsmälleen sama koodi, joka generoituu lauseista
  i = 10;  ++i;  ++i;

Jos inline- määreet jätettäisiin ynnaa- aliohjelmista pois, olisi ynnaa_kaksi kutsu yli 30 kertaa hitaampi!
9.3.10	Jako useampaan tiedostoon
Kaiken tämän jälkeen lukija varmaan taas kysyy:  Miksi vielä tuplata koodin pituus, kun aikacla4.cpp oli vielä jotenkin siedettävä alkuperäisen aikalis4.cpp:n rinnalla!  Vastaus on uudelleen käytettävyys.  Eli kun lopulta aika- luokkamme on valmis, paketoimme sen sellaiseksi, että se on helppo liittää myöhempiin omiin ohjelmiimme tai jopa luovuttaa (mahdollisesti korvausta vastaan :-) muille erillisenä komponenttina.  Tuplatyö nyt saattaa olla 1/100 työ tulevaisuudessa.
Oikeasti siis jaamme koko ohjelman 3 eri tiedostoon: 
aika.h	luokan esittely
aika.cpp	luokan toteutus
aikatest.cpp	luokan testaava pääohjelma

olioalk\aika.h - luokan esittely omaan tiedostoon
#ifndef AIKA_H           // Suoja, jottei samaa koodia "includata" kahta kertaa!
#define AIKA_H
class cAika {
  int h,m;
public:
  cAika(int ih=0, int im=0);
  void aseta(int ih,int im=0);
  void lisaa(int lisa_min);
  void tulosta() const;
};
#endif // AIKA_H

olioalk\aika.cpp - luokan metodit kirjoitettu omaan tiedostoon
#include <iostream.h>
#include <iomanip.h>
#include "aika.h"

void cAika::aseta(int ih,int im)
{
  h = ih; m = im; lisaa(0);
}

cAika::cAika(int ih, int im)
{
  aseta(ih,im);
}

void cAika::lisaa(int lisa_min)
{
  int yht_min = h * 60 + m + lisa_min;
  h = yht_min / 60;
  m = yht_min % 60;
}

void cAika::tulosta() const
{
  cout << setfill('0') << setw(2) << h << ":" << setw(2) << m << endl;
}

olioalk\aikatest.cpp - aika.cpp:n testi
#include "aika.h"
// Projektiin aika.cpp ja aikatest.cpp
int main(void)
{
  cAika a1, a2(13), a3(14,175);
  a1.tulosta(); a2.tulosta();  a3.tulosta();
  a1.aseta(12,15); a2.aseta(16,-15);
  a1.tulosta(); a2.tulosta();
  return 0;
}

Lopuksi tehtäisiin vielä projekti tai makefile johon kuuluvat aika.cpp ja aikatest.cpp.
9.3.11	this- osoitin
Jos verrataan funktiota
void lisaa(tAika *pAika, int lisa_min)
{
  int yht_min = pAika->h * 60 + pAika->m + lisa_min;
  pAika->h = yht_min / 60;
  pAika->m = yht_min % 60;
}
...
lisaa(&a1,55);

ja metodia
void cAika::lisaa(int lisa_min)
{
  int yht_min = h * 60 + m + lisa_min;
  h = yht_min / 60;
  m = yht_min % 60;
}
...
a1.lisaa(55);

niin helposti näyttää, että ensin mainitussa funktiossa on enemmän parametreja.  Tosiasiassa kummassakin niitä on täsmälleen sama määrä.  Nimittäin jokaisen metodin ensimmäisenä näkymättömänä parametrina tulee aina itse luokan osoite, this.  Voitaisiinkin kuvitella, että metodi onkin toteutettu:
"void cAika::lisaa(cAika *this,int lisa_min)"   // Näin EI SAA KIRJOITTAA!!!
{
  int yht_min = this->h * 60 + this->m + lisa_min;
  this->h = yht_min / 60;
  this->m = yht_min % 60;
}
...
"a1.lisaa(a1,55)";

Oikeasti this - osoitinta ei saa esitellä, vaan se on ilman muuta mukana parametreissa sekä esittelyssä että kutsussa.  Mutta voimme todellakin kirjoittaa:
void cAika::lisaa(int lisa_min)
{
  int yht_min = this->h * 60 + this->m + lisa_min;
  this->h = yht_min / 60;
  this->m = yht_min % 60;
}

Jonkun mielestä voi jopa olla selvempi käyttää this- osoitinta luokan attribuutteihin viitattaessa, näinhän korostuu, että käsitellään nimenomaan tämän luokan attribuuttia h, eikä mitään globaalia muuttujaa h.  Joskus this- osoitinta tarvitaan välttämättä palautettaessa oliotyyppisellä metodilla olion koko tila (esim. viite olioon).  Lisäksi joissakin kielissä this- osoittimen vastinetta (usein self) on aina käytettävä.
9.4	Perintä
9.4.1	Luokan ominaisuuksien laajentaminen
Pidimme jo aikaisemmin toiveena sitä, että voisimme laajentaa luokkaamme käsittelemään myös sekunteja.  Miksi emme tehneet tätä heti?  No tietysti olisi heti pitänyt älytä laittaa mukaan myös sekunnit, mutta tosielämässäkin käy usein näin, eli hyvästäkin suunnittelusta huolimatta toteutuksen loppuvaiheessa tulee vastaan tilanteita, jossa alkuperäiset luokat todetaan riittämättömiksi.
Tämän laajennuksen tekemiseen on olio- ohjelmoinnissa kolme mahdollisuutta:  Joko muuttaa alkuperäistä luokkaa, periä alkuperäisestä luokasta laajempi versio tai tehdä uusi luokka, jossa on alkuperäinen luokka yhtenä attribuuttina.
Tutustumme seuraavassa kaikkiin kolmeen eri mahdollisuuteen.
9.4.2	Alkuperäisen luokan muuttaminen
Läheskään aina ei voi täysin välttää sitäkään, etteikö alkuperäistä luokkaa joutuisi muuttamaan.  Jos näin joudutaan tekemään, pitäisi tämä pystyä tekemään siten, että jo kirjoitettu luokkaa käyttävä koodi säilyisi täysin muuttumattomana (tai ainakin voitaisiin päivittää minimaalisilla muutoksilla) ja vasta uudessa koodissa käytettäisiin hyväksi luokan uusia ominaisuuksia.  
Jos luokka on saatu joltakin kolmannelta osapuolelta, ei luokan päivittäminen edes ole mahdollista, vaan silloin täytyy turvautua muihin (parempiin) tapoihin.
Päivitämme nyt kuitenkin alkuperäistä luokkaa:
olioalk\aikacla6.cpp - laajentaminen muuttamalla kantaluokkaa 
#include <iostream.h>
#include <iomanip.h>

class cAika {
  int h,m,s;                                  // lisätty s
public:
  cAika(int ih=0, int im=0, int is=0);        // lisätty is=0
  void aseta(int ih,int im=0, int is=0);      // lisätty is=0
  void lisaa(int lisa_min, int lisa_sek=0);   // lisätty lisa_sek=0
  void tulosta(int tulsek=0) const;           // lisätty tulsek=0
};

void cAika::aseta(int ih,int im, int is)      // lisätty is
{
  h = ih; m = im; s = is;  lisaa(0,0);        // lisätty s=is ja ,0
}

cAika::cAika(int ih, int im, int is)          // lisätty int is
{
  aseta(ih,im,is);
}

void cAika::lisaa(int lisa_min, int lisa_sek) // lisätty int lisa_sek
{
  s += lisa_sek;                                 // lisätty
  int yht_min = h * 60 + m + lisa_min + s / 60;  // lisätty s / 60
  s = s % 60;                                    // lisätty
  h = yht_min / 60;
  m = yht_min % 60;
}

void cAika::tulosta(int tulsek) const         // lisätty int tulsek
{
  cout << setfill('0') << setw(2) << h << ":" << setw(2) << m;
  if ( tulsek ) cout << ":" << setw(2) << s;  // lisätty
  cout << endl;
}

int main(void)
{
  cAika a1, a2(13), a3(14,175);               // ei muutoksia!
  a1.tulosta(); a2.tulosta();  a3.tulosta();
  a1.aseta(12,15); a2.aseta(16,-15);
  a1.tulosta(); a2.tulosta();
  cAika a4(12,55,45); a4.tulosta(1);          // lisätty uusi
  a4.lisaa(3,30);     a4.tulosta(1);
  return 0;
}

Huh huh!  Tulipa paljon muutoksia, mutta onnistuimme kuitenkin pitämään alkuperäisen pääohjelman koodin muuttumattomana.  Tulostuksessa olisi tietysti voitu valita sekin linja, että aina tulostetaan sekunnit tai sekuntien tulostus on oletuksena.  Kumpikin valinta olisi aiheuttanut olemassa olevan koodin toiminnan muuttumisen.  Jos näin olisi haluttu, niin sitten olisi valittu niin.
Tehtävä 9.83	Sekuntien tulostus aina tai oletuksena
Muuta ohjelmaa aikacla6.cpp siten, että sekunnit tulostetaan aina.
Muuta ohjelmaa aikacla6.cpp siten, että sekunnit tulostetaan oletuksena jos ne on != 0.

9.4.3	Koostaminen
Seuraava mahdollisuus olisi uuden luokan koostaminen (aggregation) vanhasta aikaluokasta ja sekunneista.  Tämä mahdollisuus meillä on aina käytössä vaikkei alkuperäistä lähdekoodia olisikaan käytössä.  Tätä vaihtoehtoa pitää aina vakavasti harkita.  Tehdään koodi sellaiseksi, että uusi luokka tulostaa aina sekunnit (inline- muotoa käytetään tilan säästämiseksi):
olioalk\aikacla7.cpp - laajentaminen koostamalla
#include <iostream.h>
#include <iomanip.h>

class cAika {
... kuten aikacla5.cpp, mutta seuraava muutos:
  void tulosta(int lf=1) const  {
    cout << setfill('0') << setw(2) << h << ":" << setw(2) << m;
    if ( lf ) cout << endl;
  }
};

class cAikaSek {
  cAika hm;
  int s;
public:
  void aseta(int ih=0, int im=0, int is=0) {
    s = is; hm.aseta(ih,im); lisaa(0);
  }
  cAikaSek(int ih=0, int im=0, int is=0) { aseta(ih,im,is); }
  void lisaa(int lisa_min, int lisa_sek=0) {
    s += lisa_sek;
    hm.lisaa(lisa_min+s/60);
    s %= 60;
  }
  void tulosta(int lf=1) const  {
    hm.tulosta(0);
    cout <<  ":" << setw(2) << s;
    if ( lf ) cout << endl;
  }
};

int main(void)
{
  cAika a1, a2(13), a3(14,175);
  a1.tulosta(); a2.tulosta();  a3.tulosta();
  a1.aseta(12,15); a2.aseta(16,-15);
  a1.tulosta(); a2.tulosta();
  cAikaSek a4(12,55,45); a4.tulosta(1);        // lisätty uusi
  a4.lisaa(3,30);     a4.tulosta(1);
  return 0;
}

Valitettavasti emme aivan täysin onnistuneet.  Nimittäin alkuperäisen luokan tulosta oli niin tyhmästi toteutettu, että se tulosti aina rivinvaihdon.  Tämmöistä hölmöyttä ei pitäisi mennä koskaan tekemään ja siksipä alkuperäinen luokka pitää joka tapauksessa palauttaa valmistajalle remonttiin.  No luokan valmistaja muutti tulosta- metodia siten, että se oletuksena tulostaa rivinvaihdon (merk. lf = line feed), mutta pyydettäessä jättää sen tekemättä.  Näin vanha koodi voidaan käyttää muuttamattomana.  
Palaamme tulostusongelmaan myöhemmin ja keksimme silloin paremman ratkaisun, jota olisi pitänyt käyttää jo alunperin.
Luokassa on niin vähän ominaisuuksia, että uudessa luokassamme olemme joutunee itse asiassa tekemään kaiken uudelleen ja on kyseenalaista olemmeko hyötyneet vanhasta luokasta lainkaan.  Tämä on onneksi lyhyen esimerkkimme vika, todellisilla luokilla säästö kokonaan uudestaan kirjoitettuun verrattuna olisi moninkertainen.
9.4.4	Perintä
Viimeisenä vaihtoehtona tarkastelemme perintää (inheritance).  Valinta koostamisen ja perinnän välillä on vaikea.  Aina edes olioasiantuntijat eivät osaa sanoa yleispätevästi kumpiko on parempi.  Nyrkkisääntönä voisi pitää seuraavaa is-a - sääntöä:
Jos voi sanoa että lapsiluokka on (is-a) isäluokka, niin peritään.
Jos sanotaan että lapsiluokassa on (has-a) isäluokka, niin koostetaan

Kokeillaanpa: "luokka jossa on aika sekunteina" on "aika- luokka".  Kuulostaa hyvältä.  Siis perimään (taas inline- muoto tilan säästämiseksi):
olioalk\aikacla8.cpp - laajentaminen perimällä
... kuten aikacla7.cpp

class cAikaSek : public cAika {
  int s;
public:
  void aseta(int ih=0, int im=0, int is=0) {
    s = is; cAika::aseta(ih,im); lisaa(0);
  }
  cAikaSek(int ih=0, int im=0, int is=0) { aseta(ih,im,is); }
  void lisaa(int lisa_min, int lisa_sek=0) {
    s += lisa_sek;
    cAika::lisaa(lisa_min+s/60);
    s %= 60;
  }
  void tulosta(int lf=1) const  {
    cAika::tulosta(0);
    cout <<  ":" << setw(2) << s;
    if ( lf ) cout << endl;
  }
};

int main(void)
... kuten aikacla7.cpp

Tässä tapauksessa kirjoittamisen vaiva oli tasan sama kuin koostamisessakin. Joissakin tapauksissa perimällä pääsee todella vähällä.  Otamme tästä myöhemmin esimerkkejä, kunhan pääsemme eroon syntaksin esittelystä.
Lapsiluokka, aliluokka (child class, subclass) on se joka perii ja isäluokka, yliluokka (parent class, superclass) se joka peritään.  Käytetään myös nimitystä välitön ali/yliluokka, kun on kyseessä perintä suoraan luokalta toiselle, kuten meidän esimerkissämme.  
C++:ssa välitön yliluokka ilmoitetaan aliluokan esittelyssä:
class cAikaSek : public cAika {

Jos täytyy viitata yliluokan metodeihin, joille on kirjoitettu aliluokassa oma määrittely,  käytetään näkyvyysoperaattoria :: (scope resolution operator):
cAika::lisaa(lisa_min+s/60);

Näkyvyysoperaattoria EI käytetä, mikäli samannimistä metodia ei ole aliluokassa.
Perintää kuvataan piirroksessa:

Kuva 9.2 Perintä
9.4.5	Polymorfismi, eli monimuotoisuus
Edellisestä esimerkistä ei oikeastaan paljastunut vielä mitään, mikä olisi puoltanut perintää.  Korkeintaan snobbailu uudella syntaksilla.  Mutta tosiasiassa pääsemme tästä kiinni olio- ohjelmoinnin tärkeimpään ominaisuuteen, ominaisuuteen jota on vaikea saavuttaa perinteisellä ohjelmoinnilla:  polymorfismi (polymorphism) eli monimuotoisuus.
Lisätäänpä vielä testiohjelman loppuun:
  cAika a1; ...          a1.aseta(12,15);
  cAikaSek a4(12,55,45); a4.lisaa(3,30);
  pAika = &a1;  pAika->tulosta();
  pAika = &a4;  pAika->tulosta();

Tulostus:
12:15
12:59

Mistä tässä oli kyse?  Osoitin pAika oli monimuotoinen, eli sama osoitin osoitti kahteen eri tyyppiseen luokkaan.  Tämä on mahdollista, jos luokat ovat samasta perimähierarkiasta kuten tässä tapauksessa ja osoitin on tyypiltään näiden yhteisen kantaluokan olion osoitin.  
Mutta hei!   Eikös jälkimmäinen tulostus olisi pitänyt olla sekuntien kanssa, olihan jälkimmäinen tulostettava luokka "aikaluokka jossa sekunnit"?  Totta!  Taas on alkuperäisen luokan tekijä möhlinyt eikä ole lainkaan ajatellut että joku voisi periä hänen luokkaansa.  Tässä syy, miksi otamme perinnän mukaan tässä vaiheessa opintoja.  Vaikka jatkossa emme hirveästi perinnän varaan rakennakaan, emme myöskään saa tehdä luokkia, jotka olisivat "väärin" tehtyjä!
9.4.6	Myöhäinen sidonta
Ongelma täytyy ratkaista siten, että jo alkuperäisessä luokassa kerrotaan että vasta ohjelman suoritusaikana selvitetään mistä luokasta todella on kyse, kun metodia kutsutaan.  Tällaista ominasuutta sanotaan myöhäiseksi sidonnaksi (late binding) (tälle monisteellekin tulee kyllä myöhäinen sidonta), vastakohtana sille, jota olemme edellä käyttäneet, eli aikainen sidonta (early binding).  Sidonnan sisäisen mekanismin opettelun jätämme jollekin toiselle kurssille (ks. vaikkapa Olio- ohjelmointi ja C++/VL).
Myöhäinen sidonta saadaan C++:ssa aikaan liittämällä virtual- avainsana metodien eteen.  Kaikki ne metodit täytyy ilmoittaa myöhäiseen sidontaan, joita mahdolliset perilliset tulevat muuttamaan.  Siis luokat vielä kerran remonttiin:
olioalk\aikacla9.cpp - myöhäinen sidonta
#include <iostream.h>
#include <iomanip.h>

class cAika {
  int h,m;
public:
  virtual void aseta(int ih,int im=0) {                      // lisätty virtual
    h = ih; m = im; lisaa(0);
  }
  cAika(int ih=0, int im=0) { aseta(ih,im); }
  virtual void lisaa(int lisa_min)  {                        // lisätty virtual
    int yht_min = h * 60 + m + lisa_min;
    h = yht_min / 60;
    m = yht_min % 60;
  }
  virtual void tulosta(int lf=1) const  {                    // lisätty virtual
    cout << setfill('0') << setw(2) << h << ":" << setw(2) << m;
    if ( lf ) cout << endl;
  }
};

class cAikaSek : public cAika {
  int s;
public:
  virtual void aseta(int ih=0, int im=0) { cAika::aseta(ih,im); } // lisätty
  virtual void aseta(int ih, int im, int is) {               // lisätty virtual
    s = is; aseta(ih,im); lisaa(0,0);                        // pois cAika::
  }
  cAikaSek(int ih=0, int im=0, int is=0) { aseta(ih,im,is); }
  virtual void lisaa(int lisa_min) { cAika::lisaa(lisa_min); } // lisätty
  virtual void lisaa(int lisa_min, int lisa_sek) {           // lisätty virtual
    s += lisa_sek; lisaa(lisa_min+s/60); s %= 60;            // pois cAika::
  }
  virtual void tulosta(int lf=1) const  {                    // lisätty virtual
    cAika::tulosta(0);
    cout <<  ":" << setw(2) << s;
    if ( lf ) cout << endl;
  }
};

int main(void)
.. kuten  aikacla8.cpp + cAika *pAika; ...

Virtual sanat on lisätty kaikkien metodien eteen.  Aliluokassa virtual on tarpeeton uudelleenmääriteltävien (korvaaminen, syrjäyttäminen, overriding) metodien kohdalle, mutta se on hyvä pitää siellä kommenttimielessä.  
Sitten tuleekin hankalammin selitettävä asia.  Miksi yksi- parametrinen lisaa ja kaksi- parametrinen aseta on täytynyt kirjoittaa uudelleen?  Tämä johtuu kielen ominaisuuksista, sillä muuten vastaavat useampi- parametriset metodit syrjäyttäisivät alkuperäiset metodit ja alkuperäisiä ei olisi lainkaan käytössä.  Tässä tapauksessa olisimmekin voineet antaa niiden syrjäytyä, mutta koska tulevaisuudesta ei koskaan tiedä, on alkuperäiselläkin parametrimäärällä olevien metodien ehkä hyvä säilyä.
Parempi ratkaisu olisi ehkä kuitenkin ollut, jos jo alkuperäisessä luokassa olisi varauduttu sekuntien tuloon, vaikka niitä ei olisi mitenkään otettukaan huomioon:
olioalk\aikaclaA.cpp - myöhäinen sidonta, sekunnit jo kantaluokassa
... 
class cAika {                           // Muutokset aikacla8.cpp:hen verrattuna
  int h,m;
public:
  virtual void aseta(int ih=0,int im=0, int lisa_sek=0) {   // virtual, lisa_sek
    h = ih; m = im; lisaa(0);
  }
  cAika(int ih=0, int im=0) { aseta(ih,im); }
  virtual void lisaa(int lisa_min, int lisa_sek=0)  {       // virtual, lisa_sek
    int yht_min = h * 60 + m + lisa_min;
    h = yht_min / 60;
    m = yht_min % 60;
  }
  virtual void tulosta(int lf=1) const  { ... }                // virtual
};

class cAikaSek : public cAika {
  int s;
public:
  virtual void aseta(int ih=0, int im=0, int is=0) {           // virtual
    s = is; cAika::aseta(ih,im); lisaa(0);                     // järj. vaihd.
  }
  cAikaSek(int ih=0, int im=0, int is=0) { aseta(ih,im,is); }
  virtual void lisaa(int lisa_min, int lisa_sek=0) {           // virtual
    s += lisa_sek; cAika::lisaa(lisa_min+s/60); s %= 60; 
  }
  virtual void tulosta(int lf=1) const  {                      // virtual
};
...
int main(void)
{
...
  cAika *pAika;
  pAika = &a1;  pAika->tulosta();
  pAika = &a4;  pAika->tulosta();
  return 0;
}

Varoitus:  Borlandin C++5.1:en optimoivalla kääntäjällä käännettynä em. koodi kaatoi kääntäjän ympäristöineen jos cAikaSek::aseta oli muodossa:

cAika::aseta(ih,im); s = is; lisaa(0);	

Tehtävä 9.84	Miksi ensin sekuntien alustus?
Osaatko selittää miksi sekunnit pitää alustaa ensin metodissa: cAikaSek::aseta?  Jos osaat, olet jo melkein valmis C++- ohjelmoija!
Tehtävä 9.85	2-3 - parametrinen aseta
Onko nyt olemassa 2 ja 3 - parametrinen aseta- metodi?
9.4.7	Yliluokan muodostajan kutsuminen ennen muodostajaa
Joskus kirjoittamalla vähän enemmän voi saada aikaan nopeampaa ja turvallisempaa koodia.  Niin tässäkin tapauksessa.  Nimittäin säätäminen siinä, ettemme vaivautuneet kirjoittamaan kumpaankin luokkaan erikseen oletusmuodostajaa, johtaa siihen että esimerkiksi alustus
cAikaSek a4(12,55,45);

kutsuu kaikkiaan seuraavia metodeja:
cAikaSek::cAikaSek(12,55,45);
  cAika::cAika(0,0);           // Oletusalustus yliluokalle
    cAika::aseta(0,0,0);
      cAika::lisaa(0,0);       // Tässä cAika, koska ei vielä "kasvanut" cAikaSek
  cAikaSek::aseta(12,55,45);
    cAika::aseta(12,55);
      cAikaSek::lisaa(0,0);    // HUOM!  Todellakin cAikaSek::lisaa virt.takia
        cAika::lisaa(0,0);
    cAikaSek::lisaa(0,0);
      cAika::lisaa(0,0);

Olisitko arvannut!   Enää ei tarvitse ihmetellä miksi olio- ohjelmat ovat isoja ja hitaita.  Totta kai voitaisiin sanoa, että hyvän kääntäjän olisi pitänyt huomata tuosta optimoida päällekkäisyydet pois.  Mutta tämä on vielä tänä päivänä kova vaatimus kääntäjälle.  Mutta ehkäpä voisimme ohjelmoijina vähän auttaa:
olioalk\aikaclaB.cpp - oletusmuodostajat 
... 
class cAika {
  int h,m;
public:
  virtual void aseta(int ih=0,int im=0, int is=0) { ... }
  cAika() { h = 0; m = 0; } // tai cAika() : h(0), m(0) {}  // Oletusmuodostaja
  cAika(int ih, int im=0) { aseta(ih,im); }                 // ih=0 oletus pois
  virtual void lisaa(int lisa_min, int lisa_sek=0)  { ... }
  virtual void tulosta(int lf=1) const  { ... }
};

class cAikaSek : public cAika {
  int s;
public:
  virtual void aseta(int ih=0, int im=0, int is=0) {...}
  cAikaSek() : s(0), cAika() {}                             // Oletusmuodostaja
  cAikaSek(int ih, int im=0, int is=0) : cAika(ih,im), s(is) { lisaa(0); }
};
...

Nyt on saatu ainakin seuraavat edut:  oletustapauksessa kumpikin luokka alustuu pelkillä 0:ien sijoituksilla, ilman yhtään ylimääräistä kutsua.  Oletusmuodostaja luokalla cAika voidaan esitellä esim. seuraavilla tavoilla, joista keskimmäistä voidaan pitää parhaana:
cAika() { h = 0; m = 0; }   // Normaalit sijoitukset
cAika() : h(0), m(0) {}     // h:n muodostaja arvolla 0, eli h=0 kokonaisluvulle
                            // m:n muodostaja arvolla 0, eli m=0
cAika() : h(0) { m=0; }    

Vastaavasti luokalle cAikaSek pitää oletusmuodostaja tehdä seuraavanlaiseksi:
cAikaSek() : s(0), cAika() { }      // s alusetetaan 0:ksi ja peritty yliluokka
                                    // alustetaan muodostajalla cAika()
cAikaSek() { s = 0; }               // TÄMÄ on TÄSSÄ tapauksessa sama kuin
                                    // edellinen, koska jos yliluokan muodostajaa
                                    // ei itse kutsuta, kutsutaan 
                                    // oletusmuodostajaa

ELI!  Perityn yliluokan muodostajaa kutsutaan automaattisesti, jollei sitä itse tehdä.  Nyt parametrillisessa muodostajassa kutsutaan yliluokan muodostajaa, ja näin voidaan välttää ainakin oletusmuodostajan kutsu:
cAikaSek(int ih, int im=0, int is=0) : cAika(ih,im), s(is) { lisaa(0); }

Nyt alustuksesta
cAikaSek a4(12,55,45);

seuraa seuraavat metodikutsut
cAikaSek::cAikaSek(12,55,45);
  cAika::cAika(12,55);           //  NYT EI oletusalustusta yliluokalle
    cAika::aseta(12,55,0);
      cAika::lisaa(0,0);
  cAikaSek::lisaa(0,0);
    cAika::lisaa(0,0);

Turhaahan tuossa on vieläkin, mutta tippuihan kuitenkin noin puolet pois!  Joka tapauksessa periminen tuottaa jonkin verran koodia, aina kun yliluokan metodeja käytetään hyväksi.  Ja jollei käytettäisi, kirjoitettaisiin samaa koodi uudelleen, eli palattaisiin 70- luvulle.  Nykyisin kasvanut koneteho kompensoi kyllä tehottomamman oliokoodin ja olio- ohjelmoinnin ansiosta pystytään kirjoittamaan luotettavampia (?) ja monimutkaisempia ohjelmia.
Tehtävä 9.86	Yliluokan alustajan kutsu
Pöytätestaa sekä aikaclaA.cpp että aikaclaB.cpp alustuksella cAikaSek a4(1,95,70);
9.5	Saantimetodit
Osa aikaisemmista ongelmista olisi voitu kiertää, mikäli olisimme päässeet käsiksi luokan yksityisiin tietoihin.  Esimerkiksi alkuperäisen luokan tulosta olisi voitu jättää koskemattomaksi vaikka se olikin väärin tehty (paitsi se virtual sinne kyllä olisi pitänyt lisätä joka tapauksessa).  Tätä olisi voitu helpottaa sillä, että kantaluokassa cAika olisi julistettu h ja m protected suojauksella.  Tosin vain perityssä versiossa tästä olisi ollut apua.
9.5.1	Miksi ja miten
Muutenkin saattaa tulla tilanteita, joissa luokan ulkopuolinen haluaa päästä käsiksi sisäisiin tietoihin.   Ainakin lukemana niitä.  Eihän ole ollenkaan tavatonta ajankaan kanssa, että joku haluaisi tietää tunnit, muttei tulostaa?  Mikä ratkaisuksi?  Julistetaanko kaikki attribuutit julkisiksi (public)?  No ei sentään!  Kirjoitetaan saantimetodi kullekin attribuutille, jonka perustellusti voidaan katsoa tarpeelliseksi jollekin ulkopuoliselle voitavan julkaista:
"Lopullinen" versio aikaluokastamme voisikin siis olla seuraava:
olioalk\aikaclaC.cpp - saantimetodit
class cAika {
  int h,m;
public:
  virtual void aseta(int ih=0,int im=0, int is=0) {...}
  ...
  virtual void tulosta(int lf=1) const  { ... }
  int hh() const { return h; }                                // saantimetodi
  int mm() const { return m; }                                // saantimetodi
};

Huomattakoon nyt, että perinnässä ei tarvitse määritellä uudestaan saantifunktioita hh() ja mm(), ainoastaan uudet, eli esimerkissämme ss().
Nyt voitaisiin esimerkiksi kutsua:
cout << a1.hh() << ":" << pAika->mm() << ":" << a4.ss() << endl;

Mikä tässä sitten on erona attribuuttien julkaisemiseen verrattuna?  Se että attribuutit ovat nyt tietyssä mielessä vain luettavissa (read-only), eli niitä voi lukea saantimetodien avuilla, mutta niitä voi asettaa vain aseta- metodin avulla, joka taas pystyy suorittamaan oikeellisuustarkistukset ja näin olion tila ei koskaan pääse muuttumaan olion itsensä siitä tietämättä.
Saantimetodit kannattaa ilman muuta kirjoittaa inline, sillä niistä kääntäjä voi sitten kääntää nopeaa koodia, eikä saantibyrokratiasta tule yhtään kustannuksia suoraan attribuuttiin viittaamiseen verrattuna.
Kannattaakin harkita, nimeäisikö attribuutit uudelleen: ah,am,as, jotta nimet h,m,s jäisi vapaaksi saantimetodeille!  Toisaalta tärkeimmistä attribuuteista voisi tehdä myös pitkillä nimillä varustetut saantimetodit:  esimerkiksi tunnit(), minuutit() ja  sekunnit().  Rakkaalla lapsella voi olla montakin nimeä ja inlinen ansiosta tämähän ei "maksa mitään".
Tehtävä 9.87	Saantimetodi sekunneille
Täydennä aikaclaB.cpp:hen em. saantimetodit ja lisäksi ss() aliluokkaan cAikaSek.  
Kirjoita myös pitemmillä nimillä varustetut saantimetodit.
Tehtävä 9.88	Saantimetodien käyttäminen
Muuta vielä edellisessä tehtävässä jokainen mahdollinen viittaus luokan sisälläkin saantimetodeja käyttäväksi suoran attribuuttiviittauksen sijasta.   Käännä alkuperäinen koodi ja uusi koodi, sekä vertaa .exe tiedostoja keskenään jollakin vertailuohjelmalla.
9.5.2	Rajapinta ja sisäinen esitys
Kapseloinnin ansiosta luokan käyttämiseksi on tullut selvä rajapinta (interface): metodit, joilla olion tilaa muutetaan.  Tämän rajapinnan ansiosta luokka muuttuu "mustaksi laatikoksi", jonka sisällöstä ulkomaailma ei tiedä mitään, mutta jonka kanssa voi kommunikoida metodien avulla.

Kuva 9.3  Musta laatikko
Tämä luokan sisustan piilottaminen antaa meille mahdollisuuden toteuttaa luokka oleellisesti eri tavalla.  Voimme esimerkiksi toteuttaa ajan minuutteina vuorokauden alusta laskien:
olioalk\aikaclaD.cpp - sisäinen toteutus minuutteina
... 
class cAika {
  int yht_min;
public:
  virtual void aseta(int ih=0,int im=0, int is=0) {
    yht_min = 0; lisaa(60*ih+im);
  }
  cAika(int ih=0, int im=0) { aseta(ih,im); }
  virtual void lisaa(int lisa_min, int lisa_sek=0)  { yht_min += lisa_min; }
  virtual void tulosta(int lf=1) const  {
    cout << setfill('0') << setw(2) << hh() << ":" << setw(2) << mm();
    if ( lf ) cout << endl;
  }
  int hh() const { return yht_min / 60; }
  int mm() const { return yht_min % 60; }
};
...kaikki muu kuten aikaclab.cpp ...

Tehtävä 9.89	minuutteina() 
Lisää aikaclaB:hen ja aikaclaD:hen, eli molempiin sisäisiin toteutustapoihin saantimetodi minuutteina, joka palauttaa kellonajan vuorokauden alusta minuutteina laskettuna.  Lisää vielä saantimetodi sekunteina.

9.6	Mistä hyviä luokkia
Alunperin kirjoittamamme luokka cAika kokikin varsin kovia tarkemmassa tarkastelussa.  Näistä muutoksista osa oli vielä aivan perusasioita; läheskään kaikkea emme vieläkään ole ottaneet huomioon (vertailu, syöttö päätteeltä, muuntaminen merkkijonoksi ja takaisin, lisäyksessä tapahtuvan ylivuodon luovuttaminen päivämäärälle, jne...).  Miten sitten monimutkaisempien luokkien kanssa?  Niin kauan pärjää, kun luokat on omaan käyttöön.  Heti kun yritetään tehdä yleiskäyttöisiä luokkia (joka on yksi olio- ohjelmoinnin tavoite), tuleekin ongelmia vastaan.  
Paremmalla suunnittelulla luokasta olisi heti voinut tulla yleiskäyttöisempi.  Usein jopa joudutaan tekemään kahden luokan yläpuolelle abstrakti, tai muuten vaan yhteinen yliluokka, josta hieman toisistaan poikkeavat luokat peritään.  Kuljimme tämän pitkän tien sen vuoksi, että lukija oppisi ymmärtämään miksi valmiit luokat eivät ole parin rivin koodinpätkiä.
Tulevaisuudessa ohjelmoijat jakaantunevatkin selvästi kahteen ryhmään:  toiset käyttävät valmiita luokkia (mikä on helppoa, jos luokat ovat kunnossa, vrt. Delphi tai Visual Basic, ehkä myös osittain Java ja Jonnen pyynnöstä mainitaan tietysti Python).  Ammattitaitoisempi ryhmä sitten suunnittelee ja tekee näitä yleiskäyttöisiä luokkia.  Sitä mukaa kun luokkia saadaan valmiiksi eri "elämän aloille", siirtyy ammattilaiset yhä spesifimmille aloille.  
Esimerkiksi M$:in Windowsin ohjelmointiin tarkoitetut luokkakirjastot ovat paisuneet niin suuriksi, että niiden käyttämistä tuskin kukaan enää hallitsee,  ja ennenkuin entisen kirjaston on ehtinyt edes auttavasti oppia, tuleekin uusi versio. Tästä tulee oravanpyörä, jossa voi olla kova homma pysyä mukana, jollei ohjelmateollisuus keksi jotakin uutta ja mullistavaa avuksi.
Joka tapauksessa haave siitä, että näkee näyn ja keksii hyvän luokan, jota muut sitten yhtään muuttamatta voivat käyttää hyväkseen, kannattaa heittää Ylistönrinteen sillan alle.  Mieluummin kannattaa alistua siihen, että opettelee käyttämään hyviä luokkia ja imee niitä käyttäessään ideoita siitä, miten parantaa omia luokkiaan seuraavalla kerralla.
9.7	Valmiita luokkia
Olio- ohjelmoinnin eräs tavoite on tuottaa ohjelmoijien käyttöön yleiskäyttöisiä komponentteja, jotta jokainen ei keksisi samaa pyörää uudelleen.  Erityisesti graafisen ohjelmoinnin puolella ja sekä myös tietokantaohjelmoinnin puolella näitä komponentteja onkin varsin mukavasti.  Borlandin Delphillä syntyy melkein Kerho- ohjelmaamme vastaava Windows- ohjelma lähes koodaamatta, pelkästään pudottelemalla komponentteja lomakkeelle.
9.7.1	Merkkijonot
Jos kerrankin pääsisin vastakkain nykykielten kehittäjien kanssa, niin tekisi kovasti mieli kysyä ovatko he koskaan tehneet oikeaa ohjelmaa.  Nimittäin lähes kielestä riippumatta kunnolliset merkkijonot loistavat poissaolollaan.  Ja ohjelmoijat ovat käyttäneet äärettömästi työtunteja tehdessään itselleen aluksi edes auttavaa merkkijonokirjastoa.  Ainoastaan "lelukielissä" -  Basicissä ja Turbo Pascalissa on ollut hyvät ja turvalliset merkkijonot.
C- kielen char jono[10] on todellinen aikapommi, jonka aukkoisuuteen perustuu vielä tänäkin päivänä useat hakkereiden kikat murtautua vieraisiin tietojärjestelmiin.  Katsotaanpa ensin mitä C- merkkijonoille voi/ei voi tehdä:
char s1[10],s2[5],*p; 	
p = "Kana"           // Toimii!
p[0] = 'S';          // Toimii! Mutta jatkossa käy huonosti...  
                     // Männikön monisteessakin on väärin tämän käytöstä ...
s1 = "Kissa";        // ei toimi!
strcpy(s2,"Koira");  // Huonosti käy!  Miksi?  Älä käytä koskaan...
if ( s1 < s2 ) ...   // Sallittu, mutta tekee eri asian kuin lukija arvaakaan...
gets(s1);            // Itsemurha, tämä on eräs kaikkein hirveimmistä funktioista
                     // lukee päätteeltä rajattomasti merkkejä ...
fgets(s1,sizeof(s1),stdin);  // Oikein!  Tosin rivinvaihto jää jonoon jos syöte
                             // on lyhyempi kuin 
printf(s1);          // Ohohoh!   Tämä jopa toimii!!!
cout << s1;          // Ja jopa tämäkin!!!
cin >> s1;           // Taas itsemurha ....

Palaamme myöhemmin monisteessa C:n merkkijonoihin, ja siihen miten niitä voidaan kohtuullisen turvallisesti käyttää.
Onneksi C++:aan on tulossa kohtuullinen merkkijonoluokka.  Nyt jo!  Yli 10 vuotta kielen kehittämisen jälkeen...
Merkkijonoluokalla voi tehdä mm. seuraavia:
\kurssit\c\ali\strtest.cpp - merkkijonojen testaus
#include <iostream.h>
#include <string>
using namespace std;

int main(void)
{
  string mjono1 = "Ensimmainen";
  string mjono2 = "Toinen";
  string mjono;

// syotto ja tulostus
  cout << "Anna merkkijono    > ";
  getline(cin,mjono,'\n');
  cout << "Annoit merkkijonon : " << mjono << endl;

// kasittely merkeittain
  mjono[0] = mjono1[4];
  mjono[1] = mjono2[1];
  mjono[2] = 't';
  mjono[3] = '\0';       // Laitonta jos merkkijono ei ole nain iso!
                         // Ei vaikuta!
  cout << mjono << endl; // tulostaa: mot ...+jotakin

// sijoitukset
  mjono = mjono1;
  cout << mjono << endl;       // Ensimmainen

  mjono = "Eka";
  cout << mjono << endl;       // Eka

// katenointi
  mjono = mjono1 + mjono2;
  cout << mjono << endl;       // EnsimmainenToinen

  mjono = mjono1 + "Toka";
  cout << mjono << endl;       // EnsimmainenToka

  mjono = "Eka" + mjono2;
  cout << mjono << endl;       // EkaToinen

// vertailut
  if (mjono1 == mjono2) cout << "1 on 2" << endl;           // ei tulosta
  if (mjono1 == "Apua") cout << "1 on Apua" << endl;        // ei tulosta
  if ("Apua" == mjono2) cout << "Apua on 2" << endl;        // ei tulosta

  if (mjono1 != mjono2) cout << "1 ei ole 2" << endl;       // 1 ei ole 2
  if (mjono1 != "Apua") cout << "1 ei ole Apua" << endl;    // 1 ei ole Apua
  if ("Apua" != mjono2) cout << "Apua ei ole 2" << endl;    // Apua ei ole 2

  if (mjono1 < mjono2) cout << "1 pienempi kuin 2" << endl;     // 1 pienempi ku
  if (mjono1 < "Apua") cout << "1 pienempi kuin Apua" << endl;  // ei tulosta
  if ("Apua" < mjono2) cout << "Apua pienempi kuin 2" << endl;  // Apua pienempi

  if (mjono1 > mjono2) cout << "1 suurempi kuin 2" << endl;     // ei tulosta
  if (mjono1 > "Apua") cout << "1 suurempi kuin Apua" << endl;  // 1 suurempi ku
  if ("Apua" > mjono2) cout << "Apua suurempi kuin 2" << endl;  // ei tulosta

// nama eivat valttamattomia, mutta jollekin voi tulla mieleen kayttaa...
  if (mjono1 <= mjono2) cout << "1 pienempi tai yhtasuuri kuin 2" << endl;
  if (mjono1 >= mjono2) cout << "1 suurempi tai yhtasuuri kuin 2" << endl;
// Vastaavat vakiomerkkijonoilla  EI onnistu, koska vakiomerkkijonot ovat
// OSOITTIMIA!
#if 0  // Seuraavat jos linkittaa: mjonot.c ja string.cpp
  mjono1.remove(4,2);
  cout << mjono1 << endl; // Ensiainen
  mjono1.insert(4,"mm");
  cout << mjono1 << endl; // Ensimmainen
#endif
  return 0;
}

Luokan string pitäisi tulla nykyisten C++- kääntäjien mukana.  Se saadaan käyttöön lisäämällä koodin alkuun:
#include <string>     // Otetaan standardiehdotelman merkkijonoluokka
using namespace std;  // nyt ei tarvitse kirjoittaa std::string

Jos koneessa on niin vanha kääntäjä, ettei uudet luokat käänny sillä (ei esim. poikkeutusten käsittelyä), voi käyttää \kurssit\c\ali hakemistosta löytyvää "lasten" vastinetta, string, eli em. testiohjelma toimii jos otsikkotiedostojen hakupolkuun lisätään mainittu ali- hakemisto.
Tehtävä 9.90	Ensimäinen melkein järkevä olio
Täydennä seuraava ohjelma
olioalk\oppilas.cpp - 1. järkevä olio
// Taydenna ja korjaile.  Mista puuttuu virtual?  Mista const?
// Mita metodeja viela puuttuu? 
#include <iostream.h>
#include <string>
using namespace std;

class cHenkilo {
  string nimi;
  int ika;
  double pituus_m;
public:
  cHenkilo(string inimi="???",int iika=0,int ipituus=0) {}
  void tulosta()          {}
  void kasvata(double cm) {}
};

class cOpiskelija : public cHenkilo {
  double keskiarvo;
public:
  cOpiskelija(string inimi="???",int iika=0, int ipituus=0, int ikarvo=0.0) :
    cHenkilo(inimi,iika,ipituus), keskiarvo(ikarvo) {}
};

int main(void)
{
  cHenkilo Kalle("Kalle",35,1.75);
  Kalle.tulosta();
  Kalle.kasvata(2.3);
  Kalle.tulosta();
  cOpiskelija Ville("Ville",21,1.80,9.9);
  Ville.tulosta();
  return 0;
}

10.	C- kielen ohjausrakenteista ja operaattoreista
Mitä tässä luvussa käsitellään?
•	if-else - lause
•	loogiset operaattorit: &&, || ja !
•	bittitason operaattorit: &,|,^ ja ~
•	silmukat while, do-while ja  for
•	silmukan "katkaisu" break, continue, goto
•	sijoituslauseet: = += -= jne.
•	valintalause switch 

Syntaksi:
lause  joko       ylause;                            // HUOM! Puolipiste 
       tai        lohko                              // eli koottu lause            
ylause            yksinkertainen lause              
  esim              a = b + 4
                    vaihda(a,b)
lohko             { lause1 lause2 lause3 }           // lauseita 0-n
  esim              { a = 5; b = 7; }     
ehto              lauseke joka tuottaa 0 tai != 0
  esim              a < 5
                    ( 5 < a ) && ( a < 10 )
                    !a                               // jos a=0 => 1, muuten 0
  HUOM!           Vertailu a == 5

if-else           if ( ehto ) lause1
                  else lause2                        // ei pakollinen
while             while ( ehto ) lause;
do-while          do lause while ( ehto );
for               for ( ylause1a,ylause2a; ehto ; ylause1k,ylause2k ) lause
  esim              for ( i=0,s=0; i<10; i++ ) s += i;  // ylause1a                 
swicth            switch ( lauseke ) {
                    case arvo1: lause1 break;        // valintoja 0-n   
                    case arvo2:                      // arvolla 2 ja 3 sama
                    case arvo3: lause2 break;
                    default:    laused break;        // ei pakollinen
                  }

Ohjelma jossa ei ole minkäänlaista valinnaisuutta tai silmukoita on varsin harvinainen.  Kertaamme seuraavassa C/C++- kielen tarjoamat mahdollisuudet suoritusjärjestyksen ohjaamiseen.  Samalla näemme kuinka suomenkielisen algoritmin kääntäminen ohjelmointikielelle on varsin mekaanista puuhaa.
10.1	if- lause
 Mikäli meillä on kaksi lukua, jotka pitäisi olla suuruusjärjestyksessä, voisimme hoitaa järjestämisen seuraavalla algoritmilla:
1. Jos luvut väärässä järjestyksessä, 
     niin vaihda ne keskenään

Tämän kirjoittamiseksi ohjelmaksi tarvitsemme ehto- lausetta:
if ( ehto ) ylause1;
lause2; 

Huomattakoon, että tässä sulut ehdon ympärillä ovat pakolliset.  lause1 suoritetaan vain kun ehto on voimassa. lause2 suoritetaan aina.  Lause voitaisiin kirjoittaa myös muodossa
if(ehto) ylause1;
lause2; 

muttei näin tehdä, jotta erottaisimme paremmin funktion ja if- lauseen toisistaan.  Saa tulee koskemaan myös for, while ja muita vastaavia rakenteita.
10.1.1	Ehdolla suoritettava yksi lause
Olkoon meillä aliohjelma nimeltään vaihda, joka suorittaa itse vaihtamisen:
if ( a > b ) vaihda(&a,&b); 

10.1.2	Ehdolla suoritettava useita lauseita
Mikäli aliohjelmaa ei ole käytössä, täytyisi meidän voida suorittaa useita lauseita muuttujien vaihtamiseksi.  C- kielessä voidaan lausesuluilla kasata joukko lauseita yhdeksi lauseeksi (lohko, koottu lause, block):
Vinkki

Sisennä kauniisti
if ( a > b ) {
  t = a; 
  a = b; 
  b = t;
}

Huomautus! Lauseiden kirjoittaminen samalle riville ei auttaisi mitään, sillä 
if ( a > b ) t = a; a = b; b = t;
/*  vastaisi loogisesti rakennetta: */
if ( a > b ) t = a; 
a = b; 
b = t; 

Koodia voidaan kuitenkin usein lyhentää kirjoittamalla asioita samalle riville:
if ( a > b ) {
  t = a; a = b; b = t;
}
/* tai joskus jopa */
if ( a > b ) { t = a; a = b; b = t; }

Niin kauan kuin todella hallitsee asian, voi olla helpointa laittaa aina if- lauseen ainoakin suoritettava lause lausesulkuihin
if ( a > b ) {
  vaihda(&a,&b);
}

Tästä on se etu, että myöhemmin monimutkaisten makrojen kanssa ei tule ongelmia, sekä se, että nyt if- lauseen suoritettaviksi lauseiksi on helppo lisätä uusia lauseita.  Mikäli sulkuja ei olisi, täytyisi toisen lauseen lisäyksen yhteydessä muistaa lisätä myös sulut (tosin eihän hyvin suunniteltua ohjelmaa tarvinnut enää jälkeenpäin paikata?).
Tehtävä 10.91	vaihda
Esitä pöytätestin avulla miksei vaihtaminen onnistu pelkästään lauseilla:
	a = b; b = a; 
Tehtävä 10.92	abs
Kirjoita funktio 
	int itseisarvo(int i), 
joka palauttaa i:n itseisarvon (negat. muutet. posit.).
Tehtävä 10.93	jarjesta2
Kirjoita aliohjelma 
	void jarjesta2(int *a, int *b), 
joka järjestää a:n ja b: siten, että a<=b.
Tehtävä 10.94	maksimi ja minimi
Kirjoita funktio 
 	int maksimi(int a, int b), 
joka palauttaa suuremman kahdesta luvusta.

Kirjoita vastaava funktio minimi.

10.2	Loogiset lausekkeet 
C- kielessä mikä tahansa lauseke voidaan kuvitella loogiseksi lausekkeeksi.  Arvo 0 on epätosi ja kaikki muut arvot ovat tosia.
a = 4;
if ( a ) ... 

10.2.1	Vertailuoperaattorit 
Vertailuoperaattorin käyttö muodostaa loogisen lausekkeen, jonka arvo on 0 tai 1.  Vertailuoperaattoreita ovat: 
==  yhtäsuuruus
!=  erisuuruus
<   pienempi kuin
<=  pienempi tai yhtä kuin
>   suurempi kuin
>=  suurempi tai yhtä kuin

Esimerkkejä vertailuoperaattoreiden käytöstä:
if ( a < 5 )  printf("a alle viisi!\n);
if ( a > 5 )  printf("a yli viisi!\n);
if ( a == 5 ) printf("a tasan viisi!\n);
if ( a != 5 ) printf("a ei ole viisi!\n); 

10.2.2	Sijoitus palauttaa arvon! 
Yhtäsuuruutta verrataan == operaattorilla, EI sijoituksella =.  Tämä on eräs tavallisimpia aloittelevan (ja kokeneenkin) C- ohjelmoijan virheitä:
c-silm\ifsij.c - esimerkki sijoituksesta ehdossa 
/* Seuraava tulostaa vain jos a == 5 */
if ( a == 5 ) printf("a on viisi!\n");
/* Seuraava sijoittaa aina a = 5 ja tulostaa AINA! */	
if ( a = 5 ) printf("a:ksi tulee AINA 5!\n"); 

Sijoitus a=5 on myös lauseke, joka palauttaa arvon 5.  Siis sijoitus kelpaa tästä syystä vallan hyvin loogiseksi lausekkeeksi.  Onneksi useat C- kääntäjät osaavat varoittaa tästä katalasta virhemahdollisuudesta.
Joskus ominaisuutta voidaan tarkoituksella käyttää hyväksikin.  Esimerkiksi halutaan sijoittaa AINA a=b ja sitten suorittaa jokin lause, mikäli b!=0.  Tämä voitaisiin kirjoittaa useilla eri tavoilla:
c-silm\ifsij2.c - esimerkki tahallisesta sijoituksesta ehdossa
/*1*/  a = b; if ( b ) printf("b ei ole nolla!\n");
/*2*/  a = b; if ( b != 0 ) printf("b ei ole nolla!\n");
/*3*/  if ( a = b ) printf("b ei ole nolla!\n");
/*4*/  if ( (a=b) != 0 ) printf("b ei ole nolla!\n");

Edellisistä tapa 3 on C- mäisin, mutta kääntäjä saattaa varoittaa siitä (ja tämä varoitus kannattaa ottaa todesta).  Jotta C- mäinen tapa voitaisiin säilyttää, voidaan käyttää tapaa 4 jolloin varoitusta ei tule, mutta generoidaan aivan vastaava koodi.  Oleellista on, että sijoitus on suluissa (muuten tulisi sijoitus a =(b!=0) ).  Mikäli asian toimimisesta on pieninkin epäilys kannattaa käyttää tapaa 1 tai 2!
10.3	Loogisten lausekkeiden yhdistäminen
Loogisia lauseita voidaan yhdistää loogisten operaatioiden avulla.  Tietysti lauseita voidaan yhdistää myös normaaleilla operaatioilla (+,- ,*,/), mutta tämä ei ole oikein hyvien tapojen mukaista.
10.3.1	Loogiset operaattorit &&, || ja !
&&  ja
||  tai
!   muuttaa ehdon arvon päinvastaiseksi (eli 0- >1, <>0 - >0) 

Mikäli yhdistettävät ehdot koostuvat esimerkiksi vertailuoperaattoreiden käytöstä, kannattaa ehtoja sulkea sulkuihin, jottei seuraa turhia epäselvyyksiä.
if ( ( rahaa > 50 ) && ( kello < 19 ) )  printf("Mennään elokuviin!\n);
if ( ( rahaa < 50 ) || ( kello >3 ) )    printf("Ei kannata mennä kapakkaan!\n");
if ( ( 8 <= kello ) && ( kello <= 16 ) ) printf("Pitäisi olla töissä!\n");
if ( ( !rahaa ) || ( sademaara < 10 ) )  printf("Kävele!\n");

Usein tulee vastaan tilanne, jossa pitäisi testata on luku jollakin tietyllä välillä.  Esimerkiksi onko
1900 <= vuosi <= 1999

palauttaisi C- kielisenä lauseena aina 1.  Miksikö?  Koska lause jäsentyy
( 1900 <= vuosi ) <= 1999   
     0 tai 1      <= 1999  eli aina 1

Oikea tapa kirjoittaa väli olisi:
if ( ( 1900 <= vuosi ) && ( vuosi <= 1999 ) ) ... 

Huomattakoon edellä miten väliä korostettiin kirjoittamalla välin päätepisteet lauseen laidoille.  
C- kielen sidontajärjestyksen ansiosta lause toimisi myös ilman sisimpiä sulkuja, mutta ne kannattaa pitää mukana varmuuden vuoksi.  Vertailtavat kannattaa kirjoittaa nimenomaan tähän järjestykseen, koska tällöin vertailu muistuttaa eniten alkuperäistä väliämme!
Vastaavasti jos arvon halutaan olevan välin ulkopuolella, kannattaa kirjoittaa:
if ( ( vuosi < 1900 ) || ( 1999 < vuosi ) ) ... 

Tällöin epäyhtälöiden suuntaa ei joudu koskaan miettimään, vaan arvot ovat aina siinä järjestyksessä kuin lukusuorallakin:
          1900  vuosi   1999               1900<=vuosi && vuosi <=1999
-----------o==============o--------------------

  vuosi  1900          1999  vuosi         vuosi<1900 || 1999 <vuosi 
===========o--------------o====================

10.3.2	Loogisen lausekkeen suoritusjärjestys
Loogiset lausekkeet suoritetaan AINA vasemmalta oikealle, kunnes ehdon arvo on selvinnyt.
Siis: Loogisen lausekkeen evaluoiminen lopetetaan heti kun ehdon arvo selviää (boolean expression shortcut).
Esimerkiksi:
if ( a || ( (b=c)==0 ) ) printf("Kukkuu\n");

Tai- operaattorin (||) oikealla puolella oleva sijoitus suoritetaan vain mikäli a==0:
a	b	c		sij.suor	tulostetaan		
0
0
5
5	?
?
?
?	0
3
0
3		kyllä
kyllä
ei
ei	kyllä
ei
kyllä
kyllä		

Tätä ominaisuutta voidaan käyttää hyväksi esimerkiksi tiedostoja luettaessa:
while ( f && f >> luku )
   summa += luku;

Tällöin lopussa olevaa tiedostoa ei enää lueta, koska AND- operaation (&&) arvo voidaan päättää epätodeksi heti ensimmäisestä osalauseesta.  Tosin edellinen silmukka toimii myös muodossa
while ( f >> luku ) summa += luku;

10.4	Bittitason operaattorit
Yksi C- kielen vahvoista piirteistä erityisesti alemman tason ohjelmoinnissa on mahdollisuus käyttää bittitason operaattoreita.
Loogisia operaattoreita &&,|| ja! ei pidä sotkea vastaaviin bittitason operaattoreihin:
&   bittitason AND
|   bittitason OR
^   bittitason XOR
~   bittitason NOT
<<  rullaus vasemmalle, 0 sisään oikealta 
>>  rullaus oikealle, 0 sisään vasemmalta (unsigned int ja int >=0)
                    , voi tulla 0 tai 1 sisään vasemmalta (int joka <0)
                      (laiteriippuva, esim. Turbo C:ssä tulee 1). 

Bittitason operaattoreita voidaan käyttää vain kokonaisluvuiksi muuttuviin operandeihin.  
Operaattoreiden toimintaa voidaan kuvata seuraavasti.  Olkoon meillä sijoitukset a=5; b=14;.  Kuvitellaan kokonaisluvut tilapäisesti 8 bitin mittaisiksi (oikeasti yleensä 16 tai 32 bittiä):
      	  Binaarisena 	desim.	         	
a     	  0000 0101   	  5   		
b     	  0000 1110   	 14   		
a & b   	  0000 0100   	  4   		
a | b   	  0000 1111   	 15   		
a ^ b   	  0000 1011   	 11   		
~a    	  1111 1010   	 -6   		
a<<2  	  0001 0100   	 20   		
b>>3  	  0000 0001   	  1   		
a && b  	  0000 0001   	  1   		
a || b  	  0000 0001   	  1   		
!a    	  0000 0000   	  0   		

Huomautus! Tyypillinen ohjelmointivirhe on sotkea keskenään loogiset ja bittitason operaattorit:
Tehtävä 10.95	Loogiset/bittitason operaattorit
Mitä tulostaa seuraava ohjelman osa.

{ /* binoper.c */
  int a=5; b=2;
  if ( a&&b ) printf("On ne!\n");
  if ( a&b )  printf("Ei ne ookkaan!\n");
  if ( a ) printf("a on!\n");
  if ( ~b ) printf("b ehkä on!\n");
  if ( !b ) printf("b ei ole!\n");
}

Tehtävä 10.96	Luku parilliseksi
Kirjoita funktio parilliseksi, joka palauttaa parametrinaan olevan kokonaisluvun pienemmäksi parilliseksi luvuksi "katkaistuna".  Eli esim. 3 - > 2.  5 - > 4.  4 - > 4.

10.5	if -  else - rakenne 
if - lauseesta on myös versio, jossa jotakin voidaan tehdä ehdon ollessa epätosi:
if ( ehto ) ylause1; 
else ylause2; 

Jälleen, mikäli jommassa kummassa osassa tarvitaan useampia lauseita, suljetaan lausejoukko lausesuluilla.  Tosin kannattaa taas harkita lausesulkujen käyttöä aina myös yhdenkin lauseen tapauksessa.
/* Samalle riville: */
  if ( a < 5 ) printf("a alle viisi!\n");
  else printf("a vähintään viisi!\n");
  
/* Eri riville: */
  if ( a < 5 ) 
    printf("a alle viisi!\n");
  else 
    printf("a vähintään viisi!\n");
  
/* Lausesulkujen käyttö: */
  if ( a < 5 ) {
    printf("a alle viisi!\n");
  }
  else { 
    printf("a vähintään viisi!\n");
  }

/* Seuraavaa tyyliä käytetään myös usein: */
  if ( a < 5 ) {
    printf("a alle viisi!\n");
  } else { 
    printf("a vähintään viisi!\n");
  }

10.5.1	Sisäkkäiset if- lauseet
Meillä oli aikaisemmin tehtävänä kirjoittaa funktio, joka palauttaa toisen asteen yhtälön ax2+bx+c=0 toisen juuren.  Tällöin oletuksena oli, että a<>0 ja D>=0.  Mikäli ratkaisukaavaa sovelletaan sellaisenaan ja a=0 tai D<0, niin tällöin ohjelman suoritus päättyy ajonaikaiseen virheeseen.
Voisimme muuttaa tehtävän määrittelyä siten, että kumpikin juuri pitää palauttaa ja funktion nimessä palautetaan tieto siitä, tuliko ratkaisussa virhe, eli jollei juuret olekaan reaalisia.
if ( a != 0 ) {
  D = b*b -  4*a*c;
  if ( D > 0 ) {
    ...
  }
  else {
    ...
  }
}
else {
  ...
}

Tosin yhtälö pystytään mahdollisesti ratkaisemaan myös kun a==0.  Tällöin tehtävä jakautuu useisiin eri tilanteisiin kertoimien a,b ja c eri kombinaatioiden mukaan:
   	   	   	   		             	juuret   	          	          		
a 	b 	c 	D 		yhtälön muoto	reaalisia	x1      	x2      		
0 	0 	0 	? 		 0 = 0     	juu    	0      	0      		
0 	0 	c 	? 		 c = 0     	ei    	0      	0      		
0 	b 	? 	? 		 bx - c = 0	juu    	-c/b    	-c/b    		
a 	? 	? 	>=0		 ax2 + bx + c = 0	juu   	(-b-SD)/2a	(-b+SD)/2a		
a 	? 	? 	<0 		 - " -     	ei   	          	          		

Algoritmiksi kirjoitettuna tästä seuraisi:
1.  Jos a=0, niin 
      Jos b=0
         Jos c=0 yhtälö on muotoa 0=0 joka on aina tosi
           palautetaan vaikkapa x1=x2 =0
         muuten (eli c<>0) yhtälö on muotoa c=0 joka on
           aina epätosi, palautetaan virhe
      muuten (eli b<>0) yhtälö on muotoa bx=c 
          joten voidaan palauttaa vaikkapa x1=x1=- c/b
2.  Jos a<>0, niin
      Jos D>=0 kyseessä aito 2. asteen yhtälö ja käytetään
        ratkaisukaavaa
      muuten (eli D<0) ovat juuret imaginaarisia

Funktio ja sen testiohjelma voisi olla esimerkiksi seuraavanlainen:
c-silm\p2_2.cpp - esimerkki 2. asteen yhtälön ratkaisemiseta 
#include <iostream.h>
#include <math.h>

int ratkaise_2_asteen_yhtalo(double &x1, double &x2,
                             double a, double b, double c)
{
  double D,SD;
  x1 = x2 = 0;
  if ( a == 0 ) {                     /*       bx + c = 0 */
    if ( b == 0 ) {                   /*            c = 0 */
      if ( c == 0 ) {                 /*            0 = 0 */
        return 0;                     /* id. tosi         */
      }                 /* c==0 */
      else {            /* c!=0 */    /*       0 != c = 0 */
        return 1;                     /* Aina epät.       */
      }                 /* c!=0 */
    }                   /* b==0 */
    else {              /* b!=0 */    /*       bx + c = 0 */
      x1 = x2 = - c/b;
      return 0;
    }                   /* b!=0 */
  }                     /* a==0 */
  else {                /* a!= 0 */   /* axx + bx + c = 0 */
    D = b*b -  4*a*c;
    if ( D >= 0 ) {                   /* Reaaliset juuret */
      SD  = sqrt(D);
      x1 = (- b- SD)/(2*a);
      x2 = (- b+SD)/(2*a);
      return 0;
    }                   /* D>=0 */
    else {                            /* Imag. juuret     */
      return 1;
    }                   /* D<0  */
  }                     /* a!=0 */
}

double P2(double x, double a, double b, double c)
{
  return (a*x*x + b*x + c);
}

int main(void)
{
  double a,b,c,x1,x2;
  do {
    cout << "Anna 2. asteen yhtälön a b c >";
    cin >> a >> b >> c;
    if ( ratkaise_2_asteen_yhtalo(x1,x2,a,b,c) ) {
      cout << "Yhtälöllä ei ole reaalisia juuria!" << endl;
    }
    else {
      cout << "1. ratkaisu on "  << x1
           << ".  Arvoksi tulee tällöin " << P2(x1,a,b,c) << endl;
      cout << "2. ratkaisu on "  << x2
           << ".  Arvoksi tulee tällöin " << P2(x2,a,b,c) << endl;
    }
  } while (a>0);
  return 0;
}

Edellinen funktio on äärimmäinen esimerkki sisäkkäisistä if- lauseista.  Jälkeenpäin sen luettavuus on erittäin heikko ja myös kirjoittaminen hieman epävarmaa.  Parempi kokonaisuus saataisiin lohkomalla tehtävää pienempiin osasiin aliohjelmien tai makrojen avulla.  
Sisäkkäisten if- lauseiden kirjoittamista voidaan helpottaa kirjoittamalla niitä sisenevästi, eli aloittamalla ensin tekstistä:
  if ( a == 0 ) {                     /*       bx + c = 0 */
  }                     /* a==0 */
  else {                              /* axx + bx + c = 0 */
    D = b*b -  4*a*c;
  }                     /* a!=0 */

Sitten täydennetään vastaavalla ajatuksella sekä if- osan että else- osan toiminta.
Jos funktiosta karsitaan kaikki ylimääräinen (kommentit ja ylimääräiset lausesulut) pois, saamme seuraavan näköisen kokonaisuuden:
c-silm\p2_2l.cpp - karsittu versio 2. asteen yhtälöstä 
int ratkaise_2_asteen_yhtalo(double &x1, double &x2,
                             double a, double b, double c)
{
  double D,SD;
  x1 = x2 = 0;
  if ( a == 0 )
    if ( b == 0 ) {
      if ( c == 0 ) return 0;
      else return 1;
    }
    else {
      x1 = x2 = - c/b;
      return 0;
    }
  else {
    D = b*b -  4*a*c;
    if ( D >= 0 ) {
      SD  = sqrt(D);
      x1 = (- b- SD)/(2*a);
      x2 = (- b+SD)/(2*a);
      return 0;
    }
    else return 1;
  }
}

Joskus kannattaa harkita olisiko luettavuuden kannalta paras esitystapa sellainen, että käsitellään "normaaleimmat" tapaukset ensin:
c-silm\p2_2n.cpp - normaalit tapaukset ensin ratkaisussa
int ratkaise_2_asteen_yhtalo(double &x1, double &x2, 
                             double a, double b, double c)
{
  double D,SD;
  x1 = x2 = 0;
  if ( a != 0 ) {
    D = b*b -  4*a*c;
    if ( D >= 0 ) {
      SD  = sqrt(D);
      x1 = (- b- SD)/(2*a);
      x2 = (- b+SD)/(2*a);
      return 0;
    }
    else return 1;
  }
  else /* a==0 */
    if ( b != 0 ) {
      x1 = x2 = - c/b;
      return 0;
    }
    else { /* a==0, b==0 */
      if ( c == 0 ) return 0;
      else return 1;
    }
}

Usein aliohjelman return- lauseen ansiosta else osat voidaan jättää poiskin: 
c-silm\p2_2r.cpp - else -osat pois 
int ratkaise_2_asteen_yhtalo(double &x1, double &x2, 
                             double a, double b, double c)
{
  double D,SD;
  x1 = x2 = 0;
  if ( a == 0 ) {
    if ( b == 0 ) {
      if ( c == 0 ) return 0;
      return 1;
    }
    x1 = x2 = - c/b;
    return 0;
  }

  D = b*b -  4*a*c;
  if ( D < 0 ) return 1;

  SD  = sqrt(D);
  x1 = (- b- SD)/(2*a);
  x2 = (- b+SD)/(2*a);
  return 0;
}

Edellä oli useita eri ratkaisuja saman ongelman käsittelemiseksi.  Liika kommenttien määrä saattaa myös sekoittaa luettavuutta kuten 1. esimerkissä.  Toisaalta liian vähillä kommenteilla ei ehkä kirjoittaja itsekään muista jälkeenpäin mitä tehtiin ja miten.  Jokainen valitkoon edelläolevista itselleen sopivimman kultaisen keskitien.
Huomattakoon vielä lopuksi, että rakenne
if ( c == 0 ) return 0;
else return 1; 

voitaisiin korvata rakenteella
return ( c != 0 ); 

Tehtävä 10.97	else - osat pois
Kirjoita ratkaise_2_asteen_yhtalo /* p2_2n.c */ ilman else - osia.

10.5.2	Useat peräkkäiset ehdot
Vaikka rakenne
if (ehto1) lause1;
else
  if (ehto2) lause2;
  else
    if (ehto3) lause3;
    else lause4; 

jossain mallissa sisennetäänkin ylläkuvatulla tavalla, on ajatus useimmiten lähempänä seuraavaa sisennystä:
c-silm\postimak.c - esimerkki samanarvoisista ehtolauseista
double postimaksu(double paino) 
/* Palautetaan kirjemaksun suuruus. 0 tarkoittaa pakettia */
{
  if      ( paino < 50 )   return  2.10;
  else if ( paino < 100 )  return  3.50;
  else if ( paino < 250 )  return  5.50;
  else if ( paino < 500 )  return 10.00;
  else if ( paino < 1000 ) return 15.00;
  else                     return  0.00;
}

Sovimme siis, että rakenne onkin muotoa:
if      ( ehto1 ) lause1
else if ( ehto2 ) lause2
else if ( ehto3 ) lause3
else              lause4 

Tehtävä 10.98	Lääni
Kirjoita aliohjelma 
	void laani(char rek_1_merkki) 
joka tulostaa missä läänissä auto on rekisteröity.  
Kirjaimen yhtäsuuruutta testataan if ( c == 'a' ) ...
Tehtävä 10.99	if- else
Mitä on muuttujien arvot seuraavien ohjelmanpätkien jälkeen (pöytätesti!)?

if (a<5)            
/*1*/ a=1; b=2; c=3;
  b=3;              
  a=6;              
c=7;                		if (a<0) a=3; else  
/*5*/ a=1; b=2; c=3;
if (a>2) b=3; a=6;  
c=7;                

/*2*/ a=1; b=2; c=3;   
if (a<5) b=3; a=6; c=7;
		/*6*/ a=1; b=2; c=3;   
if (a<- 5) if (a<0) a=6;
else a=2; c=7;		
				
/*3*/ a=1; b=2; c=3;
if (a<5) {b=3; a=6;}
c=7;                
		/*7*/ a=1; b=2; c=3;
if (a<- 5) b=3;      
  if (a<5) a=6;     
else a=2; c=7;      		
				
/*4*/ a=1; b=2; c=3; if (a<5)
b=3; else { a=6; c=7; }      
		/*8*/ a=1; b=2; c=3;
if (a<0) a=3; else; 
if (a>2) b=3; a=6;  
c=7;                		

Sisennä ohjelmanpätkät "asianmukaisesti".
10.6	do- while - silmukka 
Aikaisemmin olemme tutustuneet erääseen algoritmiin selvittää onko luku alkuluku vai ei.  Koska algoritmi on valmis, voimme kirjoittaa vastaavan ohjelman (% - operaattori antaa jakojäännöksen, 10 % 3 == 1 ):
c-silm\alkuluku.c - testataan onko luku alkuluku
#include <stdio.h>

/****************************************************************************/
int pienin_jakaja(int luku)
/* Palautetaan luvun pienin jakaja (alkuluvulle 1).
 Algoritmi:
   Negatiivisesta luvusta otetaan itseisarvo.
   Kokeillaan jokaista pienempää jakajaa 2,3,5,7 jne, kunnes
   jako menee tasan tai jakaja on liian iso ollakseen luvun jakaja.
*/
{
  int jakojaannos,jakaja=2,kasvatus=1;
  if ( luku < 0 ) luku = - luku;
  if ( luku == 2 ) return 1;
  do {
    jakojaannos = luku % jakaja;
    if ( jakojaannos == 0 ) return jakaja;
    jakaja  += kasvatus;
    kasvatus = 2;
  } while ( jakaja < luku/2 );
  return 1;
}

/****************************************************************************/
int main(void)
{
  int luku,jakaja;
  printf("Tutkin onko luku alkuluku.  Anna luku >");
  scanf("%d",&luku);
  jakaja = pienin_jakaja(luku);
  if ( jakaja == 1 )
    printf("Luku on alkuluku.\n");
  else
    printf("Luku on jaollinen esimerkiksi luvulla %d.\n",jakaja);
  return 0;
}

Käytimme tässä silmukkaa:
do 
  lause 
while (ehto); 

Koska esimerkin silmukassa oli useita suoritettavia lauseita, oli lauseet suljettu lausesuluilla.  Jälleen voi olla hyvä tapa käyttää AINA lausesulkuja.
Huomautus! Silmukoiden kanssa on syytä olla tarkkana sekä 1. kierroksen että viimeisen kierroksen kanssa.  Myös silmukan lopetusehdon on syytä muuttua silmukan suorituksen aikana.
Eräs tyypillinen esimerkki do- while silmukan käytöstä olisi seuraava:
c-silm\dowhile.c - lukujen lukeminen kunnes halutulla välillä
#include <stdio.h>

int main(void)
{
  int luku;
  do {
    printf("Anna luku väliltä [0- 20]>");
    fflush(stdin); /* Standardin mukaan ei määritelty! */
  } while ( ( scanf("%d",&luku) < 1 ) || ( luku < 0 ) || ( 20 < luku ) );
  printf("Annoit luvun %d\n",luku);
  return 0;
}

10.7	while - silmukka 
do- while - silmukka suoritetaan aina vähintään 1. kerran.  Joskus on tarpeen silmukka, jonka runkoa ei suoriteta yhtään kertaa.  Muutamme edellisen esimerkkimme käyttämään while - silmukkaa:
while ( ehto ) lause 

Muutamme samalla algoritmia siten, että 2:lla jaolliset käsitellään erikoistapauksena.  Näin pääsemme eroon "inhottavasta" kasvatus- muuttujasta.
c-silm\alkuluk2.c - alkulukutesti while-silmukalla
int pienin_jakaja(int luku) 
{
  int jakaja=3;
  if ( luku < 0 ) luku = - luku;
  if ( luku == 2 ) return 1;
  if ( (luku % 2) == 0 ) return 2;
  while ( jakaja < luku /2 ) {
    if ( (luku % jakaja) == 0 ) return jakaja;
    jakaja += 2;
  }
  return 1;
}

10.8	for - silmukka, tavallisin muoto 
Eräs C- kielen hienoimmista rakenteista on for- silmukka.  Usein C- hakkereiden tavoite on saada kirjoitettua koko ohjelma yhteen for- silmukkaan.  Tätä ei tietenkään tarvitse tavoitella, mutta se osoittaa for- silmukan mahdollisuuksia.
Tyypillisesti for- silmukkaa käytetään silloin, kun silmukan kierrosten lukumäärä on ennalta tunnettu:
c-silm\valinsum.c - esimerkki for-silmukasta
/*  Lasketaan yhteen luvut 1..ylaraja */
int valin_summa(int ylaraja)
{
  int i,summa=0;
  for (i=1; i<=ylaraja; i++) 
    summa += i;
  return summa;
}

Tehtävä 10.100	valin_summa
Muuta valin_summa - aliohjelmaa siten, että myös alaraja viedään parametrina.  Kirjoita pääohjelma, jolla toiminta voidaan testata.

Käytännössä tällaisia silmukoita ei saa tehdä, koska ongelman ratkaisuun on valmis kaava.  Millainen?

10.9	C- kielen lauseista
10.9.1	Sijoitusoperaattori =
Olemme tutustuneet jo C- kielen "normaaliin" sijoitusoperaattoriin =.  
Sen ansiosta, että myös sijoitus palauttaa arvon, pystyimme tekemään mm seuraavia temppuja:
if ( (b=a) != 0 ) ... /* Suoritetaan jos a!=0 */
a = b = c = 0; 

Sijoitus monelle muuttujalle yhtäaikaa onnistuu, koska sijoitus jäsentyy seuraavasti:
1.  a = ( b = (c = 0) ); -    sijoitus c=0 palauttaa arvon 0
2.  a = ( b =    0 );    -    sijoitus b=0 palauttaa arvon 0
3.  a =     0; 

10.9.2	Sijoitusoperaattori +=
valin_summa aliohjelmassa meillä esiintyi myös kaksi uutta sijoitusoperaattoria, jotka ovat lyhenteitä tavallisille sijoituksille:
lyhenne	tavallinen sijoitus	
summa += i;  
i++	summa = summa + i;
i = i + 1;	

+= sijoituksessa + voidaan korvata millä tahansa operaattoreista:
+   -    *   /   %   <<   >>   ^   &  |

Esimerkiksi luvun kertominen ja jakaminen 10:llä voitaisiin suorittaa:
luku *= 10;
luku /= 10; 

Siis muuttuja O= operandi voidaan ajatella korvattavaksi seuraavasti:
0.  laita sulut operandin ympärille
      muuttuja O= (operandi)
1.  kirjoita muuttujan nimi kahteen kertaan
      muuttuja muuttuja O= (operandi)
2.  siirrä = - merkki muuttujien nimien väliin
      muuttuja = muuttuja O (operandi) 

Tehtävä 10.101	+=
Mitä ovat muuttujien arvot seuraavien sijoitusten jälkeen:

int a=10,b=3,c=5;
a %= b;
b *= a+c;
b >>= 2; 

10.9.3	Lisäysoperaattori ++
Erittäin tyypillisiä C- operaattoreita ovat ++ ja- - .  
Nämä operaattorit lisäävät tai vähentävät operandin arvoa yhdellä.  Operandin tyypin tulee olla numeerinen tai osoitin.  
Operandeista on kaksi eri versiota: esilisäys ja jälkilisäys.
lyhenne	vastaa lauseita	
a = i++;
a = i—
a = ++i;
a = --i;	a = i;   i = i+1;
a = i;   i = i-1;
i = i+1; a = i;
i = i-1; a = i;	

Vaikka C- hakkerit rakentavatkin mitä ihmeellisimpiä kokonaisuuksia ++ - operaattorin avulla, kannattaa operaattorin liikaa käyttöä välttää.  Esimerkiksi lauseet joissa esiintyy samalla kertaa useampia lisäyksiä samalle muuttujalle, saattavat olla jopa määrittelemättömiä:
c-silm\plusplus.c - esimerkki ei-yksikäsitteisestä ++ operaattorin käytöstä
#include <stdio.h>
int main(void)
{
  double i=1.0,a;
  a = i++/i++;
  printf("a = %5.2lf, i = %5.2lf\n",a,i);
  return 0;
}

Ohjelma saattaa C- kääntäjän toteutuksesta riippuen tulostaa mitä tahansa seuraavista a:n ja i:in kombinaatioista:
a: 0.5 1.0 2.0
i: 2.0 3.0

Aluksi ++ - operaattoria kannattaa ehkä käyttää vain yksinäisenä lauseena lisäämään (tai vähentämään) muuttujan arvoa.
i++;

Lisäysoperaattoria EI PIDÄ käyttää seuraavissa tapauksissa:
1.	Muuttuja johon lisäysoperaattori kohdistuu, esiintyy samassa lausekkeessa useammin kuin kerran.
1.	Makrojen kutsuissa.
1.	Myös funktioiden kutsut ovat vaarallisia, koska funktio saattaakin olla makro!
Kiellettyjä on siis esimerkiksi:
a = ++i + i*i;
b = MACRO(i- - );
c = fun(++i,a,2*i); 

10.10	for - silmukka, yleinen muoto
Yleensä ohjelmointikielissä for- silmukka on varattu juuri siihen tarkoitukseen, kuin ensimmäinen esimerkkimmekin; tasan tietyn kierrosmäärän tekemiseen.
C- kielen for- silmukka on kuitenkin yleisempi:
/*     1.             2.  5.          4.  7.           3.  6.  */
for (alustus_lauseet; suoritus_ehto; kasvatus_lauseet) lause; 

for- silmukka vastaa melkein while- silmukkaa (ero tulee continue- lauseen käyttäytymisessä):
alustus_lauseet;               /* 1.     */
while ( suoritus_ehto ) {      /* 2.  5. */
  lause;                       /* 3.  6. */
  kasvatus_lauseet;            /* 4.  7. */
}

Mikäli esimerkiksi alustuslauseita on useita, erotetaan ne toisistaan pilkulla:
c-silm\valinsum.c - useita alustuslauseita for-silmukassa
/*  Lasketaan yhteen luvut 1..ylaraja */
int valin_summa_2(int ylaraja)
{
  int i,summa;
  for (summa=0, i=1; i<=ylaraja; i++) 
    summa += i;
  return summa;
}

Erittäin C:mäinen tapa tehdä yhteenlasku olisi:
c-silm\valinsum.c - C:mäinen silmukka
int valin_summa_3(int i)
{
  int s;
  for (s=0; i; s += i- - );
  return s;
}

Tämä viimeinen esimerkki on juuri niitä C- hakkereiden suosikkeja, joita ehkä kannattaa osin vältellä.
Tehtävä 10.102	1+2+..+i
Miksi valin_summa_3 laskee yhteen luvut 1..i?

10.11	break ja continue
10.11.1	break 
Joskus kesken silmukan tulee vastaan tilanne, jossa silmukan suoritus haluttaisiin keskeyttää.  Tällöin voidaan käyttää C- kielen break- lausetta, joka katkaisee sisimmän silmukan suorituksen.
c-silm\break.c - silmukan katkaisu keskeltä
#include <stdio.h>

int main(void)
{
  int summa=0,luku;
  printf("Anna lukuja.  Summaan niitä kunnes annat 0 tai summa > 20\n");
  while ( summa <= 20 ) {
    printf("Anna luku>");
    scanf("%d",&luku);
    if ( luku == 0 ) break;
    summa += luku;
  }
  printf("Lukujen summa on %d.\n",summa);
  return 0;
}

Jos edellä olisi ollut alustus luku=1, olisi tietenkin  while - silmukan ehto voitu kirjoittaa muodossa
  while ( ( luku != 0 ) && ( summa <= 20 ) ) {

mutta aina ei voida break - lausetta korvata näin yksinkertaisesti.  Esimerkiksi seuraava olisi jo hankalampi muuttaa:
c-silm\break2.c - esimerkki, jossa vaikea tulla toimen ilman breakiä
  while ( summa <= 20 ) {
    printf("Anna luku>");
    if ( !scanf("%d",&luku ) break;
    if ( luku == 0 ) break;
    summa += luku;
  }

break - lauseen vika on lähinnä siinä, ettei siitä suoraan nähdä sisäkkäisten silmukoiden tapauksessa sitä, mihin saakka suoritus katkeaa.  Epäselvissä tapauksissa silmukan katkaisu voidaan hoitaa goto - lauseella.
Silmukka voidaan katkaista tietenkin myös muuttamalla silmukan lopetusehtoon vaikuttavia muuttujia.  Varsinkin for- lauseen tapauksessa silmukan indeksin arvon muuttaminen muualla kuin kasvatus- lauseessa on todella väkivaltaista ja rumaa, eikä tällaista pidä mennä tekemään.
10.11.2	continue 
Vastaavasti saattaa tulla tilanteita, jolloin itse silmukan suoritusta ei haluta katkaista, mutta menossa oleva kierros halutaan lopettaa.  Tällöin continue - lauseella voidaan suoritus siirtää suoraan silmukan loppuun ja näin lopettaa tämän kierroksen suoritus:
c-silm\continue.c - silmukan lopun ohittaminen
#include <stdio.h>

int main(void)
{
  int alku= - 5, loppu=5,i;
  double inv_i;
  printf("Tulostan lukujen %d -  %d käänteisluvut\n",alku,loppu);
  for (i = alku; i<=loppu; i++ ) {
    if ( i == 0 ) continue;
    inv_i = 1.0/i;
    printf("%3d:n käänteisluku on %5.2lf.\n",i,inv_i);
  }
  return 0;
}

Tehtävä 10.103	continuen korvaaminen
Kirjoita käänteislukujen tulostusohjelma ilman continue- lausetta.

10.12	goto - lause 
Lähes kaikissa ohjelmointikielissä on goto- lause, mutta usein strukturoidun ohjelmoinnin kannattajat ovat julistaneet sen pannaan.  Julistus on aivan hyvä, sillä ainakin 90% tapauksista, joissa goto- lausetta tekisi mieli käyttää, voidaan korvata strukturoidummalla tavalla.
Seuraavissa tapauksissa goto- lause voidaan hyväksyä:
1.  silmukan suorituksen katkaisu
2.  aliohjelman loppuun hyppääminen (silloin kun returnia
    ei lopetustoimenpiteiden vuoksi voida käyttää)
3.  jos strukturoitu rakenne on selvästi monimutkaisempi

Mikäli yhteen aliohjelmaan tulee useita goto - lauseita, on rakenne selvästikin suunniteltu väärin, ja se on mietittävä uudelleen.
goto - lauseen syntaksiin kuuluu paikan nimi, johon hypätään.  Tämä nimiö täytyy esitellä ohjelmassa laittamalla se tarvittavaan kohtaan:
c-silm\goto.c - hyppy ulos silmukasta
#include <stdio.h>

int main(void)
{
  int summa=0,luku;
  printf("Anna lukuja.  Summaan niitä kunnes annat 0 tai summa > 20\n");
  while (summa <= 20) {
    printf("Anna luku>");
    if ( !scanf("%d",&luku) ) goto luvut_loppu;
    if ( luku == 0 ) goto luvut_loppu;
    summa += luku;
  }
luvut_loppu:
  printf("Lukujen summa on %d.\n",summa);
  return 0;
}

Siis break ja continue ovat "piilotettuja" goto- lauseita.  Mikäli break tai continue pitäisi saada toimimaan 2 tasoa ulospäin, on pakko käyttää goto- lausetta.
Huom!  Ensisijaisesti pyri välttämään goto- lauseita!
Tehtävä 10.104	Eri silmukoiden vertailu
Kirjoita lukujen alaraja- ylaraja summausfunktio käyttäen 
	a)  while - lausetta
	b)  do- while - lausetta
	c)  goto - lausetta
Muista, että alaraja saattaa olla suurempi kuin yläraja, eli summa väliltä [3,0] on 0!
10.13	switch - valintalause 
Jäsenrekisteriohjelmamme päävalinta olisi näppärintä toteuttaa switch - lauseella:
menut.05\kerho.cpp - päävalinta switch -lauseella
int paavalinta(cKerho &kerho)
{
  char nappain;

  while (1) {

    paamenu(kerho);  // Huom päämenun muuttunut parametrilista

    nappain = odota_nappain("?012345",0,MERKKI_ISOKSI);

    switch (nappain) {
      case '?': avustus(nappain);                break;
      case '0': return 0;
      case '1': lisaa_uusi_jasen(nappain);       break;
      case '2': etsi_jasenen_tiedot(nappain);    break;
      case '3': tulosteet(nappain);              break;
      case '4': tietojen_korjailu(nappain);      break;
      case '5': paivita_jasenmaksuja(nappain);   break;
      default : cout << "Näin ei voi käydä!" << endl;  return 1;
    }

  }
}

switch - lauseessa case osien lopuksi break on yleensä välttämätön. break estää suorittamasta seuraavia rivejä.  
Joskus harvoin breakin puuttumista voidaan käyttää hyväksi, mutta tällöin pitää olla todella tarkkana:
c-silm\switch.c - swicth, jossa break tahallaan jätetty pois
switch (operaatio) {
  case 5:                 /* Operaatio 5 tekee saman kuin 4 */
  case 4: x *= 2; break;  /*           4 laskee x=2*x       */
  case 3: x += 2;         /*           3 laskee x=x+4       */
  case 2: x++;            /*           2 laskee x=x+2       */ 
  case 1: x++; break;     /*           1 laskee x=x+1       */ 
  default: x=0; break;    /* Muut nollaavat x:än            */
}

Lause default suoritetaan jos mikään case- osista ei ole täsmännyt (tai tietysti jos jokin break puuttuu). default- lauseen ei tarvitse olla viimeisenä, mutta tällöin vaaditaan taitavaa breakin käyttöä, siis paras pitää default viimeisenä!
Yleistä switch- lausetta ei voi korvata joukolla if- lauseita käyttämättä goto- lausetta.  Mikäli kuitenkin jokaisen case rakenteen perässä on break, voidaan switch-  korvata sisäkkäisillä if- else - rakenteilla.
Tehtävä 10.105	switch - > if- goto
Kirjoita switch.c ohjelmanpätkä käyttäen if ja goto - rakenteita muuttamatta itse suoritettavia lauseita.
Tehtävä 10.106	Päävalinta
Kirjoita paavalinta käyttäen vain if ja else rakenteita.
Tehtävä 10.107	lääni, versio 2
Kirjoita laani- aliohjelma käyttäen switch- rakennetta.

10.13.1	|| ei toimi switch - lauseessa!
On huomattava, että jos halutaan suorittaa jokin switch- lauseen osista kahdella eri arvolla, EI voida käyttää rakennetta:
switch (operaatio) {  /* VÄÄRIN: */ 	
  case 4 || 5: x *= 2; break;  /*    5 tai  4 laskee x=2*x       */ 	
  case 3: x += 2;              /*           3 laskee x=x+4       */
  case 2: x++;                 /*           2 laskee x=x+2       */ 
  default: x=0; break;         /* Muut nollaavat x:än            */
}

Kääntäjä ei tästä varoita, koska kaikki on aivan kieliopin mukaista. 4 || 5 on kahden loogisen lausekkeen OR eli 1 || 1 eli 1.  Siis 
case 4 || 5:
on sama kuin
case 1: 

Jos esimerkistämme ei olisi poistettu lausetta case 1:, olisi kääntäjä varoittanut koska 1 olisi esiintynyt kahdesti.
10.14	Ikuinen silmukka 
Usein silmukat lipsahtavat tahottomasti sellaisiksi, ettei niistä koskaan päästä ulos.  Ikuisen silmukan huomaa heti esimerkiksi siitä, ettei silmukan rungossa ole yhtään lausetta joka muuttaa silmukan ehdon totuusarvoa.
Joskus kuitenkin C- kielessä tehdään tarkoituksella "ikuisia" - silmukoita:
for (;;) {
  ...
  if (lopetus_ehto) break;
  ...
} 

while (1) {
  ...
  if (lopetus_ehto) break;
  ...
} 

do {
  ...
  if (lopetus_ehto) break;
  ...
} while (1); 

Näissä kahdessa ensimmäisessä korostuu silmukan ikuisuus.  Viimeinen ei ole hyvä vaihtoehto.
Tällaiset ikuiset silmukat ovat hyväksyttävissä silloin, kun silmukan lopetusehto on luonnollisesti keskellä silmukkaa.  Usein kuitenkin lauseiden uudelleen järjestelyllä lopetusehto voidaan sijoittaa silmukan alkuun tai loppuun, jolloin tavallinen while-  , do- while -  tai for - silmukka kelpaa.
10.14.1	Yhteenveto silmukoista

11.	Oliosuunnittelu
Mitä tässä luvussa käsitellään?
•	vaatimukset ohjelman toteutukselle
•	olioiden etsiminen
•	CRC- kortit
•	kuka huolehtii harrastuksista?
11.1	Olio on luokan esiintymä
Ennenkuin voimme aloittaa ohjelman toteutuksen, pitää meidän suunnitella mitä luokkia ohjelmassamme tarvitaan.  Ohjelmassa olevat oliot ovat sitten näiden luokkien esiintymiä (ilmentymiä, instance).
11.2	Tavoitteet
Asetamme ensin ohjelman toteutukselle ulkoisen toiminnan lisäksi tiettyjä lisätavoitteita:
•	käyttöliittymä (tekstipohjainen vaiko ikkunoitu) on voitava muuttaa kohtuullisella ohjelmoinnilla
•	ohjelma on voitava pienillä muutoksilla muuttaa muuksikin kuin jäsenrekisteriksi (puhelinluettelo, levyrekisteri)
•	jäseneen voitava helposti lisätä kenttiä
11.3	Luokat
Tavoitteiden aikaansaamiseksi näyttäisi, että tarvitsemme ohjelmassa ainakin seuraavat kolme luokkaa:
•	käyttöliittymää ylläpitävä luokka (cNaytto)
•	rekisteriä ylläpitävä luokka (cKerho)
•	yksittäinen jäsen (cJasen)
Kullekin luokalle täytyy antaa selvät vastuualueet ja tieto siitä, miten kommunikoidaan muiden luokkien kanssa ja minkä luokan kanssa yleensäkään tarvitsee kommunikoida.
11.4	CRC- kortit
Timothy A. Budd [B] ehdottaa luokkasuunnittelun avuksi CRC-kortteja (Class Responsibility Collaborator, luokan vastuu ja avustajat).  Kortti on 4"x6" (10 x15 cm) kooltaan ja se jaetaan 3 osaan: luokan nimi, vastuu (eli tehtävät) ja avustajat.  Kortin koko on perusteltu sillä, että se on riittävän iso, jotta luokan vastuualueet voidaan siihen kirjoittaa ja toisaalta jos vastuualueet ei mahdu korttiin, on luokka liian iso ja se pitää jakaa useammaksi osaluokaksi.  Huomattakoon että luokkaa suunniteltaessa ei juurikaan oteta kantaa siihen, kuka luokkaa käyttää!
Korttien takapuolelle voidaan kirjoittaa luokan yksityiset tiedot, eli ne tiedot joita joudutaan käyttämään jotta luokka voi hoitaa sovitun vastuunsa.
CRC-kortteja on sitten tarkoitus tutkia työryhmän jäsenten kesken antamalla kortti aina yhdelle ryhmän jäsenelle joka näin voi tarkistaa saako hän ko. luokasta tarvitsemansa tiedot (kääntämättä korttia).  Jollei saa, luokkia on vielä helppo muuttaa kun ohjelmaa ei ole kirjoitettu.	
11.4.1	Jäsen- luokka (cJasen)
Luokan nimi: cJasen	Avustajat:	
Vastuualueet:
(- ei tiedä kerhosta, eikä käyttöliittymästä)
- tietää jäsenen kentät (nimi, hetu, puhnro, jne.)
- osaa tarkistaa tietyn kentän oikeellisuuden (syntaksin)
-	osaa muuttaa |Ankka Aku|..| - merkkijonon jäsenen tiedoiksi
-	osaa antaa merkkijonona i:n kentän tiedot
-	osaa laittaa merkkijonon i:neksi kentäksi
	- merkkijonot	
 
11.4.2	Kerho- luokka (cKerho)
Luokan nimi: cKerho	Avustajat:	
Vastuualueet:
-	pitää yllä varsinaista rekisteriä, eli osaa lisätä ja poistaa jäsenen
-	lukee ja kirjoittaa kerhon tiedostoon
-	osaa etsiä ja lajitella

	-	cJasen	

11.4.3	Käyttöliittymä- luokka (cNaytto)
Luokan nimi: cNaytto	Avustajat:	
Vastuualueet:
-	hoitaa kaiken näyttöön tulevan tekstin
-	hoitaa kaiken tiedon pyytämisen käyttäjältä
(-	ei tiedä kerhon eikä jäsenen yksityiskohtia)

	- cJasen
- cKerho	

11.4.4	Luokkajaon tarkastelua
Miksi näyttö ei saa tietää jäsenen yksityiskohtia?  Jos näyttö tietäisi jäsenen yksityiskohdat, pitäisi myös cNaytto-luokkaa muuttaa kun muutetaan jotakin jäsenen yksityiskohtaa, eli esimerkiksi lisätään fax- numero.  Käytännössä näytön pitää kuitenkin saada tämä muutos tietoonsa.  Miten?
Näyttö voi esimerkiksi aina kysyä jäseneltä montako kenttää tällä on.  Samoin näyttö voi pyytää jäsentä antamaan 1. kentän (nimi) merkkijonona, 2. kentän merkkijonona jne.  Näin voidaan tulostaa jäsenen tiedot näyttöön tietämättä tarkkaan mitä kenttiä jäsenessä on.
Entä tietojen lukeminen?  Näyttö voi myös kysyä jäseneltä sen tekstin, joka täytyy käyttäjälle kirjoittaa, kun pyydetään käyttäjää antamaan 3. kentän tiedot ("Katuosoite").  Tämän jälkeen näyttö voi tulostaa tämän tekstin näyttöön ja jäädä odottamaan käyttäjältä merkkijonoa.  Kun käyttäjä antaa merkkijonon, pyydetään jäsentä muuttamaan annettu merkkijono 3:nen kentän tiedoiksi.
Aikanaan saattaa tulla vastaan tilanne, jossa on edullista jakaa näyttöluokka useampaan alaluokkaan, eli esim. yleinen näyttö (cNaytto), kerhon näyttö (cKerhon_naytto) ja jäsenen näyttö (cJasenen_naytto).
Lisäksi yksittäisten kenttien käsittelyä voi auttaa kenttä-luokan käyttö.  Peruskenttäluokasta voidaan periä erikoistuneita kenttäluokkia.  Tiedon säilyttämisen apuna kerholuokalla voi olla tietorakenne-luokka.  Etsiminen voidaan ehkä jättää etsimis- ja selailu -luokan tehtäväksi.  Alkuun päästään kuitenkin suunnitelman mukaisella kolmella luokalla.
11.5	Harrastukset mukaan
Entä sitten kun haluamme lisätä kerholaisille harrastuksia.  Ainakin tarvitaan cHarrastus- luokka lisää.  Kuka huolehtii harrastuksista?  
Jos valinta tehdään valitun tiedostomuodon mukaan, niin mahdollisuuksia on:
11.5.1	"Oliomalli"
Oletetaan aluksi että Jäsen huolehtii harrastuksistaan.  Tällöin Kerho ei tarvitse mitään muutoksia ja Näyttökin vain sen verran, että tietää kysellä ja tulostaa Jäsenelle tulleita lisäominaisuuksia.  Jäsenen ominaisuuksiin lisätään harrastuksien ylläpito vastaavasti kuin Kerho ylläpiti jäsenistöä.
11.5.2	Relaatiomalli
Jos valitaan relaatiomallin mukainen tiedostorakenne, niin ehkä myös luokat kannattaa suunnitella vastaavasti.  Tässä mallissa Jäsen ei tiedä mitään harrastuksistaan ja cJasen pysyykin muuttumattomana.  cHarrastus tekee täsmälleen samat asiat kuin cJasen, paitsi tietenkin omille ominaisuuksilleen.
Jos mahdollisimman paljon vastuuta harrastusten ylläpidosta annetaan Kerholle, huomataan että toistetaan samat ominaisuudet, joita kirjoitettiin jo jäsenistöä varten.  Tämän vuoksi Kerhon roolia kannattaakin hieman selventää siten, että tietorakenteiden ylläpitoa varten tehdään omat luokat ja Kerho komentaa näitä luokkia.
11.5.3	Harrastus- luokka (cHarrastus)
Vertaa toimintaa cJasen- luokan toimintoihin.
11.5.4	Kerho- luokka (cKerho)
Luokan nimi: cKerho	Avustajat:	
Vastuualueet:
-	huolehtii cJasenet ja cHarrastukset - luokkien välisestä yhteistyöstä ja välittää näitä tietoja pyydettäessä
-	lukee ja kirjoittaa kerhon tiedostoon pyytämällä apua avustajiltaan

	-	cJasenet
- cHarrastukset	

11.5.5	Jäsenet- luokka (cJasenet)
Luokan nimi: cJasenet	Avustajat:	
Vastuualueet:
-	pitää yllä varsinaista jäsenrekisteriä, eli osaa lisätä ja poistaa jäsenen
-	lukee ja kirjoittaa jäsenistön tiedostoon
-	osaa etsiä ja lajitella

	-	cJasen	

11.5.6	Harrastukset- luokka (cHarrastukset)
Luokan nimi: cHarrastukset	Avustajat:	
Vastuualueet:
-	pitää yllä varsinaista harrasterekisteriä, eli osaa lisätä ja poistaa harrastuksen
-	lukee ja kirjoittaa harrastukset tiedostoon
-	osaa etsiä ja lajitella

	-	cHarrastus	

Koska Harrastukset ja Jäsenet ovat täsmälleen samanlaisia lukuunottamatta sitä, mitä alkioita ne käsittelevät, voidaan käytännössä C++:lla ensin tehdä malliluokka, josta generoidaan kumpikin hieman eri versio.
Näin päästään siihen tilanteeseen, jossa myös rinnakkaisten rakenteiden lisääminen Harrastuksille vaatii vain hyvin vähän uutta ohjelmointia.
Huomattakoon, että sekä cJasenet että cHarrastukset ovat pelkkiä abstrakteja tietorakenneluokkia, niiden sisäinen talletustapa voi olla mikä vaan (taulukko, lista, puu) ulkoisen rajapinnan ollessa silti edellisen suunnitelman kaltainen.

12.	Jäsenrekisterin runko
Mitä tässä luvussa käsitellään?
•	tietorakenteen valinta
12.1	Runko ilman kommentteja
Emme vielä täysin osaa tehdä edes runkoa jäsenrekisteriohjelmaamme, mutta esitämme tästä huolimatta jonkinlaisen toimivan rungon ohjelmalistauksen.  Koodista on poistettu kommentit, jotta listaus mahtuisi tässä vaiheessa lyhyempään tilaan.  Koodissa tulee lukuisia vielä käsittelemättömiä osia, joita käsittelemme tarkemmin myöhemmin.  Samoin etsimme myöhemmin sopivan tavan kommentoida aliohjelmia.
Rungon tarkoituksena on tarjota näkyväksi ne toiminnot, jotka ohjelman suunnitelmassa päätettiin tehdä.  Samoin tavoitteena on testata valitun tietorakenteen toimivuus.  Jatkossa toimintoja lisätään tähän runkoon.
Tämä runko ei tietenkään ole syntynyt kerralla, vaan alkuperäinen runko on ollut esimerkiksi ilman tietorakenteita (vrt. menut.05\kerho.cpp)
12.2	Valittava tietorakenne 
Minkälaisen tietotyypin voisimme valita?  Vaihtoehtoja tulee ehkä lähes yhtäpaljon kuin ohjelmoijiakin on.  Voimme kuitenkin vertailla eräiden rakenteiden etuja ja haittoja.  Jos mahdollista, tietorakenteiden tulisi olla yhtenäinen tehdyn oliosuunnitelman kanssa.  Seuraavista ehdotuksista mikään ei ole ristiriidassa edellisessä luvussa tehdyn oliosuunnitelman kanssa.  Vasta kun valitaan harrastusten talletustapaa, joudumme valinnan eteen.
Lähdemme siitä ajatuksesta, että koko käsiteltävä aineisto on kerralla keskusmuistissa.  Voisimme tietenkin operoida myös suoraan levylle, mutta oppimisen tässä vaiheessa voi seuraava ratkaisu olla helpompi:
lue tiedosto muistiin
käsittele aineistoa muistissa
talleta aineisto takaisin levylle

12.2.1	Taulukko 
Taulukko on kiinteä tietorakenne, jota luotaessa täytyy jo tietää monelleko ihmiselle varaamme tilaa.  Tässä tulee äkkiä varattua tilaa joko liikaa, jolloin tila ei riitä muille toiminnoille, tai liian vähän, jolloin kaikki henkilöt eivät mahdu rekisteriin.  Esimerkissämme olemme varanneet n. 300 tavua/henkilö.  Tilan varaaminen sadalle henkilölle veisi jo 30000 tavua.  Usein sata ei edes riitä!

Kuva 12.4 Taulukko

12.2.2	Linkitetty lista
Linkitetty lista on rakenne, jossa meillä on tieto vain listan 1. alkiosta.  Tämän jälkeen kukin alkio tietää itseään seuraavan alkion, kunnes listan viimeinen alkio ei enää osoita minnekään.

Kuva 12.5 Linkitetty lista

Listan hyvänä puolena on se, ettei etukäteen tarvita mitään tietoa alkioiden lukumäärästä.  Alkioita voidaan lisätä listaan joko alkuun, keskelle tai loppuun niin kauan kuin muistia riittää.
Oppimisen tässä vaiheessa kuitenkin linkitetyn listan käsittelyalgoritmit (lisäys, poisto, lajittelu jne..) saattavat olla liian työläitä.  
Mikäli rakennamme ohjelman huolella, ei tietorakenteen vaihtaminen jälkeen päinkään ole mahdoton tehtävä.  Tätä auttaa vielä aikaisemmin tekemämme valinta käyttää  abstraktia rajapintaa (lisää, poista, etsi) tietorakenneluokan (cKerho tai cJasenet) ja käyttöliittymän (cNaytto) välillä.
12.2.3	Sekarakenne 
Valitsemme tähän toteutukseen tietorakenteeksi sekarakenteen: 
Siis perusrakenteena meillä on cKerho- tyyppi, joka pitää sisällään kerhon perustiedot.  Kerhosta on osoitin taulukkoon, jossa on osoittimet varsinaisiin henkilöiden tietoihin (cJasen).  
Henkilöiden tiedoille varattua tilaa ei ole olemassa ennenkuin sitä tarvitaan.  Siis varataan kullekin kerhoon lisättävälle henkilölle hänen tiedoilleen tarvittava uusi n. 300 tavun "möykky" lisäyksen yhteydessä.
Osoitintaulukkoon sijoitetaan sitten vastaavaan paikkaan sen muistiosoitteen arvo, josta henkilölle tarvittava tila saatiin varattua.
Tässäkin rakenteessa on se huono puoli, että osoitintaulukon koko pitää päättää ennen kuin sinne voidaan sijoittaa osoitteita.  Yksi osoite vie kuitenkin enimmilläänkin tilaa 4 tavua, joten kiinteää tilan varausta esim. 1000 henkilön taulukossa tulee vain 4000 tavua.
Hyvinä puolina rakenteessa on sen suhteellisen helppo käsittely sekä lisäyksen, poiston että lajittelun tapauksessa. 

Kuva 12.6 Tietorakenne kun kerho tallettaa jäsenet

Tehtävä 12.108	Lisäys
Kirjoita algoritmi henkilön lisäämiseksi rakenteeseen.
Tehtävä 12.109	Etsiminen
Kirjoita algoritmi tietyn henkilön etsimiseksi (vaikkapa nimellä).
Tehtävä 12.110	Poisto
Kirjoita algoritmi löydetyn henkilön (miten löytö kannattaa säilyttää?) poistamiseksi rakenteesta.
Tehtävä 12.111	Lajittelu
Kirjoita algoritmi rakenteen lajittelemiseksi aakkosjärjestykseen.  Mitä lajittelussa kannattaa vaihdella?
12.2.4	Ohjelman runko
Ohjelmalistaus löytyy listausmonisteesta tiedostosta
runko.1\kerho.cpp
12.3	Harrastukset mukaan
Jos halutaan tallettaa myös kullekin jäsenelle vaihteleva määrä harrastuksia, on jälleen mahdollisuuksia useita.  Tietorakennetta valittaessa voidaan käyttää samaa kriteeriä kuin tiedostoakin valittaessa. Välttämätöntä tämä ei kuitenkaan ole, vaan voidaan sisäisesti tietysti käyttää myös erilaista rakennetta kuin ulkoisesti.
12.3.1	Tiedot jäsenen yhteyteen
Jos tiedoston muoto on sellainen että harrastukset on lueteltu jäsenen tietojen yhteydessä, kannattaa tietorakennekin valita vastaavasti.
Ankka Aku          |010245-123U|Ankkakuja 6 |12345      |ANKKALINNA |12-12324   |          | 
- kalastus                 | 1955 | 20
- laiskottelu              | 1950 | 20
- työn pakoilu             | 1952 | 40
Susi Sepe          |020347-123T|            |12555      |Takametsä  |           |          | 
- possujen jahtaaminen     | 1954 | 20
- kelmien kerho            | 1962 |  2
Ponteva Veli       |030455-3333|            |12555      |Takametsä  |           |          | 
- susiansojen rakentaminen | 1956 | 15

Nyt tietorakenne voisi olla tilanteesta riippuen mikä tahansa edellä esitetyistä siten, että kerhosta alkava rakenne toistuu jäsenen kohdalla.
Esimerkiksi linkitetty lista:

Kuva 12.7 Harrastukset linkitettynä listana
12.3.2	Relaatiomalli
Jos tiedot on talletettu relaatiomallin mukaan, voi olla kannattavaa tehdä myös sisäinen tietorakenne vastaavaksi.  Vaikka jatkossa emme toteutakaan kerhoon vielä harrastuksia, teemme tietorakenteen ja oliot sellaisiksi, että harrastusten käsittely jälkeenpäin olisi mahdollisimman helppoa.  
Toteutettavaa ohjelmaa ajatellen tästä valinnasta seuraa yksi byrokratiaporras (cKerho <-> cJasenet) lisää, joka aluksi saattaa tuntua turhalta.  Valinta maksaa itseään takaisin vasta ongelman monimutkaistuessa.  Tähän samaan monimutkaistumisongelmaan tulemme törmäämään jatkossakin.   Yksinkertaisin mahdollisuus, jolla vaadittu toimenpide juuri ja juuri voidaan suorittaa, johtaa usein jatkoa ajatellen umpikujaan.
Tehtävä 12.112	Lisää harrastus
Kirjoita algoritmi uuden harrastuksen lisäämiseksi. (Ks. alla oleva kuva)
Tehtävä 12.113	Mitä harrastaa
Kirjoita algoritmi, joka vastaa kysymykseen "Mitä henkilö N harrastaa?"
Tehtävä 12.114	Kuka harrastaa
Kirjoita algoritmi, joka vastaa kysymykseen: "Ketkä harrastavat  harrastusta H?".
Tehtävä 12.115	Poista harrastus
Kirjoita algoritmi harrastuksen poistamiseksi.
Tehtävä 12.116	Jäsenen poistaminen
Kirjoita algoritmi, joka poistaa jäsenen jonkin nimi on N.
Tehtävä 12.117	"Roskaharrastukset"
Kirjoita algoritmi, joka poistaa "roskaharrastukset", eli ne harrastukset, joille ei löydy "omistajaa".  Tällaiseen tilanteeseen hyvässä ohjelmassa ei tietenkään koskaan päädytä.
Tehtävä 12.118	Monta saman lajin harrastajaa
Jos harrastusten nimet ovat kovin pitkiä ja harrastuksista talletetaan vielä kuhunkin harrastukseen liittyvää lisätietoa, niin edellä mainittu rakenne käy tehottomaksi heti kun löytyy useita saman lajin harrastajia.  Esitä ratkaisu, jossa hukkatilaa (mm. saman harrastuksen nimen toistamista) ei esiinny, mutta jolla voidaan tehdä kaikki samat tehtävät, kuin esitetyllä ratkaisulla.

Kuva 12.8 Harrastukset relaation avulla

12.3.3	Ohjelman runko harrastusten kanssa
Ohjelmalistaus, jossa harrastuksetkin ovat mukana, löytyy listausmonisteesta tiedostosta
runko.1\kerhohar.
 
13.	C- kielen taulukoista
Mitä tässä luvussa käsitellään?
•	C- kielen taulukot
•	taulukoiden ja osoittimien yhteys
•	C- merkkijonot (char *)
•	C- merkkijonojen ja C++ merkkijonojen yhteiskäyttö
•	päällekkäiset muistialueet (union) ja luetteloidut tyypit (enum)
•	moniulotteiset taulukot

Syntaksi:
Taulukon esittely:    alkiontyyppi taulukonnimi[koko_alkioina];
Alkioon viittaaminen: taulukonnimi[alkion_indeksi]
Muista                1. indeksi = 0
                      viimeinen  = koko_alkiona-1 
Silmukoissa           for (i=0; i<koko; i++) ...
C-mjonon esittely     char jono[tarvittava_merkkimaara+1];
2-ul.taulukon es:     alkiontyyppi taulukonnimi[rivimaara][sarakemaara];

13.1	Yksiulotteiset taulukot
C- kielessä taulukoita ei oikeastaan ole, tai ainakin ne ovat '2. luokan kansalaisia'.  Lausuma tarkoittaa sitä, että taulukoista on käytettävissä vain 1. alkion osoite ja esimerkiksi taulukon sisällön sijoittaminen toiseen taulukkoon ei onnistu sijoitusoperaattorilla.
13.1.1	Taulukon määrittely
Taulukko määritellään kertomalla taulukon alkioiden tyyppi ja alkioiden lukumäärä:
int k_pituudet[12]; 

Tällöin taulukon 1. alkion indeksi on 0 ja 12. alkion indeksi on 11.
Määrittelyllä muuttujasta k_pituudet tulee osoitin kokonaislukuun; taulukon alkuun.
13.1.2	Taulukon alkioihin viittaaminen indeksillä
Taulukon alkioon voidaan viitata alkion indeksin avulla
k_pituudet[0]=31; /* tammikuu */
k_pituudet[1]=28; /* helmikuu */

Vaarallista on, että kukaan ei kiellä viittaamasta 
k_pituudet[24]=31; 

vaikka moista paikkaa taulukkoon ei alunperin ole edes varattu.
Indeksiviittaus k_pituudet[2] tarkoittaa itse asiassa viittausta *(k_pituudet+2)
k_pituudet+2 ──┐ 
               │
k_pituudet     │
  │            v  
  │      0  1  2  3  4  5  6  7  8  9 10 11 
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│31│28│31│30│31│30│31│31│30│31│30│31│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

eli 2 paikkaa eteenpäin taulukon alusta lukien.  
Taulukko voitaisiin nollata seuraavalla silmukalla:
int i;
...
for (i=0; i<12; i++) k_pituudet[i]=0; 

Huomautus!  Taulukoiden käsittelyssä on muistettava, että indeksi liikkuu välillä [0,YLÄRAJA[.
13.1.3	Viittaaminen osoittimella 
Taulukoihin voidaan viitata myös osoittimen avulla:
c-taul\kuut.c - esimerkki osoittimista taulukkoon
  int *tammikuu,*helmikuu,*joulukuu; 
...
  tammikuu  = k_pituudet;
  helmikuu  = tammikuu+1;
  joulukuu  = k_pituudet+11;
  *tammikuu = 31;
  *helmikuu = 28;
  *joulukuu = 31;

helmikuu  ─────┐ 
tammikuu  ──┐  │
            │  │                joulukuu ───┐  
k_pituudet  │  │                            │
  │         v  v                            v
  │         0  1  2  3  4  5  6  7  8  9 10 11 
  │       ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └──────>│31│28│ 0│ 0│ 0│ 0│ 0│ 0│ 0│ 0│ 0│31│
          └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘

Taulukon nollaaminen voitaisiin hoitaa myös:
  int i,*p;
  for (i=0, p=k_pituudet; i<12; i++, p++) *p=0;
/* tai */
  for (i=0, p=k_pituudet; i<12; i++) *p++=0;
/* tai */
  for (p=k_pituudet; p<k_pituudet+12; p++) *p=0; 

Viimeistä esimerkkiä lukuunottamatta näissä ei kuitenkaan ole järkeä, koska myös indeksi i tarvitaan joka tapauksessa.  Viimeisenkin esimerkin mukaista käyttöä kannattaa harkita: se mitä tehdään, piiloutuu osoittimien taakse!
13.1.4	Taulukon alustaminen
Taulukko voidaan alustaa (vain) esittelyn yhteydessä:
                  /*  1. 2. 3. 4. 5. 6. 7. 8. 9.10.11.12 */
int k_pituudet[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; 

Tehtävä 13.119	Taulukon alkioiden summa
Kirjoita funktio- aliohjelma taulukon_summa, joka palauttaa taulukon alkioiden summan.  Kirjoita pääohjelma, jossa aliohjelmaa kutsutaan valmiiksi alustetulla taulukolla k_pituudet ja tulostetaan vuoden päivien lukumäärä.

13.2	Merkkijonot 
Merkkijonot ovat eräs ohjelmoinnin tärkeimmistä tietorakanteista.  Valitettavasti tämä on lähes poikkeuksetta unohtunut ohjelmointikielten tekijöiltä.  Heille riittää että kielellä VOI tehdä merkkijonotyypin.  Tavallista käyttäjää kiinnostaa tietysti onko se tehty ja onko se hyvä.  Usein vastaus on EI.  Näin myös C- kielen kohdalla!  C++:han oli jo uusimmassa ehdotuksessa välttävä merkkijonoluokka.  Itse asiassa tämä oli merkkittävin tekijä miksi tätä kurssia edeltävän alkeisohjelmointikurssin kieleksi valittiin C++ puhtaan C- kielen sijaan.
13.2.1	Merkkityyppi 
Yksittäinen merkki on C- kielessä tyyppiä char:
char rek_1_merkki;
rek_1_merkki = 'X'; 

Huomattakoon, että merkkityypin vakio  kirjoitetaan yksinkertaisiin lainausmerkkeihin.  Vakio on tosin kokonaislukutyyppinen, eli 
sizeof('a') == sizeof(int) ( sizeof(char) == 1 )

Merkkityyppi on käytännössä (yleensä) 8- bittinen kokonaisluku.  Toteutuksesta riippuen joko etumerkillinen tai etumerkitön.  Siis merkkimuuttujiin voidaan vallan hyvin sijoittaa myös lukuarvoja:
char m;
m = 65;
if ( m == 'A' ) ... 

Lukuarvo tarkoittaa merkin (ASCII- ) koodia.  
Merkkien etumerkillisyys tulee ilmi esimerkiksi Turbo- C:ssä seuraavasti: 
c-taul\merkit.c - esimerkki merkkien vertailusta
  char m1,m2,m3; 
  m1='a';
  m2='z';
  m3='ä';
  if ( m1 < m2 ) printf("%c < %c \n",m1,m2);
  printf("%c = %d,  %c = %d \n",m1,m1,m2,m2);
  if ( m1 < m3 ) printf("%c < %c \n",m1,m3);
  printf("%c = %d,  %c = %d \n",m1,m1,m3,m3); 

Jälkimmäistä if tulostusta ei tapahdu. ä:n koodi on 132, joka olisi suurempi kuin a:n ASCII- koodi eli 97, mutta koska Turbo- C:ssä merkit ovat etumerkillisiä, on ä:n koodi - 124!
HUOM! Tästä syystä merkkityyppisten muuttujien käyttö taulukoiden indeksinä on vaarallista!
13.2.2	C- kielen merkkijonot
Vaikka C++:sta löytyykin hyvä merkkijonoluokka <string>, niin joudumme silti tutustumaan myös puhtaan C- kielen merkkijonoihin.  Miksikö?  Koska esimerkiksi sijoituksessa 
cpp_jono = "Kissa";

sijoitetaan tosiasiassa C- merkkijono C++:n merkkijonoon.  Lisäksi valtava määrä valmiista aliohjelmista haluaa parametrikseen nimenomaan C- merkkijonon tai palauttaa C- merkkijonon.  Itse asiassa C++:n merkkijonojen käyttö on vielä tänä päivänä (1997) varsin harvinaista.  
Tämä on sääli, sillä suurin osa C:n "huonoudesta" johtuu nimenomaan aloittelijoiden (ja kokeneidenkin) käsissä VAARALLISISTA merkkijonoista.  Valtava määrä UNIXeistakin löytyvistä tietoturva- aukoista perustuu väärin käytettyihin merkkijonoihin!  Näytimmekin jo aikaisemmin valmiiden olioluokkien kohdalla muutamia asioita, joita EI voi tehdä C- merkkijonoilla, tai jotka näyttävät hyvältä, mutta toimivat väärin.  Nyt tutustumme vähän tarkemmin ongelmien syihin ja siihen miten niitä voidaan välttää.
Vinkki

Varaa tila myös loppumerkille
C- kielen merkkijonot eivät ole mitään muuta kuin taulukoita kirjaimista.  Merkkijonoista on kuitenkin sovittu, että merkkijonon lopuksi taulukossa on merkki jonka koodi on 0 (NUL, '\0').  Siis merkkijono täytyy muistaa varata yhtä alkiota isommaksi, kuin aiottu merkkien maksimimäärä.  
Huomattakoon, ettei ole olemassa valmista NUL- vakiota, vaan pitää käyttää joko muotoa 0 tai '\0'.
string.h- kirjastosta löytyy iso kasa merkkijonojen käsittelyyn tarkoitettuja aliohjelmia.  Esimerkiksi sijoitus
elain = "Kissa"

ei onnistu (tai onnistuu, muttei kopioi merkkejä "Kissa" taulukkoon elain), vaan on käytettävä esimerkiksi strcpy funktiota: 
char elain[12];
strcpy(elain,"Kissa");           /* VAARALLINEN!!! Tässä toimii!!! */
...

Seuraavassa koodit on esitetty heksana ja 
? tarkoittaa ettei muistipaikan arvoa tunneta.

elain
  │      0  1  2  3  4  5  6  7  8  9 10 11 
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│00│ ?│ ?│ ?│ ?│ ?│ ?│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a  NUL ?  ?  ?  ?  ?  ? 

Merkkijonon pituus voitaisiin laskea seuraavalla aliohjelmalla: 
c-taul\merkit.c - esimerkki merkkijonon viemisestä parametrina
int pituus(const char jono[])
{
  int i;
  for (i=0; jono[i]; i++);
  return i;
}

/* Tai: */

int pituus(const char jono[])  /* const char [] ei char []            */
{                              /* koska jonoa ei muuteta              */
  char *p;
  for (p=jono; *p; p++);
  return p- jono;
}

Tosin string.h:sta löytyy funktio strlen, joka tekee täsmälleen saman asian.
Funktio pituus voitaisiin aivan yhtähyvin esitellä:
int pituus(const char *jono) 

13.2.3	Kirjaimen ja merkkijonon ero
Ohjelmasta
{
  char c,jono[2];
  c = 'A';
  strcpy(jono,"A");
...
}

seuraisi seuraavat muistin sisällöt (heksana):
     ┌──┐                  ┌──┬──┐ 
  c: │41│         jono────>│41│00│          
     └──┘                  └──┴──┘

Siis muuttuja c ja taulukon alkio jono[0] käyttäytyvät samalla tavoin ja ovat kumpikin muistipaikkoja jotka sisältävät yhden merkin.  Vastaavasti jono on osoitin merkkijonon alkuun (merkkiin jono[0]). Tyhjä merkkijonokin veisi vähintään yhden paikan!  Miksi?  
13.2.4	Merkkijono, osoitin merkkiin ja vakiojono
Seuraavat esittelyt saattavat näyttää samanlaisilta
char *p = "Kissa", jono[10] = "Kissa";

mutta näiden ero on siinä, että *p on osoitin johonkin merkkiin ja jono 10- paikkaisen merkkijonon 1. merkkiin.  Osoitin p on alustettu osoittamaan vakiomerkkijonoa jossa sisältönä on "Kissa" ja 10- paikkainen merkkijono on sisällöltään alustettu arvoksi "Kissa".  Osoittimen p arvoa voidaan muuttaa, osoittimen jono arvoa sen sijaan ei voida muuttaa! 
Esimerkiksi seuraava selvältä näyttävä ohjelma saattaisi tuottaa yllätyksen:
c-taul\strptr.c - varoittava esimerkki vakiojonon muuttamisesta
#include <stdio.h> 	
#include <string.h>
int main(void)
{
  char *p="IsoKissa", jono[10];
  strcpy(p,"Koira");             /* Tämä on tyhmää!!! */	
  strcpy(jono,"Kissa");

  printf("p: %s jono: %s\n",p,jono);
  return 0;
}

Ohjelma saattaa (kääntäjän optioista, kääntäjästä ja laiteympäristöstä riippuen) tulostaa:
p: Koira jono: ra

Miksikö?  Koska "IsoKissa" on vakiomerkkijono, jonka alkuun osoitin p alustetaan.  Tänne osoitteeseen kopioidaan teksti "Koira".  Koska kääntäjän mielestä "IsoKissa" on vakiojono ja lauseessa strcpy(jono,"Kissa") tarvitaan vakiojonoa "Kissa", päättää kääntäjä antaa käännösaikana strcpy- funktiolle osoitteen "IsoKissa" - merkkijonon sisältä!
                     osoitin "Koira"─────────────────────────┐  
                     osoitin "Kissa"─────────────┐           │
                     osoitin "IsoKissa"────┐     │           │
                                           v     v           v
                                          ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
alustus *p="IsoKissa"               p────>│I│s│o│K│i│s│s│a│0│K│o│i│r│a│0│
                                          └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
                     osoitin "Kissa"─────────────┐                       
                                                 v
                                          ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
kopiointi strcpy(p,"Koira")         p────>│K│o│i│r│a│0│s│a│0│K│o│i│r│a│0│   
                                          └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘                      
                       ┌─────────────────────────┐ 
                       │                         v
                       │                  ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
kopiointi strcpy(jono,"Kissa")      p────>│K│o│i│r│a│0│s│a│0│K│o│i│r│a│0│   
 ┌───────────────┘                        └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘                      
 v            
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│r│a│0│?│?│?│?│?│?│?│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 

Edellisessä esimerkissä sijoitus
p = jono;   /* OIKEIN */

on sallittu, muttei sijoitus 
jono = p;   /* VÄÄRIN */

Myös sijoitus 
p = "Koira"; 

olisi sallittu, mutta ei suinkaan sijoita osoitteeseen p jonoa "Koira", vaan sijoittaa osoittimen p osoittamaan vakiomerkkijonoon "Koira".
Eroa käsitellään vielä lisää sizeof - operaattorin kohdalla.
13.3	string.h 
C- merkkijonojen käsittelyyn on standardin mukaan seuraavat valmiit aliohjelmat string.h:ssa 
memchr        memcmp        memcpy        memmove       memset
strcat        strchr        strcmp        strcpy        strcspn
strerror      strlen        strncat       strncmp       strncpy
strpbrk       strrchr       strspn        strstr        strtok
strcoll       strxfrm

Lisäksi esim. Turbo- C:ssä on:
memccpy       memicmp        movedata     movmem        setmem
stpcpy        strcmpi        strdup       _strerror     stricmp
strlwr        strncmpi       strnicmp     strnset       strrev
strset        strupr

Kirjaston nimien yksi idea on siinä, että str- alkuiset nimet tarkoittavat string- funktioita, eli taulukon käsittely lopetetaan NUL- alkioon. mem- alkuiset toimivat yleensä vastaavasti, mutta niissä välitetään parametrina taulukon pituus ja NUL- tavusta ei välitetä. strn- alkuiset funktiot toimivat kuten str- funktiot, mutta NUL- tavun lisäksi taulukon käsittely lopetetaan, kun annettu maksimimäärä merkkejä on käsitelty.
13.3.1	strcpy ja strncpy
Esimerkiksi strcpy:n käyttö on erittäin vaarallista, mikäli merkkijono jonne kopioidaan jää pienemmäksi kuin mitä kopioidaan.  Tällöin kopioidaan seuraavien muistialueiden päälle.  Esimerkiksi saattaisi olla:
c-taul\strcpy.c - esimerkki tilan ylityksestä
char jono1[6],jono2[6]; 
...
strcpy(jono2,"Koira");
strcpy(jono1,"Kissa on!");
...
printf("%s %s\n",jono1,jono2); /* - > Kissa on! on! */

                           ┌─────jono2
jono1                      v
  │      0  1  2  3  4  5  0  1  2  3  4  5 
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│20│6F│6E│21│00│61│00│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a     o  n  !  NUL a NUL 

Edellä jono2:n pilaaminen voitaisiin estää kutsulla strncpy:
char jono1[6],jono2[6];
...
strncpy(jono2,"Koira",6);
strncpy(jono1,"Kissa on!",6);
...
printf("%s %s\n",jono1,jono2); /* - > Kissa Koira Koira */

                           ┌─────jono2
jono1                      v
  │      0  1  2  3  4  5  0  1  2  3  4  5 
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│20│6B│6F│69│72│61│00│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a     K  o  i  r  a  NUL 

Vikana on kuitenkin edelleen se, että nyt strncpy ei laita NUL merkkiä jonon loppuun, mikäli käsittely lopetetaan maksimipituuden takia!
Usein tämä varmistetaan laittamalla  NUL- merkki varmuuden vuoksi kopioinnin jälkeen:
...
strncpy(jono1,"Kissa on!",6);
jono1[6- 1] = 0;
...
                           ┌─────jono2
jono1                      v
  │      0  1  2  3  4  5  0  1  2  3  4  5 
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│00│6B│6F│69│72│61│00│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a  NULK  o  i  r  a  NUL 

Tehtävä 13.120	kopioi_jono
Kirjoita aliohjelma 
	int kopioi_jono(char *tulos, int max_koko, const char *jono) 
joka kopioi merkkijonon jono paikkaan tulos, muttei ylitä tulos- jonon maksimipituutta max_pit. tulos jono loppuu aina NUL- merkkiin.  Funktio palauttaa 0 mikäli kopiointi onnistuu kokonaan ja muuten jonkin muun arvon.

13.3.2	memmove
Mikäli kopioinnin kohde ja lähde ovat päällekäin, saattaa kopiointi tuottaa yllätyksiä.  Jos jonot olisivat esimerkiksi seuraavasti
                           ┌─────jono2
jono1                      v
  │      0  1  2  3  4  5  0  1  2  3  4  5 
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│00│6B│61│6E│61│00│??│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a  NULk  a  n  a  NUL    

ja tavoitteena olisi siirtää Kissa merkkijonoa 3 pykälää vasemmalle sekä muuttaa kana muotoon kkana, voitaisiin kuvitella tämän tapahtuvan 
strcpy(jono1,jono1+3);
strcpy(jono2+1,jono2); 

Yleensä käyttöjärjestelmästä riippuen ainakin toinen edellisistä sekoaa.  Ainoa turvallinen tapa tehdä em. temppuja on memmove, jonka taataan toimivan vaikka lähde ja kohde olisivat osittain päällekäinkin.
memmove(jono1,jono1+3,strlen(jono1+3)+1);
memmove(jono2+1,jono2,strlen(jono2)+1); 

13.3.3	strcat ja strncat
Merkkijonojen liittämiseen on funktiot strcat ja strncat.  Esimerkiksi:
char jono1[10],jono2[6];
...
strcpy(jono1,"Kissa");
strcpy(jono2," on!");

                                       ┌─────jono2
jono1                                  v
  │      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│00│??│??│??│??│20│6F│6E│21│00│??│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a  NUL               o  n  !  NUL

strcat(jono1,jono2);

                                       ┌─────jono2
jono1                                  v
  │      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
  │    ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ 
  └───>│4B│69│73│73│61│20│6F│6E│21│00│20│6F│6E│21│00│??│
       └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
        K  i  s  s  a     o  n  !  NUL   o  n  !  NUL

Funktiossa strcat on sama vaara kuin strcpyssäkin.  Vaaraa voidaan osittain välttää strncat kutsulla, muistaen kuitenkin, että pituutena annetaan lisättävien merkkien maksimimäärä (NUL mukaanlukien, joka strncatin tapauksessa laitetaan), EI tuloksen maksimikoko.  Siis turvallinen muoto olisi:
strncat(jono1,jono2,10- (strlen(jono1)+1)); 

Tehtävä 13.121	liita_jono
Kirjoita aliohjelma 
  int liita_jono(char *tulos, int max_koko, const char *jono) 
joka liittää merkkijonon jono jonon tulos perään, muttei ylitä tulos- jonon maksimipituutta max_pit. tulos- jono loppuu aina NUL- merkkiin.  Funktio palauttaa 0 mikäli liittäminen onnistuu täysin ja muuten jonkin muun arvon.

13.3.4	Tulos myös funktion nimessä
string.h - kirjaston funktioista useat ovat sellaisia, että ne palauttavat myös nimessään osoittimen tulosmerkkijonoon.  Miksikö?  Siksi, että tällöin merkkijonokutsuja voidaan ketjuttaa funktiomaisesti:
char jono[30],st[40];
...
strcat(strcpy(st,strcat(strcpy(jono,"Kissa")," istuu"))," puussa!"); 

Esimerkissä jonot saisi arvokseen:
jono: "Kissa istuu"
st  : "Kissa istuu puussa!" 

Tehtävä 13.122	Ketjutus
Miksi jonot jono ja st saisivat edellä mainitut arvot?
Tehtävä 13.123	string.h
Ota selvää mitä mikäkin string.h:n aliohjelmista tekee.

13.4	Merkkijonojen lukeminen
13.4.1	Valmiit funktiot VAARALLISIA
Vinkki

ÄLÄ LUOTA
valmiisiin merkkijonofunktioihin
Merkkijonojen lukeminen on eräs C- kielen vaarallisimpia operaatioita.  Seuraavan malliesimerkin takia merkkijonot on aina luettava käyttäen jotakin itse tehtyä turvallista funktiota.  Funktiot scanf ja gets pitää unohtaa.  
Funktiolla fgets jono voidaan lukea turvallisesti, koska fgetsille viedään parametrina jonon maksimipituus:
fgets(jono,max_pituus,tiedosto) 

Haittana on kuitenkin se, että jonon loppuun jää '\n'- merkki.  Tämä ongelma on poistettu kappaleen lopussa esitetyssä malliohjelmassa.
Seuraava ohjelma on erittäin vaarallinen:
c-taul\jonovaar.c - esimerkki tilan ylityksestä päätesyötössä
#include <stdio.h>

char jono1[5]="";
int  i=0;
char jono2[5]="Kana";

int main(void)
{
  printf("Anna merkkijono>");
  scanf("%s",jono1); 	
  printf("|%s| %x |%s|\n",jono1,i,jono2);
  return 0;
}

Tehtävä 13.124	Kanahaukka
Jos edelliselle ohjelmalle syötetään "Kanahaukka", niin se tulostaa:
	|Kanahaukka| 7561 |kka|
Miksi?

13.4.2	Formaatin käyttö helpottaa hieman 
Kanahaukka - esimerkin tilanne voitaisiin välttää käyttämällä lukemisessa muotoja:
c-taul\jonovaa2.c - tilanylityksen välttäminen formaatin avulla
  scanf("%4s",jono1); 
  scanf("%4[^\n]",jono1); 

Jälkimmäisessä on %s - formaatin tilalla käytetty formaattia [merkit- jotka- sallitaan], ja sen muotoa [^merkit- joita- ei- sallita].  Näin merkkijonoon saadaan mukaan myös välilyönnit, mitä ei %s- formaatissa saada.  Muotoa voidaan käyttää tilapäisissä testiohjelmissa, mikäli alla olevaa kirjastoa ei ole käytössä.  Vikana on se, että kentän maksimipituus on erittäin vaikeata saada seuraamaan merkkijonolle varattua tilaa!
13.4.3	getline apuun
Onneksi edes C++:ssa on tehty toimiva metodi merkkijonojen lukemiseen.
c-taul\jonolue.cpp - esimerkki toimivasta merkkijonon lukemisesta 
#include <iostream.h>
#include <stdio.h>

char jono1[5] = "";
int  i=0;
char jono2[5] = "Kana";

int main(void)
{
  cout << "Anna merkkijono>";
  cin.getline(jono1,sizeof(jono1));
  printf("|%s| %x |%s|\n",jono1,i,jono2);
  return 0;
}
13.5	Malliohjelmia merkkijonojen käsittelyyn
13.5.1	Aliohjelmia C- merkkijonojen käsittelyyn
Seuraavassa on joukko aliohjelmia merkkijonojen lukemiseen ja käsittelyyn. Niistä tehokkaimmalla - lue_jono_oletus  -  voidaan tulostaa näyttöön samalla hoput- teksti ja mikäli kysymykseen vastataan pelkkä [RET], käytetään oletusarvoa.  Tämä aliohjelma on suunniteltu nimenomaan toimimaan kerhon jäsenrekisterin tarpeiden mukaisesti.
ali\mjonot.c - aliohjelmia merkkijonojen käsittelyyn 
 /****************************************************************************/
/*  
**        M J O N O T . C
**
** Yleisiä merkkijonojen käsittelyyn liittyviä aliohjelmia.               
**
** Aliohjelmat:
**    tee_jono               - luo uuden merkkijonon jonne jono kopioidaan    
**    kopioi_jono            - kopioi kork. annetun määrän merkkejä           
**    liita_jono             - liittää jonon toisen perään, tulos
**                             korkeintaan max.pituus mittainen               
**    f_lue_jono             - lukee tiedostosta merkkijonon                  
**    alusta_lue_jono        - alustaa merkkijonon lukemisen                  
**    lue_jono               - lukee päätteeltä merkkijonon                   
**    lue_jono_oletus        - lukee päätteeltä merkkijonon.
**                             Näyttöön tulostetaan haluttu viesti ja
**                             jonon oletusarvo, mikäli painetaan RET         
**    lue_kokluku_oletus     - luetaan kokonaisluku, jolle käytetään
**                             oletusarvoa mikäli heti painetaan RET          
**    poista_alkutyhjat      - poistaa merkkijonon alussa olevat välilyönnit  
**    poista_lopputyhjat     - poistaa merkkijonon lopussa olevat välil.      
**    poista_2_tyhjat        - muuttaa merkkijonossa kaikki peräkkäiset
**                             välilyönnit yhdeksi välilyönniksi              
**    poista_tyhjat          - poistaa alusta ja lopusta kaikki sekä
**                             muualta moninkertaiset välilyönnit             
**    poista_alku_ja_2_tyhjat- poistaa alusta ja 2x tyhjät                    
**    isoksi                 - muuttaa kirjaimen isoksi kirjaimeksi huomioiden
**                             skandit                                        
**    pieneksi               - muuttaa pieneksi huomioiden skandit            
**    jono_isoksi            - muuttaa jonon kaikki merkit isoiksi            
**    jono_pieneksi          - muuttaa jonon kaikki merkit pieniksi           
**    jono_alku_isoksi       - muuttaa jonon kaikki sanojen alut isoiksi
**                             ja kaikki muut pieniksi                        
**    jono_1_isoksi          - muuttaa jonon 1. kirjaimen isoksi              
**    wildmat                - vertaa onko sana == maski, missä maskissa
**                             voi olla jokeri-merkkejä (* tai ?)             
**    onko_samat             - ensin muutetaan jonot isoiksi ja poistetaan
**                             tyhjät ja sitten wildmat
**                             (eli "  Kalle " == "    k*  ")                 
**    palanen                - ottaa merkkijonosta seuraavan erotinmerkkien
**                             määräämän palasen                              
**    laske_merkit           - laskee annettujen merkkien esiintymismäärän
**                             merkkijonossa                                  
**    paikka                 - palauttaa kirjaimen 1. indeksin merkkijonossa  
**    tayta_valit            - täyttää syötön "A-F" muotoon "ABCDEF"          
**    joku_jono              - vastaa kysymykseen onko "EY" joku jonoista
**                             "EU|EY|EL"                                     
**    joku_jono_func         - kuten edellä, mutta vertailufunktio voidaan
**                             antaa itse, esim
**                               joku_jono("EU","E?|USA",wildmat) => 1        
**    jono_arvosanaksi       - muuttaa merkkijonon "7-" reaaliluvuksi 6.75    
**    arvosana_jonoksi       - muuttaa reaaliluvun 6.75 merkkijonoki "7-"     
**    sallituissa            - paluttaa -1 jos tutkittavan jonon kaikki
**                             merkit ovat annetussa joukossa, muuten
**                             1. väärän merkin indeksin                      
**    poista_merkit          - poistaa jonosta kaikki valitut merkit          
**    poista_alusta          - poistaa merkkejä jonon alusta                  
**    lisaa_alkuun           - lisää merkkejä jonon alkuun                    
**    lisaa_merkki           - lisää merkin merkkijonon valittuun kohtaan     
**    vaihda_jonot           - vaihtaa jonossa merkit toisiksi
**                               ^ - rivin alussa tarkoittaa, etta
**                                   vaihto tehdaan vain rivin alusta         
**    vaihda_merkit          - vaihtaa jonossa yksittäiset merkit
**                             toisiksi                                       
**    muunna_C_symbolit      - muuttaa \t, \n ja \0x61 muotoiset
**                             C-symbolit vastaaviksi merkeiksi               
**    jonossa_vain_merkit    - poistaa jonosta kaikki ne merkit, jotka
**                             eivät ole valitussa joukossa                   
*/

Tehtävä 13.125	Merkkijonojen käsittely
Valitse muutamia aliohjelmia mjonot.c aliohjelmakirjastosta ja yritä itse toteuttaa ne.

13.5.2	Aliohjelmia C++ - merkkijonojen käsittelyyn
Edellisistä aliohjelmista suurin osa on toteutettu myös käsittelemään C++ - merkkijonoja:
ali\mjonotpp.h - aliohjelmia C++ -merkkijonojen käsittelyyn
/****************************************************************************/
/*     M  J  O  N  O  T  P  P.  H 
**
**
**   Tiedostossa on merkkien ja merkkijonojen käsittelyohjelmien yleiset
**   aliohjelmien otsikot.  Tiedosto on lähinnä muunnostiedosto string-
**   tyypille vastaavasta C-kirjastosta mjonot.c
**
**   Tekijät:          Vesa Lappalainen
**   Tehty:            02.01.1996
**   Muutettu          07.03.1996/vl
**   Mitä muutettu:    lue_jono_oletus laitettu toimimaan
**
**  Tiedostossa on seuraavia aliohjelmia:
**    remove(st)             - poistaa tiedoston st
**    onko_tiedostoa(st)     - palauttaa 1 jos on tiedosto nimellä st
**    rename(&vanha,&uusi)   - vaihtaa tiedoston nimen
**    tarkennin_alku(&tied)  - palauttaa mistä kohti tarkennin alkaa
**                             tiedoston nimessä
**    poista_tarkennin(tied) - poistaa tarkentimen tiedostonnimestä
**    laita_tarkennin(tied,tark) - laittaa tiedoston nimen tarkentimen
**    vaihda_tarkennin(tied,tark)- vaihtaa tiedoston nimen tarkentimen
**
**    istream &lue_rivi(istream &is,char *s,int max_koko);
**    istream &lue_rivi(istream &is,string &s);
**    istream &lue_rivi(istream &is,int &i,int def=0);
**
**  Seuraavat ovat mjonot.c:n muunnoksia string-luokalle
**  (- merk ei totetuttu erikseen string-luokalle
**     tai ei tarvitse toteuttaa)
**
**    tee_jono               - luo uuden merkkijonon jonne jono kopioidaan
**    kopioi_jono            - kopioi kork. annetun määrän merkkejä
**    liita_jono             - liittää jonon toisen perään, tulos
**                             korkeintaan max.pituus mittainen               
** -  f_lue_jono             - lukee tiedostosta merkkijonon                  
** -  alusta_lue_jono        - alustaa merkkijonon lukemisen                  
** -  lue_jono               - lukee päätteeltä merkkijonon                   
**    lue_jono_oletus        - lukee päätteeltä merkkijonon.
**                             Näyttöön tulostetaan haluttu viesti ja
**                             jonon oletusarvo, mikäli painetaan RET         
** -  lue_kokluku_oletus     - luetaan kokonaisluku, jolle käytetään
**                             oletusarvoa mikäli heti painetaan RET          
**    poista_alkutyhjat      - poistaa merkkijonon alussa olevat välilyönnit  
**    poista_lopputyhjat     - poistaa merkkijonon lopussa olevat välil.      
**    poista_2_tyhjat        - muuttaa merkkijonossa kaikki peräkkäiset
**                             välilyönnit yhdeksi välilyönniksi              
**    poista_tyhjat          - poistaa alusta ja lopusta kaikki sekä
**                             muualta moninkertaiset välilyönnit             
** -  isoksi                 - muuttaa kirjaimen isoksi kirjaimeksi huomioiden
**                             skandit                                        
** -  pieneksi               - muuttaa pieneksi huomioiden skandit            
**    jono_isoksi            - muuttaa jonon kaikki merkit isoiksi            
**    jono_pieneksi          - muuttaa jonon kaikki merkit pieniksi           
**    jono_alku_isoksi       - muuttaa jonon kaikki sanojen alut isoiksi
**                             ja kaikki muut pieniksi                        
**    wildmat                - vertaa onko sana == maski, missä maskissa
**                             voi olla jokeri-merkkejä (* tai ?)             
**    onko_samat             - ensin muutetaan jonot isoiksi ja poistetaan
**                             tyhjät ja sitten wildmat
**                             (eli "  Kalle " == "    k*  ")                 
**    palanen                - ottaa merkkijonosta seuraavan erotinmerkkien
**                             määräämän palasen                              
**    laske_merkit           - laskee annettujen merkkien esiintymismäärän
**                             merkkijonossa                                  
**    paikka                 - palauttaa kirjaimen 1. indeksin merkkijonossa  
**    tayta_valit            - täyttää syötön "A-F" muotoon "ABCDEF"          
**    joku_jono              - vastaa kysymykseen onko "EY" joku jonoista
**                             "EU|EY|EL"                                     
**    jono_arvosanaksi       - muuttaa merkkijonon "7-" reaaliluvuksi 6.75
**    arvosana_jonoksi       - muuttaa reaaliluvun 6.75 merkkijonoki "7-"
**    sallituissa            - paluttaa -1 jos tutkittavan jonon kaikki
**                             merkit ovat annetussa joukossa, muuten
**                             1. väärän merkin indeksin                      
**    poista_merkit          - poistaa jonosta kaikki valitut merkit          
**    poista_alusta          - poistaa merkkejä jonon alusta                  
**    lisaa_alkuun           - lisää merkkejä jonon alkuun                    
**    lisaa_merkki           - lisää merkin merkkijonon valittuun kohtaan     
**    vaihda_jonot           - vaihtaa jonossa merkit toisiksi                
**    muunna_C_symbolit      - muuttaa \t, \n ja \0x61 muotoiset              
**                             C-symbolit vastaaviksi merkeiksi
*/

13.6	C++ - merkkijonojen ja C- merkkijonojen yhteiskäyttö
Koska suurta osaa valmiina olevista aliohjelmista ei ole tehty kuin käsittelemään pelkkiä C- merkkijonoja, tutkimme seuraavassa hieman eri merkkijonotyyppien ristiinkäyttöä.  Tämä on tietenkin mahdollista vain C++ - ohjelmissa.
13.6.1	C- merkkijonon muuttaminen C++ - jonoksi
C- merkkijono muuttuu C++ - jonoksi esimerkiksi sijoituslauseessa tai muodostajassa.  Tämän ansiosta voidaan myös kutsua C- merkkijonoilla kaikkia niitä C++ - aliohjelmia, jonne viedään parametrina joko string tai const string &.  Miksikö?  Koska kääntäjä voi tällöin luoda tilapäisen string- olion kutsun ajaksi ja alustaa tämän kutsussa olleella C- merkkijonolla:
c-taul\c2cpp.cpp - C-jonojen muuttaminen C++ -jonoiksi 
#include <iostream.h>
#include <string>
using namespace std;

void tulosta(const string &cppS)
{
  cout << cppS << endl;
}

void muuta_eka(string &cppS)
{
  cppS[0] = 'R';
}

void muuta_toka(string *cppS)
{
  (*cppS)[1] = 'u';
}

int main(void)
{
  char cs1[10] = "Kissa";
  string cppS1(cs1), cppS2("Koira");  // 1. tapa muuttaa
  cout << cppS1 << " " << cppS2 << endl;

  string cppS3,cppS4,cppS5;
  cppS3 = cs1;  cppS4 = "Kana";       // 2. tapa muuttaa
  cppS5 = 'A';                        // Jopa merkki voidaan sijoittaa
  cout << cppS3 << " " << cppS4 << " " << cppS5 << endl;

  tulosta(cppS4);     // Toimii ilman muuta!
  tulosta(cs1);       // Muuttaa automaattisesti cs1    => tilapäinen apujono
  tulosta("Mato");    // Muuttaa automaattisesti "Mato" => tilapäinen apujono

  muuta_eka(cppS3);  tulosta(cppS3); // Tottakait toimii
  muuta_eka(cs1);    tulosta(cs1);   // Ei virheitä, mutta varoitus ja ei toimi
  muuta_eka("Emu");  tulosta("Emu"); // Onneksi ei toimi!  Varoitus.

  muuta_toka(&cppS4); tulosta(cppS4);// Tottakait toimii
//  muuta_toka(&cs1);   tulosta(cs1);  // Ei käänny

  return 0;
}

Ohjelman tulostus:
Kissa Koira
Kissa Kana A
Kana
Kissa
Mato
Rissa
Kissa
Emu
Kuna

Huomattakoon, että esimerkiksi kutsusta muuta_eka(cs1); seuraa esimerkiksi varoitus:
Warn :  c2cpp.cpp(35,17):Temporary used for parameter 'cppS' in call to   
  'muuta_eka(string &)'

Tässä varoitetaan ohjelmoijaa siitä, että tilapäinen string- olio luodaan kutsun ajaksi, mutta tämä olio sitten häviää kutsun jälkeen ja cs1 jää muuttumatta ehkä vastoin ohjelmoijan toiveita.  Tässäkin on hyvä esimerkki siitä, että varoitukset kannattaa ottaa todesta!
Kutsussa  muuta_eka("Emu"); ei ole mitään järkeä, mutta sekin menee kääntäjästä läpi pelkällä varoituksella.  Onneksi tulee se tilapäinen jono ja vakiomerkkijono jää muuttumatta!
Tähän asiaan osittain kuulumaton vaaranpaikka on taas osoittimissa.  Jos aliohjelma muuta_toka olisikin ollut muodossa (oli nimittäin minulla ja kaatoi koneen):
void muuta_toka(string *cppS)
{ 	
  cppS[1] = 'u';
}

menisi tämä täysin ilman varoituksia kääntäjästä lävitse.  Eihän tässä nimittäin ole mitään virhettä.  Lauseessahan vain käsketään sijoittamaan merkkijonotaulukon cppS toiseen paikkaan merkkijono u.  Kirjain u muuttuu ensin automaattisesti merkkijonoksi ja tämä taas kuten aiemmin todettiin, voidaan sijoittaa C++ merkkijonoon.  Taas yksi syy lisää VAROA osoittimia (ei kieltää niiden käyttöä).  Toisaalta myös varoitus siitä, etteivät automaattiset tyypinmuunnokset ehkä sittenkään ole niin ihana asia!
13.6.2	C++ - merkkijonon muuttaminen C- jonoksi
Toisensuuntainen muunnos ei yleensä onnistu.  C++ - merkkijonosta kyllä saadaan tilapäinen osoitin vastaavaan VAKIO C- merkkijonoon, jota voidaan käyttää hyväksi aliohjelmakutsuissa, joiden parametrilistassa on vakiomerkkijono:
c-taul\cpp2c.cpp - C++ -merkkijonojen käyttö C-merkkijonoja vaativissa kutsuissa 
#include <iostream.h>
#include <string>
#include <string.h>
using namespace std;

int montako_pta(const char *s)
{ /* oikein C:mäinen */
  int i,n = 0;
  for (i=0; s[i]; i++) n +=  s[i] == 'p';
  return n;
}

void muuta_eka(char s[])
{
  s[0] = 'R';
}

int main(void)
{
  string cppS1("Kolme porsasta padassa porisee pippurikastikkeen kera!");

  int p_lkm = montako_pta(cppS1.c_str());
  cout << "Jonossa oli " << p_lkm << " kpl p-kirjaimia\n";

//  muuta_eka(cppS1.c_str()); // Ei onnistu! Täytyy tehdä apumuuttujan kautta
  char cs[100];
  strncpy(cs,cppS1.c_str(),100); cs[99] = 0;
  muuta_eka(cs);
  cppS1 = cs;
  cout << cppS1 << endl;

  return 0;
}

Muoto jono.c_str() palauttaa nimenomaan osoittimen vakiomerkkijonoon.  Tämän osoittimen elinikää ei taata, korkeintaan se elää niin kauan kuin vastaava jonokin.  Kuitenkin esimerkiksi edellä joka tapauksessa aliohjelmakutsujen ajan.  Tilanne on hankalampi jos jonoa pitää muuttaa, koska (onneksi) kääntäjä ei hyväksi tätä osoitinta muuttuvan merkkijonon tilalle kutsussa.  Tällöin ainoaksi mahdollisuudeksi jää tehdä C++- jonosta itse tilapäinen kopio C- merkkijonoksi ja käyttää tätä jonoa aliohjelman parametrina ja aliohjelman jälkeen sitten sijoitetaan tilapäinen jono takaisin alkuperäiseen.  
Jos useasti pitää kutsua samaa C- aliohjelmaa C++ - merkkijonolla, kannattaa tehdä "välittäjä"funktio, joka tekee em. kopioinnin.   Funktioiden nimien kuormittamisen ansiostahan tämä välittäjäfunktio voidaan tehdä samalle nimelle.  Tätä tekniikkaa on käytetty mm. muutettaessa mjonot.c:n funktioita C++:ksi kirjastossa mjonotpp.h.
Tehtävä 13.126	Välittäjäfunktio
Kirjoita C++ jonon hyväksyvä funktio muuta_eka käyttäen hyväksi cpp2c.cpp:n C- funktiota muuta_eka.
13.7	Tietueet, union ja enum
struct:n tapainen varattu sana on union, jolla tehdään päällekkäin olevia muistialueita, joilla on eri tilanteessa eri käyttö.  Esimerkiksi henkilötietueessa naisilla voisi olla synnyttanyt_lapsia ja miehillä armeija_kayty, jotka jakaisivat saman muistialueen (huom. esimerkki tehty -91).
c-taul\union.c - vaihtuva muistipaikan tyyppi
#include <stdio.h>                               Matti                    
                                                 ---------------------------
typedef enum {                                   |          -------------  |
  mies,                                          |nimi:     |M|a|t|t|i|0|  |
  nainen                                         |          -------------  |
} tSukupuoli;                                    |sukupuoli:| 0 |          |
                                                 |          -----          |
typedef union {                                  |tiedot:   | 1 |          |
  int syn_lapsia;                                ---------------------------
  int armeija_kayty;                             Maija                       
} tTiedot;                                       ---------------------------
                                                 |          -------------  |   
typedef struct {                                 |nimi:     |M|a|i|j|a|0|  |  
  char       nimi[40];                           |          -------------  | 
  tSukupuoli sukupuoli;                          |sukupuoli:| 1 |          |   
  tTiedot    tiedot;                             |          -----          |  
} tHenkilo;                                      |tiedot:   | 4 |          |    
                                                 ---------------------------    
void tulosta_henkilo(tHenkilo *henkilo) 
{ 
  printf("Nimi: %- 40s\n",henkilo- >nimi);
  switch (henkilo- >sukupuoli) {
    case mies:
      printf("Mies, armeija käyty: %d\n",henkilo- >tiedot.armeija_kayty);
      break;
    case nainen:
      printf("Nainen, synnyttanyt lapsia: %d\n",henkilo- >tiedot.syn_lapsia);
      break;
    default: printf("Eunukki\n");
  }
}

int main(void)
{
  tHenkilo Matti = {"Matti",mies,1}, Maija = {"Maija",nainen,4};

  tulosta_henkilo(&Matti);
  tulosta_henkilo(&Maija);
  return 0;
}

Tällä kurssilla emme kuitenkaan suosittele union- tyyppisten muuttujien käyttöä!  Mieluummin käytämme olioita ja polymorfismia.
Edellä enum tarkoitti lueteltua tyyppiä, jolloin mies vastasi vakiota 0 ja nainen vakiota 1.  Tähän palataan myöhemmin.
Tehtävä 13.127	union
Mitä union.c ohjelma tulostaa.  Piirrä kuva tietueista jos union vaihdettaisiinkin structiksi.

Korvaa enum sekä sen nainen ja mies #define- vakiolla.
Tehtävä 13.128	Tietue tietueessa
Olkoon meillä seuraavat C- määritykset:

typedef struct {
  char nimi[50];
  int  paino;
  int  ika;
} Elain_tiedot;

typedef struct {
  char laji[40];
  Elain_tiedot elain;
  int nro; 
} Laji_tiedot;

int main(void)
{
  Laji_tiedot miuku;
  ...
  return 0;
}

Täydennä ... - kohtaan seuraavat "sijoitukset" muuttujalle miuku:

  laji  <-  "Kissa"
  nro   <-   5
  nimi  <-  "Miuku"
  paino <-  3
  ika   <-  5

Piirrä kuva miuku - muuttujasta sijoitusten jälkeen.
Tehtävä 13.129	Miukusta olio
Muuta edellisen tehtävän tietueet C++ - luokiksi ja tee sijoituksien avuksi metodit, jotka hoitavat sijoitukset.  Saat nyt - tottakai - käyttää myös C++:n string- luokkaa.

13.8	Moniulotteiset taulukot
Moniulotteiset taulukot voidaan C- kielessä esitellä monella eri tavalla.  Käymme nopeasti lävitse eri vaihtoehtoja.
13.8.1	Kiinteä esittely
Kaikkein helpoin tapa esitellä moniulotteinen taulukko on aivan normaali esittely:
int matriisi[3][4];         
              ┌───┬───┬───┬───┐ 
matriisi─────>│   │   │   │   │<─── &matriisi[0][3]
              ├───┼───┼───┼───┤
matriisi+1 ──>│   │   │   │   │
              ├───┼───┼───┼───┤
matriisi+2───>│   │   │   │   │
              └───┴───┴───┴───┘

Taulukon nimi on osoitin sen 1. RIVIIN!.  Mallin tapauksessa kokonaislukuvektoriin int [4].
Taulukon alkioina voi tietysti olla mikä tahansa olemassa oleva tyyppi.  C- kielessä matriisi talletetaan rivilistana, eli muistissa on ensin rivin 0 alkiot ja sitten rivin 1 alkiot jne.  Myös moniulotteinen taulukko voidaan alustaa esittelyn yhteydessä:
double yks[3][3] = {
  { 1.0, 0.0, 0.0 },
  { 0.0, 1.0, 0.0 },
  { 0.0, 0.0, 1.0 }
} 

Tehtävä 13.130	Matriisit
Kirjoita seuraavat aliohjelmat, jotka saavat parametrinaan 2 3x3 matriisia ja palauttavat 3x3 matriisin:
1.	Laskee yhteen 2 matriisia.
1.	Kertoo kaksi matriisia keskenään.  (Kirjoita avuksi funktio, joka kertoo matriisin  rivin i toisen matriisin sarakkeella j).

13.8.2	Muuttuvat dimensiot
Kun moniulotteinen taulukko esitellään aliohjelman parametrina, pitää suurinta ulottuvuutta lukuunottamatta (tietysti senkin saa kiinnittää) kiinnittää kaikki muut ulottuvuudet.  Esimerkiksi:
c-taul\matriisi.c - esimerkki vaihtuvasta rivimäärästä 2-ulotteisessa taulukossa
#include <stdio.h>

#define SARAKKEITA 3

double alkioiden_summa(double mat[][SARAKKEITA], int riveja)
{
  int i,j; double summa=0;

  for (i=0; i<riveja; i++)
    for (j=0; j<SARAKKEITA; j++)
      summa +=mat[i][j];
  return summa;
}

int main(void)
{
  double s2,s3, mat2[2][SARAKKEITA] = { {1,2,3},{4,5,6} },
                mat3[3][SARAKKEITA] = { {1,0,0},{0,1,0},{0,0,1} };

  s2 = alkioiden_summa(mat2,2);
  s3 = alkioiden_summa(mat3,3);

  printf("Summat on %5.2lf ja %5.2lf\n",s2,s3);

  return 0;
}

13.8.3	Yksiulotteisen taulukon käyttäminen moniulotteisena
On tietysti surullista, ettei taulukon kaikkia ulottuvuuksia voida välittää parametrina.  Eihän juuri koskaan ohjelmaa tehtäessä tiedetä lopullista tilan tarvetta.  Tai sitten ohjelman aikana samoja aliohjelmia tarvittaisiin erikoisille taulukoille.  Tietysti voitaisiin esitellä liian suuri taulukko, ja "käyttää" vain vasenta ylänurkkaa.
Toisaalta moniulotteinenkin taulukko on todellisuudessa muistissa vain 1- ulotteisena.  Tästä muunnoksestahan puhuttiin jo monisteen alkuosassa.  On makuasia kumpiko järjestys esimerkiksi matriisissa valitaan: sarakelista vaiko rivilista.  Rivilista on C- kielen mukainen, mutta toisaalta maailma on pullollaan Fortran aliohjelmia, joissa matriisit on talletettu sarakelistana.  Siis kumpikin tapa on syytä hallita.
Tehtävä 13.131	Matriisi 1- ulotteisena
 Kirjoita aliohjelma tee_yksikko, jolle tuodaan parametrina neliömatriisin rivien lukumäärä ja 1- ulotteisen taulukon alkuosoite, ja joka alustaa tämän neliömatriisin yksikkömatriisiksi.

13.8.4	Taulukko taulukoista
Eräs tapa käsitellä useampiulotteisia taulukoita on määritellä ensin rivityyppi ja sitten 1- ulotteinen taulukko näitä rivityyppejä:
c-taul\mat2.c - matriisi parametrina riviosoittimen avulla
#include <stdio.h>

#define SARAKKEITA 3
typedef double Rivi_tyyppi[SARAKKEITA];

double alkioiden_summa(Rivi_tyyppi *mat, int riveja)
{
  int i,j; double summa=0;

  for (i=0; i<riveja; i++)
    for (j=0; j<SARAKKEITA; j++)
      summa +=mat[i][j];
  return summa;
}

int main(void)
{
  double s2,s3; 
  Rivi_tyyppi mat2[2] = { {1,2,3},{4,5,6} },
              mat3[3] = { {1,0,0},{0,1,0},{0,0,1} };

  s2 = alkioiden_summa(mat2,2);
  s3 = alkioiden_summa(mat3,3);

  printf("Summat on %5.2lf ja %5.2lf\n",s2,s3);

  return 0;
}

Tosiasiassa tällä ei ole mitään eroa edelliseen vastaavaan ohjelmamalliin verrattuna, tämä ehkä vain paremmin kuvastaa sitä, mihin matriisin nimi on osoitin.
13.8.5	Taulukko osoittimista
Mikäli halutaan tehdä esimerkiksi 2- ulotteinen taulukko, jonka kumpikin ulottuvuus on muuttuva, mutta silti taulukkoa käytetään kuten 2- ulotteista tavallista taulukkoa, pitää käyttää taulukkoa osoittimista:  
mat─┐      mat[0]         ┌────────── mat[0][2]
    │      │              │      
    │      │              v 
    v      └───>┌───┬───┬───┬───┐
  ┌───┐   ┌────>│ 1 │ 2 │ 3 │ 4 │  r0
0 │ o─┼───┘     └───┴───┴───┴───┘ 
  ├───┤         ┌───┬───┬───┬───┐
1 │ o─┼────────>│ 5 │ 6 │ 7 │ 8 │  r1
  ├───┤         └───┴───┴───┴───┘ 
2 │ o─┼───┐     ┌───┬───┬───┬───┐
  └───┘   └────>│ 9 │ 0 │ 1 │ 2 │  r2
                └───┴───┴───┴───┘ 

c-taul\mat3.c - matriisi osoitintaulukon avulla
#include <stdio.h>

double alkioiden_summa(double **mat, int riveja, int sarakkeita)
{
  int i,j; double summa=0;

  for (i=0; i<riveja; i++)
    for (j=0; j<sarakkeita; j++)
      summa +=mat[i][j];
  return summa;
}

int main(void)
{
  double s1,s2; 
  double r0[] = {1,2,3,4}, r1[] = {5,6,7,8}, r2[] = {9,0,1,2};
  double *mat[3];
  mat[0] = r0; mat[1] = r1; mat[2] = r2;

  s1 = alkioiden_summa(mat,2,3);
  s2 = alkioiden_summa(mat,3,4);

  printf("Summat on %5.2lf ja %5.2lf\n",s1,s2);

  return 0;
}

Esimerkissä on sama esitelläänkö aliohjelmassa:
double alkioiden_summa(double **mat,...
/* vai */
double alkioiden_summa(double *mat[],...

Tässä menettelyssä on vielä se etu, ettei kaikkien rivien välttämättä tarvitsisi edes olla yhtäpitkiä.  Harvassa matriisissa osa osoittimista voisi olla jopa NULL- osoittimia, mikäli rivillä ei ole alkioita (aliohjelman pitäisi tietysti tarkistaa tämä).  Oikeasti rivit usein vielä luotaisiin dynaamisesti ajonaikana tarvittavan pituisina.
Tehtävä 13.132	Transpoosi
Kirjoita taulukko- osoittimia käyttäen aliohjelma, joka saa parametrinaan kaksi matriisia ja niiden dimensiot.  Aliohjelma tarkistaa voiko toiseen matriisiin tehdä toisen transpoosin (vaihtaa rivit ja sarakkeet keskenään) ja tekee transpoosin jos pystyy.  Onnistuminen palautetaan aliohjelman nimessä.

13.9	Komentorivin parametrit (argv)
Esimerkiksi C- kielinen pääohjelma saa käyttöjärjestelmältä tällaisen taulukon kutsussa olleista argumenteista:
c-taul\argv.c - komentorivin parametrit
#include <stdio.h>

int main(int argc, char *argv[])
{
  int i;
  printf("Argumentteja on %d kappaletta:\n",argc);
  for (i=0; i<argc; i++)
    printf("%d: %s\n",i,argv[i]);
  return 0;
}

Edellä nimet tulevat
argc = argument count
argv = argument vector

Kun ohjelma käännettäisiin (vaikkapa nimelle argv.exe) ja ajettaisiin komentoriviltä saattaisi tulostus olla seuraavan näköinen (MS- DOS - koneessa):
C:\KURSSIT\CPP\MONISTE\ESIM\C-TAUL>argv kissa istuu puussa[RET]
Argumentteja on 4 kappaletta:
0: C:\KURSSIT\CPP\MONISTE\ESIM\C-TAUL\ARGV.EXE
1: kissa
2: istuu
3: puussa
C:\KURSSIT\CPP\MONISTE\ESIM\C-TAUL>_
                                                        
argv─┐             ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬
     │   ┌────────>│ C │ : │ \ │ K │ U │ R │ S │ S │ I │ T │
     v   │         └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴
   ┌───┐ │         ┌───┬───┬───┬───┬───┬───┐    
0  │ o─┼─┘ ┌──────>│ k │ i │ s │ s │ a │NUL│      argc = 4
   ├───┤   │       └───┴───┴───┴───┴───┴───┘ 
1  │ o─┼───┘       ┌───┬───┬───┬───┬───┬───┐
   ├───┤     ┌────>│ i │ s │ t │ u │ u │NUL│
2  │ o─┼─────┘     └───┴───┴───┴───┴───┴───┘    
   ├───┤           ┌───┬───┬───┬───┬───┬───┬───┐
3  │ o─┼──────────>│ p │ u │ u │ s │ s │ a │NUL│
   ├───┤           └───┴───┴───┴───┴───┴───┴───┘ 
4  │ o─┼─┐                   ^
   └───┘ │  argv[3][2]───────┘ 
        ─┴─

HUOM! Myös C++- ohjelma saa saman taulukon.  Siis joukon osoittimia C- merkkijonoihin, ei mitään string taulukkoa 
Tehtävä 13.133	Palindromi
Kirjoita ohjelma pali, jota kutsutaan komentoriviltä seuraavasti:

C:\OMAT\OHJELMOI\VESA>pali kissa[RET]
kissa EI ole palindromi!
C:\OMAT\OHJELMOI\VESA>pali saippuakauppias[RET]
saippuakauppias ON palindromi!
C:\OMAT\OHJELMOI\VESA>_

14.	Parametrin välityksestä ja osoittimista, kertaus
Mitä tässä luvussa käsitellään?
•	kerrataan aliohjelmien kutsumista
•	kerrataan aliohjelmien suunnittelua
•	kerrataan osoittimia parametreina
•	kerrataan referenssejä parametreina
14.1	Miksi aliohjelmia käytetään
Aliohjelmia käytettiin mm. seuraavista syistä:
•	kyseessä on luonteeltaan oma looginen kokonaisuutensa
•	joku toinen (tai itse aikaisemmin) on kirjoittanut halutun tehtävän suorittavan aliohjelman
Mikäli aliohjelma kirjoitetaan itse, tulee ongelmaksi tietysti se, mitä parametreja aliohjelmaan esitellään.
Mikäli aliohjelman on kirjoittanut joku toinen, on ongelmana se miten aliohjelmaa kutsutaan.
14.2	Yksinkertaisen aliohjelman kutsuminen
Valmiin aliohjelman kutsuminen on helppoa:  etsitään aliohjelman esittely ja kirjoitetaan kutsu, jossa on vastaavan tyyppiset parametrit vastaavissa paikoissa.
14.2.1	sin
Esimerkiksi funktion sin esittely saattaa olla muotoa:
double sin (double x);
For real sin, x is in radians.
sin of a real argument returns a value in the range - 1 to 1. 

Funktion tyyppi on double ja sille viedään double tyyppinen parametri.  Funktio ei muuta mitään parametrilistassa esiteltyä parametriaan (mistä tietää?).  Siis funktiota ei ole mitään mieltä kutsua muuten kuin sijoittamalla sen palauttama arvo johonkin muuttujaan tai käyttämällä funktiota osana jotakin lauseketta. x:ää vastaava parametri voi olla mikä tahansa double tyyppisen arvon palauttava lauseke (tietysti mielellään sellainen joka tarkoittaa kulmaa radiaaneissa):
double kulman_sini,a,b,x,y;
...
kulman_sini = sin(x);
...
y = sin(x/2) + cos(a/3);
... 

Funktiota voitaisiin tietysti kutsua myös muodossa:
double x = 3.1;
sin(x); 	

mutta kutsussa olisi yhtä vähän järkeä kuin kutsussa
double x=3.1;
x + 3.0;  	
tai jopa
3.0; 	

Mihin lausekkeiden arvot menisivät?  Eivät minnekään!  Siis lausekkeissa ei ole mieltä!
14.2.2	strcmp
Esimerkki moniparametrisesta tavallisesta funktiosta olisi vaikkapa strcmp, joka on esitelty seuraavasti:
Compares one string to another

  int strcmp (const char *s1, const char *s2);

Prototype in  string.h

Returns a value that is

  < 0 if s1 is less than s2
 == 0 if s1 is the same as s2
  > 0 if s1 is greater than s2

Performs a signed comparison. 

Funktio siis vertaa kahta merkkijonoa toisiinsa.  Funktion esittelyn mukaan funktiolle viedään parametrina kaksi osoitinta. const sanalla korostetaan sitä, ettei funktio muuta niiden muistipaikkojen sisältöä, joihin osoittimet osoittavat.  Siis tämäkään funktio ei muuta parametrejaan, joten sen ainoa järkevä käyttö on sijoittaa tulos johonkin muuttujaan tai käyttää funktiota muuten osana lauseketta: 
char j1[20]="Kissa", j2[20]="Koira";
int samat;
...
samat = strcmp(j1,j2);
if ( samat == 0 ) printf("Jonot ovat samat!\n");
...tai..
if ( strcmp(j1,j2) < 0 ) printf("Jono 1 on ensin!\n");

14.2.3	Varo toistoa
Vaikka yksinkertaista funktiota voidaankin käyttää mukavasti lausekkeen osana, tulee seuraavankaltaista rakennetta välttää:
int i,a_lkm=0;
char st[50]="Saippuakauppias";
for (i=0; i<strlen (st); i++) 	
  if ( jono[i]=='a' ) a_lkm++;
... 

Miksikö?  Koska strlen- funktio suoritetaan jokaisella silmukan kierroksella ja strlen suorittaminenhan vaati koko jonon läpikäymisen!  Rakenne tulee korvata seuraavasti:
int i,a_lkm=0,st_pit;
char st[50]="Saippuakauppias";
...
st_pit=strlen(st);
for (i=0; i<st_pit; i++)
  if ( jono[i]=='a' ) a_lkm++;
... 

Edellä kävisi myös seuraava, miksi?
for (i=strlen(st)- 1; i >= 0; i- - )
  if ( jono[i]=='a' ) a_lkm++;

14.3	Funktio, joka muuttaa myös parametrejaan
Aina kun palautettavia arvoja on enemmän kuin yksi, on vain kaksi mahdollisuutta:  tulos palautetaan funktion nimessä tietueessa tai parametreissa osoitteen avulla.  Esimerkiksi C- kielen valmiissa kirjastoissa ei juurikaan käytetä tietueita funktioiden arvoina.
14.3.1	strcpy
Copies string src to dest

  char *strcpy (char *dest, const char *src);

Prototype in  string.h

Returns dest. 

Tämä funktio laittaa tuloksensa (eli src- jonon kopion) dest- merkkijonoon.  Toisaalta tulos- jonon osoite palautetaan myös funktion nimessä.  Koska C- kielessä lausekkeen arvoa ei ole pakko sijoittaa mihinkään (muuten esim. i++ ei olisi mielekäs), voidaan tällaista funktiota kutsua joko itsenäisenä proseduurimaisesti tai sitten lausekkeen osana:
char jono[30], *p;
...
strcpy(jono,"Kissa");
...
p = strcpy(jono,"Susikoira") + 4;  /* p osoittaa jonoon "koira" */

On tietysti olemassa myös void- funktioita, jotka eivät palauta mitään arvoa nimessään.
14.4	Vaihteleva määrä parametreja
C- kielessä on myös mahdollista tehdä funktiota, joiden parametrilistan pituus (ja jopa parametrien tyyppi) on vaihteleva.  
14.4.1	printf ja scanf
Formatted output to stdout

  int printf (const char *format
             [, argument, ...]);

Prototype in  stdio.h

printf formats a variable number of arguments according to the format,
sending the output to stdout. Returns the number of bytes output. In the
event of error, it returns EOF. 

Kaikki tällaiset muuttuvaparametriset ohjelmat tarvitsevat ainakin yhden parametrin (1., miksi) joka ilmoittaa muiden parametrien määrän ja luonteen (=tyypin).  
Funktiossa printf tämä parametri on 1. merkkijono, format - jono, jonka % - merkkien määrä suurinpiirtein ilmoittaa muiden parametrien määrän ja % - merkkien jälkeinen kirjain ilmoittaa niiden tyypin.  Kääntäjä ei tietenkään voi tietää merkkijonon sisällöstä, joten parametrien tyypin tarkastus jää tekemättä ja vääristä kutsuista saattaa seurata jopa koneen kaatuminen!  Tyypillisin väärä esimerkki on kutsu:
int lkm;
printf("Anna kissojen lkm>");
scanf("%d",lkm);  /* VÄÄRIN VÄÄRIN VÄÄRIN */	

14.5	Aliohjelman esitteleminen
Käytännössä usein tulee tilanne, jossa tarvittaisiin aliohjelmaa, jota ei valmiina mistään löydy.  Tällöin aliohjelma tietenkin kirjoitetaan itse.  Ongelmaksi saattaa muodostua aliohjelman parametrilistan keksiminen.
14.5.1	Syöttö-  ja tulosparametrit
Eräs tilanne voisi olla vaikka seuraava.  Jostakin on saatu (esim. päätteeltä luettu) merkkijono, jossa on päivämäärä.  Toisaalta ohjelmassa on esitelty päivämäärätyyppi
typedef struct {
  int pv,kk,vv;
} Pvm_tyyppi; 

Merkkijonossa oleva päivämäärä pitäisi saada muutetuksi Pvm_tyyppiseksi.  Millainen aliohjelma kirjoitetaan?  Helpointa on ehkä miettiä kutsun kautta:
Aliohjelmalle täytyy välittää parametrina muutettava merkkijono.  Aliohjelman tulee palauttaa muutettu päivämäärä.  Siis kutsu voisi olla muotoa:
char jono[40]="13.3.1992";
Pvm_tyyppi pvm;
...
muuta_jono_pvmksi(jono,pvm);   /* ??? */

Tämä ei tietenkään voi toimia C- kielessä, koska aliohjelma ei pystyisi muuttamaan pvm- muuttujaa!  Siis kutsu täytyy olla muotoa:
muuta_jono_pvmksi(jono,&pvm); 

Tästä seuraa että aliohjelma täytyy vastaavasti esitellä muodossa:
void muuta_jono_pvmksi(const char *jono, Pvm_tyyppi *pvm)
{
...
}

Toisaalta C++:ssa myös muoto 
muuta_jono_pvmksi(jono,pvm);   /* ??? */

voisi toimia, mikäli aliohjelma esiteltäisiin viitemuuttujaa käyttäväksi
void muuta_jono_pvmksi(const char *jono, Pvm_tyyppi &pvm)

Aliohjelman tyyppi voisi olla myöskin int, jolloin aliohjelman nimessä palautetaan tieto siitä, onnistuiko muutos vai ei (miksi ei onnistuisi?).
Joskus tällainen aliohjelma voidaan esitellä jopa char * - tyyppiseksi, jotta nimessä voidaan palauttaa osoitin virhettä kuvaavaan viestiin:
const char *viesti,...
...
viesti = muuta_jono_pvmksi(jono,&pvm);
if ( viesti ) {
  printf("%s\n",viesti);
  return 1;
}

Koko ohjelma testiohjelmineen voisi olla esimerkiksi seuraava:
param\pvmjono.c - esimerkki tietueesta parametrina
#include <stdio.h>
#include <string.h>

typedef struct {
  int pv,kk,vv;
} Pvm_tyyppi;

static char *VAARA_MUOTO="Väärä muoto!";

const char *muuta_jono_pvmksi(const char *jono, Pvm_tyyppi *pvm)
{
  if ( sscanf(jono,"%d%.%d.%d",&pvm- >pv,&pvm- >kk,&pvm- >vv) != 3 )
    return VAARA_MUOTO;
  return NULL;
}

int tulosta_pvm_jono(const char *jono)
{
  const char *viesti;
  Pvm_tyyppi pvm;
  viesti = muuta_jono_pvmksi(jono,&pvm);
  if ( viesti ) {
    printf("%s\n",viesti);
    return 1;
  }
  printf("%02d.%02d.%d",pvm.pv,pvm.kk,pvm.vv);
  return 0;
}

int main(void)
{
  tulosta_pvm_jono("18.5.1992");  printf("\n");
  tulosta_pvm_jono("12.3");  printf("\n");

  return 0;
}

Tehtävä 14.134	pvmjono.cpp
Kirjoita pvmjono.c:stä C++ - versio.  Ensimmäinen versio pelkästään muuttamalla osoitteet referensseiksi ja toinen muuttamalla koko Pvm_tyyppi luokaksi cPvm  ja kaikki mahdolliset funktiot tietysti luokan metodeiksi.

14.5.2	Syöttö-  ja tulosparametri samassa
Joissakin tilanteissa ei välttämättä tarvita erillisiä parametreja syötölle ja tulokselle.  Esimerkiksi päivämäärän tapauksessa voisi tulla vastaan tarve lisätä päivämäärää yhdellä (tai useammalla päivällä).  Tällöin kutsu olisi muotoa:
Pvm_tyyppi pvm;
...
seuraava_pvm(&pvm); 

Aliohjelma esiteltäisiin vastaavasti:
void seuraava_pvm(Pvm_tyyppi *pvm)
{
...
}

Toisaalta funktion nimessä voitaisiin palauttaa vaikkapa tieto siitä, muuttuiko kuukausi
int seuraava_pvm(Pvm_tyyppi *pvm)
{
...
}
...
  int kuukausi_muuttui;
  kuukausi_muuttui = seuraava_pvm(&pvm);
  if ( kuukausi_muuttui )... 

Aikojen kuluessa aliohjelman tarve saattaisi muuttua muotoon
  lisaa_pvm(&pvm,3); 

Esittely muuttuisi tietysti:
int lisaa_pvm(Pvm_tyyppi *pvm, int lkm)
{
...
}

Tehtävä 14.135	Päivämäärän lisäys
Täydennä pvmjono.cpp:ssä luokkaan cPvm myös metodi lisaa.

14.5.3	Tietueet osoitteiden avulla tai viitteiden avulla
Usein tietueet ja oliot välitetään osoitteiden tai viitteiden avulla, vaikkei niitä olisikaan tarkoitus muuttaa aliohjelmassa.  Tähän on syynä se, että parametrin välityksessähän kopioidaan kutsumuuttujien arvot aliohjelman lokaaleihin muuttujiin.  Tietueiden tapauksessa kopiointi saattaa olla isokin, ja tämän vuoksi osoitteen välityksessä menee pelkkä osoite, ei koko tietueen sisältö.
int vertaa_pvm(Pvm_tyyppi pv1, Pvm_tyyppi pv2)
{
...
}
...
  int ero;
  Pvm_tyyppi pvm1,pvm2;
  ero = vertaa_pvm(pvm1,pvm2);  /* Oikein, mutta iso kopiointi */
  if ( ero < 0 ) printf("Pvm1 on ensin!\n");

Parametrin välitys osoitteiden avulla kopioinnin välttämiseksi:
int vertaa_pvm(Pvm_tyyppi *pv1, Pvm_tyyppi *pv2)
{
...
}
...
  int ero;
  Pvm_tyyppi pvm1,pvm2;
  ero = vertaa_pvm(&pvm1,&pvm2);  /* Oikein */
  if ( ero < 0 ) printf("Pvm1 on ensin!\n");

Tällöin tietysti aliohjelman kirjoittajan on oltava huolellinen, ettei muuta pv1 ja pv2 osoitteiden osoittamia päivämääriä!  Usein tätä kommentoidaan const esittelyllä:
int vertaa_pvm(const Pvm_tyyppi *pv1, const Pvm_tyyppi *pv2)
{
...
}
... 

Tehtävä 14.136	vertaa_pvm
Kirjoita aliohjelma vertaa_pvm, joka palauttaa - 1, mikäli pv1 on ennen pv2:sta, 0 jos päivämäärät ovat samoja ja 1 muuten.
Tehtävä 14.137	vertaa- metodi
Täydennä pvmjono.cpp:ssä luokkaan cPvm myös metodi vertaa(const cPvm &pv2), joka toimii kuten edellinen vertaa_pvm, mutta verrattavina ovat *this ja pv2.

14.5.4	Useita parametreja
Olkoon vaikkapa seuraava tilanne:  Laskettava henkilön bruttotulosta verottajalle ja henkilölle itselleen jäävät osuudet.  Miten tätä aliohjelmaa kutsuttaisiin?  Aliohjelma tarvitsee tietysti parametrikseen henkilön bruttotulon, veroprosentin sekä tiedon siitä mihin tulokset laitetaan.  Tulos muuttujia pitää voida muuttaa, joten niiden kohdalle kutsuun tulee tietysti osoittimet.  Siis kutsu voisi olla esimerkiksi:
double tulo,pid_pros,verottaja,netto;
...
laske_verot(tulo,pid_pros,&verottaja,&netto); 

Aliohjelman esittely kutsun perusteella täytyisi olla siis
void laske_verot(double brutto, double pros,
                 double *pros_osuus, double *netto)
{
...
}

Tehtävä 14.138	Sama viitteiden avulla
Toista edellinen päättely parametrien tyypeistä, jos voit käyttää viiteparametreja (referenssi).

14.5.5	Parametrien lisääminen
Usein itse tehtyä aliohjelmaa voidaan jälkeenpäin tehdä yleiskäyttöisemmäksi tai paremmaksi lisäämällä siihen parametreja.
Olkoon alkuperäinen ongelma sellainen, että on esitetty merkkijono muodossa "a- f".  Tämä pitäisi saada muutettua muotoon "abcdef".  Tarkoitusta varten kirjoitetaan aliohjelma tayta_valit.  
Mitkä ovat aliohjelman parametrit?  Tietysti alkuperäinen jono.  Mihin tulos?  Tähän tarvitaan ehkä toinen parametri.  Nimessään aliohjelma voi vielä palauttaa osoitteen tulosjonoon, kuten merkkijonofunktioilla yleensäkin on tapana:
char merkit[4]="a- f", tulos[20], sallitut[20];
...
tayta_valit(tulos,merkit);
...tai...
strcat(sallitut,tayta_valit(tulos,merkit)); 

Funktion esittely on siis muotoa:
char *tayta_valit(char *tulos, const char *jono);
... 

Myöhemmin huomataan, että tulos- jonon maksimipituus voidaan ylittää!  Siksi funktion kutsuun lisätäänkin yksi parametri:
tayta_valit(tulos,20,merkit); 

ja vastaavasti esittelyyn:
char *tayta_valit(char *tulos, int max_pit, const char *jono); 

Toinen vastaava esimerkki oli seuraava_pvm aliohjelman muuttaminen aliohjelmaksi lisaa_pvm.
14.5.6	Ei printf eikä scanf tai tietovirtoja
On aina muistettava, että aliohjelman tehtävä on työskennellä syöttö-  ja tulosparametriensa avulla.  Siis jollei ongelmaan ole erikseen määritelty päätesyöttöä tai tulostusta, ei aliohjelmassa saa olla printf ja scanf eikä muita vastaavia lauseita (mm. cin >>, cout <<) !
Olisihan todella yllätys, mikäli kutsussa 
y = sin(x); 

rupeaisi tulostumaan näytölle jotakin tai jopa odotettaisiin syöttöä päätteeltä!
14.6	Osoitteista ja osoittimista
Mikäli ohjelmassa esitellään osoitintyyppisiä muuttujia, pitää aina muistaa perustella mihin osoittimet osoittavat.  Esimerkiksi seuraava ohjelma olisi todella väärin:
char *jono;              /*  VÄÄRÄ ESIMERKKI!!!! */ 
strcpy(jono,"Kissa"); 	

Mihin muuttuja jono osoittaisi?  Satunnaiseen paikkaan?  Ja tänne satunnaiseen paikkaan kopioidaan teksti "Kissa"!
Vastaavasti seuraava ohjelma olisi jo oikeampi:
char *jono, st[30];
strcpy(st,"Kissa");
jono = st+5;
strcpy(jono,"tarha");  /*  - > st = "Kissatarha" */

Vikana olisi tietysti vielä se, ettei merkkijonojen maksimipituuksien ylittämistä valvota!
14.6.1	Muista aina sijoitus tai malloc tai new
Aina kun ohjelmassa esiintyy osoitintyyppinen muuttuja, pitää muistaa, että ennen sen käyttöä se on alustettu joko sijoituksella toiseen osoitteeseen tai osoittimelle on annettu arvo malloc- funktiolla tai new- operaattorilla (joihin palataan myöhemmin!).  Siis aina:
                  int *osoitin;
/* Aina joko */
/* 1 */           osoitin = &muuttuja;
/* tai */
/* 2 */           osoitin = malloc(...);
// tai
/* 3 */           osoitin = new...

Noudattaako aliohjelman parametrit tätä sääntöä?
void laske_verot(double brutto, double pros,
                 double *pros_osuus, double *netto) 

Kyllä, koska kutsu
laske_verot(10000.0,40.2,&verottajalle,&itselle) 

tarkoittaa sijoitusta aliohjelman parametreihin:
brutto     = 10000.0
pros       = 40.2
pros_osuus = &verottajalle
netto      = &itselle

14.6.2	Milloin osoitin?
Yleensä ohjelman muuttajat esitellään tavallisiksi muuttujiksi (paitsi taulukot, jotka ovat aina osoitteita).  Osoitinmuuttujia esitellään pääsääntöisesti 
•	parametrilistoissa
•	kun käytetään dynaamisia muuttujia 
•	kun edetään taulukkoa osoittimen avulla (indeksien sijasta)
•	virheviestien (tai sen tyylisten) vakio- osoitteiden saamisessa
14.6.3	Varoitus
Aloittelevalla ohjelmoijalla seuraavannäköinen aliohjelman alku on pääsääntöisesti väärin:
int oma_ali(...)
{
  int *a; 	// aloittelijalla 
... 

Siis tarkista aina huolellisesti kaikki lohkon alkumerkin jälkeen määritellyt osoitintyyppiset muuttujat!
14.6.4	Kertaustehtäviä
Kirjoita seuraavat funktiot tai aliohjelmat sekä kirjoita kullekin oma testipääohjelma.
Tehtävä 14.139	suurin_kirjeen_paino
Kirjoita funktio int suurin_kirjeen_paino(double rahaa) joka palauttaa mikä on suurin kirjeen paino, joka voidaan lähettää rahamäärällä rahaa.
Olkoon postimaksut:
korkeintaan  50 g   2.10 mk
korkeintaan 100 g   3.40 mk
korkeintaan 200 g   5.20 mk
korkeintaan 500 g   9.30 mk
Tehtävä 14.140	kysy_ika
Kirjoita aliohjelma kysy_ika, joka kysyy henkilön iän ja palauttaa sen parametrinaan.  Ikää kysytään kunnes iäksi on vastattu luku väliltä 18- 65 vuotta.  Aliohjelman nimessä palautetaan tieto siitä, monestiko ikää piti kysyä ennenkuin hyväksytty tulos saatiin.
Tehtävä 14.141	palindromi
Kirjoita funktio palindromi, joka palauttaa nimessään tiedon siitä (1=kyllä, 0=ei) onko parametrina välitetty sana palindromi vai ei.
Tehtävä 14.142	laske_merkin_maarat
Kirjoita funktio laske_merkin_maarat, joka palauttaa nimessään tiedon siitä, monestiko parametrina välitetty merkki esiintyy parametrina välitetyssä jonossa (HUOM!  ei täysin sama kuin laske_merkit, mikä ero?).
merkki = 's'
jono   = "Kissa"  - > palauttaa 2
Tehtävä 14.143	vero
Kirjoita aliohjelma vero, joka toimii seuraavasti:
                          esimerkki 
aliohjelma   vero                     (nimessä ei mitään)
parametrit:  brutto       20000.0     (syöttö, input)
             vero            20.0     (syöttö, input)
      - >     netto        16000.0     (palautus, output)
      - >     verottajalle  4000.0     (palautus, output) 

Tehtävä 14.144	maara_alennus
Kirjoita funktio maara_alennus, joka laskee tavaroiden hinnan ja myöntää siihen määräalennuksen, mikäli tavaraa ostetaan enemmän kuin tiettyä rajaa (toimii esim. seuraavasti):

                           esimerkki 1 esimerkki 2
funktio      maara_alennus   
parametrit:  kpl_hinta         10.0      10.0
             ostos_maara        3.0      10.0
             alennus_raja       5.0       5.0
             alennus %         20.0      20.0
             nimessä      - >   30.0      80.0

Tehtävä 14.145	mjono_ajaksi
Kirjoita muuta_jono_pvmksi matkien aliohjelma mjono_ajaksi.
Muuta mjono_ajaksi aliohjelmaa siten, että se toimii seuraavasti:
  jono             tun min sek sad
  "14"          - >  14   0   0   0
  "14:30"       - >  14  30   0   0
  "14:30:25"    - >  14  30  25   0
  "14:30:25.20" - >  14  30  25  20 

Tehtävä 14.146	aika_mjonoksi
Kirjoita edelliselle käänteinen aliohjelma aika_mjonoksi.

Muuta aika_mjonoksi - aliohjelmaa siten, että välitetään aika- tyypin lisäksi muotoa kuvaava parametri seuraavasti:

   0  - >  muutetaan muotoon  "14:30"
   1  - >  muutetaan muotoon  "14:30:25"
   2  - >  muutetaan muotoon  "14:30:25.20" 

Kirjoita testipääohjelma, jossa kysytään kellonaikaa merkkijonoon kunnes ajaksi vastataan q (kannattaa kutsua lue_jono_oletus - aliohjelmaa) ja muutetaan tämä sitten mjono_ajaksi - aliohjelmalla ja tulostetaan aikatyyppi käyttäen apuna aika_mjonoksi - aliohjelmaa.

Muuta aika_mjonoksi - aliohjelmaa siten, että muoto- parametrin arvoilla 0 ja 1 suoritetaan katkaisun sijasta pyöristys.

  14 30 35 45 - >  "14:31"
  14 30 29 30 - >  "14:30"
  14 30 30 00 - >  "14:31"
  14 30 35 45 - >  "14:30:36"

Tehtävä 14.147	cAika
Lisää aiemmin esitettyyn luokkaan cAika metodit jonoksi ja sijoita, jotka mukailevat aliohjelmia mjono_ajaksi ja aika_mjonoksi.

15.	Kommentointi ja jakaminen osiin
Mitä tässä luvussa käsitellään?
•	ohjelman kommentointi
•	ohjelman jakaminen osiin 
•	ehdollinen kääntäminen
•	makefile
•	projekti
15.1	Kommentointi 
"Löysin vuosi sitten kirjoittamani kolmen rivin aliohjelman.  Siinä ei ollut yhtään kommenttia.  Miten se hoiti tehtävänsä?  Mietin asiaa kaksi päivää ja sitten ymmärsin miksi tehtävä oli triviaali eikä tarvinnutkaan kommenttia." (Vapaa käännös verkossa olleesta kirjeestä / vl).
Kommentoimme jatkossa aliohjelmat huolellisesti.  Seuraavana on kuvattu eräs tapa.  Tietenkin on miljoona muutakin tapaa, mutta ainakin seuraavan tavan ajatus kannattaa pitää mielessä.
15.1.1	Valmiin kommenttilohkon lukeminen
Aliohjelman kirjoittaminen aloitetaan vaikkapa seuraavan f.c - nimisen tiedoston lukemisella koodin sekaan (seuraavassa kursiivilla kirjoitetut osat eivät ole mukana  tiedostossa): 
komloh\f.cpp -  pohja funktioiden kirjoittamiselle 
//***************************************************************************
int                       //
funktio(                  // Funktion eri paluuarvot                        
                          //                                                
  p1                     ,// s   Selitys Parametrin merkitys                
  p2                     ,// t   Selitys                                    
  p3                      // s   Selitys                                    
)    
/* 
** Funktiolla ... Lyhyt (mutta riittävä) kuvaus siitä, MITÄ funktio tekee
**
** Globaalit: Globaalit muuttujat joita ohjelma tarvitsee
** Muuttuu: Muuttujat (yleensä glob) joita muutetaan ja eivät ole param.listassa
** Syöttö: Mistä saadaan syöttö
** Tulostus: Mihin tulosteet
** Kutsuu: Mitä aliohjelmia kutsutaan (usein tarpeeton rivi)
** Tekijä:    Vesa Lappalainen
** Pvm:       26.01.1997
** Algoritmi: Miten funktio tekee tehtävänsä
** Esimerkki: Muutama selventävä esimerkki
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
}

Lohkokommenteissa (kommenttia monta riviä peräkkäin) ** pyritään saamaan alkamaan sarakkeesta 1.  Miksikö?  Tällöin ohjelman dokumentointia voidaan automatisoida siten, että kerätään kustakin tiedostosta ne rivit, joissa on ** 1. sarakkeessa.
15.1.2	Parametrilistan kommentointi
Tämän jälkeen korjataan funktion tyyppi ja nimi sekä parametrilista (helpointa päällekirjoitustilassa).  Tarpeettomat rivit poistetaan tai tarvittaessa kopioidaan rivi uudeksi.
Parametrilistan kommenteissa on esimerkiksi seuraavat symbolit:
s   = syöttömuuttuja (tulee aliohjelmaan)
t   = tulosmuuttuja (aliohjelma muuttaa tämän arvoa)
s,t = parametri on sekä syöttö, että tulos

Mikäli joku muuttuja on puhtaasti syöttömuuttuja, mutta siitä huolimatta sen arvo muuttuu (esim. jotkin pätkimisaliohjelmat), kannattaa parametri mainita vielä Muuttuu: kohdassa.
15.1.3	Funktion toiminta ja muu esittely
Täytetään lyhyt kuvaus funktion toiminnasta.  Täytetään globaalit: yms. kohdat.  Kenttien tarkoitus on seuraava:
Globaalit: globaalit muuttujat joita aliohjelma tarvitsee
Muuttuu:   ne muuttuvat arvot jotka eivät ole parametrilistassa
           = lähinnä globaalit muuttujat (hyvä aliohjelma ei 
           näitä tarvitse)
Syöttö:    pääte, tiedostot
Tulostus:  pääte, tiedostot
Kutsuu:    mitä EI- tunnettuja aliohjelmia tai makroja tarvitaan
           C:n standardikirjastojen kutsuja ei tarvitse luetella
           jolleivat ne ole kovin eksoottisia, ei myöskään samassa tiedostossa
           tai projektissa olevia "tunnettuja" aliohjelmia
Tekijä:    kuka tehnyt (jos triviaali aliohjelma, voidaan
           jättää pois)
Pvm:       milloin tehty ja myös muutokset
Algoritmi: algoritmin karkea kuvaus ja mahdolliset lähdeviitteet 
Esimerkki: esimerkkejä, joilla havainnollistetaan aliohjelman 
           toimintaa ja saadaan itsellekin kirjattua ylös
           se mitä ollaan tekemässä

Näistä tarpeettomat (triviaalit tai tyhjiksi jäävät) rivit poistetaan.
15.1.4	Koodin kommentointi
Itse ohjelmakoodi kommentoidaan seuraavasti:
•	selviä C- kielen rakenteita ei saa kommentoida.  Ei siis
i=5; /* sijoitetaan i on 5 */               /* TURHA! */
•	kuitenkin mikäli lauseella on selvä merkitys algoritmin kannalta, kommentoidaan tämä
i=5; /* aloitetaan puolestavälistä */
•	ryhmitellään lauseet tyhjien rivien avulla loogisiksi kokonaisuuksiksi.  Tällaisen kokonaisuuden alkuun voidaan laittaa kommenttirivi, joka kuvaa kaikkien seuraavien lauseiden merkitystä.
•	mikäli tekee mieli kommentoida lauseryhmä, kannattaa miettiä voitaisiinko koko ryhmä kirjoittaa aliohjelmaksi.  Aliohjelman nimi sitten kuvaisi toimintaa niin hyvin, ettei kommenttia enää tarvittaisikaan.  Kuitenkin jos näin suunnitellulle aliohjelmalle tulee iso kasa (liki 10) parametreja, täytyy asiaa ajatella uudestaan.
•	muuttujien nimet valitaan kuvaaviksi.  Kuitenkin mitä lokaalimpi muuttujan käyttö, sitä lyhyemmäksi nimi voidaan jättää. i ja j sopivat aivan hyvin silmukkamuuttujien nimiksi ja p yms. osoittimen nimeksi (lokaalisti).
•	globaaleja muuttujia vältetään 'kaikin keinoin'
•	olioiden ansiosta globaalit muuttujat voidaan yleensä välttää kokonaan!
•	mikäli globaaleja muuttujia kuitenkin tarvitaan, kasataan ne yhteen struktuuriin
typedef struct {
  int jasen_maara;
  int nayton_koko;
...
} globaalit_tyyppi;
globaalit_tyyppi GLOBAALIT;
...
GLOBAALIT.jasenmaara=5; 
jolloin globaaleja muuttujia ei muuteta vahingossa!
•	tarvittaessa määritellään useita eri nimisiä globaaleja tietueita.
•	vakiotyyliset (alustetaan esittelyn yhteydessä eikä ole tarkoitus ikinä muuttaa) globaalit muuttujat on sallittu sellaisenaan ja niiden nimet kannattaa ehkä kirjoittaa isolla.
•	funktioiden paluuarvolle valitaan tietty tyyli, joka pyritään säilyttämään koko ohjelman ajan.  Esimerkiksi 0 = onnistui ja muut virheilmoituksia.
15.2	Omat aliohjelmakirjastot
Aiemmin rakensimme joukon merkkijonojen käsittelyssä tarvittavia apuohjelmia.  Nämä aliohjelmat voitaisiin kopioida suoraan myös kerhorekisteriimme.  Käytännössä näin ei kuitenkaan kannata tehdä, sillä valmiiksi testatut aliohjelmat olisivat mukana vain turhaan lisäämässä käännösaikaa.  
Tämän takia aliohjelmat kirjoitetaan omaksi tiedostokseen, vaikkapa nimelle mjonot.c.  Aliohjelmien otsikot (esittelyrivit) ja muut kaikille tarkoitetut määrittelyt kirjoitetaan tiedostoon mjonot.h.  
15.2.1	mjonot.h
Tiedosto mjonot.h voisi olla vaikkapa seuraavanlainen:
/* Makro jolla saadaan muuttujan nimi ja koko peräkkäin */
#define N_S (nimi) nimi,sizeof(nimi)

/****************************************************************************/
/* vakiot syötön onnistumiselle                                             */
#define  SYOTTO_OK 2
#define  EI_MAHDU 1
#define  OLETUS 0
#define  TIEDOSTO_LOPPU - 1
#define  VIRHE_SYOTOSSA - 2

extern char *VALIMERKIT;

/****************************************************************************/
char *tee_jono(char *);
int f_lue_jono(FILE *, char *, int);
int lue_jono(char *, int );
...
int wildmat(register char *, register char *);

Tämä tiedosto on ehkä helpointa tehdä kopioimalla kaikki alkuperäiset aliohjelmat tiedostoon ja tämän jälkeen tuhoamalla aliohjelmien suoritusosat.  On olemassa myös ohjelmia, jotka tekevät tiedostosta näitä prototyyppitiedostoja.
Huomattakoon, että myös seuraavia muotoja voi esiintyä .h - tiedostoissa:
int lue_jono(char *jono, int max_pituus);
...
extern int lue_jono(char *jono, int max_pituus);
...
int lue_jono(char *, int);
...
extern int lue_jono(char *, int); 

Funktioiden yhteydessä extern oleminen tai puuttuminen ei kuitenkaan haittaa mitään.  Toisin on muuttujien kanssa!
Huom! Nyt funktioiden esittelyjen perään täytyy muistaa laittaa puolipiste!
15.2.2	mjonot.c 
Vastaavasti itse tiedosto, jossa aliohjelmat ovat, olisi seuraavan näköinen:
ali\mjonot.c - aliohjelmia merkkijonojen käsittelyyn 
/****************************************************************************/
/*  
** M J O N O T . C
**
** Yleisiä merkkijonojen käsittelyyn liittyviä aliohjelmia.               
**
...
** Aliohjelmat:
**    tee_jono               -  luo uuden merkkijonon jonne jono kopioidaan
**    kopioi_jono            -  kopioi kork. annetun määrän merkkejä
...
**                             merkkijonossa
**    wildmat                -  vertaa onko sana == maski, missä maskissa
**                             voi olla jokeri- merkkejä
...
*/       

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "mjonot.h"

char *VALIMERKIT=" .,- ;:?!";  

/****************************************************************************/
char                    /* = jonon kopion osoite                            */
*tee_jono(              /* NULL = ei voida kopioida                         */
  char *jono            /* s   Kopioitava merkkijono                        */
)
...
... Kuten ennenkin ...
...                                       

/****************************************************************************/
int                       /*                                                */
wildmat(                  /* 0 = jono täsmää maskiin                        */
                          /* 1 = jono ei täsmää maskiin                     */
  const register char *s ,/* s   Tutkittava jono                            */
  const register char *m  /* s   Maski, johon jonoa verrataan               */
)
/*
** Funktiolla tutkitaan täsmääkö annettu jono verrattavaan maskiin.
** Maski saa sisältää seuraavia erikoismerkkejä:
**   * vastaa 0-n merkkiä
**   ? vastaa mitä tahansa yhtä merkkiä
**
** Algoritmi: Kysymysmerkki ja tavallinen kirjain normaalisti
**            Jos tulee vastaan tähti joka ei ole jonon lopussa,
**            niin ongelmahan on oikeastaan
**            (koska tähän asti kaikki on ollut oikein)
**            "Onko loppujono sama kuin toisen jonon loppu JOSTAKIN
**             kohdasta alkaen"?
**            Siis kokeillaan sovittaa loppujonoa aliohjelman itsensä
**            (rekursio) avulla kaikkiin mahdollisiin loppupaikkoihin.
** Esimerkki: s = "Kissa" m = "*ss*" -> 0
**                          = "*ss"  -> 1
** Vika alkuperäisessä algoritmissa:
**   Jos m="*a" ja s="" ja s[1]!=0 (mikä tietysti sallittua!)
**   niin vastaus oli 0.
**   Korjattu 29.1.1994/vl muuttamalla rekursion jälkeinen
**             if (!*++s) return 1;
**   muotoon
**             if (!*s || !*++s) return 1;
----------------------------------------------------------------------------*/
{
  while (*m) {                    /* Jos kokeiltavaa jonoa on jäljellä      */
    if (*m == '?') {              /* Jos kysymysmerkki, niin kaikki kelpaa  */
      if (!*s) return 1;          /* paitsi jos jono on loppunut!           */
    }
    else if (*m == '*') {         /* Jos * niin katsotaan onko viimeinen    */
      if (*++m)                   /* Jollei * viimeinen, niin kokeillaan    */
        while (wildmat(s, m))     /* loppujonoa jokaiseen eri paikkaan.     */
          if (!*s || !*++s) return 1;/* Jos jono loppuu kesken ei täsmää!   */
      return 0;                   /* Muuten samat (* viimeinen tai loppujono*/
    }                             /* täsmäsi)                               */
    else if (*s != *m)            /* Jos samat merkit niin tietysti OK!     */
      return 1;
    s++; m++;                     /* Kummassakin jonossa eteenpäin          */
  }

  return *s;                      /* Jos jono loppui yhtäaikaa, niin OK!    */
}
...

Olemme lisänneet kirjastoon muutamia uusia aliohjelmia, joiden tarpeellisuuden totesimme jo kerhorekisterin suunnitteluvaiheessa.
Kirjastossa on mm. wildmat- aliohjelma merkkijonojen samaistamiseksi, kun jonossa saa esiintyä jokerimerkkejä * ja ? (vrt. MS- DOS).
Tehtävä 15.148	wildmat (opettavainen)
Pöytätestaa wildmat- aliohjelma syötöllä Kissa ja *ss*.  Ohje:  Kirjoita normaalin pöytätestin mukainen taulukko.  Koska funktio kutsuu itseään, kannattaa jokaista uutta itseään kutsumista varten kirjoittaa oma uusi sarake uuden kutsukerran muuttujista.  Osoittimet s ja m kannattaa säilyttää muodossa, jossa ne näyttävät loppumerkkijonon.  Esimerkiksi:
aluksi  s = "Kissa";  *s='K'
s++  - > s = "issa"    *s='i'
s++  - > s = "ssa"     *s='s'
15.2.3	Kirjaston testaus
Kirjaston testausta varten voimme kirjoittaa vaikkapa ohjelman t_mjonot.c:
komloh\t_mjonot.c -  testiohjelma merkkijonokirjastolle
#include <stdio.h>
#include "mjonot.h"

/****************************************************************************/
/* testiohjelmat:                                                           */

void lue_jono_testi(void)
{
/* täytä! */
}

int f_lue_jono_testi(void)
{
/* täytä! */
}

void lue_kok_testi(void)
{
/* täytä! */
}

void lue_jono_oletus_testi(void)
{
  int paluu;
  char st_ole[50],st[50] = "Ankka Aku";

  printf("Testi loppuu, kun painat ^Z.\n");
  printf("1234567890123456789012345678901234567890\n");

  do {
    strcpy(st_ole,st);
    paluu=lue_jono_oletus("Anna jäsenen nimi",19,33,st_ole,st,50);
    poista_tyhjat(st);
    jono_alku_isoksi(st);
  } while ( paluu >= OLETUS );
}

void wild_testi(void)
{
  char jono[80],maski[80];
  while (!feof(stdin)) {
    printf("Anna jono ja maski>");
    scanf("%s %s",jono,maski); /* Älä käytä oikeasti!!!! */
    printf("%d <-  %s %s\n",wildmat(jono,maski),jono,maski);
  }
}

int main(void)
{
#if 0
  lue_jono_testi(); 
#endif
#if 0
  f_lue_jono_testi(); 
#endif
#if 0
  lue_kok_testi();
#endif
#if 0
  lue_jono_oletus_testi();
#endif
#if 1
  wild_testi();
#endif
  return 0;
}

Huomattakoon, että nyt täytyy olla lainausmerkit lauseessa
#include "mjonot.h"

Jotta ohjelma voidaan kääntää, tarvitsemme projektin tai MAKEFILEn.  Kunnes olemme ne käsitelleet, voidaan "kerho.h" tilapäisesti korvata "kerho.c".  Normaalisti c- tiedostoja EI SAA "includeta"!
15.3	Ehdollinen kääntäminen
Edellisessä pääohjelmassa testattava osa  voidaan valita muuttamalla #if - lauseissa esiintyviä 0 ja 1:iä.
Kyseessä on esiprosessorin direktiivi, jolla määrätään minkä osan esiprosessori antaa varsinaiselle kääntäjälle käsiteltäväksi.
Tällä periaatteella voidaan rakentaa ohjelmia, joissa tekstit tulevat eri kielillä (englanti, ruotsi, suomi) sen mukaan, mitä ohjelmassa on määritelty. 
Samalla tempulla teimme myös lue_merkki - aliohjelman toimimaan joko Turbo- C:ssä tai standardi ANSI- C:ssä sen mukaan onko määritelty sana __TURBO__ (huomaa kaksi _ kummallakin puolella!): 
#ifdef __TURBOC__
#  define GETCH
#  include <conio.h>
#endif
...
char lue_merkki(void)
{
#ifdef GETCH
  ... /* lukeminen conio- kirjaston avulla */
#else
  ... /* lukeminen standardikirjaston avulla */
#endif
}

Usein kääntäjään liittyy kääntäjän määrittelemiä symboleja, joiden olemassaoloa testaamalla voidaan tehdä laiteriippuvia osia.  Juuri tällainen oli __TURBOC__.  Ehtona voi olla myös jokin oma sana kuten edellä GETCH.  Usein itse määriteltyjä sanoja käytetään estämään .h- tiedoston moninkertaiset esiintymät: 
ali\mjonot.h - estetään monikertainen käyttö
... 
#ifndef MJONOT_H
#define MJONOT_H
... /* varsinainen h- tiedoston sisältö */
#endif

Nyt voidaan pääohjelmassa kirjoittaa
#include "mjonot.h" /* Lukee mjonot.h - tiedoston kokonaan */
#include "mjonot.h" /* Ei tee mitään koska MJONOT_H määr. */

15.4	Header- tiedostot ja prototyypit
Kutakin isompaa aliohjelmakokonaisuutta varten kirjoitetaan oma header- tiedosto (.h - tiedosto).  Kääntäjän toiminta on sellainen, että kun kääntäjä tulee esimerkiksi lauseeseen
int a,i;
...
a = ynnaa_yksi(i); 

voidaan suluista päätellä aina, että kyseessä on aliohjelman kutsu.  Loppu onkin kääntäjälle vaikeampaa.  Aliohjelmalle pitäisi välittää oikean tyyppinen parametri.  Kääntäjä voi yrittää arvata, että esimerkin aliohjelma olisi muotoa
int ynnaa_yksi(int luku); 

koska sille välitetään parametrina int - tyyppinen luku.  Mikäli arvaus osoittautuisi vääräksi ja aliohjelma olisikin esitelty
double ynnaa_yksi(double luku); 

olisi käännös mennyt väärin arvauksen kohdalta, koska kokonaisluku ja reaaliluku talletetaan aivan eri tavalla.  Virhe voidaan normaalisti välttää sillä, että kirjoitetaan (esitellään) aliohjelma ennen sen käyttöä.
Aliohjelmakirjastojen kanssa on hankalampaa.  Jottei aliohjelmia tarvitsisi esitellä useita kertoja, on aliohjelman parametrien tyypit mahdollista esitellä prototyypillä
int lue_jono(char *jono, int max_pituus); 

Kun kaikkien tarvittavien aliohjelmien prototyypit laitetaan .h - tiedostoon, ja tämä tiedosto luetaan esiprosessorin 
#include "mjonot.h"

direktiivillä käännöksen aikana koodin mukaan, saadaan kullekin aliohjelmalle oikea tyyppi.  
Vastaavasti koko ohjelmaan saattaa tulla eri tiedostojen välisiä globaaleita muuttujia, ja kuitenkin muuttujan saa esitellä vain yhdessä paikassa.  Jotta kaikki tiedostot näkisivät globaalit muuttujat, voidaan ne esitellä header- tiedostossa extern - määreellä.  Itse varsinainen muuttuja varataan sitten siinä TASAN YHDESSÄ tiedostossa, johon se "eniten" kuuluu.  Tällöin muuttujaan ei tule extern - määritystä.
kerho.h:
  extern const char *VERSIO;
...
kerho.c
  const char *VERSIO = "18.2.1992"
..
muissa tiedostoissa esimerkiksi:
  printf("Versio %s\n",VERSIO); 

Siis yleensä header- tiedostoon tulee
•	#define -  vakiot
•	typedef -  esitellyt tietueet
•	#define -  esitellyt yleiskäyttöiset makrot
•	extern -  muuttujien esittelyt (hyi!)
•	funktioiden prototyypit
•	luokkien esittelyt
Header- tiedostoon EI yleensä kirjoiteta varsinaisia kääntyviä lauseita, kuten muuttujien varaamista tai ohjelmakoodia (paitsi inline- funktioiden koodi)!
15.5	Kääntäminen ja linkittäminen
Header - tiedosto oli siis kääntäjää varten. 
Kukin kokonaisuuteen kuuluva osa  voidaan kääntää erikseen.  Lopuksi nämä osat linkitetään yhteen, jolloin muodostuu haluttu valmis ohjelma.
Ongelmana on sitten muistaa kääntää uudestaan aina kaikki muuttuneet osat.  Tietysti voisimme aina varmuuden vuoksi kääntää kaikki osat, mutta tämä veisi turhaa aikaa.
Tätä varten on MAKE- niminen ohjelma, jolle kirjoitetaan ohjetiedosto siitä, mitkä tiedostot muodostavat valmiin työmme ja miten eri tiedostot riippuvat toisistaan.
Esimerkiksi mikäli mjonot.c tiedostoon tulee muutos, tarvitsee se kääntää sekä suorittaa linkitys uudestaan.  Mikäli mjonot.h muuttuu, pitää sekä mjonot.c ja t_mjonot.c kääntää uudelleen.
15.5.1	Make- ohjelma 
Esimerkiksi HP- UX - ympäristössä kerho- ohjelman tarkistukset suorittava versio voitaisiin kääntää seuraavan tiedoston (kirjoitettu nimelle makefile) avulla:
komloh\makefile - kerho-ohjelman C-version kääntäminen Unixissa
# makefile kerho- ohjelmaa varten
kerho: kerho.o kerhorak.o kerhoets.o kerhotar.o kerhoali.o\
       kerhotal.o kerhoopt.o kerholra.o                   \
       mjonot.o pvm.o help.o
	cc  - Aa - o kerho *.o - lm
.c.o:
	cc - c - Aa $*.c

Huomattakoon, että cc- alkuisten (c- kääntäjän kutsu Unixissa) rivien on oltava sisennetty TAB- näppäintä käyttäen.  Seuraavassa hieman selityksiä:
kerho:   -  tiedosto kerho riippuu näistä .o (obj)
           tiedostoista
  cc ... -  jos jossakin .o:ssa on uudempi päiväys kuin kerho
           tiedostossa, luodaan uusi kerho tällä komennolla
           - Aa  = ANSC- C käännös
           - o   = tulostiedosto (output)
           - lm  = linkitetään matematiikkakirjasto
.c.o:    -  .c tiedostoista tehdään .o tiedosto seuraavasti
           jos .c:n päiväys on uudempi kuin .o:n. 

Käännös ja ajaminen suoritetaan seuraavasti
$ make[RET]
...
$ kerho[RET]
...
Tällä ohjelmalla... 

15.5.2	Projektit 
Useissa integroiduissa ympäristöissä on mahdollisuus tehdä projekteja, eli ilmoittaa mitkä ovat työhömme vaikuttavia tiedostoja.  Usein näiden projektien käyttäminen on helpompaa kuin MAKE- tiedostojen tekeminen.  
Projektin merkitys on vastaava kuin MAKE- tiedostonkin: pitää kirjaa siitä, mitkä tiedostot kuuluvat kokonaisuuteen.  Toisaalta projektitiedostoa tarvitaan erityisesti linkityksessä.  
Mikäli merkkijonontestaus esimerkkimme mjonot.c käännettäisiin irrallisena, syntyisi tiedosto mjonot.obj.  Tähän ei vielä projektia tarvita.  Vastaavasti voidaan kääntää pääohjelma t_mjonot.c.  Vieläkään ei projektia tarvittaisi.  Ongelmaksi tuleekin linkitys.  Päämodulissa viitataan aliohjelmaan lue_jono.  Mistä linkittäjä arvaisi etsiä tätä aliohjelmaa tiedostosta mjonot.obj?  
Tämä tieto löytyy projektista, johon esimerkissämme mainittaisiin ne C- tiedostot, jotka kääntämisessä tarvitaan, eli
t_mjonot.c
mjonot.c

Mikäli C- kielistä koodia ei ole olemassa, voidaan projektiin usein ilmoittaa myös jo käännettyjä tiedostoja, eli esimerkiksi
t_mjonot.c
mjonot.obj

Siis linkittäjä etsii aliohjelmia ja globaaleita muuttujia projektissa mainituista tiedostoista C- kielen standardikirjastojen lisäksi.
Esimerkiksi Turbo- C:ssä valitaan aluksi uuden projektin luonti.  Tämän jälkeen projekti- ikkunaan osoitetaan ne *.c tiedostot, joita haluamme käyttää.  Voidaan myös lisätä *.obj tai *.lib tiedostoja, mikäli lähdekielisiä tiedostoja ei ole. 
Header- tiedostoja (*.h) ei koskaan laiteta projektiin!
Itse kääntäminen suoritetaan sitten normaalisti.  
Projektin perusteella kääntäjä ensin kääntää kaikki ne .c- tiedostot, joista ei vielä ole .obj - tiedostoa tai .obj- tiedoston päiväys on vanhempi kuin .c- tiedoston.  Tämän jälkeen linkitetään kaikki projektissa mainitut .obj- tiedostot yhdeksi .exe- tiedostoksi.
Huomattakoon että projektissa tulee olla tasan yksi .c- tiedosto, jossa on main- funktio.
15.6	Kerho- ohjelman jako osiin (ks. jako.15- hakemisto)
Kirjoittamisen ja ylläpidon kannalta voi olla helpompi jakaa ohjelma muutamaan loogiseen osaan.  Nyt on valmiiksi kirjoitettuna jäsenrekisterin runko- osa.  Tällä rungolla voidaan testata tietorakenteiden toimivuus:  Rungossa meillä on käytössä alkeellinen näytölle tulostava aliohjelma.
Rungossa osa aliohjelmista on hyvin yleisluonteisia tulostus/luku aliohjelmia ja nämä kaikki voitaisiin siirtää vaikkapa tiedostoon nimeltä ioali.c.  Muuten jako kannattaa tehdä luokkapohjaisesti: 
•	kerhomain.cpp	- pääohjelma
•	jasen.cpp	- yksittäisen jäsenen käsittely
•	jasenet.cpp	- jäsenistön käsittely
•	kerho.cpp	- kerho kokonaisuudessaan
•	naytto.cpp	- näyttöön liittyvä kerhon käsittely

Vastaaviin .h- tiedostoihin tulee kunkin luokan esittely 
class cLuokka {
 ...
};

Varsinainen todellinen koodi tulee sitten vastaaviin .cpp - tiedostoihin.
Lisäksi käytämme tietysti valmiita kirjastoja sekä aikaisemmin tehtyä merkkijonokirjastoa mjonot.
On turha toivo, että keksisimme kaikki määritykset ja aliohjelmat kerralla.  Tehtävää täytyy hahmotella palanen kerrallaan.   Kun jokin homma tuntuu venyvän liian pitkäksi tai monimutkaiseksi, määrittelemme tehtävän useampaan alatoimintoon ja toteutamme nämä toiminnot sitten aliohjelmina/luokkina.  Aliohjelmien/metodien parametrit saattavat vielä myöhemmin muuttua, kun huomataan saman tehtävän käyvän sekä tähän että tuohon tehtävään.  Esimerkiksi etsiminen ja selailu käy samantien myös korjailuun ja poistoon.  Ainoana erona on, että korjaus-  ja poistonäppäimet eivät ole pelkässä etsimisessä sallittuja.

16.	Dynaaminen muistinkäyttö
Mitä tässä luvussa käsitellään?
•	Dynaaminen muistinhallinta 
•	Dynaamiset taulukot
•	Hajottaja (destructor)

Syntaksi:
Dyn.muut.luonti C:    pMuuttuja = malloc(koko);
                      pTaulukko = malloc(alkioiden_lkm * alkion_koko); // ei alus
                      pTaulukko = calloc(alkioiden_lkm,alkion_koko); // alust. 0
                C++:  pOlio = new cLuokka;         // oletusmuodostaja
                      pOlio = new cLuokka(muodostajan_param_lista);
                      pOlioTaul = new cLuokka[alkioiden_lkm];
Koon vaihto     C:    pTaulukko = realloc(pVanha,uusi_koko_tavuina);
Hävittäminen    C:    free(pMuuttuja);
                      free(pTaulukko);
                C++:  delete pOlio;
                      delete [] pTaulukko;

Hajottaja             ~cLuokka();    
 
Yhdessä käytettävät funktiot tai operaattorit:
C:   malloc - calloc - realloc - free
C++: new - delete
     new [] - delete []

Olemme oppineet varaamaan muuttujia esittelyn yhteydessä.  Muuttujia voidaan luoda (= varata muistitilaa) myös ohjelman ajon aikana.  Tämä on tarpeellista erityisesti silloin, kun ohjelman kirjoittamisen aikana ei tiedetä muuttujien määrää tai ehkei jopa edes kokoa (taulukot).
16.1	Muistin käyttö
Karkeasti ottaen tavallisen ohjelman muistinkäyttö näyttäisi ajan funktiona seuraavalta:

            ^
muistin     │     
kaytto      │    ┌───────────────────────────────┐ 
            │    │                               │
            │    │                               │
            │    │                               │
            │    │                               │
            │    │                               │
            │    │                               │
            │    │                               │
            │    │                               │
            │    │                               │
            └────┴───────────────────────────────┴──────>
                ohjelman                        ohjelman
                alku                            loppu

Edellinen kuva on hieman yksinkertaistettu, koska "oikeasti" aliohjelmien lokaalit muuttujat (automaattiset muuttujat) syntyvät aliohjelmaan tultaessa ja häviävät aliohjelmasta poistuttaessa.   Näin ollen käytetyn muistin yläraja vaihtelee sen mukaan mitä aliohjelmia on kesken suorituksen.
Dynaamisia muuttujia voidaan tarvittaessa luoda ja kun muistitilaa ei enää tarvita, voidaan vastaavat muuttujat vapauttaa:

            ^
muistin     │                  
kaytto      │                 ┌──┐          
            │       ┌────┐    │  │     ┌───┐ 
            │       │    │    │  │   ┌─┘   │      
            │    ┌──┘    └────┘  └───┘     └─────┐ 
            │    │                               │
            │    │                               │
            └────┴──┼────┼────┼──┼───┼─┼───┼─────┴──────>
           ohjelman malloc    calloc malloc     ohjelman
            alku         free    free  realloc   loppu
                                           free

Näin muistin maksimimäärä saattaa pysyä huomattavasti pienempänä kuin ilman dynaamisia muuttujia.  Idea on siis siinä, että muistia varataan aina vain sen verran, kuin sillä hetkellä tarvitaan.  Kun muistia ei enää tarvita, vapautetaan muisti.
Ajonaikana luotaviin muuttujiin tarvitaan osoitteet.  Nämä osoitteet pitää sitten tallettaa johonkin.  Talletus voitaisiin tehdä esimerkiksi taulukkoon tai sitten alkioista pitää muodostaa linkitetty lista.
16.2	Dynaamisen muistin käyttö C- kielessä
Vaikka seuraavassa käsitelläänkin asioita C:n ja C++:n ominaisuuksia rinnakkain, pitää muistaa käyttää samaan muuttujaan kohdistuen aina "saman sarjan" operaatioita (malloc-free  tai new-delete, muttei esimerkiksi: malloc- delete).
16.2.1	malloc, C
Tilaa voidaan varata malloc- funktiolla.  Funktiolle viedään parametrina haluttu koko tavuina ja funktio palauttaa muistista löytyneen alueen alkuosoitteen.  
Mikäli tilaa ei saada allokoitua, palautetaan NULL.  Tämä pitää muistaa AINA tarkistaa!
Apuna alkion koon laskemisessa käytetään usein käännösaikaista operaattoria sizeof (ks. sizeof), joka palauttaa parametrinsa koon.  
pKokonaislukuTaulu = malloc(20*sizeof(int));

Esimerkiksi kerhon jäsenten osoitintaulukko voitaisiin luoda seuraavasti:
runko.1\kerho.cpp - jäsentaulukon luominen 
const char *cKerho::luo_taulukko(int koko)
{
  jasenet = (cJasen **)malloc( koko * (sizeof(cJasen *)) );
  jasenia = 0;
  max_jasenia = 0;
  if ( jasenet == NULL ) return EI_VOI_LUODA;
  max_jasenia = koko;
  return NULL;
}

Huomautus! malloc ei alusta varattua muistia mitenkään!
16.2.2	calloc, C
calloc toimii lähes kuten malloc, mutta se on tarkoitettu pääasiassa taulukoiden varaamiseen. callocille ilmoitetaan taulukon alkioiden määrä ja koko (huomaa 2 parametria, mallocissa vain 1). calloc alustaa kunkin taulukon alkion nollia täytteen.  Ei kuitenkaan ole syytä uskoa, että osoitintaulukko tulisi täyteen NULL osoittimia tai reaalilukutaulukko täytteen 0.0 lukuja.  Toisaalta merkkijonoista tulee tyhjiä ja kokonaislukutaulukoista täynnä 0:ia olevia taulukoita.
Esimerkiksi jäsentaulukko voitaisiin luoda myös kutsulla
jasenet = (cJasen **)calloc( koko , (sizeof(cJasen *)) );

16.2.3	free, C
Varattu muistitila pitää aina vapauttaa, kun sitä ei enää tarvita.  Ohjelman lopuksi tietenkin kaikki ohjelman aikana varattu muistitila vapautuu.  Kuitenkin usein aliohjelmien pitää varata itselleen työtilaa ja aliohjelman lopuksi tämä työtila pitää vapauttaa.  Tällöin aliohjelmasta ei voida poistua return - lauseella, vaan pitää tehdä goto vapauta tai vastaava hyppy aliohjelman loppuun.
Kun jäsenistöstä poistetaan jäsen, voidaan tämä tehdä vaikkapa seuraavasti:
runko.1\kerho.cpp - jäsenistön poistaminen 
void cKerho::poista_taulukko()
{
  if ( max_jasenia > 0 ) free(jasenet);
  max_jasenia = 0;
}

16.2.4	realloc, c
Funktioilla malloc tai calloc varattua muistitilaa voidaan tarvittaessa muuttaa realloc - funktiolla.  Funktiolle viedään parametrina muistitilan osoite ja haluttu uusi koko.  Käytännössä realloc voi toimia siten, että se ensin varaa kokonaan uuden muistitilan ja tämän jälkeen kopioi vanhan muistitilan vastaavan osan (kaikki tai osan jos pienennetään) uuteen paikkaan.  Funktio palauttaa uuden paikan osoitteen tai NULL, mikäli koon muuttaminen ei onnistu.
Jäsenen lisäyksessä voisimme tehdä myös seuraavasti, eli kasvatettaisiin jäsenistön kokoa 50%, mikäli jäsenistö tulee täyteen (cKerho jaettiin kahteen osaan: cKerho ja cJasenet):
talletus.2\jasenet.cpp - taulukon koon kasvattaminen
//----------------------------------------------------------------------------
const char *cJasenet::kasvata_kokoa()
/*
** Yritetään allokoida uutta tilaa 50% maksimimäärään nähden lisää.
** Paitsi jos vanha koko on 1, niin kasvatetaan 2:ksi.
** Jos vanha tila on 0, niin tehdään uusi tila.
----------------------------------------------------------------------------*/
{
  if ( max_lkm <= 0 ) return luo_taulukko(2);
  int uusi_koko = 3*(max_lkm)/2; if ( uusi_koko <= 1 ) uusi_koko=2;

  cJasen **uusi_tila = (cJasen **)realloc(alkiot,uusi_koko*sizeof(cJasen *));

  if ( uusi_tila == NULL ) return LIIKAA_ALKIOITA;
  alkiot = uusi_tila;
  max_lkm = uusi_koko;
  return NULL;
}
...
  if ( jasenia >= max_jasenia ) virhe = kasvata_kokoa();
  if ( virhe ) return virhe;

16.2.5	Ole varovainen reallocin kanssa
reallocin kanssa on kuitenkin oltava erittäin huolellinen, koska kaikki allokoitavassa osassa olevat kentät saattavat siirtyä uuteen paikkaan.  Mikäli meillä on osoittimia allokoitavan alueen sisälle, on niillä reallocin jälkeen täysin väärät arvot!
Siis alkuvaiheessa realloc kannattaa ehkä unohtaa, mutta periaatteessa sillä voitaisiin korjata väärää kokomäärittelyä.  Ja voidaanhan se aina lisätä jälkeenpäin kuten edellisessä esimerkissä!
C++:ssa ei ole vastaavaa funktiota.  Tämä olisikin osaltaan hankala toteuttaa, koska luomisessa pitää kutsua olion (olioiden)  muodostajaa ja vastaavasti poistamisessa hajottajaa.  realloc- tilanteessa voidaan koko muistialue joutua siirtämään toiseen kohti muistia ja kloonaamaan sitten sisällöt sinne.  Olioiden tapauksessa tämä kloonaaminen ja sitten vanhojen poisto ei kuitenkaan ole aina yksikäsitteistä ja näin kielen tekijät ovat päätyneet siihen, että reallocia vastaavan toiminnon tekeminen jätetään ohjelmoijalle itselleen.
Tehtävä 16.149	Jäsenistön realloc?
Onko kerhon jäsenistön kanssa em. realloc ongelmia?

16.3	Dynaamisen muistin käyttö C++:ssa 
16.3.1	new, C++
C++:ssa tilan varaamisen kannattaa ilman muuta käyttää new- operaattoria.  Tässä on se suuri etu, että luonnin yhteydessä kutsutaan kunkin luotavan olion muodostajaa ja näin mikään syntyvistä olioista ei jää ilman alkuarvoa.  Ainoastaan luotaessa perustietotyyppien mukaisia muuttujataulukoita, voidaan puolustella mallocin käyttöä, tällöinkin vain jos taulukon kokoa pitää jälkeenpäin muuttaa.
Esimerkiksi uusi jäsen lisättäisiin seuraavalla aliohjelmalla:
runko.1\kerho.cpp - jäsenen lisääminen
const char *cKerho::lisaa(const cJasen &jasen)
{
  cJasen *uusi_jasen;

  if ( jasenia >= max_jasenia ) return LIIKAA_JASENIA;
  uusi_jasen = new cJasen(jasen);  // uudelle jäsenelle jäsenen tiedot
  if ( uusi_jasen == NULL ) return EI_SAA_JASENTA;

  jasenet[jasenia] = uusi_jasen;
  jasenia++;

  return NULL;
}

Itse asiassa edellä on kutsuttu Jäsen- luokan kopiointimuodostajaa (copy constructor), joka luo uuden olion ja tekee siitä samalla sisällöltään samanlaisen kuin muodostajan parametrina viety olio.  Kopiointimuodostajan parametrilistassa on tasan yksi parametri ja sen tyyppi on sama kuin luokan tyyppi.
16.3.2	delete, C++
Jos olio on luotu new- operaattorilla, pitää se poistaa delete- operaattorilla:
void cKerho::poista_jasenet()
{
  for (int i=0; i<jasenia; i++)
    delete jasenet[i];
  jasenia = 0;
}

16.3.3	Taulukon luominen new [] ja tuhoaminen delete [] 
Jos new- operaattorilla on luotu taulukko (luonnissa käytettiin hakasulkeita), pitää vastaavasti tuhoaminen suorittaa delete- operaattorin taulukkoversiolla
pMonta = new cLuokka[20];
...
delete [] pMonta;

Käytettäessä delete- operaattoria, kutsutaan jokaisen tuhottavan olion hajottajaa (ks. vähän myöhemmin).  Tällä on se etu, että olion poistuessa muistista se voi samalla siivota mahdolliset muut jäljet oliosta.  Esimerkiksi graafinen olio voi pyyhkiä itsensä pois näytöltä samalla kun se hävitetään.
16.4	Dynaamiset taulukot
Kerhon jäsenrekisterissä käytettiin osoitintaulukkoa dynaamisesti.  Vastaavan rakenteen tarve tulee usein ohjelmoinnissa.  Tällöin tulee aina vastaan ongelma:  montako alkiota taulukossa on nyt ja montako sinne mahtuu?  Jäsenrekisterissä tämä oli ratkaistu cJasenet- luokassa tekemällä sinne kentät, joista nämä rajat selviävät.
Tavallinen taulukkokin voidaan usein esitellä vastaavasti
dyna\taul_d.c - esimerkki dynaamisesta taulukosta
typedef struct {    
  int max_koko;
  int lkm;                  
  int *alkiot; 
} Taulukko_tyyppi;             
...
Taulukko_tyyppi luvut;
luvut.max_koko = 7;
luvut.alkiot = calloc(7,sizeof(luvut.alkiot[0]));
luvut.alkiot[0] = 0; luvut.alkiot[1] = 2;
luvut.lkm = 2;

Taulukko_tyyppi luvut
           ┌─────┐ 
  max_koko │  7  │         0  1  2  3  4  5  6 
  lkm      │  2  │       ┌──┬──┬──┬──┬──┬──┬──┐ 
 *alkiot   │  o──┼──────>│ 0│ 2│ ?│ ?│ ?│ ?│ ?│
           └─────┘       └──┴──┴──┴──┴──┴──┴──┘

Tällaisen taulukon kuljettaminen parametrina on helppoa, kun se voidaan välittää vain yhtenä parametrina.
Viittausten puolesta samanalainen vastaava staattinen esittely olisi:
dyna\taul_s.c - staattinen taulukko
typedef struct {    
  int max_koko;
  int lkm;                  
  int alkiot[7]; 
} Taulukko_tyyppiS;             
...
Taulukko_tyyppiS luvut = { 7, 2, {0,2} };

Taulukko_tyyppiS luvut
           ┌─────┐ 
  max_koko │  7  │  
  lkm      │  2  │  
           ├──┬──┼──┬──┬──┬──┬──┐ 
  alkiot   │ 0│ 2│ ?│ ?│ ?│ ?│ ?│
           └──┴──┴──┴──┴──┴──┴──┘
             0  1  2  3  4  5  6 

Staattisessa esittelyssä taulukko tulisi tietueen osaksi.  Kuitenkin kummassakin tapauksessa viitataan taulukon yksittäiseen alkioon muodossa luvut.alkiot[3]=5;.
Staattisen esittelyn "hyvä" puoli olisi siinä, että tällainen taulukko voitaisiin sijoittaa toiseen taulukon yhdellä käskyllä:
Taulukko_tyyppiS apu,luvut = { 7, 2, {0,2} };
apu = luvut; 

Tietysti sijoitus onnistuu dynaamisessakin tapauksessa, mutta tulos saattaa olla muuta kuin on haluttu!  Miksi?
Viimeksi esiteltyä staattistakin esittelyä voidaan käsitellä dynaamisesti luomalla muuttujat luvut dynaamisesti.  Koska C- kielessä ei useinkaan ole indeksitarkistusta, niin luomisessa malloc - funktiolle voidaan "valehdella" alkion koko:
Taulukko_tyyppiS *luvut;
...
luvut = malloc( sizeof(Taulukko_tyyppiS) + 4*sizeof(int) );
luvut- >max_koko = 7 + 4;
... 

Tosin koko taulukon sijoittaminen yhdellä käskyllä ei enää onnistu tämän tempun jälkeen!  Miksi?
16.5	Dynaamiset taulukot C++:ssa
C++:ssa edällä mainittu dynaaminen taulukko voidaan toteuttaa käyttäjän kannalta todella joustavaksi:
dyna\taul_d.cpp -esimerkki dynaamisesta taulukosta 
#include <iostream.h> 
#include <iomanip.h>

class cTaulukko {
  int max_koko;
  int lkm;
  int *alkiot;
public:
  cTaulukko(int akoko) {
    max_koko = 0;
    alkiot = new int[akoko];
    if ( alkiot ) max_koko = akoko;
    lkm = 0;
  }
  ~cTaulukko() { if ( max_koko ) delete [] alkiot; max_koko = 0; }
  int lisaa(int luku) {
    if ( lkm >= max_koko ) return 1;
    alkiot[lkm] = luku;
    lkm++;
    return 0;
  }
  void tulosta(ostream &os=cout) const;
};

void cTaulukko::tulosta(ostream &os) const
{
  int i;
  for (i=0; i < lkm; i++)
    os << setw(5) << alkiot[i];
  os << endl;
}

int main(void)
{
  cTaulukko luvut(7);
  luvut.lisaa(0); luvut.lisaa(2);   // Ilo on täällä!!!

  luvut.tulosta();

  return 0;
}

Edellä olemme käyttäneet muutamia C++:n aikaisemmin käsittelemättömiä ominaisuuksia:
16.6	Hajottaja (destructor)
Muodostaja ja hajottaja (destructor) ovat eräs olio- ohjelmoinnin kulmakiviä.  C++:n lisäetuna (?) on vielä automaattisesti kutsuttavat muodostajat ja hajottimet.  Eli esimerkiksi edellisessä esimerkissä (taul_d.cpp) kutsutaan automaattisesti olion luvut hajottajaa silloin, kun olion vaikutusalue lakkaa, eli poistutaan tässä tapauksessa pääohjelmasta.
Hajottaja on  parametriton ja tyypitön metodi, jonka nimi on ~Luokan_nimi.  Vaikka muodostajia saattoi olla useita, on hajottajia aina VAIN YKSI luokkaa kohti.
Jos luokkaa on mahdollista periä, pitää hajottaja esitellä virtuaaliseksi.
16.6.1	Jäsenistön poisto
Esimerkiksi kerhossa on jäsenistö poistettava kun kerho lakkaa olemasta:
runko.1\kerho.cpp - hajottaja
class cKerho {
...
  void  poista_kaikki() { poista_jasenet(); poista_taulukko();            }
...
  ~cKerho() { 
    if ( muutettu ) talleta(); 
    poista_kaikki();     
  }
};
...

16.7	Tietovirta parametrina ja oletusparametri
Metodi tulosta on esitelty
void tulosta(ostream &os=cout) const;

Näin voidaan tulostusvaiheessa valita mille laitteelle tulostetaan.  Koska oletuksena on cout, tulostetaan näytölle jos kutsu on muodossa:
luvut.tulosta();

Tiedostoon tulostettaisiin esimerkiksi:
ofstream fo("luvut.dat");
...
luvut.tulosta(fo);

Oletusparametri tarkoittaa sitä, että mikäli kutsussa ei anneta jollekin parametrille arvoa, käytetään sille oletusarvoa.  Ominaisuutta käytetään erittäin usein muodostajan yhteydessä.
Tietysti sama asia voidaan hoitaa funktioiden ja metodien kuormituksen avulla:
void tulosta(ostream &os) const; // tulostaa tiedostoon os
void tulosta() const;            // tulostaa näyttöön

mutta tästä seuraa enemmän kirjoittamista.

17.	Tiedostot ja makrot
Mitä tässä luvussa käsitellään?
•	Tiedostojen käsittely C- funktiolla
•	Tiedostojen käsittely C++ - tietovirroilla
•	sizeof  strlen 
•	Parametrilliset makrot
•	Tiedostot joissa rivillä monta kenttää

Syntaksi:
Tied. avaaminen C:    FILE *f = fopen(nimi,tyyppi);
                C++:  ifstream fi(nimi);
                      ofstream fo(nimi);
                      tai: ifstream fi; … fi.open(nimi);
Lukeminen       C:    fscanf(f,format,osoite,...);
                      fgets(mjono,max_pit,f);
                C++:  fi >> muuttuja;
                      fi.getline(mjono,max_pit);
Kirjoittaminen  C:    fprintf(f,format,lauseke,...);
                C++:  fo << lauseke;
Sulkeminen      C:    fclose(f);
                C++:  fi.close(); 
                      tai automaattisesti hajottajan ansiosta
Muuttujan koko        sizeof(muuttuja)             // tavuina
Tyypin viemä tila     sizeof(tyyppi)               // tavuina 
 
Yhdessä käytettävät funktiot tai operaattorit:
C:   FILE *fi = fopen(nimi,"rt") - fscanf(fi,...) - fgets(...,fi) - feof(fi) - 
                fclose(fi)
     FILE *fo = fopen(nimi,"wt") - fprintf(fo,...) - fclose(fo)
C++: ifstream fi(nimi) - fi >>... - fi.getline(...) - fi.eof() - fi.close() 
     ofstream fo(nimi) - fo <<... - fo.close()

Pyrimme seuraavaksi lisäämään kerho- ohjelmaamme tiedostosta lukemisen ja tiedostoon tallettamisen.  Tätä varten tutustumme ensin lukemiseen mahdollisesti liittyviin ongelmiin.  
Jos lukija haluaa keskittyä pelkästään C++:aan, hän voi hypätä C- ongelmia käsittelevien kappaleiden ylitse.  Jos luetaan molemmat osat, on kuitenkin muistettava käyttää pareina C- funktioita ja C++- funktioita (ei siis esim. ifstream fi("oma.txt") ja fclose(fi) vaan  fi.close() ).
17.1	Tiedostojen käsittely C- funktioilla
Tiedostojen käsittely ei eroa päätesyötöstä ja tulostuksesta, siis tiedostojen käyttöä ei ole mitenkään syytä vierastaa!  Itse asiassa päätesyöttö ja tulostus ovat vain stdin ja stdout - nimisten tiedostojen käsittelyä.
Tiedostoja on kahta päätyyppiä:  tekstitiedostot ja binääritiedostot.  Tekstitiedostojen etu on siinä, että ne käyttäytyvät täsmälleen samoin kuin päätesyöttökin.  Binääritiedoston etu on taas siinä, että talletus saattaa viedä vähemmän tilaa (erityisesti numeerista muotoa olevat tyypit) ja suorasaannin käyttö on järkevämpää.
Keskitymme aluksi tekstitiedostoihin, koska niitä voidaan tarvittaessa editoida tavallisella editorilla.  Näin ohjelman testaaminen helpottuu, kun tiedosto voidaan rakentaa valmiiksi ennenkuin ohjelmassa edes on päätesyöttöä lukevaa osaa.
17.1.1	Avaaminen, fopen 
Tiedosto (esimerkissä "readst.txt" - niminen, nimi voi tietysti olla muukin) avataan komennolla fopen:
  FILE *f;
...
  f = fopen("readst.txt","rt");
  if ( f == NULL ) ... toimenpiteet jos tiedosto ei aukea
... 

Mikäli avaus epäonnistuu, palautetaan NULL.  Ensimmäinen parametri on tiedoston nimi levyllä ja toinen sen tyyppi.  Tässä tapauksessa tyyppinä on "rt" eli avaus lukemista (read) varten ja tekstitiedosto (text).  Lukemista varten avattaessa tiedoston täytyy olla olemassa tai avaus epäonnistuu.  Tätä voidaan tietenkin käyttää hyväksi esimerkiksi tutkittaessa onko tiedostoa lainkaan olemassa.
17.1.2	Lukeminen 
Muutamme hieman alkuperäistä suunnitelmaamme jäsenrekisteritiedoston sisällöstä:
Kelmien kerho ry
100
; Kenttien järjestys tiedostossa on seuraava:
id| nimi         |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin...
1|Ankka Aku      |010245- 123U|Ankkakuja 6 |12345      |ANKKALINNA |12- 12324   ...
2|Susi Sepe      |020347- 123T|            |12555      |Perämetsä  |           ...
3|Ponteva Veli   |030455- 3333|            |12555      |Perämetsä  |           ... 

Olemme lisänneet rivin, jossa kerrotaan tiedoston maksimikoko.  Tätähän tarvittiin jäsenlistan luomisessa.  Nyt kokoa voidaan tarvittaessa muuttaa tiedostosta tekstieditorilla tarvitsematta tietää ohjelmoinnista mitään.
Tiedoston 1. rivi on siis kerhon koko nimi. AVATUSTA tiedostosta (jos avattu kahvaan nimellä f) tämä voitaisiin lukea seuraavasti:
fgets(jono,80,f); 

Toisaalta meillä on valmis funktio f_lue_jono, joka suorittaa tarvittavia korjailuja, mikäli jono ei mahdu kokonaisuudessaan luettavaan muuttujaan:
if ( f_lue_jono(f,kerho- >kerhon_nimi,
                  sizeof(kerho- >nimi)) <OLETUS ) return TIEDOSTO_VAARIN; 

Seuraava rivi voitaisiin lukea 
fscanf(f,"%d",&kerho- >max_jasenia); 

Tässä ainoa riski on siinä, että riville jäisi vielä jotakin, jolloin joutuisimme poistamaan loppurivin.   Siksi voitaisiin myös ensin lukea rivi merkkijonona ja tämän jälkeen sscanf- funktiolla ottaa jonosta kokonaisluku.
if ( f_lue_jono(f,jono,sizeof(jono))<OLETUS ) return TIEDOSTO_VAARIN;
if ( sscanf(jono,"%d",&kerho- >max_jasenia)<1 ) return TIEDOSTO_VAARIN; 

17.1.3	Tiedoston lopun testaaminen, feof
 Tiedoston loppumista voidaan testata funktiolla feof.  Usein tiedostoa luetaan silmukalla
  while ( !feof(f) ) {
    if ( f_lue_jono(f,jono,sizeof(jono)) ...
    ...
  }

17.1.4	Tiedoston sulkeminen, fclose
Avattu tiedosto on aina lukemisen tai kirjoittamisen lopuksi syytä sulkea.  Tiedoston käsittely on usein puskuroitua, eli esimerkiksi kirjoittaminen tapahtuu ensin apupuskuriin, josta se kirjoittuu fyysisesti levylle vain puskurin täyttyessä tai tiedoston sulkemisen yhteydessä.  Käyttöjärjestelmä päivittää tiedoston koon levylle usein vasta sulkemisen jälkeen.  Sulkemattoman tiedoston koko saattaa näyttää 0 tavua.
Tiedosto suljetaan funktiokutsulla
fclose(f); 

17.1.5	Tiedostoon tulostaminen, fprintf 
Kirjoittamista varten avattuun tiedostoon voidaan tulostaa printf- funktiota vastaavalla funktiolla fpritnf.  Esimerkiksi:
f = fopen("valit.dat","wt");
…
fprintf(f,"%03d - %03d\n",alku,loppu ); /* tallettaa esim rivin:  001 -  007 */
17.1.6	Esimerkki tiedoston käsittelystä
Olkoon meillä tiedosto nimeltä LUVUT.DAT:
13.4
23.6
kissa
1.9
<EOF>     <-   ei aina välttämättä mikään merkki

Tiedostossa olisi yksi luku rivillä.  Tehtävä ohjelma joka tulostaa lukujen summan ja keskiarvon, mikäli tiedostossa on vääriä merkkejä, ne jätetään huomiotta:
tiedosto\tied_ka.c - esimerkki tiedoston lukemisesta
#include <stdio.h>

int main(void)
{
  FILE *f;
  double luku,summa,ka;
  int n;

  f = fopen("luvut.dat","rt");

  if (!f) {
    printf("Tiedosto ei aukea!\n");
    return 1;
  }

  summa = 0.0;  n = 0;  ka = 0.0;

  while ( !feof(f) ) {
    if ( fscanf(f,"%lf",&luku)<=0 ) {
      fgetc(f);
      continue;
    }
    summa += luku;
    n++;
  }

  fclose(f);

  if ( n > 0 ) ka = summa/n;

  printf("Lukuja oli %d kappaletta.\n",n);
  printf("Niiden summa oli %5.2lf\n",summa);
  printf("ja keskiarvo oli %5.2lf.\n",ka);

  return 0;
}

Tehtävä 17.150	Tiedoston lukujen summa
1.	Muuta tiedoston tied_ka.c - ohjelmaa siten, että väärän merkin kohdalla tulostetaan väärä merkki ja lopetetaan koko ohjelma.
2.	Muuta edelleen ohjelmaa siten, että väärät merkit tulostetaan näyttöön:
Tiedostossa oli seuraavat laittomat merkit:
kissa
Lukuja oli... 
3.	Ilmoitusta ei tietenkään tule, mikäli tiedostossa ei ole laittomia merkkejä.
Tehtävä 17.151	Kommentit näytölle
Kirjoita ohjelma, joka kysyy tiedoston nimen ja tämän jälkeen tulostaa tiedostosta rivien /******* ja- - - - - - */välisen osan näytölle.
17.2	Tiedostojen käsittely C++:n tietovirroilla
Vastaavasti kuin C- funktiolla on tiedostojen käsittely C++:ssa vain cin ja cout - tietovirtoja vastaavien tietovirtojen käsittelyä.
Kirjoitetaanpa vastaava tiedoston luvut lukeva ohjelma C++:n tietovirroilla:
tiedosto\tied_ka.cpp - Lukujen lukeminen tiedostosta 
// Ohjelma lukee tiedostosta LUVUT.DAT lukuja ja tulostaa niiden
// summan ja keskiarvon. 
#include <iostream.h>
#include <fstream.h>

int main(void)
{
  double luku,summa,ka;
  int n;

  ifstream fi("luvut.dat");
  if ( !fi ) {
    cout << "Tiedosto ei aukea!" << endl;
    return 1;
  }

  summa = 0.0;  n = 0;  ka = 0.0;

  while ( !fi.eof() ) {
    fi >> luku;
    if ( fi.fail() ) {
      fi.clear();
      char ch;  fi.get(ch);
      cout << ch;
      continue;
    }
    summa += luku;
    n++;
  }

  fi.close();

  if ( n > 0 ) ka = summa/n;

  cout.precision(2); cout.setf(ios::showpoint | ios::fixed);  
  cout << "\n";
  cout << "Lukuja oli " << n << " kappaletta\n";
  cout << "Niiden summa oli " << summa << "\n";
  cout << "ja keskiarvo oli " << ka << endl;

  return 0;
}

17.2.1	Tiedoston avaaminen muodostajassa tai open 
Tiedosto voidaan siis avata heti kun tiedostoa vastaava tietovirta esitellään:
ifstream f("luvut.dat");  // Input File STREAM

Tiedosto voidaan myös jättää avaamatta esittelyn yhteydessä ja avata sitten myöhemmin open- metodilla:
  ifstream f; 
...
  f.open("luvut.dat");

Tiedoston aukeamisen tila voidaan testata tietovirtaolion arvosta esimerkiksi
if ( !f ) { ...

17.2.2	Tiedostosta lukeminen >> ja tiedostoon kirjoittaminen << 
Tiedostosta lukeminen on jälleen analogista päätesyötön kanssa:
fi >> luku;

Vastaavasti kirjoittamista varten avattuun tiedostoon kirjoitettaisiin
ofstream fo("tulos.dat");
...
fo << luku;

Myös kaikki muut cin ja cout - olioista tutut metodit ovat käytössä, esim:
fi.getline(s,80);

17.2.3	Tiedoston lopun testaaminen eof 
Totuusarvotyyppinen metodi eof palauttaa tosi, kun ollaan tiedoston lopun kohdalla. 
while ( !fi.eof() ) {

Valitettavasti arvo on tosi vasta kun kuvitteellisen loppumerkin kohdalle on saavuttu, EI silloin kun se on seuraavana.  Esimerkiksi tiedostosta
13.4<EOF>

saataisiin kaksi reaalilukua silmukalla:
while ( !fi.eof() ) { 	
  fi >> luku;
  summa += luku;
  n++;
}

Tehtävä 17.152	Ylimääräinen lukeminen
Miten voitaisiin ilman if- lauseita  järjestää niin, ettei ylimääräinen lukeminen haittaisi summan laskua jos lukumäärää ei tarvittaisi.

Seuraava auttaisi jos oltaisiin varmoja ettei tiedostossa ole muuta kuin lukuja:
while ( 1 ) { 	// Jos muuta kuin lukuja 
  f >> luku;
  if ( f.eof() ) break;
  summa += luku;
  n++;
}

Jos silmukka saadaan pysäyttää ensimmäiseen virheeseen, olisi seuraava varmin tapa lukea:
while ( f >> luku ) {
  summa += luku;
  n++;
}

17.2.4	Tiedoston sulkeminen close 
C++:ssa tiedosto voidaan joskus jopa jättää sulkematta, koska tietovirtaolion hajottaja sulkee tiedoston joka tapauksessa.   Tästä huolimatta tiedosto kannattaa sulkea, jos sen käyttö on loppu ja ohjelmalohkon lopussa on vielä jonkin aikaa kestäviä operaatioita:
fi.close();
17.3	sizeof 
Useille C- kirjaston valmiille aliohjelmille sekä myös monille itse kirjoittamillemme aliohjelmille täytyy viedä parametrina käsiteltävän merkkijonon maksimikoko:
char jono[80];
...
lue_jono(jono,80);
...
f_lue_jono(f,jono,80);
...
kopioi_jono(jono,80,"Kissa");
...
cin.getline(jono,80);

Edellisessä on vielä vaarana se, että muutettaisiin jonon maksimikokoa 80, mutta samalla unohdettaisiin päivittää aliohjelmien kutsut.  Yksi ratkaisu on määritellä vakio:
#define MAX_JONO 80
char jono[MAX_JONO];
...
cin.getline(jono,MAX_JONO);
... 

17.3.1	sizeof palauttaa muuttujaan varaaman tilan
Toinen usein kätevämpi tapa on käyttää käännösaikaista sizeof - operaattoria, joka palauttaa muuttujan tai tyypin tarvitseman muistitilan.  Esimerkiksi
char jono[80]; int koko;
...
koko = sizeof(jono); 

sijoittaisi muuttujalle koko arvon 80.  
Voisimme siis kirjoittaa kutsuja:
char jono[80];
...
lue_jono(jono,sizeof(jono));
...
f_lue_jono(f,jono,sizeof(jono));
...
kopioi_jono(jono,sizeof(jono),"Kissa");

cin.getline(jono,sizeof(jono));

sizeof - operaattorille voidaan antaa parametrina myös tyypin nimi:
typedef struct {
  int  pv;
  char kk_nimi[20];
  int  vv;
} Pvm_tyyppi;
...
int vuosi; 
Pvm_tyyppi pvm;
...
... sizeof(vuosi) ...   /* Esim 2 toteutuksesta riippuen */
... sizeof(int)   ...   /*   -  " -                        */
... sizeof(char)  ...   /*  Aina 1                       */
... sizeof(Pvm_tyyppi)  /* Esim. 2+20+2 == 24 tot. riip. */
... sizeof(pvm.kk_nimi) /*  20                           */
... 

17.3.2	sizeof ei ole strlen 
sizeof - operaattoria EI PIDÄ sotkea strlen - funktioon:
char jono[80]; int koko,pituus;
...
koko = sizeof(jono);                /*  80 */
kopioi_jono(jono,koko,"Kissa");
pituus = strlen(jono);              /*   5 */

17.3.3	sizeof:in vaarat
sizeof - operaattoria ei pidä käyttää huolimattomasti.  Esimerkiksi:
char *viesti     ="Kissat uimaan!";
char vastaus[40] ="Ei kissat ui!";

==>

sizeof(viesti)  == 2 tai 4 tai vastaavaa muistimallista riippuen
sizeof(vastaus) == 40  !!! 

Helposti tulee käytettyä osoitinta ja saadaan osoittimen koko, kun itse asiassa tarvittaisiin itse muistipaikan koko.  Tällaisista virheistä kääntäjä ei varoita mitään (koska mitään syntaksivirhettä ei ole)!
17.4	Parametrilliset makrot 
Esimerkiksi edellä jouduimme  käyttämään muotoja
char jono[80];
...
cin.getline(jono,sizeof(jono));
...
kopioi_jono(jono,sizeof(jono),"Kissa");

Voisimme tietysti määritellä makron
#define JONOS jono,sizeof(jono) 

jolloin kutsut supistuisivat muotoon
cin.getline(JONOS);
...
kopioi_jono(JONOS,"Kissa");

17.4.1	Helpottaa kirjoittamista
Mutta entäpä jos haluaisimme käyttää toisen nimistä muuttujaa.  Määriteltäisiinkö uusi makro?  Onneksi C- kielen esiprosessori sallii parametrin käytön makroissa.  Siispä voimme rakentaa vaikkapa makron (N_S, name and size)
#define N_S(nimi) nimi,sizeof(nimi) 

jonka esiintymän esiprosessori muuttaa vastaavasti
  char jono[80],elain[20];
  f_lue_jono(f,N_S(jono));
  kopioi_jono(N_S(elain),"Kissa");
  cin.getline(N_S(jono));
esiprosessori ==>
  f_lue_jono(f,jono,sizeof(jono));
  kopioi_jono(elain,sizeof(elain),"Kissa");
  cin.getline(jono,sizeof(jono));
kääntäjä ==>
  f_lue_jono(f,jono,80);
  kopioi_jono(elain,20,"Kissa");
  cin.getline(jono,80);

Siis makron parametrilistassa olevat sanat korvataan ensin niillä sanoilla jotka ovat makron esiintymässä.  Tämän jälkeen esiintymä korvataan tällä uudella merkkijonolla ja lopulta korvattu merkkijono annetaan kääntäjän käsiteltäväksi.
17.4.2	Ole kuitenkin varovainen
Makrot ovat kuitenkin hyvin vaarallisia huolimattomasti käytettynä:
#define K_2(i) (i + i)
...
a = K_2(4);    - > a = (4 + 4);
a = K_2(n++);  - > a = (n++ + n++); /* !!!!!!!!!! */

Onneksi C++:ssa ei enää tarvita niin paljon parametrillisia makroja kuin puhtaassa C:ssä.   Sama asia voidaan useimmiten hoitaa inline- funktiolla:
inline int k_2(int i) { return (i + i); }
...
a = k_2(4);    - > a = 8;
a = k_2(n++);  - > a = 2*n; n++;  // Toimii oikein!

Tehtävä 17.153	Makron yllätykset
Keksi muita esimerkkejä missä K_2 - makron käyttö tuottaisi yllätyksiä.
17.5	Tiedoston yhdellä rivillä monta kenttää, lukeminen C:llä
Jäsenrekisterissä on tiedoston yhdellä rivillä useita kenttiä.  Kentät saattavat olla myös eri tyyppisiä.  Miten lukeminen hoidetaan varmimmin?  Usein lukeminen voitaisiin hoitaa sopivasti muotoillulla fscanf - lauseella.
17.5.1	fscanf
Olkoon meillä vaikkapa seuraavanlainen tiedosto:
tiedosto\tuotteet.dat - esimerkkitiedosto
    Volvo |  12300 | 1
    Audi  |  55700 | 2
    Saab  |   1500 | 4
    Volvo | 123400 | 1<EOF>

Tiedosto voitaisiin lukea ja vaikkapa tulostaa näytölle seuraavalla ohjelmalla:
tiedosto\tuotteet.c  - tuotetiedoston lukeminen
#include <stdio.h>
#include <stdlib.h>

typedef struct{
  char   nimike[20];
  double hinta;
  int    kpl;
} Tuote_tyyppi;

int tulosta_tuotteet(void)
{
  FILE *f; Tuote_tyyppi tuote;

  f = fopen("TUOTTEET.DAT","rt");
  if (!f) return 1;

  printf("\n\n\n");
  printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n");
	
  while ( !feof(f) ) {
    fscanf(f,"%s |%lf |%d",&tuote.nimike,&tuote.hinta,&tuote.kpl);
    printf("%- 20s %7.0lf %4d\n",tuote.nimike,tuote.hinta,tuote.kpl);
  }

  printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n");
  printf("\n\n\n");

  fclose(f);

  return 0;
}

int main(void)
{
  if (tulosta_tuotteet()) {
    printf("Tuotteita ei saada luetuksi!\n");
    return 1;
  }
  return 0;
}

Ohjelmassa on kuitenkin seuraavia huonoja puolia:
•	mikäli tiedoston loppu ei olekaan viimeisen rivin lopussa, tulostaa ohjelma viimeisen rivin 2 kertaa
•	mikäli jokin rivi on väärää muotoa, menee ohjelma varsin sekaisin
Tehtävä 17.154	Tiedoston lopun testaus
Miten edellisessä ohjelmassa estettäisiin viimeisen rivin tulostuminen kahdesti?
Tehtävä 17.155	Ohjelman "sekoaminen"
Mitä edellinen ohjelma tulostaisi seuraavasta tiedostosta:

    Volvo |  12300 | 1
    Audi     55700 | 2
    Saab  |   1500 | 4
    Volvo | 123400 | 1
<EOF>

17.5.2	Rivi kerrallaan lukeminen
Ongelmaa voidaan osittain ratkaista lukemalla tiedostoa merkkijonoon aina rivi kerrallaan:
  FILE *f; Tuote_tyyppi tuote; char rivi[80];
...
  while ( !feof(f) ) {
    if ( f_lue_jono(f,N_S(rivi)) <= OLETUS ) continue;
    sscanf(rivi,"%s |%lf |%d",&tuote.nimike,&tuote.hinta,&tuote.kpl);
    printf("%- 20s %7.0lf %4d\n",tuote.nimike,tuote.hinta,tuote.kpl);
  }
... 

Tässäkin vaihtoehdossa on vielä muutamia huonoja puolia:
•	virheellinen rivi hyväksytään ja lopuille arvoille jää edellisen kerran arvot
•	sscanf käytetyllä formaatilla on vaarallinen, mikäli nimikekenttään sattuisi tulemaan nimike, jossa on yli 20 merkkiä
•	mikäli nimikekentässä tarvittaisiin nimi, jossa on välilyöntejä, menisi syöttö jälleen sekaisin
•	mikäli rivillä olisi ennalta tuntematon määrä kenttiä, ei tämä formaatti toimisi
17.6	Merkkijonon paloittelu
Tutkitaanpa ongelmaa tarkemmin.  Tiedostosta on siis luettu rivi, joka on muotoa
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │|│ │ │1│2│3│0│0│ │|│ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┘ 
                                        NUL- merkki
Jos saisimme erotettua tästä 3 merkkijonoa:
 pala1               pala2           pala3
 ┌─┬─┬─┬─┬─┬─┬─┬     ┬─┬─┬─┬─┬─┬─┬   ┬─┬─┬  
 │ │V│o│l│v│o│ │     │1│2│3│0│0│ │   │1│ │ 
 └─┴─┴─┴─┴─┴─┴0┴     ┴─┴─┴─┴─┴─┴0┴   ┴─┴0┴

voisimme kustakin palasesta erikseen ottaa haluamme tiedot.  Esimerkiksi 1. palasesta saadaan tuotteen nimi, kun siitä poistetaan turhat välilyönnit. Hinta saataisiin 2. palasesta kutsulla
sscanf(pala2,"%lf",&tuote.hinta); 

17.6.1	Osoitintaulukko
Siis tarvitsemme aliohjelman, joka paloittelee sille annetun merkkijonon valittujen merkkien kohdalta.  Miten tulos palautetaan.  Yksi mahdollisuus olisi palauttaa osoitintaulukko

          0   1   2   3   4
        ┌───┬───┬───┬───┬───┐ 
palaset │ o │ o │ o │ o │ o │
        └─┼─┴─┼─┴─┼─┴───┴───┘
  ┌───────┘   │   └─────────────────┐ 
  │           └───┐                 │
  v               v                 v
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │ │ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴0┴─┴─┴─┴─┴─┘ 
                                        NUL- merkki

Tässä olisi muutamia ongelmia:
•	kuinka pitkä osoitintaulukko varattaisiin ja minne
•	alkuperäinen jono täytyy pilata

17.6.2	"Liikkuva" osoitin
Kun hieman tutkimme ongelmaa, toteamme, ettei alkuperäistä jonoa ehkä tarvitakaan, siis se voidaan pilata.  Toisaalta emme tarvitse pätkiä yhtäaikaa, riittää kun saamme ensin ensimmäisen ja otamme siitä nimen, sitten toisen, josta otamme hinnan jne...
17.6.3	strtok 
Erään mahdollisuuden tällaisen palanen kerrallaan ottamiseen tarjoaa string.h:n funktio strtok joka toimii seuraavasti:
Ennen kutsua:

  ┌───rivi
  v
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │|│ │ │1│2│3│0│0│ │|│ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┘ 

1. Kutsu:  p = strtok(rivi,"|");
  ┌───rivi
  v
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │|│ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┘ 
  ^
  └─── p

2. Kutsu:  p = strtok(NULL,"|");
  ┌───rivi
  v
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │ │ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴0┴─┴─┴─┴─┴─┘ 
                  ^
       p ─────────┘ 

3. Kutsu:  p = strtok(NULL,"|");
  ┌───rivi
  v
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │ │ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴0┴─┴─┴─┴─┴─┘ 
                                    ^
       p ───────────────────────────┘ 
   
4. Kutsu:  p = strtok(NULL,"|");
  ┌───rivi
  v
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │ │ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴0┴─┴─┴─┴─┴─┘ 
              
       p ────> NULL

Näyttäisi siis siltä, että strtok sopii hyvin tarkoituksiimme:
tiedosto\tuot_sto.c - rivin pätkiminen strtok:n avulla
int rivi_tuotteeksi(char *rivi, Tuote_tyyppi *tuote) 
{
  char *p; 
  p = strtok(rivi,"|"); if (!p) return 1;
  poista_tyhjat(p); kopioi_jono(N_S(tuote- >nimike),p);

  p = strtok(NULL,"|"); if (!p) return 1;
  if ( sscanf(p,"%lf",&tuote- >hinta) != 1 ) return 1;

  p = strtok(NULL,"|"); if (!p) return 1;
  if ( sscanf(p,"%d",&tuote- >kpl) != 1 ) return 1;

  return 0;
}

int tulosta_tuotteet(void)
{
...
  while ( !feof(f) ) {
    if ( f_lue_jono(f,N_S(rivi)) <= OLETUS ) continue;
    if ( rivi_tuotteeksi(rivi,&tuote) ) continue;
    printf("%- 20s %7.0lf %4d\n",tuote.nimike,tuote.hinta,tuote.kpl);
  }
...
}

17.6.4	palanen ja static 
Vaikka edellä strtok näyttikin hyvältä, on siinä muutamia puutteita:
•	mikäli erotinmerkit ovat peräkkäin, esimerkiksi
Audi||20
tulee 1. palaseksi "Audi" ja toiseksi "20" eikä kolmatta enää saada (tosin tämä saattaa olla toivottavakin ominaisuus katkottaessa välilyönnein erotettuja jonoja!)
•	erotinmerkkien loputtua funktio palauttaa NULL- osoittimen, käytännössä voisi olla parempi palauttaa osoitin tyhjään merkkijonoon, jolloin tulosta voitaisiin käyttää ilman ylimääräistä if- lausetta

Teemme strtok funktiota vastaavan funktion nimeltä palanen (mjonot.h), jossa meidän kannaltamme puutteelliset kohdat on korjattu.  Aluksi tulee ongelma siitä, miten ensimmäisen kutsukerran jälkeen osataan jatkaa seuraavan erotinmerkin etsimistä.  Aliohjelman täytyy "muistaa" mitä on tehty viimeksi.  Normaalisti aliohjelmasta poistuttaessa aliohjelman sisäiset muuttujat häviävät.  Mikäli muuttuja esitellään static etuliitteellä, säilyy muuttuja myös aliohjelmasta poistumisen jälkeenkin (eikä ole enää automaattinen muuttuja):
ali\mjonot.c - merkkijonon paloittelu
/****************************************************************************/
char                      /*                                                */
*palanen(                 /* Osoitin merkkijonon palaseen.                  */
  char *jono             ,/* s   Pätkittävä jono, turmeltuu!                */
  char *erottimet        ,/* s   Merkit joiden kohdalta katkaistaan.        */
  int  *jaljella          /* t   Paljonko jonoa on vielä jäljellä (- 1 loppu)*/
) 
/* 
** Funktiolla pätkitään merkkijonoa osiin.  1. kutsukerralla välitetään
** tutkittava jono ja tämän jälkeen seuraavilla NULL osoitin.
** Funktio vaihtaa löytämänsä erotinmerkit null- merkeiksi!
**
** Muuttuu:   jono
** Algoritmi:       
** Esimerkki:       012345678901234
**            jono="Aku|Ankka||12" erottimet="|"
**            1. kutsu palanen(jono,"|",&j) - > "Aku"  , j=10
**            2. kutsu palanen(NULL,"|",&j) - > "Ankka", j=4
**            3. kutsu palanen(NULL,"|",&j) - > ""     , j=3
**            4. kutsu palanen(NULL,"|",&j) - > "12"   , j=0
**            5. kutsu palanen(NULL,"|",&j) - > ""     , j=- 1  
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
{
  static char *st="";
  static int p1=0,p2=0,pit=0;

  if (jono) {      /* 1. kutsukerta, alustetaan apumuuttujat */
    st  = jono;
    pit = strlen(jono);
    p1  = 0;
  }
  else
    p1  = p2+1;    /* Muilla kerroilla jatketaan siitä mihin viim. jäätiin. */

  if ( p1 > pit ) {
    *jaljella = - 1;
    return st+pit; /* Tyhjä merkkijono, kun osoitetaan jonon null- tavuun.   */
  }

  p2 = p1+strcspn(st+p1,erottimet);
  st[p2] = 0;
  *jaljella = pit- p2;

  return st+p1;

}

Funktio strcspn palauttaa ensimmäisen indeksin, josta löytyy erottimet merkkijonon jokin merkki.  Mikäli merkkiä ei löydy, palautetaan jonon pituus.
Funktion toimintaidea on seuraava:

2. Kutsu:  p = palanen(NULL,"|",&j);
 Kutsuun tultaessa: (pit == 19, p1 == 0, p2 == 7)
  ┌───st          ┌───st+p2+1
  v               v
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │|│ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┘ 
                  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
                    
Etsitään |- merkkiä alkaen paikasta 8.  Löytyy jonoon st+p2+1
nähden paikasta 8.  Siis alkuperäiseen jonoon nähden
paikasta 16.  

Muutetaan arvot: (pit == 19, p1 == 8, p2 = 8+8)
  ┌───st          ┌───st+p1       ┌────st+p2
  v               v               v
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 
 │ │V│o│l│v│o│ │ │ │ │1│2│3│0│0│ │ │ │1│ │ │ │ │ │ │
 └─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴─┴─┴─┴─┴─┴─┴0┴─┴─┴0┴─┴─┴─┴─┴─┘ 
                  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6

palautetaan st+p1 ja *jaljella = 19- 16 = 3                

Näin olemme saaneet funktion, jota voidaan huoletta kutsua myös merkkijonon loppumisen jälkeenkin:
tiedosto\tuot_pal.c - rivin pillkominen palanen- aliohjelman avulla
int rivi_tuotteeksi(char *rivi, Tuote_tyyppi *tuote)
{
  char *p; int j;
  p = palanen(rivi,"|",&j); 
  poista_tyhjat(p); kopioi_jono(N_S(tuote- >nimike),p);

  p = palanen(NULL,"|",&j); 
  if ( sscanf(p,"%lf",&tuote- >hinta) != 1 ) return 1;

  p = palanen(NULL,"|",&j);
  if ( sscanf(p,"%d",&tuote- >kpl) != 1 ) return 1;

  return 0;
}

Tehtävä 17.156	Parempi palanen
Kirjoita palanen - aliohjelmasta versio, joka ei pilaa alkuperäistä merkkijonoa.
Tehtävä 17.157	Vielä parempi palanen
Voiko palanen - aliohjelma olla yhtäaikaa kahdessa eri käytössä, eli voiko sillä esimerkiksi paloitella rinnakkain kahta eri merkkijonoa?

Mikäli ei voi, niin esitä ratkaisu miten voitaisiin.  Mikäli voi, niin perustele miksi.

17.7	Tiedoston yhdellä rivillä monta kenttää, lukeminen C++:lla
Toteutetaan tuot_pal.c:tä vastaava ohjelma C++:n tietovirroilla:
tiedosto\tuot_pal.cpp - esimerkki tiedoston lukemisesta  
// Projektiin +ALI\mjonot.c 
#include <iostream.h>
#include <fstream.h>
#include <strstrea.h>
#include <stdio.h>
#include <string>
using namespace std;
#include "mjonotpp.h"

struct tTuote {
  string nimike;
  double hinta;
  int    kpl;
};

void alusta(tTuote &tuote)
{
  tuote.nimike = "";
  tuote.hinta = 0.0;
  tuote.kpl   = 0;
}

int lue(istream &is,tTuote &tuote) {
  char jono[50];
  alusta(tuote);
  is.getline(N_S(jono),'|');   tuote.nimike = poista_tyhjat(jono);
  is.getline(N_S(jono),'|');
  if ( sscanf(jono,"%lf",&tuote.hinta) < 1 ) return 1;
  is.getline(N_S(jono),'\n');
  if ( sscanf(jono,"%d",&tuote.kpl) < 1 ) return 1;
  return 0;
}

int rivi_tuotteeksi(char *rivi, tTuote &tuote)
{
  istrstream sstr(rivi);
  return lue(sstr,tuote);
}

int tulosta_tuotteet(void)
{
  tTuote tuote;
  char rivi[80];

  ifstream fi("TUOTTEET.DAT");
  if ( !fi ) return 1;

  cout << "\n\n\n";
  cout << "-------------------------------------------\n";

  while ( 1 ) {
    if ( !fi.getline(rivi,sizeof(rivi))) break;
    if ( rivi_tuotteeksi(rivi,tuote) ) continue;
    printf("%-20s %7.0lf %4d\n",tuote.nimike.c_str(),tuote.hinta,tuote.kpl);
  }

  cout << "-------------------------------------------\n";
  cout << "\n\n\n";

  return 0;
}

int main(void)
{
  if ( tulosta_tuotteet() ) {
    cout <<  "Tuotteita ei saada luetuksi!\n";
    return 1;
  }
  return 0;
}

17.7.1	Merkkijonosta tietovirta
Ohjelmassa on pystytty välttämään palanen- tyylisen aliohjelman käyttö luomalla merkkijonosta tietovirta:
int rivi_tuotteeksi(char *rivi, tTuote &tuote)
{
  istrstream sstr(rivi);
  return lue(sstr,tuote);
}

Koska tämä tietovirta käyttäytyy aivan samoin kuin päätesyöttökin, voidaan tehdä aliohjelma lue, joka lukee tuotteen tiedot ikäänkuin tiedostosta.  Itse asiassa ohjelman lukusilmukassa voitaisiin aivan hyvin kutsua myös
while ( 1 ) {
  if ( lue(fi,tuote) ) break;
  printf("%-20s %7.0lf %4d\n",tuote.nimike.c_str(),tuote.hinta,tuote.kpl);
}

mutta tällöin lukeminen loppuisi ensimmäiseen virheeseen tai sopivalla tiedostolla rivijako menisi sekaisin.  Tämän vuoksi luettaessa tiedostoja joissa rivi on "yksi tietue", kannattaa lukea ensin rivin sisältö merkkijonoon ja sitten muuttaa tämä merkkijono tietueeksi.
C:ssä merkkijonoa yritettiin purkaa sscanf- funktiolla, joka onkin erittäin helppokäyttöinen niin kauan kuin merkkijono sisältää vain lukuja, jotka voidaan kaikki erottaa kerralla.  Jos kuitenkin erotettavien osien joukossa on merkkijonoja, menee ongelma tunnetusti hankalammaksi (tosin voidaan hoitaa erikoistapauksissa).
Jos kuitenkin merkkijonosta pitää saada 0-n erillistä osaa (merkkijonoa tai lukua), ei sscanf:ää voida käyttää!
C++:n strstream on taas siitä mukava, että se tekee merkkijonoon samanlaisen "lukuosoittimien" kuin muutkin tietovirrat tiedostoon.  Näin voidaan palasia erotella yksi kerrallaan ja "lukemista" voidaan jatkaa siitä, mihin edellinen lukeminen jäi.
17.7.2	Olio joka lukee itsensä
Muutetaan vielä edellistä oliomaisemmaksi, eli annetaan tuotteelle kuuluvat tehtävät kokonaan tuote- luokan vastuulle, samalla lisätään tuotteet- luokka.
tiedosto\tuot_pao.cpp  - esimerkki oliosta joka käsittelee tiedostoaa 
// Projektiin +ALI\mjonot.c
#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
#include <strstrea.h>
#include <stdio.h>
#include <string>
using namespace std;
#include "mjonotpp.h"

//----------------------------------------------------------------------------
class cTuote {
  string nimike;
  double hinta;
  int    kpl;
  void alusta() { nimike = "";  hinta = 0.0;  kpl = 0; }
public:
  int alusta(const char *rivi) {  // Alustaa jonosta Volvo|4500|3
    istrstream sstr((char *)rivi);
    return lue(sstr);
  }
  cTuote() { alusta(); }
  ostream &tulosta(ostream &os) const {
    long oldf = os.setf(ios::left);
    os << setw(20) << nimike << " " << setiosflags(ios::right)
       << setw(7)  << hinta  << " "
       << setw(4)  << kpl;
    os.flags(oldf);
    return os;
  }
  int lue(istream &is) {
    char jono[50];
    alusta();
    is.getline(N_S(jono),'|');   nimike = poista_tyhjat(jono);
    is.getline(N_S(jono),'|');
    if ( sscanf(jono,"%lf",&hinta) < 1 ) return 1;
    is.getline(N_S(jono),'\n');
    if ( sscanf(jono,"%d",&kpl) < 1 ) return 1;
    return 0;
  }
};

ostream &operator<<(ostream &os,const cTuote &tuote) 
  { return tuote.tulosta(os); }
istream &operator>>(istream &is,cTuote &tuote) { tuote.lue(is); return is; }

//----------------------------------------------------------------------------
class cTuotteet {
  string nimi;
public:
  cTuotteet(const char *n) { nimi = n; }
  int tulosta(ostream &os) const;
};

int cTuotteet::tulosta(ostream &os) const
{
  cTuote tuote;
  char rivi[80];

  ifstream f(nimi.c_str());  if ( !f ) return 1;

  cout << "\n\n\n";
  cout << "-------------------------------------------" << endl;

  while ( !f.eof() ) {
    if ( !f.getline(N_S(rivi)) ) continue;
    if ( tuote.alusta(rivi) ) continue;
    os << tuote << endl;
  }

  cout << "-------------------------------------------\n";
  cout << "\n\n\n" << endl;;

  return 0;
}

//----------------------------------------------------------------------------
int main(void)
{
  cTuotteet tuotteet("TUOTTEET.DAT");
  if ( tuotteet.tulosta(cout) ) {
    cout << "Tuotteita ei saada luetuksi!" << endl;
    return 1;
  }
  return 0;
}

17.8	Esimerkki tiedoston lukemisesta
Seuraavaksi kirjoitamme ohjelman, jossa tulee esiin varsin yleinen ongelma:  tietueen etsiminen joukosta.  Kirjoitamme edellisiä esimerkkejä vastaavan ohjelman, jossa tavallisen tulostuksen sijasta tulostetaan kunkin tuoteluokan yhteistilanne.
tiedosto\tuoterek.cpp - esimerkki tiedoston lukemisesta C-funktiolla
// tuoterek.cpp 
/*
  Ohjelma lukee tiedostoa TUOTTEET.DAT, joka on muotoa:

    Volvo |  12300 | 1
    Audi  |  55700 | 2
    Saab  |   1500 | 4
    Volvo | 123400 | 1

  Ohjelma tulostaa kuhunkin tuoteluokkaan kuuluvien tuotteiden
  yhteishinnat ja kappalemäärät sekä koko varaston yhteishinnan
  ja kappalemäärän.  Eli em. tiedostosta tulostetaan:

-------------------------------------------
Volvo                 135700    2
Audi                  111400    2
Saab                    6000    4
-------------------------------------------
Yhteensä              253100    8
-------------------------------------------

  Vesa Lappalainen 15.3.1996
  Projektiin: tuoterek.cpp,ALI\mjonot.c
*/

#include <iostream.h>
#include <iomanip.h>
#include <strstream.h>
#include <fstream.h>
#include <stdio.h>
#include <string.h>
#include <string>
using namespace std;
#include <mjonot.h>
#include <mjonotpp.h>

//---------------------------------------------------------------------------
class cTuote {
  string nimike;
  double hinta;
  int    kpl;
public:
  int alusta(const char *n,double h,int k=0) {
    nimike = n; hinta = h; kpl = k; return 0;
  }
  int alusta(const char *rivi) {  // Alustaa jonosta Volvo|4500|3
    istrstream sstr((char *)rivi);
    return lue(sstr);
  }
  int alusta()                            { return alusta("",0,0);           }
  cTuote()                                { alusta();                        }
  cTuote(const char *rivi)                { alusta(rivi);                    }
  int lue(istream &is) {...}
  ostream &tulosta(ostream &os) const { ... }

  void ynnaa(const cTuote &tuote) {
    hinta += tuote.hinta * tuote.kpl;
    kpl   += tuote.kpl;
  }
  cTuote &operator+=(const cTuote &tuote) { ynnaa(tuote); return *this;      }

  const string &Nimike() const            { return nimike;                   }
};

ostream &operator<<(ostream &os,const cTuote &tuote)
  { return tuote.tulosta(os); }
istream &operator>>(istream &is,cTuote &tuote) { tuote.lue(is); return is; }

//---------------------------------------------------------------------------
const int MAX_TUOTTEITA = 10;

class cTuotteet {
  string nimi;
  int    tuotteita;
  cTuote tuotteet[MAX_TUOTTEITA];
  cTuote yhteensa;
public:
  cTuotteet(const char *n) : nimi(n), yhteensa("Yhteensä") { tuotteita = 0;  }
  ostream &tulosta(ostream &os) const;
  int etsi(const string &tnimi) const;
  int lisaa(const string &tnimi);
  int ynnaa(const cTuote &tuote);
  int lue(const char *s=NULL);
};

int cTuotteet::etsi(const string &tnimi) const
{
  int i;
  for (i=0; i<tuotteita; i++)
    if ( tuotteet[i].Nimike() == tnimi ) return i;
  return -1;
}

int cTuotteet::lisaa(const string &tnimi)
{
  if ( tuotteita >= MAX_TUOTTEITA ) return -1;
  tuotteet[tuotteita].alusta(tnimi.c_str(),0.0,0);
  return tuotteita++;
}

int cTuotteet::ynnaa(const cTuote &tuote)
{
  if ( tuote.Nimike() == "" ) return 1;
  int i = etsi(tuote.Nimike());
  if ( i < 0 ) i = lisaa(tuote.Nimike());
  if ( i < 0 ) return 1;

  tuotteet[i] += tuote;
  yhteensa    += tuote;

  return 0;
}

int cTuotteet::lue(const char *s)
{
  char rivi[80];

  if ( s ) nimi = s;
  ifstream f(nimi.c_str());  if ( !f ) return 1;

  while ( f.getline(N_S(rivi)) ) {
    cTuote tuote(rivi);
    if ( ynnaa(tuote) )
      cout << "Rivillä \"" << rivi << "\" jotain pielessä!" << endl;
  }
  return 0;
}

ostream &cTuotteet::tulosta(ostream &os) const
{
  int i;
  os << "\n\n\n";
  os << "-------------------------------------------\n";
  for (i=0; i<tuotteita; i++)
    os << tuotteet[i] << "\n";
  os << "-------------------------------------------\n";
  os << yhteensa << "\n";
  os << "-------------------------------------------\n";
  os << "\n\n" << endl;
  return os;
}

int main(void)
{
  cTuotteet varasto("tuotteet.dat");

  if ( varasto.lue() ) {
    cout << "Tuotteita ei saada luetuksi!" << endl;;
    return 1;
  }

  varasto.tulosta(cout);

  return 0;
}

Tehtävä 17.158	Tietorakenne
Piirrä kuva cTuotteet - luokan tietorakenteesta.
Tehtävä 17.159	Perintä
Miten voisit perinnän avulla saada tiedoston tuote_pao.cpp luokasta cTuote tiedoston tuoterek.cpp vastaavan luokan (tietysti eri nimelle, esim. cRekTuote).  Mitä muutoksia olisi hyvä tehdä alkuperäisessä cTuote- luokassa.
Tehtävä 17.160	Tunnistenumero
Lisää tuoterek.cpp- ohjelmaan tunnistenumeron käsittely mahdollista tulevaa relaatiokäyttöä varten.
Tehtävä 17.161	Mittakaava
Kirjoita mittakaavaohjelma, jossa on vakiotaulukko
yks	mm	
mm 
cm  
dm  
m   
inch	1.0
  10.0
 100.0
1000.0
  25.4	

ja jonka toiminta näyttäisi seuraavalta:
...
Mittakaava ja matka>1:10000 10 cm[RET]
Matka maastossa on 1.00 km.
Mittakaava ja matka>1:200000 20[RET]
Matka maastossa on 4.00 km.
Mittakaava ja matka>loppu[RET]
Kiitos! 

Muuta ohjelmaa siten, että yksiköiden muunnostaulukko luetaan ohjelman aluksi tiedostosta MUUNNOS.DAT.

Muuta ohjelmaa vielä siten, että mikäli mittakaava jätetään antamatta, käytetään edellisellä kerralla annettua mittakaavaa ja ensimmäinen luku onkin matka. 

...
Mittakaava ja matka>1:10000 10 cm[RET]
Matka maastossa on 1.00 km.
Mittakaava ja matka>0.20 dm[RET]
Matka maastossa on 0.20 km.
Mittakaava ja matka>loppu[RET]
Kiitos! 

18.	Operaattoreiden kuormitus
Mitä tässä luvussa käsitellään?
•	uusien merkityksien antaminen operaattoreille
•	automaattinen tyypinmuunnos
•	hajottajan hyödyntäminen

Syntaksi:
<< luokan ulkopuolella:   ostream &operator<<(ostream &os,const cLuokka &olio);
>> luokan ulkopuolella:   istream &operator>>(istream &is,cLuokka &olio);
+= luokassa:              cLuokka &operator+=(const cLuokka &olio2);
+ luokan ulkopuolella:    cLuokka3 operator+(const cLuo1 &o1, const cLuo2 &o2);
+ luokassa:               cLuokka3 operator+(const cLuo2 &o2);
= luokassa:               cLuokka &operator=(const cLuo2 &o2);
muunnos intiksi luokassa: operator int(); 

Ohjelmassa tuoterek.cpp oli esimerkki siitä, miten C++:ssa voidaan lisämääritellä (eli kuormittaa, overload) myös operaattoreita.
18.1	operator+= 
Tuote oli sellainen, että siihen lisättiin aina saman tuotteen yhteensä- tiedot:
  tuotteet[i] += tuote;
  yhteensa    += tuote;

Sama voitaisiin tehdä kutsulla
  tuotteet[i].ynnaa(tuote);
  yhteensa.ynnaa(tuote);

Osittain on makuasia kummalla tavalla lisääminen tehdään. += - operaattorin käyttö on tullut mahdolliseksi, koska luokassa cTuote on esitelty miten operaattorin on käyttäydyttävä jos luokan olioon halutaan lisätä toinen saman luokan olio:
cTuote &operator+=(const cTuote &tuote) { ynnaa(tuote); return *this;      }

Operaattori on laitettu palauttamaan saman luokan olio, koska joskus voidaan haluta C:mäistä ketjusijoitusta:
tuote3 = tuote2 += tuote1;

Toisaalta ketjusijoitus ei ole aina selvin mahdollinen tapa tehdä asioita, samahan voitaisiin tehdä
tuote2 += tuote1; tuote3 = tuote2;

joten myös operaattori += voitaisiin suhteellisen hyvällä omalla tunnolla määritellä myös (luokassa cTuote)
void operator+=(const cTuote &tuote) { ynnaa(tuote);  }

Oikeasti operaattoria kutsuttaisiin:
tuote2.operator+=(tuote1);
// vrt:
tuote2.ynnaa(tuote1);

mutta operaattori käsitellään erikoistapauksena ja em. kutsu voidaan kirjoittaa lyhyemmässä muodossa
tuote2 += tuote1;

Huomattakoon että samasta operaattorista voi olla useita eri versiota riippuen siitä mitä lisätään.  Eli voisi olla esimeriksi:
tuote2 += 2000.0;   // lisätään reaaliluku hintaan;
tuote2 += 5;        // lisätään kokonaisluku kappalemäärään

Tehtävä 18.162	Useita versioita += operaattorista
Kirjoita em. versiot reaaliluvun ja kokonaisluvun lisäämiseksi tuotteeseen.

18.2	operator<< ja operator>> 
Aikaisemmin mainittiin että eräs tietovirtojen merkittävimmistä eduista printf- tyyliseen tulostukseen on se, että tietovirtoihin voi tulostaa myös itse tehtyjä olioita.  printf:hän ei ole mahdollista enää itse muuttaa ja lisätä uusia %- formaatteja.
Kuormitettaessa esimeriksi << - operaattoria, pitäisi oikeastaan muuttaa sitä tietovirta- luokkaa, johon ollaan tulostamassa.  
cout << "Kissa\n";           // tarkoittaa periaateessa:
cout.operator<<("Kissa\n");

Onneksi operaattoreista on myös 2- operandiset versiot.  Eli 
operator<<(cout,"Kissa\n");

Tätä tietoa hyödyntäen voimme kirjoittaa operaattorista version, jonka ansiosta tulostus
cout << tuotteet[i] << "\n";

todella tulostaa tietorakenteen i:n tuotteen.
ostream &operator<<(ostream &os,const cTuote &tuote)
{ 
  return tuote.tulosta(os); 
}

Koska tuotteen tulostaminen on sinänsä jo valmis, pitää meidän vain kutsua tuotteen tulosta- metodia operaattorin määrittelyssä.  Operaattorin PITÄÄ palauttaa vastaava tietovirta, jotta ketjutulostus:
cout << tuotteet[i] << "\n";

olisi mahdollinen.  Tässähän oli itse asiassa kyseessä kutsu
operator<<(operator<<(cout,tuotteet[i]),"\n");

Itse tuote on esitelty vakioviitteeksi operaattorin << parametrilistassa.  Viitteeksi, koska on turha siirrellä kokonaisia olioita ja vakioviitteeksi koska tulostuksen aikana tuotetta ei tietenkään muuteta!
Aivan vastaavasti esitellään >> - operaattori:
istream &operator>>(istream &is,cTuote &tuote) 
{ 
  tuote.lue(is); 
  return is; 
}

Ohjelman tuoterek.cpp lukusilmukka voisi tämän ansiosta olla jopa muodossa
cTuote tuote;
while ( f >> tuote ) ynnaa(tuote);

Toisaalta aikaisemmin todettiin että tässä on vaara sotkea rivien järjestys joten parempana voidaan pitää alunperin esitettyä
lue_rivi
käsittele_rivi

tapaa.
18.3	Binäärisen operaation kuormitus
Vastaavasti kuin binäärisiä << ja >> - operaattoreita, voidaan myös muitakin binäärisiä operaattoreita kuormittaa.  Binääristä operaatiota kuormitettaessa vähintään toisen operandin tulee olla luokka (eli ei skalaari kuten int, double jne.)
Olkoon meillä vaikkapa luokka, joka kuvaa hyvin usein vastaan tulevaa tilannetta, jossa käytössä onkin hyvin rajoitettu kokonaislukualue (esim. kellon minuutit [0,59[, tunnit [0,24[ jne.):
operator\rajoit.cpp - esimerkki binäärisen operaation kuormituksesta  
#include <iostream.h>
class cRajoitettu {
  int arvo;
  int raja;
public:
  int aseta(int aarvo) {
    arvo = aarvo;
    int yli = arvo/raja;
    arvo %= raja;
    return yli;
  }

  cRajoitettu(int aarvo, int araja) {
    raja = araja; if ( raja < 1 ) raja = 1;
    aseta(aarvo);
  }
  int Arvo() const { return arvo; }
  int Raja() const { return raja; }
};

ostream &operator<<(ostream &os, const cRajoitettu &r) {return os << r.Arvo(); }

cRajoitettu operator+(const cRajoitettu &r1, int i)
{
  return cRajoitettu(r1.Arvo()+i,r1.Raja());
}

int operator+(int i,const cRajoitettu &r2)
{
  return i+r2.Arvo();
}

int main(void)
{
  cRajoitettu m1(55,60),m2(20,60);
  int i;
  cout << m1 << " " << m2 << "\n";
  m2 = m1 + 20;
  cout << m1 << " " << m2 << "\n";
  i = 20 + m2;
  cout << i << "\n";
  return 0;
}

Tässä ensimmäinen + - operaattori voitaisiin tehdä myös luokan metodiksi:
operator\rajoit2.cpp - esimerkki operaatioden kuormituksesta  
#include <iostream.h>
class cRajoitettu {
...
  cRajoitettu operator+(int i)  { return cRajoitettu(arvo+i,raja);  }
};

Tämä näyttää yksi- parametriselta operaattorilta, mutta on tosiasiassa binäärinen, nimittäin vasempana operandina on *this.
m2 = m1.operator+(20);

18.4	Tyypinmuunnosoperaattori ja sijoitus
Esimerkin rajoit.cpp toinen +- operaattori voitaisiin välttää tekemällä muunnosoperaattori, joka muuttaa cRajoitettu - tyyppisen olion kokonaisluvuksi:
class cRajoitettu {
...
  operator int() { return arvo; } 
};

Tällöin yhteenlaskussa
i = 20 + m2;

m2 ensin muuttuu kokonaisluvuksi ja sitten normaali kokonaislukujen yhteenlasku huolehtii lopusta.  Muoto
m1 = 20 + m2;

saataisiin toimimaan määrittelemällä  sijoitusoperaattori:
class cRajoitettu {
...
  cRajoitettu &operator=(int i) { aseta(i); return *this; }
};

Tällöin sijoituslauseen oikea puoli lasketaan kokonaislukuna ja sitten tämä kokonaisluku sijoitetaan em. sijoitusoperaattorilla.  Sijoitusoperaattorin tulee palauttaa sijoituksen kohteena olevan luokan tyyppinen tulos, jotta ketjusijoitus 
m1 = m2 = 20;

olisi mahdollinen.  
Tehtävä 18.163	Ajan lisääminen
Aikaisemmin oli esitetty cAika- luokka.  Lisää luokkaan += - operaattori ja + - operaattori joiden ansiosta seuraavat sijoitukset olisivat mahdollisia
cAika a1(10,30),a2(15,55);
a1 += 50;
a2 = a1 + 20;
Tehtävä 18.164	Aika rajoitettujen kokonaislukujen avulla
Miten voisit tehdä luokan cAika käyttämällä edellä ollutta cRajoitettu- luokkaa.  Tarvitseeko cRajoitettu joitakin muutoksia?

18.5	Hajottajan hyödyntäminen
18.5.1	Tulostusmuotoilun palauttaminen
Oletetaanpa tuoterek.cpp - ohjelmassa että hinnat haluttaisiinkin tulostaa kahdella desimaalilla:
  ostream &tulosta(ostream &os) const {
    os.precision(2); os.setf(ios::left | ios::showpoint | ios::fixed );	
    os << setw(20) << nimike << " " << setiosflags(ios::right)
       << setw(10)  << hinta  << " "
       << setw(4)  << kpl;
    return os;
  }
...
ostream &operator<<(ostream &os,const cTuote &tuote) { 
  return tuote.tulosta(os); }

Nyt tietysti voisi olla ohjelmoijalle yllätys, jos hän kutsuisi
  cTuote tuote("Volvo|23700|1");
  const double pi = 3.14159265;
  cout.precision(6);
  cout << pi << '\n';
  cout << tuote    << '\n';
  cout << pi << '\n';

ja jälkimmäinen piin arvo tulostuisikin vain kahdella desimaalilla.  Tämän vuoksi pitäisi alkuperäiset tulostusasetukset palauttaa, mikäli niitä muutetaan.
  ostream &tulosta(ostream &os) const {
    long oldf = os.setf(ios::left | ios::showpoint | ios::fixed );
    int olddes = os.precision(2);
    ... tulostusta ...
    os.precision(olddes); os.flags(oldf);
    return os;
  }

Mutta kuka tämän jaksaa tehdä kerrasta toiseen?
Entäpä jos teemmekin luokan cStreamPre (stream precision):
  ostream &tulosta(ostream &os) const {
    cStreamPre pre(os,2);
    ... tulostusta ...
    return os;
  }

ja nyt piikin tulee taas pyydetyllä 6:lla desimaalilla molemmilla kerroilla!  Miten?
ali\StreamPr.h - Luokka tulostustarkkuuden asettamiseksi 
class cStreamPre { 
  ostream &os;
  long oldf;
  int  oldp;
public:
  cStreamPre(ostream &aos=cout,int npre=1,long flags=0) : os(aos) {
    oldf = os.setf(ios::showpoint | ios::fixed | flags);
    oldp = os.precision(npre);
  }
  ~cStreamPre() { os.flags(oldf); os.precision(oldp); }
};

Luokan muodostaja tallettaa olion attribuutteihin tulostuksen muotoilun  ennen kuin muotoiluja on muutettu.  Kun syntyneen olion vaikutusalue lakkaa, eli poistutaan siitä ohjelmalohkosta jossa olio on syntynyt (auto), kutsutaan olion hajottajaa, joka palauttaa alkuperäiset arvot.  Nyt ohjelmoijan tarvitsee vain antaa olion syntyä automaattisesti 
cStreamPre pre(os,2);

lohkon alussa.  Ja tämähän saadaan vähemmällä kirjoittamisella kuin tarkkuuden muuttaminen normaalisti:
os.precision(2); os.setf(ios::left | ios::showpoint | ios::fixed );

Huomattakoon, että "temppu" toimii vaikka kesken tulostuksen poistuttaisiin ylimääräisellä return—lauseella.  Samoin "temppu" toimii, vaikka tulostuksen sisällä kutsuttaisiin toista samalla tavalla toteutettua tulostusta!
18.5.2	Tiedoston automaattinen sulkeminen
Samaan ideaanhan perustuu myös se, että C++:ssa voidaan tiedosto "jättää sulkematta":
{
  ifstream fi(nimi);
  ... lukeminen ...
}

eli kun olion fi vaikutusalue lakkaa, kutsutaan tietovirtaolion hajottajaa, joka sulkee tiedoston.
18.6	Ystäväfunktiot
Usein kirjallisuudessa mm. tulostus opetetaan tekemään seuraavasti:
class cTuote {
...
 friend ostream &operator<<(ostream &os,const cTuote &tuote);
}

ostream &operator<<(ostream &os,const cTuote &tuote) { 
  ... viitataan suoraan luokan suojattuihin attribuutteihin
}

Funktion tai luokan esitteleminen ystäväksi tarkoittaa sitä, että funktio tai luokka pääsee suoraan käsiksi olion attribuutteihin.  Tämä ei yleensä kuitenkaan ole kovin suotavaa.  Jopa itse kielen tekijäkin on myöntänyt että ystäväfunktiot (friend) ovat tarpeettomia.  Mehän vältimme ystäväfunktion seuraavalla tekniikalla:
class cTuote {
...
  ostream &tulosta(ostream &os) const { ... }
}

ostream &operator<<(ostream &os,const cTuote &tuote) { 
  return tuote.tulosta(os);
}

emmekä joutuneet edes kirjoittamaan yhtään enempää koodia.  Toinen tapa välttää ystäväfunktioita on saantimetodien käyttäminen:
class cRajoitettu {
...
  int Arvo() const { return arvo; }
};

ostream &operator<<(ostream &os, const cRajoitettu &r) {return os << r.Arvo(); }

Tässäkään tapauksessa ei ole kenellekään luovutettu pääsyä muuttamaan suojattuja attribuutteja.
Vastaavasti ystävyyttä esitetään kirjallisuudessa muidenkin operaattoreiden kuormittamisessa käytettäväksi, mutta tottakai voimme em. tekniikoilla välttää ystäväfunktioiden käytön.
Siis pyrimme jatkossa välttämään ystäväfunktioita (jopa enemmän kuin goto- lausetta).

19.	Kerho- ohjelman talletukset
Mitä tässä luvussa käsitellään?
•	kerhon talletus
•	kerhon lukeminen
•	template- funktiot
•	muunnos: numeerinen tieto <-> merkkitieto

Olemme nyt testanneet kerho- ohjelmamme tietorakenteen käyttämällä syöttönä "arvottuja" henkilöitä.  Tämä on nopeampaa kuin syöttää käsin henkilön vaatimat tiedot usealle henkilölle ja todeta sitten, ettei tietorakenne toimikaan.  Uudelleen yrityksessä sitten helposti testi jää vajavaiseksi jos syöttöjä pitää tehdä paljon.
Vastaavasta syystä kannattanee vielä hetken malttaa mieli ja tehdä jopa tiedostojen käsittely ennen päätesyöttöä.  Nimittäin olisi aika masentavaa syöttää 10 ihmisen tiedot ja huomata sitten, ettei talletus toimikaan.
Jatkossa aliohjelmista esitetään vain osia ja nekin usein ilman kommentteja, koska ohjelmasta on täydellinen listaus monisteen liitteenä.
19.1	Talletus
Siispä ryhdymme ensin miettimään miten ohjelman talletuksen tulisi toimia:
- jos ei muutoksia, turha tallettaa
-  tee (mahdollisesta) vanhasta tiedostosta.BAK 
-  avaa tiedosto kirjoittamista varten
-  talleta kokonimi ja maksimikoko (otsikkotiedot)
-  talleta kukin tietue

Seuraavaksi pitää miettiä mikä toimenpide suunnitelluista kuuluu millekin luokalle.  Luokkinahan olivat:
cNaytto
cKerho
cJasenet
cJasen

19.1.1	Näytön osuus talletuksesta
Koska näytön tehtävä on huolehtia kaikesta käyttöliittymään liittyvästä, voisi .bak- tiedoston tekeminen kuulua osittain näytölle, varsinainen talletus menköön kerhon tehtäviin.  Toisaalta jos kerhoon lisätään harrastukset, niin myös harrastusten .bak- tiedoston tekeminen jäisi näytön huoleksi.  Siispä sitten huolehtikoon kerho myös .bak- tiedostojen tekemisestä.  Laiskana kerho tietysti delegoi tämänkin homman eteenpäin.  Näytön tehtäviin jää siis vain delegoida tehtäviä kerholle:
talletus.2\naytto.cpp - kerhon talletus 
int cNaytto::talleta()
{
  logo();
  if ( !kerho->Muutettu() ) return 0;
  int vanhat_pilalla = kerho->TeeBak(VANHATARK);

  if ( ilmoitus(kerho->talleta()) ) return 1;

  cout << endl;
  cout << "Tiedot talletettu tiedostoon "
       << kerho->Jasenet().Tiedoston_nimi() << endl;
  if ( !vanhat_pilalla )
    cout << "Vanhat tiedot tiedostossa    "
         << kerho->Jasenet().Bak_nimi() << endl;

  return 0;
}

19.1.2	Kerhon osuus talletuksesta
Kerhon tehtävä on lähinnä vain delegoida tehtäviä "alamaisilleen", esim. jäsenistölle ja harrastuksille:
talletus.2\kerho.h - kerho talletukset 
class cKerho {
...
  const char *talleta(const string &tied="");
...
  int TeeBak(const string &bak_tark)     { return jasenet.TeeBak(bak_tark);
};

talletus.2\kerho.cpp - talleta  
const char *cKerho::talleta(const string &tied)
{
  return jasenet.talleta(tied);
}

19.1.3	Jäsenistön osuus talletuksesta
Jäsenistö ei enää paljoa pysty tehtäviään välttelemään:
talletus.2\jasenet.cpp - talletus 
//-------------------------------------------------------------------------
int cJasenet::TeeBak(const string &bak_tark)
{
  bak_nimi = Tiedoston_nimi();
  vaihda_tarkennin(bak_nimi,bak_tark);

  remove(bak_nimi); /* Vanha .BAK täytyy poistaa jotta rename toimii */
  return rename(Tiedoston_nimi(),bak_nimi);
}
...
//-------------------------------------------------------------------------
const char *cJasenet::talleta(const string &tied)
{
  if ( !muutettu ) return NULL;
  string tiedosto(tied); if ( tied == "" ) tiedosto = tiedoston_nimi;
  ofstream f(tiedosto.c_str());

  if ( !f ) return TIED_EI_AUKEA;

  f << koko_nimi << endl;
  f << max_lkm << endl;

  if ( !f ) return OTS_EI_KIRJ;

  for (int i=0; i<lkm; i++) {
    f << *alkiot[i] << endl;
    if ( !f ) return ALKIO_EI_KIRJ;
  }

  muutettu = 0;
  tiedoston_nimi = tiedosto;
  return NULL;
}

Myös kommentit kannattaisi tavalla tai toisella siirtää vanhasta alkuperäisestä tiedostosta.  Tätä varten voisimme kirjoittaa aliohjelman 
kopioi_kommentit(f,bak_nimi) 

Myöhemmin ehkä käytännössä huomataan, että olisi siistimpää tulostaa kenttiä hieman täsmällisemmin allekkain - kuten alkuperäisessä suunnitelmassa oli.  Tämä voitaisiin toteuttaa mallirivin avulla, missä mallirivi on alkuperäisen tiedoston kommenteista luettu rivi.  Tästä rivistä tutkitaan erotinmerkkien paikkoja ja pyritään saamaan erotinmerkit vastaaviin paikkoihin myös tulosjonossa.
19.1.4	Jäsenen tehtävät talletuksessa
Jäsenellekin on jäänyt tehtäviä.  Jäsenen tulostus tietovirtaan voitaisiin tehdä esimerkiksi:
ostream &operator<<(ostream &os,const cJasen &jasen)
{
  char erotin = jasen.erotin;
  os << jasen.tunnus_nro     << erotin
     << jasen.nimi           << erotin
     << jasen.hetu           << erotin
     << jasen.katuosoite     << erotin
...
     << jasen.jmaksu         << erotin
     << jasen.maksu          << erotin
     << jasen.lisatietoja    << erotin;
}

Nyt maksut tulostuisivat desimaaleiltaan varsin mielivaltaisesti.  Jos tämä tyydyttää, niin em. tapa on aivan hyvä.  Toisaalta voitaisiin tehdä apufunktio jonoksi, jonka tehtävänä on muotoilla reaaliluku merkkijonoksi siististi, esimerkiksi kahdella desimaalilla:
string jonoksi(double d)
{
  char st[40];
  double_jonoksi(N_S(st),d,"%4.2lf");
  return string(st);
}

Nyt reaalilukukenttien tulostus voitaisiin tehdä
...
     << jonoksi(jasen.jmaksu)         << erotin
     << jonoksi(jasen.maksu)          << erotin
...

Symmetriasyistä kaikille muillekin tietotyypeille voitaisiin tehdä vastaava funktio C++:an kuormitusmahdollisuuden ansiosta.  Näin jäsenen tietovirtaan tulostaminen voisi olla myös:
talletus.2\jasen.cpp - talletus  
ostream &operator<<(ostream &os,const cJasen &jasen)
{
  char erotin = jasen.erotin;
  os << jonoksi(jasen.tunnus_nro)     << erotin
     << jonoksi(jasen.nimi)           << erotin
     << jonoksi(jasen.hetu)           << erotin
     << jonoksi(jasen.katuosoite)     << erotin
     << jonoksi(jasen.postinumero)    << erotin
     << jonoksi(jasen.postiosoite)    << erotin
     << jonoksi(jasen.kotipuhelin)    << erotin
     << jonoksi(jasen.tyopuhelin)     << erotin
     << jonoksi(jasen.autopuhelin)    << erotin
     << jonoksi(jasen.liittymisvuosi) << erotin
     << jonoksi(jasen.jmaksu)         << erotin
     << jonoksi(jasen.maksu)          << erotin
     << jonoksi(jasen.lisatietoja)    << erotin;
  return os;
}

Tästä on vielä se lisäetu, että voidaan esimerkiksi tehdä jonoksi- funktiosta sellainen, että tietty kokonaislukuarvo tai reaalilukuarvo (esim. -1) tallettuu tyhjänä merkkijonona, tarkoittaen ettei arvoa ole syötetty.  0:han ei yleensä voi tällainen arvo olla, koska 0 on usein aivan järkevä syöttö.
Tehtävä 19.165	Ystävyys pois
Toteuta jäsenen operator<< siten, ettei ystäväfunktiota tarvita.

19.2	Lukeminen
Lähdemme hahmottelemaan lue_tiedosto - metodia:
-  selvitä kerhon nimi
-  avaa vastaava tiedosto
-  mikäli ei aukea, niin kysy tuleeko uusi ja aloita alusta
-  mikäli aukesi, niin lue alkutiedot, eli koko nimi ja
  kerhon maksimijäsenmäärä
-  luo jäsenistö
-  lue jäsenet tiedostosta

19.2.1	Näytön tehtävät lukemisessa
Tässä pitää taas valita mikä tehtävä kuuluu millekin luokalle.  Selvästi nimen kysyminen ja muiden tietojen uteleminen on käyttöliittymäluokan cNaytto tehtäviä:
talletus.2\naytto.cpp - kerhon lukeminen 
int cNaytto::lue_tiedosto()
/*
** Luetaan kerho levyltä.
** Ensin kysytään kerhon nimi.  Jos kerhoa ei ole, utellaan
** lisätietoja ja luodaan se.
----------------------------------------------------------------------------*/
{
  string tied,nimi; int maksimi;

  do { // Kysellään kunnes tiedosto aukeaa tai luodaan uusi
    cout << endl;
    cout << "Anna kerhon nimi>";  lue_rivi(cin,tied);
    if ( tied == "" ) return ilmoitus("Tiedoston nimeä ei annettu");

    laita_tarkennin(tied,TARKENNIN);
    if ( onko_tiedostoa(tied) ) return ilmoitus(kerho->lue_tiedostosta(tied));

    cout << "Tiedostoa " << tied << " ei ole!" << endl;
  } while ( kylla_kysymys("Luodaanko uusi tiedosto?") == 0 );

  cout << endl;
  cout << "Anna kerhon koko nimi    >";  lue_rivi(cin,nimi);
  cout << "Anna kerhon maksimi koko >";  lue_rivi(cin,maksimi);

  return ilmoitus(kerho->luo(tied,nimi,maksimi));

}

19.2.2	Kerhon tehtävät lukemisessa
Kerho vaan taas jakaa kutsuja eteenpäin jäsenistölle.  Jos lisätään harrastukset, kerho jakaa kutsuja myös harrastuksille.
19.2.3	Jäsenistön tehtävät lukemisessa
Lopulta jäsenistö on taas se joka joutuu hommiin:
talletus.2\jasenet.cpp - lukeminen
const char *cJasenet::luo(const string &tied,const string &nimi,int max_koko)
{
  IF_ERR_RETURN(luo_taulukko(max_koko));

  tiedoston_nimi = tied;
  koko_nimi      = nimi;

  muutettu = 1;
  return NULL;
}
...
const char *cJasenet::lue_tiedostosta(const string &tied)
{
  ifstream f(tied.c_str());
  if ( !f ) return TIED_EI_AUKEA;

  string nimi;  lue_rivi(f,nimi);       if ( !f ) return EI_NIMEA;
  int max_koko; lue_rivi(f,max_koko);   if ( !f ) return EI_MAXKOKOA;

  IF_ERR_RETURN(luo_taulukko(max_koko));

  tiedoston_nimi = tied;
  koko_nimi      = nimi;

  char rivi[400];
  cJasen uusi;
  while ( f ) {
    lue_rivi(f,rivi,sizeof(rivi)); 
    if ( rivi[0] == 0 || rivi[0] == ';' ) continue;
    uusi.alusta(rivi);
//    f >> uusi;  // vaatisi kunkin rivin olemisen täydellisenä, muuten OK!
    IF_ERR_RETURN(lisaa(uusi));
  }

  muutettu = 0;
  return NULL;
}

19.2.4	Jäsenen tehtävät lukemisessa
Jälleen tehtäviä riitti vähän myös itse jäsenelle.  Jäsen pitäisi saada selvitettyä merkkijonosta:
talletus.2\jasen.cpp - alusta
int cJasen::alusta(const char *rivi) 
{
  char *urivi = tee_jono(rivi); // Kopio, koska ei ole const char * alustajaa
  istrstream sstr(urivi);       // merkkivirralle
  sstr >> *this;
  free(urivi);
  return 0;
}

Seuraavaksi pitäisi jäsen lukea tietovirrasta:
istream &operator>>(istream &is,cJasen &jasen)
{
  char jono[50]; 
  is.getline(N_S(jono),'|'); sscanf(jono,"%d", &jasen.tunnus_nro);
  is.getline(N_S(jono),'|'); nimi = poista_tyhjat(jono);
  is.getline(N_S(jono),'|'); sotu = poista_tyhjat(jono);
  ...
  is.getline(N_S(jono),'|'); sscanf(jono,"%lf", &jasen.maksu);
  ...
}

Tämä on taas muuten hyvä, mutta ratkaisua vaivaa tietty epäsymmetria eri tietotyyppien välillä.  Lisäksi jos talletuksessa on sovittu, että esimerkiksi -1 tarkoittaa syöttämätöntä arvoa ja talletetaan tyhjänä,  pitäisi lukemisessa tämä käsitellä kääntäen.  Voitaisiin yrittää myös seuraavaa:
talletus.2\jasen.cpp - lukeminen 
istream &operator>>(istream &is,cJasen &jasen)
{
  char erotin = jasen.erotin;
  ota_seuraava(is,jasen.tunnus_nro      ,erotin);
  ota_seuraava(is,jasen.nimi            ,erotin);
  ota_seuraava(is,jasen.hetu            ,erotin);
  ota_seuraava(is,jasen.katuosoite      ,erotin);
  ota_seuraava(is,jasen.postinumero     ,erotin);
  ota_seuraava(is,jasen.postiosoite     ,erotin);
  ota_seuraava(is,jasen.kotipuhelin     ,erotin);
  ota_seuraava(is,jasen.tyopuhelin      ,erotin);
  ota_seuraava(is,jasen.autopuhelin     ,erotin);
  ota_seuraava(is,jasen.liittymisvuosi  ,erotin);
  ota_seuraava(is,jasen.jmaksu          ,erotin);
  ota_seuraava(is,jasen.maksu           ,erotin);
  ota_seuraava(is,jasen.lisatietoja     ,erotin);
  if ( jasen.tunnus_nro >= jasen.seuraava_nro )
    jasen.seuraava_nro = jasen.tunnus_nro + 1;
  return is;
}

Funktio ota_seuraava on polymorfinen eri tietotyypeille ja sen tehtävänä on päästä eroon turhista välilyönneistä ja lukea tietovirtaa seuraavaan erotinmerkkiin saakka.  ota_seuraava huolehtii myös tyhjän arvon käsittelystä (-1 <=> "").
19.3	Funktiomallit (template- funktiot)
Funktio ota_seuraava C- merkkijonoa varten voidaan tehdä seuraavasti:
talletus.2\jasen.cpp - ota_seuraava
static int ota_seuraava(istream &is, char *s, int max_koko, char erotin)
{
  if ( max_koko <= 0 ) return 0;
  s[0] = 0;
  if ( !is ) return 1;
  is.getline(s,max_koko,erotin);
  poista_tyhjat(s);
  return ( is == 0 );
}

Tätä käyttäen voimme tehdä saman funktion kuormitettuja versioita muille tietotyypeille:
int ota_seuraava(istream &is, double &d,  char erotin)
{
  char s[100];
  int err = ota_seuraava(is,s,sizeof(s),erotin);
  d = jono_doubleksi(s,"%lf");
  return err;
}

int ota_seuraava(istream &is, int &i,  char erotin)
{
  char s[100];
  int err = ota_seuraava(is,s,sizeof(s),erotin);
  i = jono_intiksi(s,"%d");
  return err;
}

Ja vielä tarvittaisiin lisää!  Kuitenkin nämä näyttävät keskenään yllättävän samanlaisilta.  Kirjoittamalla pari apualiohjelmaa ja vaihtamalla nimiä saadaankin niistä täsmälleen samanlaisia:
int ota_seuraava(istream &is, double &a,  char erotin)
{
  char s[100];
  int err = ota_seuraava(is,s,sizeof(s),erotin);
  muunna_jono(s,a);
  return err;
}

int ota_seuraava(istream &is, int &a,  char erotin)
{ ... ihan sama ... }

inline void muunna_jono(const char *s,int &i)
{
  i = jono_intiksi(s,"%d");
}

inline void muunna_jono(const char *s,double &d)
{
  d = jono_doubleksi(s,"%lf");
}

Seuraavaksi herääkin kysymys:  Kannattaako ota_seuraava- funktiota kirjoittaa erikseen useita eri versioita yhden sanan muutoksella.  Kyllähän näitä leikkaa- liimaa - menetelmällä syntyy kuin sieniä sateella, mutta entäpä ylläpito, jos yleiseen kaavaan tuleekin pieni muutos.  Kokemus osoittaa että jostakin "samanlaisesta" funktiosta muutos unohtuu ja virhe paljastuu vasta aikojen kuluttua!
Onneksi C++:ssa on apu tätä varten:  funktiomallit (function template).  Idea on siinä, että jokin yleinen funktio kirjoitetaan tyyppiä vaille valmiiksi ja kääntäjä generoi sitten tästä mallista (muotista) tarvittavan määrän vastaavia todellisia funktioita niille tyypeille, joilla funktiota kutsutaan:
template <class TYPE>
int ota_seuraava(istream &is, TYPE &a,  char erotin)
{
  char s[100];
  int err = ota_seuraava(is,s,sizeof(s),erotin);
  muunna_jono(s,a);
  return err;
}

Jos nyt on esimerkiksi kutsu:
int i;
ota_seuraava(is,i,'|');

generoituu tästä funktio jossa TYPE on korvattu tyypillä int:
int ota_seuraava(istream &is, int &a,  char erotin);

Nyt joudumme kirjoittamaan vaan muunna_jono- funktion kutakin eri tietotyyppiä varten.  Tämä ei ole kohtuuton vaatimus, sillä kullekin tietotyypille olisi hyvä olla tapa muuntaa se merkkijonoksi (meillä jonoksi) ja päinvastoin.
Siis jos lisätään uusi tietotyyppi, kirjoitetaan funktiot:
void muunna_jono(const char *s,uusi_tyyppi &u);
string jonoksi(const uusi_tyyppi &u);

19.4	Virheilmoitukset, static 
Talletukseen liittyvät aliohjelmat on suunniteltu const char * - tyyppisiksi.  Mikäli jokin menee pieleen, palauttavat ne nimessään osoittimen itse virheilmoitukseen, jolloin kutsunut ohjelma voi halutessaan tulostaa virheilmoituksen.
Mikäli aliohjelma päättyy onnellisesti, voidaan palauttaa NULL- osoitin, eli ei virheilmoitusta.
jasenet.cpp:
static const char *TIED_EI_AUKEA  = "Tiedosto ei aukea!";
...
  if ( !f ) return TIED_EI_AUKEA;
  ...
  return NULL;
...
  if ( (virhe=talleta_kerho(&kerho))!=NULL ) {
  printf("%s",virhe);

naytto.cpp:
  const char *virhe;
  virhe = kerho->lue_tiedostosta(tied);
  if ( virhe ) {
    cout << virhe << endl;
    return 1;
  }
  return 0;

Tässä static tarkoittaa, että muuttuja on vain tämän tiedoston (jasenet.cpp) sisäinen eikä näin ollen nimenä näy tiedoston ulkopuolelle.  Kuitenkin muuttuja säilyttää  arvonsa koko ohjelman suorituksen ajan, joten ulospäin voimme välittää osoitteita tällaisiin muuttujiin, ja näin jokin toinenkin ohjelman osa pääsee niihin käsiksi (tosin tässä ei ole suotavaa, että joku niitä muuttaisi, siis osoitteet on käsitettävä "read only").  Tähän käyttöön merkkijonot olisi voitu esitellä myös aliohjelman sisäisinä staticeina.
static- muuttujat varataan eri alueesta kuin aliohjelman lokaalit (automaattiset) muuttujat.  Lokaalit muuttujat otetaan yleensä ajonaikana pinosta, joka saattaa myös loppua.  Näin ollen ei- rekursiivisissa aliohjelmissa usein lokaaleillekin isoille muuttujille annetaan static- määritys.
C- kielen static - sanalla on siis kaksi merkitystä, ja sen tilalla pitäisikin olla oikeastaan kaksi eri sanaa: PRIVATE ja SAVE.  Joku saattaisikin määritellä 
#define PRIVATE static
#define SAVE    static

Tehtävä 19.166	static
Mikä oli static - sanan toinen (SAVE) merkitys?
19.4.1	Aliohjelma käsittelemään virheilmoitus
Jos usein tarvitaan käsittelyä
  virhe = joku_jomma_josta_mahdollisesti_virheilmoitus_tai_NULL(...)
  if ( virhe ) {
    cout << virhe << endl;
    return 1;
  }
  return 0;

voidaan tehdä ehkä mieluummin aliohjelma ilmoitus, jota voidaan käyttää:
return ilmoitus(kerho->lue_tiedostosta(tied));

Eli aliohjelma tulostaa mahdollisen virheilmoituksen ja palauttaa 0 jos virheilmoitusta ei ollut ja 1 jos virheilmoitus oli.  Lyhentää kirjoittamista kivasti, joten virhekäsittely tulee helpommin tehtyä.
19.4.2	Makro virhekäsittelyn avuksi
Toisaalta usein voi tulla tilanne, jossa aliohjelman jatkaminen on kiinni siitä, tuleeko jostakin apualiohjelmasta virhe vai ei:
...
const char *virhe;
virhe = luo_taulukko(max_koko);
if ( virhe ) return virhe;
...

Tätä käsittelyä varten ei voida helpolla tehdä aliohjelmaa, mutta voidaan tehdä kyllä makro, jota voitaisiin kutsua:
IF_ERR_RETURN(luo_taulukko(max_koko));

Makron toteutus olisi vaikkapa seuraavanlainen:
#define IF_ERR_RETURN(v) { const char *virhe=(v); if ( virhe ) return virhe; }

Tehtävä 19.167	IF_ERR_RETURN 
Pura auki makrokutsu IF_ERR_RETURN(luo_taulukko(max_koko));

19.5	Muunnokset numeerinen <- > merkkitieto
Numeeristen kenttien käsittelyä varten pitää voida muuttaa numeerista tietoa merkkijonoksi ja päinvastoin:
1.25 <- - > "1.25"

Muunnoksia varten on valmiina mm. funktiota
atoi
atof
sscanf
sprintf

Usein nämä funktiot kelpaavatkin sellaisenaan.  Meidän tarkoituksessamme tarvitsemme myös tyhjän arvon; kentän johon ei ole vielä syötetty arvoa.  Numerona tyhjää ei sellaisenaan voi esittää, joten valitsemme vaikkapa - 1:en esittämään tyhjää arvoa (koska emme tarvitse negatiivisia lukuja).  Kuitenkin tulosteissa - 1 näyttäisi hassulta ja sitä pitäisi selitellä käyttäjille.  Siis on helpompi tulostaa tyhjä arvo - 1:en tilalle.
Näitä muunnoksi varten kirjoitamme avuksi aliohjelmat, joita voidaan kutsua esim: 
talletus.2\muunnos.c - luvut <-> merkkijonot
int_jonoksi (N_S(st),i,"%d");
double_jonoksi (N_S(st),d,"%4.2lf");
i = jono_intiksi (st,"%d");
d = jono_doubleksi (st,"%lf");

Tehtävä 19.168	int_jonoksi, jono_doubleksi
Kirjoita alkeelliset versiot yllä mainituista funktioista, siten että  -1 <=> "".

19.5.1	Varoitus
Vaikka jonoksi muuttavat funktiot onkin kirjoitettu siten, että ne palauttavat nimessään osoittimen muunnettuun jonoon, on esimerkiksi seuraavan kaltaisten kutsujen kanssa oltava tarkkana:
char st[80];
...
printf("%s %s",double_jonoksi(N_S(st),d1,"%4.2lf"),
               double_jonoksi(N_S(st),d2,"%4.2lf"));

Tehtävä 19.169	Vaarallinen ??_jonoksi
Miksi edellä mainittu kutsu on vaarallinen?

19.6	Talletuksen testaus
Testaamme ohjelmaamme lisättynä tiedoston lukemisella ja talletuksella.  Aivan aluksi ohjelmaa ajetaan, lisätään muutama henkilö ja lopetetaan ohjelma.  Katsotaan tekstieditorilla onko syntynyt tiedosto sellainen kuin sen pitäisi.  Vasta kun on siirrytään testeissä eteenpäin.
Seuraavana kannattaa ehkä käyttää syöttönä tekstieditorilla tehtyä tiedostoa. Mikäli (aikanaan) kaikki menee halutulla tavalla, voidaan lisätä edellä huomatut puutteet muotoilussa ja kommenttien kopioinnissa ja/tai siirtyä eteenpäin suunnittelemaan päätesyöttöä.

20.	Päätesyöttö
Mitä tässä luvussa käsitellään?
•	päätesyöttö yksinkertaisimmalla tavalla
•	näyttö tietämättömäksi jäsenen ominaisuuksista
•	"indeksoidut" kentät

Oikeassa ohjelmassa päätesyöttö hoidetaan usein erilaisilta lomakepohjilta.  Tällaisen ohjelman tekeminen ensimmäisenä ohjelmana on kuitenkin varsin työlästä.  Siispä teemme aluksi suunnitelman mukaisen yksinkertaisen syötön, jossa ainoa korjailumahdollisuus on oletusarvon käyttö.  Näppäinten painallusten lukumäärää laskettaessa nyt toteutettava syöttö on aivan yhtä hyvä kuin lomakepohjainen syöttö. Jos pystymme pitämään kaiken laiteriippuvuuden cNaytto- luokassa, on ohjelma kohtuullisella työllä muutettavissa myös lomakepohjaisesti toimivaksi esimerkiksi Windows- käyttöliittymään.
20.1	Lukeminen ilman tarkistuksia 
Aluksi kannattaa oikeellisuustarkistukset unohtaa ja yrittää saada edes jotakin luettua.
20.1.1	lisaa_uusi_jasen (naytto.cpp) 
Ohjelman edellinen versio osasi lisätä vain aina vakiojäsenen "Ankka Aku".  Muutamme lisaa_uusi_jasen aliohjelmaa siten, että vakiojäsenen tilalla kysytäänkin jäsenen tiedot:
lukemine.3\naytto.cpp - päätesyöttö
//----------------------------------------------------------------------------
void cNaytto::lisaa_uusi_jasen(char valinta) 
/*
** Kysyllään uusien jäsenien tietoja ja lisätään jäsenet.
----------------------------------------------------------------------------*/
{
  cJasen jasen;

  otsikko(valinta,"Uuden jäsenen lisäys");

  while ( 1 ) {
    jasen.tyhjenna_omat();
    jasen.rekisteroi();
    cout << endl;
    cout << "Jäseniä on nyt " << kerho->Jasenia() << "." << endl;
    cout << "Anna uusi nimi muodossa sukunimi etunimi"; // ei endl

    int ei_lisata = 1;
    do {
      cout << endl;                        // Jotta uudella kier. rivi vaiht
      if ( kysy_tiedot(jasen) ) return;

      cout << "Lisätäänkö" << endl;
      tulosta(cout,jasen);
      cout << ":";
      if ( kylla_vastaus() )
        ei_lisata = ilmoitus(kerho->Jasenet().lisaa(jasen));
      if ( ei_lisata ) kerho->poista(jasen.Tunnus_nro());
    } while ( ei_lisata );
  }
}

20.1.2	kysy_tiedot (naytto.cpp)
Varsinainen tietojen kysyminen on jätetty aliohjelmalle kysy_tiedot.  Yksinkertaisimmillaan tämä olisi:
int cNaytto::kysy_tiedot(cJasen &jasen)
{
  cJasen apujasen(jasen);
  char jono[50];

  cout << "Jäsenen nimi  >"; getline(cin,apujasen.nimi,'\n');
  if ( apujasen.nimi == "" ) return 1;
  cout << "Hetu          >"; getline(cin,apujasen.hetu,'\n');
...
  cout << "Jäsenmaksu mk >"; cin.getline(N_S(jono),'\n');
  apujasen.jmaksu = jono_doubleksi(jono,"%lf");
...
  jasen = apujasen;
  return 0;
}

Tässä on vielä vähän vikoja.  Ensinnäkin syötöstä ei voi poistua minkä tahansa kentän antamisen jälkeen.  Tämä voidaan korjata esimerkiksi lisäämällä merkin "q" käsittely jokaisen lukemisen jälkeen:
  cout << "Jäsenen nimi  >"; getline(cin,apujasen.nimi,'\n');
  if ( apujasen.nimi == "" ) return 1;
  if ( apujasen.nimi == "q" ) return 1;
  cout << "Hetu          >"; getline(cin,apujasen.hetu,'\n');
  if ( apujasen.hetu == "q" ) return 1;
...
  cout << "Jäsenmaksu mk >"; cin.getline(N_S(jono),'\n');
  if ( strcmp(jono,"q") == 0 ) return 1;
  apujasen.jmaksu = jono_doubleksi(jono,"%lf");
...

Vieläkään ei ole ehdotettu oletusarvoa kentälle syöttöä varten.   Lisäyksessähän tämä ei niin haittaa, mutta jos samaa aliohjelmaa haluttaisiin käyttää korjailussa, olisi oletusarvot toivottavia.
Toimiakseen se, että näyttö voi viitata suoraan jäseneen vaatii näytön jäsenen ystäväksi.  No tämähän ei ollut kovin toivottava ominaisuus.  Lisäksi olisi toivottavaa muutenkin, ettei näyttö tietäisi näin paljoa jäsenestä.  Nyt nimittäin jos jäseneen lisätään yksikin kenttä, pitää muutoksia tehdä monessa paikassa (jo turhan monessa ilman näytönkin muuttamista).
20.2	Poistetaan riippuvuus näytön ja jäsenen väliltä
20.2.1	Algoritmi
Lisäämällä byrokratiaa näytön ja jäsenen välille, voidaan näyttö pitää tietämättömänä siitä, mitä kenttiä jäsenessä todella on.  Minkälaista byrokratiaa?  Esimerkiksi "keskustelu" näytön ja jäsenen välillä voisi olla seuraavanlainen:
1.	Näyttö:  montako kenttää sinulla on jäsen?
2.	Jäsen:   13 kenttää
3.	Näyttö:  no annappa minulle 1. kenttä merkkijonona!
4.	Jäsen:   Ankka Aku
5.	Näyttö:  Milläs kysymyksellä tämä kenttä kysytään?
6.	Jäsen:   Jäsenen nimi
7.	Näyttö kysyy käytäjältä Jäsenen nimi (Ankka Aku) >  => jono
8.	Näyttö tutkii vastattiinko q tms. erikoismerkki, jos niin pois
9.	Näyttö:  Sijoitapa jäsen tämä jono 1. kentäksi.
10.	Näyttö jatkaa kohdasta 3 mutta kentälle 2 kunnes kaikki 13 kenttää käsitelty 

20.2.2	kysy_tiedot 
Kirjoitetaanpa edellinen algoritmi C++:lla:
lukemine.3\naytto.cpp - kysy_tiedot 
int cNaytto::kysy_tiedot(cJasen &jasen)
{
  cJasen apujasen(jasen);
  string jono;
  int k,kenttia = apujasen.kenttia(), eka = apujasen.eka_kysymys();

  for (k=eka; k<kenttia; k++) {
    jono = apujasen.kentta_jonoksi(k);
    if ( kysy_kentta(apujasen.kysymys(k),jono) ) return 1;
    if ( k == eka && jono == "" ) return 1; /* 1. kys pääsee pois pelk. ret *
    apujasen.sijoita(k,jono);
  }
  jasen = apujasen;
  return 0;
}

20.2.3	kysy_kentta 
"Likainen työ" jätettiinkin aliohjelmalle kysy_kentta, joka ei juurikaan ole sidoksissa kerho- ohjelmaan:
lukemine.3\naytto.cpp - kysy_kentta 
/****************************************************************************
static int                /*                                                *
kysy_kentta(              /*                                                *
  const char *viesti     ,/* s   Viesti joka tulee näytölle                 *
  string &jono            /* t   Jono johon kentän vastaus luetaan.         *
) 
/*
** Funktiolla luetaan vastaus kenttään.
**
** Globaalit: POIS (jono, jolla syöttö katkaistaan)
** Syöttö:    päätteeltä
** Tulostus:  näyttöön
** Kutsuu:    lue_jono_oletus
----------------------------------------------------------------------------*
{
  char apu[80]; int paluu;

  kopioi_jono(N_S(apu),jono.c_str());

  paluu = lue_jono_oletus(viesti,OLET_ALKU,VAKANEN,apu,N_S(apu));

  if ( paluu < OLETUS ) return 1;
  if ( strcmp(apu,POIS) == 0 ) return 1;

  poista_tyhjat(apu);
  jono = apu;

  return 0;
}

Tulosta ei lueta suoraan muuttujaan jono, jotta mahdollisessa q - vastauksessa ei pilattaisi kentän alkuperäistä arvoa.
Tehtävä 20.170	lisaa_uusi_jasen - kutsut
Piirrä puumainen kuva siitä, mitä aliohjelmia/metodeja  metodista lisaa_uusi_jasen alkaen kutsutaan.
20.3	Muutokset jäsen- luokaan
Edellä näytti siltä, että onnistuimme muuttamaan näyttö- luokkaa siten, että uuden kentän lisääminen tai kentän poistaminen ei vaadi muutoksia luokaan cNaytto.  Tämä on tietenkin jo askel kohti oikeata ohjelmointia.
20.3.1	kenttia ja eka_kysymys 
Koska  näyttö keskustelee jäsenen kanssa, pitää tietysti toteuttaa myös ne metodit, joilla näyttö (tai kuka tahansa) voi kysellä kriittisiä tietoja jäseneltä:
lukemine.3\jasen.cpp - kenttia, eka_kysymys 
class cJasen {
...
  int kenttia() const                          { return 13;                  }
  int eka_kysymys() const                      { return  1;                  }
...
};

20.3.2	kentta_jonoksi
Jäsenen tietyn kentän pääsemme muuttamaan merkkijonoksi käyttäen talletuksen yhteydessä tehtyä polymorfista jonoksi- funktiota:
lukemine.3\jasen.cpp - kentta_jonoksi
const string cJasen::kentta_jonoksi(int k) const
{
  switch ( k ) {
    case  0: return jonoksi(tunnus_nro);
    case  1: return jonoksi(nimi);
    case  2: return jonoksi(hetu);
    case  3: return jonoksi(katuosoite);
...
    case 10: return jonoksi(jmaksu);
    case 11: return jonoksi(maksu);
    case 12: return jonoksi(lisatietoja);
    default: return string("VIRHE");
  }
}

20.3.3	kysymys() 
Näytön pitää päästä myös selvittämään mikä teksti näyttöön pitää laittaa tietyn kentän kysymiseksi käyttäjältä:
lukemine.3\jasen.cpp - kysymys
const char *cJasen::kysymys(int k) const
{
  switch ( k ) {
    case  0: return "Tunnus nro";
    case  1: return "Jäsenen nimi";
    case  2: return "Hetu";
    case  3: return "Katuosoite";
...
    case 10: return "Jäsenmaksu mk";
    case 11: return "Maksettu maksu mk";
    case 12: return "Lisätietoja";
    default: return "VIRHE!";
  }
}

20.3.4	sijoita 
Kun näyttö on merkkijonon kysynyt, pitäisi merkkijonon muuttunut arvo saattaa jäsenen tietoon:
lukemine.3\jasen.cpp - sijoita
int cJasen::sijoita(int k,const string &st)
{
  switch ( k ) {
    case  0: muunna_jono(st,&tunnus_nro);      return 0;
    case  1: muunna_jono(st,&nimi);            return 0;
    case  2: muunna_jono(st,&hetu);            return 0;
    case  3: muunna_jono(st,&katuosoite);      return 0;
...
    case  9: muunna_jono(st,&liittymisvuosi);  return 0;
    case 10: muunna_jono(st,&jmaksu);          return 0;
    case 11: muunna_jono(st,&maksu);           return 0;
    case 12: muunna_jono(st,&lisatietoja);     return 0;
    default:                                   return 1;
  }

Tehtävä 20.171	Montako muutosta
Jos jäseneen lisätään vaikkapa kenttä Jasenmaksu-97 - kenttä, niin kuinka monta muutosta ja mihin tarvitsee tehdä (yksi muutos on aina yksi muutettu/lisätty/poistettu rivi).

20.3.5	Arvostelu valinnoista
Edellä tehdyillä valinnoilla ohjelmasta saadaan toimiva tiedon pääteeltä lukua myöten.  Kuitenkin kuten tehtävässäkin todettiin, tulee muutoksi paljon, jos jäsenen attribuuttien määrä muuttuu.
Itse asiassa sijoita ja kentta_jonoksi sekä kysymys() ovat riittävä määrä jäsenen kysymysten toteuttamiseksi.  Näitä metodeja käyttäen voimme samalla muuttaa ison osan aikaisemmin tehtyjä "itseään toistavia" jäsenen metodeja ja muita aliohjelmia silmukoiksi, mm:
ostream &operator<<(ostream &os,const cJasen &jasen)
{
  int k,kenttia=jasen.kenttia();

  for (k=0; k<kenttia; k++)
    os << jasen.kentta_jonoksi(k) << jasen.erotin;

  return os;
}

istream &operator>>(istream &is,cJasen &jasen)
{
  int k,kenttia=jasen.kenttia();

  for (k=0; k<kenttia; k++) {
    jasen[k].ota_seuraava(is,jasen.erottimet);
  }
  if ( jasen.tunnus_nro >= jasen.seuraava_nro )
    jasen.seuraava_nro = jasen.tunnus_nro + 1;

  tyhjenna(is);  // ohittaa loppurivin
  return is;
}
20.4	Testaus 
Jälleen testataan ohjelman toimivuutta.  Mikäli virheitä havaitaan, korjataan ne.  Muuten voidaan jatkaa seuraavaan vaiheeseen joka voisi olla joko oikeellisuustarkistusten lisäys tai tietojen etsimisen lisäys.

21.	Oikeellisuustarkistukset ja avustukset
Mitä tässä luvussa käsitellään?
•	yksinkertainen tarkistus
•	muita vaihtoehtoja tarkistusten tekemiseen
•	funktio- osoittimet
21.1	Miksi tarkistukset
Oikeassa ohjelmassa on todella tärkeää, että syöttötietojen oikeellisuus tarkistetaan.  Aina on lähdettävä siitä oletuksesta, että käyttäjät ovat täysiä idiootteja (älkää kertoko tätä mahdollisille käyttäjille).  Toisaalta idioottitarkistuksia ei saa olla liikaa.  Esimerkiksi "Oletko varma" - tyyliset kysymykset rupeavat ennen pitkää ärsyttämään kokenutta käyttäjää.
Hyvä ohjelma olisi muutettavissa käyttäjän mukaan.  Aloittelijalle enemmän ohjeita ja varmistuskysymyksiä ja kokenut käyttäjä saa itse vastata tekosistaan.  
Eri kielten toiminta eri tietotyyppejä luettaessa vaihtelee. Pascal - ohjelma kaatuu mikäli numeeriseen tietoon vastataan merkkitietoa.  Mikä olisikaan harmillisempaa kuin se, että sihteeri on naputellut tietoja koko päivän (EIKÄ OLE TALLETTANUT NIITÄ VÄLILLÄ!???) ja iltapäivän väsymyksessä vastaa kysymykseen 
Jäsenmaksu>kymppi

ja koko ohjelman suoritus loppuu!
C- kieli on huomattavasti siedettävämpi numeerisen tiedon luvussa. scanf ei vain muuta merkkitietoa numeeriseksi ja näin tavallisen scanf- funktion käyttö on aivan suotavaa numeerisen tiedon lukemiseen.  Rivin lukematta jäänyt (mahdollisesti virheellinen) osa on sitten syytä poistaa (fflush).
C- kielen huono puoli on siinä, että vastaavasti merkkijonojen lukeminen standardifunktioilla on vaarallista.  Näiden lukemiseen pitääkin lähes poikkeuksetta tehdä oma aliohjelma (tai käyttää sopivaa kirjastoa).  Aina on ihmisiä, jotka sanovat: "Laita jonon pituudeksi 200, niin ei kukaan jaksa kirjoittaa niin pitkää vastausta!".  Näinhän se on, mutta monestiko itselläsi on manuaali tai jokin muu esine jäänyt nojaamaan näppäimistöön ja näin auto- repeat - toiminto työntää määrättömästi merkkejä?
Todellisissa ohjelmissa voidaan näppäimistön lukua tehdä merkki kerrallaan aliohjelmilla, joille on tarkkaan kerrottu syötön muoto ja sen aikana sallitut näppäimet.  Tällöin mahdollisista virhepainalluksista voidaan piipata tai niihin voidaan reagoida muuten.  Samoin kentälle varatun koon ylittyessä voidaan heti reagoida.  Tämä kuitenkin vaatii puskuroimatonta syötön käsittelyä (ohjelmien siirrettävyys kärsii) ja hieman lisää vaivaa.
21.2	Lukeminen rivi kerrallaan
Eräs suhteellisen vaivaton tapa käsitellä syöttöä on lukea syöttö aina merkkijonona.  Tämän jälkeen voidaan syöttörivi käsitellä palanen (tai strtok) tyyppisillä aliohjelmilla tai C++:n merkkijonotietovirralla osa kerrallaan.  Kustakin palasesta sitten otetaan tarvittava syöttö esimerkiksi C- kielen sscanf- funktiolla, joka toimii aivan scanf- funktiota vastaavasti:
jono <= "52 mk"
if ( sscanf(jono,"%d",&hinta)<1 ) /* virhe.... */;

21.3	Tarkistusaliohjelmat
Kutakin ohjelmassa esiintyvää tietotyyppiä kohden voidaan kirjoittaa tarkistusfunktiot, jotka tarkistavat parametrina tuodun tiedon oikeellisuuden ja sitten silmukassa luetaan tietoa kunnes ko. funktio hyväksyy tiedon:
string hetu;
...
do {
  printf("Sosiaaliturvatunnus>");
  getline(cin,hetu,'\n');
} while ( tarkista_hetu(hetu) )
...
Henkilötunnus>1234[RET]
Hetu väärin! Anna uudelleen!
Henkilötunnus>020347- 123T[RET]
... 

Tässä tapauksessa aliohjelma voi myös tulostaa virheilmoituksen, jolloin kutsuvan ohjelman ainoa tehtävä on lukea kunnes tulee oikea vastaus.
21.4	Kerhorekisterin tarkistukset?
Entä kerhon jäsenrekisteri.  Ohjelma on jo kirjoitettu hyvin pitkälle, eikä tarkistuksiin ole puututtu juuri lainkaan.  Merkkijonoina lukemisen ansiosta ohjelmaa ei voida kaataa väärillä syöttötiedoilla, mutta rekisterin kannalta virheellisiä tietoja, kuten samoja nimiä ja sotuja, voi esiintyä useita, numeerisiksi tarkoitetuissa kentissä voi olla kirjaimia jne.   Osa ominaisuuksista voi olla toivottujakin.
Mutta entäpä jos tiettyjä tarkistuksia haluttaisiin kuitenkin tehdä.  Pitääkö koko ohjelma kirjoittaa uusiksi? 
Ei!  Kriittinen on tietenkin vain kysy_tiedot.  Mikäli aliohjelma on toteutettu ilman silmukkaa, kysymällä kukin tieto omalla lauseellaan, niin siitä sitten vaan lisäämään tarkistus kunkin lukemisen ympärille.  Tämä on aivan hyvä ratkaisu, mikäli kenttiä on vähän tai kentät ovat eri tietotyyppiä.  
int cNaytto::kysy_tiedot(cJasen &jasen)
{
. . .
  do {
    cout << "Hetu          >"; getline(cin,apujasen.hetu,'\n');
    poista_tyhjat(apujasen.hetu);
    if ( apujasen.hetu == "q" ) return 1;
  } while ( tarkista_hetu(apujasen.hetu) );
. . .
 do {
   cout << "Jäsenmaksu mk >"; cin.getline(N_S(jono),'\n');
   if ( strcmp(jono,"q") == 0 ) return 1;
   apujasen.jmaksu = jono_doubleksi(jono,"%lf");
 } while ( (apujasen.jmaksu < 0) || ( 500 < apujasen.jmaksu ) );

Voiko tarkista_hetu edes selvitä tarkistuksista?  Osasta voi, mutta entäpä jos esimerkiksi hetu pitää olla erisuuri kullekin jäsenelle.  Aliohjelma ei tiedä mihin hetuja verrattaisiin!  Siis aliohjelmalle pitäisi parametrina viedä tietysti myös käsiteltävä kerho tai ainakin jäsenistö.
21.4.1	grep 
Näin käy useasti!  Myöhemmin huomataan jonkin aliohjelman vaativan lisää parametreja ja niitä joudutaan jälkeenpäin lisäämään.  Lisätään parametri!   Samalla metodille kysy_tiedot kannattaa välittää tieto siitä, onko kyseessä lisäys vaiko päivitys (korjailu).
Entä kuka kutsui aliohjelmaa.  Jokaiseen vastaavaan paikkaan täytyy myös tietysti lisätä kutsuparametri.
Ohjelmointiympäristöjen mukana tulee usein apuohjelma nimeltä grep.  Ohjelmalla voidaan etsiä sanoja (tai tiettyä hakuehtoa) valitusta joukosta tiedostoja.  Esimerkiksi
E:\KURSSIT\CPP\KERHO\TARKISTU.4>grep - n+ kysy_tiedot *.cpp *.h
File NAYTTO.CPP:
359     int cNaytto::kysy_tiedot(cJasen &jasen)
426           if ( kysy_tiedot(jasen) != 0 ) return;
File NAYTTO.H:
32        int  kysy_tiedot(cJasen &jasen);
E:\KURSSIT\CPP\KERHO\TARKISTU.4>

Kun saamme listan kaikista esiintymistä, muutetaan tarvittavat kohdat.  Päivitetään myös kommentoinnin muutos- osaan, että metodin parametrien määrä on muuttunut, jottei joku muu saman kirjaston käyttäjä  sitten ihmettele liian kauan sitä, miksi aliohjelma ei enää  toimi.  Voidaan myös kirjoittaa aivan uusi aliohjelma eri nimelle (esim. kysy_ja_tark_tiedot).  
21.5	Sijoittaminen tarkistaa
Jos tietojen kysyminen hoidetaan silmukassa, voitaisiin tarkistus jättää sijoittamisen huoleksi.  Eli jos sijoittaminen ei jäsenen mielestä onnistu, palautetaan tästä tieto.  Paluutietoja voisi olla useita, eli osa olisi varoitusluonteisia ja osa tiedon uudelleen kysymistä vaativia.  Osa taas vaatii lisätarkistuksia.  Esimerkiksi hetu.  Jäsenhän ei tiedä mitä muita jäseniä on, joten se ei myöskään voi tarkistaa hetusta muuta kuin muodollisen oikeellisuuden.  
cNaytto::kysy_kentta(int k,cJasen &jasen)
. . . 
  virhe = jasen.sijoita(k,jono);

  switch ( virhe ) {
    case KENTTA_OK:
      return TOIM_SEURAAVA;

    case KENTTA_VAROITUS:
      cout << virhe.Virhe() << endl;
      return TOIM_SEURAAVA;

    case KENTTA_UUDELLEEN:
    case KENTTA_MUUTETTU_KYSY:
      cout << "Tarkista ja anna tieto uudelleen" << endl;
      return TOIM_KYSY_UUDELLEEN;

    case KENTTA_OK_ONKO_AINOA:
      lkm = kerho- >Jasenet().laske_montako_muuta(jasen,k,kuka);
      if ( lkm == 0 ) return TOIM_SEURAAVA;
      kerho- >Jasenet().anna(kuka).tulosta(cout);
      cout << "On jo ennestään!" << endl;
      jasen.sijoita(k,edell);
      return TOIM_KYSY_UUDELLEEN;

    case KENTTA_OK_VAROITA_MUUT:
      lkm = kerho- >Jasenet().laske_montako_muuta(jasen,k,kuka);
      if ( lkm == 0 ) return TOIM_SEURAAVA;
      kerho- >Jasenet().anna(kuka).tulosta(cout);
      if ( kylla_kysymys("On jo, lisätäänkö silti uusi!") )
        return TOIM_SEURAAVA;
      jasen.sijoita(k,edell);
      return TOIM_KYSY_UUDELLEEN;

    default: ;
  } // virhe
. . .  
int cNaytto::kysy_tiedot(cJasen &jasen)
{
. . . kaille kentille
  do {
     virhe = kysy_kentta(int k,cJasen &jasen)
  } while ( virhe != TOIM_SEURAAVA );

21.5.1	sijoita 
Yksinkertaisessa versiossa cJasen voi tarkistaa tiedon järkevyyden sijoituksen yhteydessä.
int cJasen::sijoita(int k,const string &st)
{
  switch ( k ) {
    case  0: … return KENTTA_OK;
…
    case  2: // Hetu
      if ( tarkista_hetu(st) ) return KENTTA_UUDELLEEN;
      hetu = st;
      return KENTTA_OK_ONKO_AINOA;
…
    case  9: 
      if ( sscanf(st.c_str(),"%d"&liittymisvuosi) != 1 ) return KENTTA_UUDELLEEN;
      if ( liittymisvuosi < 1950 ) return KENTTA_UUDELLEEN;
      if ( liittymisvuosi > 1998 ) return KENTTA_UUDELLEEN;
      return KENTTA_OK;
…
    default:  return KENTTA_OK; // Vääriä kenttiä ei sijoiteta mutta ne kelp.
  }
}

21.6	Funktio- osoitin 
Usein esimerkiksi matemaattisissa tehtävissä tulee vastaan tilanne, missä jollekin funktiolle tehty aliohjelma kelpaisi tekemään saman homman myös jollekin toiselle funktiolle, mikäli funktio pystyttäisiin vaihtamaan.  Tyypillisiä tällaisia esimerkkejä ovat numeerinen derivointi ja integrointi, nollakohdan hakeminen, kuvaajan piirtäminen jne.
Esimerkiksi funktion integrointi suorakaidesäännöllä voitaisiin hoitaa seuraavasti:
integroi.c -  esimerkki numeerisesta integroinnista
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

double integroi(double x1, double x2, int tiheys)
{
  double x,dx,summa=0;

  dx = (x2- x1)/tiheys;

  for (x=x1+dx/2 ; x<x2; x+=dx) 
    summa += sin(x)*dx;

  return summa;
}

int main(void)
{
  printf("Integraali sin(x) väliltä [0,pi] on noin %7.5lf\n",
          integroi(0,M_PI,100));
  return 0;
}

Entäpä mikäli haluaisimme integroida vaikka ex.  No vaihdetaan sin(x) tilalle exp(x)!  Entäpä jos tarvitsemme samassa ohjelmassa sekä sin(x) että exp(x) integraalit?  Kirjoitammeko integroi_sin ja integroi_exp.  Ei kuulosta järkevältä!
Otamme käyttöön funktio- osoittimet.  Määritellään aluksi funktio_tyyppi
typedef double (*funktio_tyyppi)(double x); 

Sitten voimme esitellä tarpeellisen määrän vastaavaa tyyppiä olevia funktiota.  Itse integrointi muutetaan käyttämään yhtä "ylimääräistä"  parametria; funktiota jota integroidaan:
integro2.c -  esimerkki funktiosta parametrina
double oma_funktio(double x)
{
  return 2*x- 5;
}

double integroi(funktio_tyyppi f,double x1, double x2, int tiheys)
{
  double x,dx,summa=0;

  dx = (x2- x1)/tiheys;

  for (x=x1+dx/2 ; x<x2; x+=dx) 
    summa += f(x)*dx;

  return summa;
}

Nyt voimme kutsua esimerkiksi:
double ifx;
...
ifx = integroi(oma_funktio,0,5,100);
...
ifx = integroi(sin,0,M_PI,1000);
...
ifx = integroi(exp,0,1,500);
... 

Huomautus! Hienot adaptiiviset integrointimenetelmät muuttavat itse välin tiheyttä funktion käyttäytymisen mukaan.
Tehtävä 21.172	Minimointi
Kirjoita funktioaliohjelma min_fun, jolla voidaan etsiä parametrina annetun funktion minimi väliltä [x1,x2]. Väliä käydään läpi tarkkuudella dx.

Kirjoita aliohjelmat ja pääohjelma, jolla lasketaan funktioiden
  f(x) = x2 + 2 ja f(x) = sin2(x- 3)
minimit väliltä [- 2,3] tarkkuudella 0.01.
21.7	Tarkistus- funktio - osoitin
Joissakin tapauksissa tarkistus voitaisiin hoitaa myös siten, että jäseneltä kysytään minkälaisella funktiolla oikeellisuus tarkistetaan:
/* Funktio- osoitin tarkistusfunktioon:                                      */
typedef int (*Tarkistus_funktio)(Tarkistus_tyyppi *, string &);
. . .
int cNaytto::kysy_tiedot(cJasen &jasen)
{
  Tarkistus_funktio tark;
. . . kaikille kentille
  do {
    . . . kysy kenttä => jono
    tark = jasen.Tarkistus(k);
    virhe = 0;
    if ( tark != NULL ) virhe = tark(jono);
  } while ( virhe );
. . .
}

Edellä totesimme, että tarkista_hetu(kerho,jono) ei ehkä riitäkään.  Miksi?  Jos lisäämme uutta nimeä, niin asia on aivan oikein.  Mutta entäpä mikäli korjaamme sotua ja kirjoitamme vastaukseksi täsmälleen saman tekstin kuin alkuperäinenkin.  Eikö alkuperäinen hetu tällöin löydy?  Löytyypä hyvinkin.  Siis tarkista_hetu "hermostuu" ja tulostaa virheilmoituksen:  
Sotu esiintyy ennestään 

ja vaatii uutta syöttöä!
Siis parametrit eivät riitä!  Mitä pitää lisätä?  Tästä ajattelutavasta saattaa seurata loputon kasa uusia parametreja.  Kasataan kaikki tarkistusten tarvitsemat parametrit yhteen ainoaan tietueeseen Tarkistus_tyyppi ja kutsutaan tarkistusfunktioita muodossa:
t_sotu(tarkistus_tiedot,jono); 

Vastaavasti tietysti tarkistusfunktio-osoittimien kanssa
typedef int (*Tarkistus_funktio)(Tarkistus_tyyppi &, string &);
. . .
int cNaytto::kysy_tiedot(cJasen &jasen)
{
  Tarkistus_funktio tark;
  Tarkistus_tyyppi tiedot(…
. . .
    if ( tark != NULL ) virhe = tark(tiedot,jono);
. . .

21.7.1	kerhotar.cpp
Itse tarkistusfunktiot kirjoitetaan vaikkapa tiedostoon kerhotar.cpp
21.7.2	pvm.c 
Esimerkiksi päivämäärän tarkistukseen tarvittavat rutiinit ovat lähes valmiina aikaisemmin kirjoitetuissa aliohjelmissa.  Nämä voitaisiin kasata tiedostoiksi pvm.h ja pvm.c. 
21.7.3	Uusien tarkistusfunktioiden lisäys
Aluksi loogisen toimivuuden tarkistamiseksi riittää tietysti kirjoittaa vain aliohjelma tark_ok.  Muut edellä esitetyt voidaan lisätä myöhemmin vaikkapa yksi kerrallaan.
Kun uusi tarkistusfunktio kirjoitetaan, pitää nyt
•	kirjoittaa se tiedostoon kerhotar.cpp
•	osoite lisätä tarvittavaan kohtaan metodissa cJasen::Tarkistus(int k)
•	lisätä funktion prototyyppi tiedostoon kerhotar.h
21.8	Etsiminen
Nimi tai hetu piti tarkistaa siten, että samaa ei saa esiintyä, mutta toisaalta kohdalla olevan jäsenen tiedot eivät saa aiheuttaa virheilmoitusta.  Tämä voidaan hoitaa esimerkiksi seuraavalla aliohjelmalla:
int cJasenet::laske_montako_muuta(const cJasen &jasen, int k,int &kuka) const
// Lasketaan monnellako muulla kerholaisella on sama tieto kentässä k
// kuin jasenella.  Palautetaan muiden maara ja sijoitetaan muuttujaan
// kuka viimeinen sellainen jolla oli sama tieto
{
  int i,samoja=0;
  string kentta = jasen.kentta_jonoksi(k);
  for (i=0; i<lkm; i++) {
    if ( alkiot[i]- >kentta_jonoksi(k) == kentta &&
         alkiot[i]- >sama_rekisteri(jasen) == 0 ) {
      samoja++;
      kuka = i;
    }
  }
  return samoja;
}

Kohdalla olevan jäsenen tiedot voidaan välttää tarkistamalla ettei rekisterinumero ole sama (alkiot[i]- >sama_rekisteri(jasen)).
21.9	cKentta ja perintä
Malliohjelmassa tarkistukset on tehty hieman edellä kuvatulla tavalla, paitsi että jäsenen kentät eivät olekaan merkkijonoja tai reaalilukuja, vaan yleisestä cKentta luokasta perittyjä kenttä- luokkia, joista jokainen tietää itse miten ko. kenttä tulee käsitellä (yksikäsitteinen nimi, puhelinnumero, jossa vain numeroita jne…).  Nämä luokat hoitavat sitten itse merkkijonosijoitukset, tiedon ottamisen tietovirrasta, oikeellisuustarkistukset jne.
Tekniikan etuna on se, että ajan oloon kertyy kattava määrä erilaisia kenttä- luokkia ja seuraava ohjelma voidaan kasata vain valitsemalla mitä luokkia tarvitaan:
class cJasen {
  cIntKentta         tunnus_nro;
  cNimi1Kentta       nimi;
  cHetu1Kentta       hetu;
  cJono1isoksiKentta katuosoite;
  cPostinumeroKentta postinumero;
  cJonoIsoksiKentta  postiosoite;
  cPuhKentta         kotipuhelin;
  cPuhKentta         tyopuhelin;
  cPuhKentta         autopuhelin;
  cIntKentta         liittymisvuosi;
  cDoubleKentta      jmaksu;
  cDoubleKentta      maksu;
  cJonoKentta        lisatietoja;
…
21.10	Avustus
Avustusta tarvitaan kahdenlaista:  
1.	Yleistä avustusta, jossa mielellään voidaan selata ohjelman eri kohtien toimintaa.  Tämä on suhteellisen helppo toteuttaa yleisesti.
2.	Erityistä avustusta kutakin toimenpidettä kohti.  Tällaisen sisältöriippuvan avustuksen (context sensitive help) tekeminen vaatii ohjelmaan lisäyksiä mm. kunkin eri kentän lukurutiinin toimintoihin.
Kirjoitamme yleiskäyttöisen aliohjelmakirjaston help.c, jolla molemmat edellä mainitut ominaisuudet voidaan toteuttaa.  Koska käytännössä avustustietoutta on aina liian vähän ja siinä on kirjoitusvirheitä, pyrimme sijoittamaan avustuksen omaksi tiedostokseen, josta help.c- kirjaston on sitä helppo lukea.  Olkoon avustustiedoston muoto vaikkapa seuraava:
tarkistu.4\kerho.hlp - avustustiedosto
[?] 
  ? = Avustuksen avustus
  ======================
  ?- merkillä saa yleensä joka paikassa avustusta!
  Jos avustuksessa kysytään aihetta, josta avustusta halutaan,
  voidaan vastata esimerkiksi:
....
[Lisäys]
  Lisäys
  ======

  Lisäystoiminolla lisätään uusia henkilöitä.  Lisättävä henkilö
...
  Katso myös: Tietojen syöttö, Asetukset

[Tietojen syöttö]
  Tietojen syöttö
  ===============

  Tietoja syötettäessä näytössä näkyy suluissa arvo, joka tulee kentän
...
[t_sotu]#
  Sotuksi kelpaa...
...
[SISÄLLYS]
  Sisällysluettelo:
  =================
  ? 
  Lisäys
  Etsiminen
... 

Nyt esimerkiksi kutsulla 
help(NULL); 

päästäisiin avustuksen sisällysluetteloon, josta sitten käyttäjä voi tarvittaessa siirtyä haluamaansa kohtaan vaikka kirjoittamalla Tie*.
Sisältöriippuvassa avustuksessa kutsuttaisiin sitten suoraan haluttua kohtaa, esimerkiksi:
help("[Lisäys]"); 

Helpoimmin tämä kävisi esimerkiksi lisäämällä kysy_kentta - aliohjelmaan kutsu funktioon: char *avustus(int nro), jonka mukaan avustusta pyydettäisiin halutusta kentästä jos käyttäjä painaisi?.  
Toisaalta avustusta tarvitaan ehkä mieluumminkin kentän tyypin mukaan, ei niinkään itse kentän mukaan (koska "Jäsenen nimi>" tai "Sotu>" sinänsä ovat jo itse selittäviä).  Tällöin teemme tarkistusfunktion perusteella löytyvän avustuksen tarkistus_nimi(t_funk), joka palauttaa vastaavan nimen (esim. t_sotu =>"t_sotu").  Näin kysy_kentta voi funktion perusteella saada selville tarkistusfunktiota vastaavan nimen ja nimen perusteella voi kutsua avustusta.  Avustukseen on kirjoitettu valmis funktio help_aihe(nimi), joka lisää sulut []nimen ympärille ("t_sotu" =>"[t_sotu]").   
Toteutamme tämän vasta ohjelman viimeisessä versiossa.

22.	Etsiminen ja lajittelu
Mitä tässä luvussa käsitellään?
•	yksinkertainen etsiminen
•	etsiminen taulukkoon
•	lajittelu valmiilla qsort- aliohjelmalla
•	permutaatiotaulukko
•	"oikeaoppinen lajittelu"

Etsiminen ja lajittelu ovat eräitä yleisimmin tietojenkäsittelyssä vastaan tulevia tilanteita.  Esimerkiksi C- merkkijonon pituuden laskeminen on oikeastaan NUL- merkin paikan etsimistä.
HUOM!  Tämän luvun ohjelmalistaukset ovat "pseudo"- koodia, ja niissä voi olla pieniä syntaksivirheitä.
22.1	Etsiminen 
Etsimistä voidaan suorittaa monella tavalla.  Mikäli tietorakenne ei tarjoa parempaa vaihtoehtoa, ei ole muuta mahdollisuutta kuin peräkkäishaku.  Järjestetystä taulukosta pystyttiin hakemaan binäärihaulla.  Puumaisesta rakenteesta voidaan hakea puun ominaisuuksia käyttäen.  Joskus voidaan tehdä avuksi hakemistoja, jotka ohjaavat haun suurinpiirtein oikealle paikalleen, josta sitten jatketaan jollakin toisella hakumenetelmällä.
C- kielen kirjastosta löytyy funktio bsearch, joka etsii lajitellusta aineistosta binäärihaulla ja palauttaa NULL mikäli ei löydy ja muuten palauttaa osoittimen taulukon löytyneeseen alkioon.  Funktion käyttö on vastaava kuin myöhemmin esiteltävän qsort - funktion käyttö, joten emme tässä puutu siihen enempää.
Kerhorekisterissä tietorakenne on varsin alkeellinen, eikä sitä ole ainakaan toistaiseksi edes järjestetty.  Siis tietyn jäsenen etsiminen on suoritettava raakana peräkkäishakuna.
Tehtävä 22.173	etsi_nimi
Kirjoita metodi etsi_nimi, joka etsii jäsenistöstä ensimmäisen henkilön, jolla on TÄSMÄLLEEN parametrina annettu nimi.  Funktio palauttaa nimessään löytöpaikan indeksin ja - 1 jollei löydy.
Voitaisiinko metodia käyttää apuna lisäyksessä tarkistamaan onko jäsen jo ennestään tiedostossa?
Voitaisiinko metodia käyttää apuna korjaamisessa tarkistamaan onko nimi muuttunut sellaiseksi, joka on jo ennestään tiedostossa?

22.1.1	wildmat 
Jos etsimisessä halutaan sallia jokerimerkkien käyttö, saattaa etsiminen johtaa joka tapauksessa peräkkäishakuun.  Voitaisiin kuitenkin erotella erikoistapauksia:
Ankka Aku -  voitaisiin käyttää mitä tahansa järkevää
            järjestetyn joukon hakualgoritmia
Ankka*    -  voitaisiin etsiä järjestetystä joukosta 1. Ankka
            ja tämän jälkeen loppu peräkkäishakuna
*Aku*     -  vaikea keksiä muuta kuin peräkkäishaku

Lähdimme kuitenkin alunperin oletuksesta, että kerhon koko on pieni.  Siis tuskin peräkkäishakukaan vie kohtuuttomasti aikaa.  Näin ollen käsittelemme kaikki hakutilanteet samalla tavalla.
Entäpä mikäli etsimisessä löytyy useita ehdon täyttäviä henkilöitä.  Eräs tyypillinen tapa on tehdä aliohjelmapari:
etsi_ensimmainen
etsi_seuraava

Vähän samaa ideaa sovellettiin jo aikaisemmin palanen - aliohjelmassa.
Tehtävä 22.174	etsi_nimi_alkaen
Kirjoita metodi etsi_nimi_alkaen, joka etsii kerhosta nimeä aloittaen etsimisen parametrina välitetystä indeksistä.  Tällä kertaa hakuehtona saa olla myös jono "*Aku*".  Metodi palauttaa löytöpaikan indeksin tai - 1, mikäli etsittävää ei löydy.

22.1.2	Etsiminen taulukkoon
Toinen mahdollisuus etsimiseen on tallettaa taulukkoon kukin löytynyt ehdot täyttävä tietue.  Määrittelemme tyypin
jarjestys.h -  taulukko löytyneistä
class cJarjestys {   /* Luokka jonka avulla voidaan pitää eri järjestyksiä.  */
  int max_koko;                 /* Indeksitaulukon maksimikoko.              */
  int indekseja;                /* Taulukosta nyt käytetty indekseja.        */
  int *indeksit                 /* Osoitin indeksitaulukkoon (dynaaminen).   */
...
};  

Jos seuraavasta aineistosta etsittäisiin vaikkapa hakuehdolla nimi=="*k*"
                   **jasenet    │      o      │
cJarjestys                      └──────┼──────┘                cJasen
   etsi  ┌───┐                         │                     ┌──────────────┐   
max_koko │ 7 │                         v                     │Kassinen Katto│   
indekseja│ 2 │                    ┌──────────┐               │Katto         │   
*indeksit│ o │  ┌───>jasenet[0]   │    o─────┼──────────────>│3452          │   
         └─┼─┘  │                 ├──────────┤               │...           │    
           │    │    jasenet[1]   │    o─────┼────────┐      └──────────────┘   
           v    │                 ├──────────┤        │      ┌─────────────┐    
         ┌───┐  │ ┌─>jasenet[2]   │    o─────┼──────┐ │      │Susi Sepe    │    
       0 │ 0 ├──┘ │               ├──────────┤      │ └─────>│Takametsä    │    
         ├───┤    │     ...       │          │      │        │-            │    
       1 │ 2 ├────┘               ├──────────┤      │        │...          │   
         ├───┤                    │          │      │        └─────────────┘    
       2 │ ? │                    ├──────────┤      │        ┌─────────────┐
         ├───┤                    │          │      └───────>│Ankka Aku    │
       3 │ ? │                    ├──────────┤               │Ankkalinna   │
         ├───┤                    │          │               │1234         │
       4 │ ? │                    ├──────────┤               │...          │   
         ├───┤                    │          │               └─────────────┘
       5 │ ? │                    └──────────┘ 
         ├───┤               osoitintaulukko henkilöihin
       6 │ ? │
         ├───┤
       7 │ ? │ indeksitaulukko löytyneisiin
         └───┘

löytyisi jasenet[0] ja jasenet[2], jotka täyttävät hakuehdon.  Tällöin etsimisaliohjelma palauttaisi tämän tiedon seuraavasti:

cJarjestys etsi
           ┌─────┐ 
  max_koko │  7  │         0  1  2  3  4  5  6 
  indekseja│  2  │       ┌──┬──┬──┬──┬──┬──┬──┐ 
 *indeksit │  o──┼──────>│ 0│ 2│ ?│ ?│ ?│ ?│ ?│
           └─────┘       └──┴──┴──┴──┴──┴──┴──┘

Nyt tietäisimme heti, että meillä on kaksi löytynyttä, joten löytyneet  voitaisiin tulostaa:
for (i=0; i<etsi.Indekseja(); i++)
  Kerho- >Jasenet()- >anna(etsi.anna(i)).tulosta(cout) 

22.1.3	Etsiminen valitun kentän mukaan
Mitä jos haluamme etsiä jonkin tietyn kentän mukaan.  Välitämme etsimisaliohjelmalle tiedoksi kentän numeron, jonka mukaan haluamme etsiä.  Käytännössä osoittautuu helpoimmaksi välittää myös etsittävä isona tietueena (tai taulukkona), johon on täytetty nimenomaan etsittävään kenttään halutut tiedot.
22.1.4	etsi ja indeksit
Etsimistä voitaisiin hieman monipuolistaa siten, että voitaisiin laittaa ehto jokaiseen tutkittavan jasen- luokan kenttään.  Sitten löytymisen ehtona voisi olla, että jokin kentistä täsmää (TAI) tai että kaikki kentät täsmäävät (JA).  Tämä voitaisiin ilmaista etsimisaliohjelmalle vaikkapa siten, että JA etsimisessä kentta=- 1 ja TAI etsimisessä kentta=- 2.
Tehtävä 22.175	etsi- olio
Hahmottele mitä metodeja tulisi olla luokassa cJarjestys ja mitä sitä käyttävälle etsimismetodille tulisi viedä parametrina ja mihin luokkaan etsimismetodi tulisi kirjoittaa?

Tehtävä 22.176	TAI- etsiminen
Miten käyttäjä saisi TAI- etsimisen avulla vastauksen kysymykseen:  Onko missään kentässä missään kohti sana aku?

22.1.5	cHaku
Etsimisaliohjelmalla kannattaa ehkä viedä parametrina cHaku- luokkaa oleva olio sen sijaan, että vietäisiin cJasen - tyyppinen olio.  Tähän on kaksi syytä.  
Ensinnäkin on käyttäjän kannalta mukavaa, että jos hän on joskus hakenut nimihaussa nimellä "*aku*", niin seuraavassakin nimihaussa hänellä on oletuksena sama hakuehto.  Siis eri kentissä viimeksi käytetyt hakuehdot kannattaa kukin säilyttää erikseen.
Toisaalta jos haluamme toteuttaa JA ja TAI - tyyppiset haut, tarvitsemme etsimisaliohjelmalle hakuehdon kullekin kentälle.  
cHaku voisi olla myös cJasen, mutta koska cJasen kentillä on mahdollisuus saada myös numeerisia arvoja, ei numeeriseen kenttään pystyttäisi tallettamaan esimerkiksi hakuehtoa "4*" tai "<50".  Siis cHaku on parasta tehdä merkkijono taulukoksi
class cHaku {
  string kentat[KENTTIA];
…
}

22.2	Selailu
Kun etsittävän ehdon täyttävät tietueet ovat löytyneet, voitaisiin niitä selailla suhteellisen helposti:
22.2.1	selaile
  suunta = 0;
  while (1) {
    - lisää kohdalla olevaan suunta
    - korjaa sallitun välin sisälle 
    - tulosta kohdalla oleva
    painettu = odota_nappain(sallitut,RET,VAIN_ISOT);
    switch (painettu) {
      case '- ': suunta = - 1; break;
      case '+': suunta =  1; break;
      default : return painettu;
    }
  } 

Tehtävä 22.177	Koko kerhon selailu
Jos meillä on käytössä hakuehdon kysyminen, etsiminen ja selailu, niin tarvitseeko koko kerhon selaamista varten tehdä oma aliohjelma?

22.3	Lajittelu 
Lajittelun vaatimia algoritmeja olemme käsitelleet jo aikaisemmin.  Mikä tahansa aikaisemmin mainituista algoritmeista olisi helppo soveltaa jäsentaulukkoon.  Kuitenkin lähes kaikki oppimamme algoritmit olivat kompleksisuudeltaan O(n2).  Jos jäsenmäärä kasvaa paljon yli sadan, tarkoittaa tämä suhteellisen hidasta lajittelua. 
C- kielen standardikirjasto stdlib.h tarjoaa valmiin QuickSort lajittelualiohjelman qsort, jonka kompleksisuus on O(n log n).  Miten lajitteluohjelma voidaan tehdä yleiskäyttöiseksi?  Idea on siinä, että lajiteltiin mitä aineistoa tahansa, niin lähes ainoa aineistosta riippuva seikka on se, miten päätetään kahdesta alkiosta kumpiko on toistaan suurempi vai ovatko ne samoja.
Siis alkioiden vertailun suorittava aliohjelma kirjoitetaan itse.  Sitten tämän aliohjelman osoite viedään qsort aliohjelmalle, jolloin tarvittaessa qsort kutsuu tätä vertailijaa.
22.3.1	qsort
Seuraavassa on eräs esimerkki qsort - aliohjelman käytöstä.  Ohjelma arpoo (rand) satunnaisen kokonaislukutaulukon ja tämän jälkeen järjestää sen. qsort kutsuu vertailualiohjelmaa kahdella osoitintyyppisellä muuttujalla, jotka osoittavat verrattaviin alkioihin.  Osoittimet on määritelty void * - tyyppisiksi.  Koska esimerkissämme alkiot ovat kokonaislukuja, pitää osoittimet käytön yhteydessä muuttaa int * - tyyppisiksi.  
Vertailualiohjelman pitää palauttaa negatiivinen luku, mikäli 1. alkio on pienempi kuin toinen,  0 mikäli ne ovat samoja ja positiivinen luku, mikäli 1. on suurempi ("erotuksen etumerkki").  Siis tässä tapauksessa lukujen vähennyslasku tuottaa valmiiksi halutunlaisen tuloksen.
...
/* Alkioiden vertailufunktio.                          */
int sort_function(const void *a, const void *b)
{
  return *(int *)a -  *(int *)b;
}

int main(void)
{
  int i,x[KOKO];

  for (i=0; i<KOKO; i++)  x[i] = rand();
  tulosta(x);

  qsort((void *)x,KOKO,sizeof(x[0]),sort_function);

  tulosta(x);
  return 0;
}

qsort - aliohjelman kutsussa pitää kertoa lajiteltavan taulukon (osaa lajitella vain taulukoita) alkuosoite, koko alkioina sekä kunkin alkion koko, sekä vertailufunktion osoite.
Huomattakoon, ettei kutsun viimeinen parametri sort_function aiheuta kutsua aliohjelmaan sort_function, vaan välittää qsort aliohjelmalle lajittelufunktion osoitteen.  Kääntäjä tietää tämän eron siitä, ettei funktion nimen perässä ole sulkuja!
22.3.2	const 
Edellä const void *a tarkoitti vain sitä, että tällä korostetaan ettei aliohjelmassa muuteta  osoittimen a osoittamaa muistipaikkaa.  Jokin kääntäjä antaisi virheen sijoituksesta
int ali(const int *a)
{
  int b;
  *a = 5;  // Laiton sijoitus, koska const osoitin ei saa muuttaa kohdettaan
  a = b;   // Sallittu sijoitus, kohde ei muutu.
}
22.3.3	volatile 
const- määreelle lähes päinvastainen on volatile- määre (suom. epävakaa).  Tällä korostetaan sitä, että jokin taustaprosessi tai rinnakkainen aliohjelma saattaa muuttaa muistipaikan sisältöä ilman mitään ohjelmassa näkyvää sijoitusta.  Esimerkiksi tietoliikenneohjelmassa voisi olla muuttuja linja_varattu, jota muuttaisi taustalla toimiva kommunikointipaketti:

volatile int linja_varattu;
...
set_param(LINE_RESERVED,&linja_varattu);
...
dial_number("112"); /* soittelee taustalla kunnes pääsee läpi */
while ( linja_varattu && !lopetus() ) 
  do_sound(VARATTU_AANI);
... 

Esimerkissä voisi olla myös
const volatile int linja_varattu; 

koska itse pääohjelmalla ei ole mielekästä sijoittaa arvoa ko. muuttujaan.  Nämä ominaisuudet ovat kuitenkin edistyneempien kurssien asioita.
22.3.4	Permutaatiotaulu 
Jäsenrekisteristä voitaisiin lajitella se taulukko, jossa on osoittimet jäseniin.  Mikäli joku kuitenkin haluaisi säilyttää alkuperäisenkin järjestyksen, voisimme yrittää keksiä tapoja lajitella aineisto siten, että siihen olisi yhtäaikaa useita eri järjestyksiä.
Helpoin ratkaisu tähän on permutaatiotaulukoiden käyttö.  Aikaisempi etsimisessä mallina ollut jäsenrekisteri voitaisiin lajitella vaikkapa nimen ja postinumeron mukaan:
                   **jasenet    │      o      │
cJarjestys                      └──────┼──────┘                Jasen_tyyppi
         ┌───┐nimen_mukaan             │                     ┌──────────────┐   
max_koko │ 7 │                         v                     │Kassinen Katto│   
indekseja│ 3 │                    ┌──────────┐               │Katto         │   
*indeksit│ o │  ┌───>jasenet[0]   │    o─────┼──────────────>│3452          │   
         └─┼─┘  │                 ├──────────┤               │...           │    
           │    │┌──>jasenet[1]   │    o─────┼────────┐      └──────────────┘   
           v    ││                ├──────────┤        │      ┌─────────────┐    
         ┌───┐ ┌┼┼──>jasenet[2]   │    o─────┼──────┐ │      │Susi Sepe    │    
       0 │ 2 ├─┘││                ├──────────┤      │ └─────>│Takametsä    │    
         ├───┤  ││      ...       │          │      │        │-            │    
       1 │ 0 ├──┘│                ├──────────┤      │        │...          │   
         ├───┤   │                │          │      │        └─────────────┘    
       2 │ 1 ├───┘                ├──────────┤      │        ┌─────────────┐
         ├───┤                    │          │      └───────>│Ankka Aku    │
       3 │ ? │                    ├──────────┤               │Ankkalinna   │
         ├───┤                    │          │               │1234         │
       4 │ ? │                    ├──────────┤               │...          │   
         ├───┤                    │          │               └─────────────┘
       5 │ ? │                    └──────────┘ 
         ├───┤               osoitintaulukko henkilöihin
       6 │ ? │
         ├───┤
       7 │ ? │ indeksitaulukko joka osoittaa järjestyksen
         └───┘

cJarjestys nimen_mukaan
           ┌─────┐ 
  max_koko │  7  │         0  1  2  3  4  5  6 
  indekseja│  3  │       ┌──┬──┬──┬──┬──┬──┬──┐ 
 *indeksit │  o──┼──────>│ 2│ 0│ 1│ ?│ ?│ ?│ ?│
           └─────┘       └──┴──┴──┴──┴──┴──┴──┘

cJarjestys postin_mukaan
           ┌─────┐ 
  max_koko │  7  │         0  1  2  3  4  5  6 
  indekseja│  3  │       ┌──┬──┬──┬──┬──┬──┬──┐ 
 *indeksit │  o──┼──────>│ 1│ 2│ 0│ ?│ ?│ ?│ ?│
           └─────┘       └──┴──┴──┴──┴──┴──┴──┘

Lajittelu aloitetaan siten, että järjestettävä permutaatiotaulukko alustetaan johonkin järjestykseen.  Esimerkiksi:
cJarjestys nimen_mukaan
           ┌─────┐ 
  max_koko │  7  │         0  1  2  3  4  5  6 
  indekseja│  3  │       ┌──┬──┬──┬──┬──┬──┬──┐ 
 *indeksit │  o──┼──────>│ 0│ 1│ 2│ ?│ ?│ ?│ ?│
           └─────┘       └──┴──┴──┴──┴──┴──┴──┘
                          ^   ^
                      a───┘   └────b 

Tämän jälkeen lajitteluohjelmalle annetaan lajiteltavaksi taulukko nimen_mukaan.indeksit. qsort välittää vertailualiohjelmalle verrattavaksi osoittimet a ja b.  Meidän täytyy siis katsoa miten suhtautuvat toisiinsa nimet
"kerho- >jasenet[*(int *)a]- >nimi"
"kerho- >jasenet[*(int *)b]- >nimi"

Tämän homman hoitaa esimerkiksi strcmp - funktio, joka tosin järjestää skandit hieman väärään paikkaan, mutta olkoon tällä kertaa.  Aliohjelma on helppo kirjoittaa uusiksi, mikäli ominaisuus haittaa enemmän.  Vertailu on case- sensitive, eli Zorro < aapinen!
Jos haluamme lajittelusta aliohjelman, joka osaa järjestää minkä kentän mukaan tahansa, täytyisi kentän numero saada välitettyä vertailu- aliohjelmalle!  Tätä mahdollisuutta qsort ei tarjoa, joten joudumme keksimään jonkin toisen ratkaisun.
22.3.5	Aliohjelmien yhteiset muuttujat
Haluamme lisäksi säilyttää koko kerhorekisterin läpi säilyneen ominaisuuden, että meillä olisi mahdollista rinnakkain käsitellä useita eri kerhoja (!), joten myös kerho pitäisi välittää tavalla tai toisella vertailu- aliohjelmalle.  
Aliohjelmien väliseen tiedonvälitykseen tehdään tietue, johon kasataan kaikki yhteisesti tarvittavat muuttujat.  Ongelmassamme niitä on kentan_nro ja osoitin käyttämämme kerhon jasenet - taulukon alkuun.  Siis ne tietueeseen.  Seuraavasta esimerkistä puuttuvat saantimetodit, joten se on vain hahmotelma:
typedef struct {   /* Lajittelussa apuna käytettävä tietue.                 */
  int kentan_nro;               /* Kenttä jonka mukaan lajitellaan.         */
  cKerho *kerho;                /* Lajiteltava kerho                        */
} tVertaa;                      /*                                          */

static tVertaa VERTAA;          /* Tiedonvälitys lajittele - > vertaile      */

int vertaile(const void *a, const void *b)
{
  string s1,s2;
  s1 = VERTAA.kerho->anna(*(int *)a).kentta_jonoksi(VERTAA.kentan_nro,);
  s2 = VERTAA.kerho->anna(*(int *)b).kentta_jonoksi(VERTAA.kentan_nro,);
  return (strcmp(s1.c_str(),s2.c_str());
}

void cKerho::lajittele(cJarjestys *jarj,int kentan_nro)
{
  int j,ind,max_ind;

  if ( (kentan_nro < 0) || !jarj- >jarjesta ) return;

  if ( jarj- >Indekseja() == 0 ) { /* Alustetaan perm. taulukko */
    for (j=0; j<jasenia; j++) 
       jarj- >lisaa(j);
  }

  VERTAA.kentan_nro = kentan_nro;
  VERTAA.kerho      = this;

  qsort( (void *)(jarj- >Indeksit()), /* Lajiteltava taulukko                 */
         jarj- >Indekseja(),          /* Taulukon alkioiden lukumäärä         */
         sizeof(jarj- >anna(0)),      /* Yksittäisen alkion koko              */
         vertaile);                  /* Vertailualiohjelman nimi             */
}

lajittele - aliohjelma tekee tarvittavat alustukset, mm. laittaa permutaatiotaulukon johonkin järjestykseen (tässä tapauksessa ehdollisesti, eli jos taulukolla jo on järjestys, ei sitä sotketa), laittaa arvot yhteisille muuttujille ja kutsuu qsort - aliohjelmaa tarvittavilla parametreilla.
Tehtävä 22.178	Etsimisen tuloksen lajittelu
Tarvitseeko lajittele - aliohjelma muutoksia, jotta se voisi lajitella myös etsimisen tuloksena saadun taulukon?  Miten etsimisen tulos lajiteltaisiin etsimisen päätteeksi?
Tehtävä 22.179	Selailu puhelinnumeron mukaan
Miten käyttäjä voisi selata koko kerhon jäsenistöä puhelinnumeron mukaisessa järjestyksessä?
Tehtävä 22.180	Vertailu isot ja pienet samaistaen
Mikäli haluttaisiin järjestys, jossa aapinen < Zorro, pitäisi vertailun ajaksi isot ja pienet kirjaimet samaistaa.  Ongelmaa voitaisiin ratkaista seuraavalla aliohjelmalla

int vertaile(const void *a, const void *b)
{
  string s1,s2;
  s1 = VERTAA.kerho->anna(*(int *)a).kentta_jonoksi(VERTAA.kentan_nro,);
  s2 = VERTAA.kerho->anna(*(int *)b).kentta_jonoksi(VERTAA.kentan_nro,);
  jono_isoksi(s1); jono_isoksi(s2);
  return (strcmp(s1.c_str(),s2.c_str());
}

Mitä vikaa aliohjelmassa kuitenkin on?

22.4	Oikeaoppinen lajittelu
Käytännössä lajittelusta on hyvin tarkat säännöt.  Esimerkiksi kirjaston lajittelusääntöjen mukaan kaikki välimerkit samaistetaan.  Saksalaisessa lajittelussa Ä:t samaistetaan A kirjaimeen ja englantilaisessa McAnkka ja MacAnkka lajitellaan samaan kohtaan.
Näiden sääntöjen kirjoittaminen vertaile aliohjelmaan hidastaisi lajittelua huomattavasti.  Käytännössä ongelma ratkaistaan siten, että lajittelussa käytetään avaimia, jotka muodostetaan ennen lajittelun alkua.
Esimerkiksi tyypissä Jasen_tyyppi voisi olla yksi ylimääräinen tietue, joka toimisi avaimena.  Mikäli lajittelu tehdään  nimen mukaan, muodostetaan nimestä lajittelusäännöt täyttävä avain tähän kenttään. 
saksa:  Äystö Yrjö   - >  AYSTO YRJO
        Gauß Karl F  - >  GAUSS KARL F

suomi:  Äystö Yrjö   - >  \YST] YRJ] (koska ASCIIssa XYZ[\] ) 

Mikäli lajittelu tehtäisiin sotun mukaan, muodostettaisiin sotu- kentästä avain siten, että vuosiluku siirrettäisiin ensimmäiseksi, kuukausi seuraavaksi jne.  
010245- 123U          - >  450201- 123U
020347- 123T          - >  470302- 123T 

22.4.1	vertaile 
Itse vertailu olisi
int vertaile(const void *a, const void *b)    
{
  return strcmp(VERTAA.kerho->anna(*(int *)a).avain,
                VERTAA.kerho->anna(*(int *)b).avain);
}

22.4.2	lajittele
lajittele aliohjelmaan lisättäisiin lauseet avaimien muodostamiseksi:
  ...
  for (j=0; j<jarj- >Indekseja(); j++) 
    anna(jarj- >anna(j)).tee_avain(kentan_nro);
   
  qsort(...

22.4.3	alusta_jarjestys 
Kirjainten samaistus suoritettaisiin siten, että olisi käytössä taulukko, jossa olisi kunkin kirjaimen ASCII- koodiarvon kohdalla kirjaimelle uusi koodi, sen mukaan mihin kohti kirjain lajitellaan.  Voisimme esimerkiksi numeroida:

0x20  = välimerkit
0x30 = '0'
0x31 = '1'
..
0x39 = '9'
0x41 = 'A' ja 'a' (saksassa myös 'Ä' ja 'ä')
0x42 = 'B' ja 'b'
... 

Tätä varten tarvitsemme luokan:

cJarjestys_avain {   /* Avaimen muodostuksessa käytettä tyyppi             */
  char avainarvo[MERKKEJA];     /* Miten kukin kirjain muuttuu              */
  void alusta();
public:
 cJarjestys_avain() { alusta(); )
 int Avainarvo(int i) { return avainarvo[i]; }
} ;      

static cJarjestys_avain JARJESTYS;

Taulukko alustettaisiin seuraavasti:
void cJarjestys_avain::alusta() {
 avainarvo[(unsigned char) ','] = 0x20;
 avainarvo[(unsigned char) ' '] = 0x20;
...
 avainarvo[(unsigned char) 'A'] = 0x41;
 avainarvo[(unsigned char) 'a'] = 0x41;
 avainarvo[(unsigned char) 'B'] = 0x42;
 avainarvo[(unsigned char) 'b'] = 0x42;
...
 avainarvo[(unsigned char) 'Ä'] = 0x5c;
 avainarvo[(unsigned char) 'ä'] = 0x5c;
... 
}

Ehkä on kuitenkin helpompi käyttää muutamaa makroa:

#define A_1 (mika,miksi) avainarvo[(unsigned char)mika] = miksi
#define A_A (mi,mp,miksi) A_1(mi,miksi); A_1(mp,miksi)

/****************************************************************************/
void cJarjestys_avain::alusta() {
{
  int i;

  for (i=0; i<MERKKEJA; i++) A_1(i,0x20); /* Kaikki tuntemat. merkit välil. */

  A_1('0',0x30); A_1('1',0x31); A_1('2',0x32); A_1('3',0x33); A_1('4',0x34); 
  A_1('5',0x35); A_1('6',0x36); A_1('7',0x37); A_1('8',0x38); A_1('9',0x39); 

  A_A('A','a',0x41);  A_A('B','b',0x42);  A_A('C','c',0x43);  
  A_A('D','d',0x44);  A_A('E','e',0x45);  A_A('F','f',0x46);
  A_A('G','g',0x47);  A_A('H','h',0x48);  A_A('I','i',0x49);
  A_A('J','j',0x4a);  A_A('K','k',0x4b);  A_A('L','l',0x4c);
  A_A('M','m',0x4d);  A_A('N','n',0x4e);  A_A('O','o',0x4f);
  A_A('P','p',0x50);  A_A('Q','q',0x51);  A_A('R','r',0x52);
  A_A('S','s',0x53);  A_A('T','t',0x54);  A_A('U','u',0x55);
  A_A('V','v',0x56);  A_A('W','w',0x57);  A_A('X','x',0x58);
  A_A('Y','y',0x59);  A_A('Z','z',0x5a);
  A_A('š','',0x59);  
  A_A('Å','å',0x5b);  A_A('Ä','ä',0x5c);  A_A('Ö','ö',0x5d);

}
#undef A_1
#undef A_A

Avain muodostetaan nyt JARJESTYS.avainarvo - taulukkoa käyttäen.  Kuitenkin erikoistapaukset kuten Mac ja Mc täytyisi käsitellä ensin erikseen vaikkapa Mc - >Mac.
Tehtävä 22.181	A_1 ja A_A
Kirjoita auki muutama makrojen A_1 ja A_A esiintymä.

22.4.4	tee_avain 
Seuraava on hahmotelma siitä, miten cJasen voisi alustaa avaimensa.  Jos on käytössä "viisaat kenttä- luokat", niin silloin tietenkin kukin kenttä itse osaa muuttua avaimeksi.
/****************************************************************************/
void cJasen::tee_avain(int kentan_nro)    
{
  void *p; char s[20]; unsigned char *a; 
  int i; double d; 
  int tyyppi;

  i = sizeof(avain);
  if ( i < 20 ) {
    printf("TEE AVAIN: Liian pieni avainkenttä!\n");
    return;
  }

  tyyppi = kentan_tyyppi(kentan_nro);
  p      = kentan_osoite(kentan_nro);

  switch (tyyppi) {
    case Tjono:
      kopioi_jono(N_S(avain),p);
      poista_tyhjat(avain);
      for (a=(unsigned char *)avain; *a; a++) {
        *a = JARJESTYS.avainarvo[*a];
      }
      poista_tyhjat(avain);
      return; 

    case Tsotu:
      kopioi_jono(N_S(s),p); liita_jono(N_S(s),"      ");
      kopioi_jono(N_S(avain),s);
      avain[0] = s[4];  /* Vaihdetaan pv ja vuosi keskenään! */
      avain[1] = s[5];
      avain[4] = s[0];
      avain[5] = s[1];
      return;

    case Tint: 
      i = *(int *)p;
      sprintf(avain,"%+05d",i);
      if ( avain[0] == '- ' ) avain[0]='+'- 1; /* jotta -  < + */
      return;

    case Tdouble: 
      d = *(double *)p;
      sprintf(avain,"%+015.7lf",d);
      if ( avain[0] == '- ' ) avain[0]='+'- 1; /* jotta -  < + */
      return;

    default:
      printf("TEE AVAIN: Ei ole kenttää %d!\n",kentan_nro); 
      return;
      
  }
}

Tehtävä 22.182	tee_avain
Miksi kokonais-  ja reaaliluvut käsiteltiin kuten edellä eikä käyttäen aliohjelmaa kentta_jonoksi?

"Kommentoi" (eli miksi tehdään?) tee_avain aliohjelman jokainen rivi.

Miten MacAnkka ja McAnkka käsiteltäisiin?
Miten käsiteltäisiin Gauß?
22.5	Haku epäyhtälö - ehdoilla 
Miten toteuttaisimme haun sillä tavoin, että haku olisi mahdollista myös ehdoilla:
>=50
!=*ankka*
<=Kassinen
==               eli etsitään tyhjää kenttää

Puuttuuko ohjelmastamme paljon, jotta tämä voitaisiin tehdä?  Mistä yleensä on kyse?
Ensin pitäisi erottaa annetusta hakuehdosta onko kyseessä normaali haku vaiko epäyhtälöhaku.  Miten tämä tehtäisiin?  Tutkitaan onko hakuehdon alussa jokin merkkijonoista
"==", "!=", "<=" "<" ">" ">="

Mitenkä tutkittaisiin?  Esimerkiksi:
if ( strstr(maski,"==") == maski )...
else if ( strstr(maski,"!=") == maski )...
... 

22.5.1	EHDOT - taulukko (kerhoets.c)
Tällainen vertailu sopii, mikäli erikoisehtoja olisi hyvin vähän.  Nyt niitä kuitenkin on suhteellisen monta, joten on ehkä helpompi tehdä taulukko, jossa on ehdot ja mitä ehdoilla tehdään:

/****************************************************************************/
typedef enum { 
 YHT,
 ERIS,
 PIEN,
 PIENYHT,
 SUUR,
 SUURYHT
} Vertailu_oper_tyyppi;

typedef struct {
  char *ehto;
  int  pit;
  Vertailu_oper_tyyppi kasky;
} Vertailu_tyyppi;

static Vertailu_tyyppi EHDOT [] = {
  { ""  , 0, YHT},
  { "==", 2, YHT},
  { "!=", 2, ERIS},
  { "<=", 2, PIENYHT},
  { "<" , 1, PIEN},
  { ">=", 2, SUURYHT},
  { ">" , 1, SUUR},
  { NULL, 0, YHT}
};

22.5.2	tutki_tasmaako 
Metodissa etsi_indeksit muutetaan wildmat - kutsu kutsuksi tutki_tasmaako.  Aliohjelma tarvitsee muutaman lisäparametrin, jotta tarpeen vaatiessa osataan muuttaa kenttiä avaimiksi.  Miksikö avaimiksi?  No tietysti siksi, että avain rakennettiin siten, että aakkosjärjestys, sotujen järjestys yms. pitävät paikkansa.
Aliohjelman aluksi etsitään onko kyse jostakin erikoisoperaatiosta.  Mikäli ei ole toimitaan kuten ennenkin.  Mikäli on kyse jostakin erikoisesta operaatiosta, poistetaan hakumaskin alusta operaatio.

/****************************************************************************/
int                       /* 1 = ei täsmää                                  */
cJasen::tutki_tasmaako(   /* 0 = täsmää                                     */
  const char *maski      ,/* s   Maski muutettuna isoiksi kirjaimeksi       */
  int knro                /* s   Kentän numero.                             */
)    
{
  int i,tulos,ehto_ind=0; const char *m; cJasen mjasen;
  char ikentta[80]; // Kentän knro sisältö isoksi muutettuna merkkijonona

  kopioi_jono(N_S(ikentta),kentta_jonoksi(knro));

  for (i=1; EHDOT[i].ehto; i++) 
    if ( strstr(maski,EHDOT[i].ehto) == maski ) { 
      ehto_ind = i; break; 
    }

  m = maski+EHDOT[ehto_ind].pit;

  switch ( EHDOT[ehto_ind].kasky ) {

    case YHT:     
      return wildmat(ikentta,m);

    case ERIS:    
      return !wildmat(ikentta,m);

    case PIEN:
    case PIENYHT:
    case SUUR:
    case SUURYHT:
      mjasen.sijoita(knro,m);
      mjasen.tee_avain(knro);
      tee_avain(knro);
      tulos = strcmp(Avain(),mjasen.Avain());
      switch ( EHDOT[ehto_ind].kasky ) {
        case PIEN:    return !(tulos<0);
        case PIENYHT: return !(tulos<=0);
        case SUUR:    return !(tulos>0);
        case SUURYHT: return !(tulos>=0);
      }

    default:      
      return 1;
  }
}

Tehtävä 22.183	tutki_tasmaako
Miksi EHDOT taulukossa "<=" on ennen "<" operaatiota?

"Kommentoi" metodin tutki_tasmaako jokainen lause!
22.6	enum 
Tyypissä Vertailu_oper_tyyppi "luotiin" luettelo vakioita, joille ei kuitenkaan tarvinnut itse antaa arvoa.  Sama olisi voitu tehdä myös vakioilla tai #defineillä:
// Vakioilla                            // #definellä
const YHT     = 0;                      #define YHT      0
const ERIS    = 1;                      #define ERIS     1
const PIEN    = 2;                      #define PIEN     2
const PIENYHT = 3;                      #define PIENYHT  3
const SUUR    = 4;                      #define SUUR     4 
const SUURYHT = 5;                      #define SUURYHT  5 

Oman tyypin Vertailu_oper_tyyppi luomisella on kuitenkin se etu, että voidaan tehdä myös muuttuja joka on samaa tyyppiä.  Tällaiseen muuttujaan ei voi sijoittaa muita arvoja (vaikka muuttuja olisikin todellisuudessa tyyppiä int).  Lisäksi useat debuggerit näyttävät luettelotyyppisen muuttujan arvon nimellä eikä mitään sanomattomalla kokonaisluvulla.
Hyvässä ohjelmassa on jokaista selvästi eri luettelomaista tyyppiä kohti tehty oma luettelo ja käytetään vain ko. luettelotyypin tyyppisiä muuttujia.  Esimerkiksi aliohjelmien virhepalautukset voisivat olla luettelotyyppiä.  Valitettavasti luettelotyypin arvon tulostaminen tulostaa vain kokonaisluvun.  Usein tulostuksia varten joudutaan tyypin rinnalle tekemään vähän EHDOT- taulukon kaltainen taulukko, jolla tyyppejä voidaan muuttaa selväkielisiksi ja päinvastoin.
Tehtävä 22.184	Viikonpäivät
Esitä sekä #define että enum avulla miten symboleille Su, Ma, Ti... saataisiin arvot 0,1,2...

Tehtävä 22.185	Pelikortit
Esitä sekä #define että enum avulla miten symboleille Pata, Hertta, Risti ja Ruutu saataisiin arvot 0,1,2,3.  Määrittele sitten cKortti- luokka, jossa on kortin maa ja arvo.  Määrittele vielä luokka cKorttipakka, jossa on 52 korttia jotka ovat tyyppiä cKortti.

22.7	Lopuksi
Tässä luvussa ohjelma on kerralla kehittynyt varsin paljon!  Lukijan ei kuitenkaan pidä tälläkään kertaa erehtyä luulemaan, että kaikki olisi tehty kerralla ja oikein.  Ohjelmaan on jälleen lisätty aina pienin mahdollinen lisä kerrallaan ja tämä on testattu.  
Näin hitaasti edeten koko kokonaisuus on saatu valmiiksi.  Välillä on jouduttu muuttamaan joidenkin aliohjelmien parametreja, jotta ne kävisivät myös jonkin toisen ongelman ratkaisemiseen.
Jos kaikki tässä luvussa esitetty yritettäisiin lisätä kerralla ohjelmaan, tulisi sen testaamisesta varsinainen ongelma:  Kuka muistaa testata kaikki kohdat joihin lisäykset vaikuttivat?
Samoin tehdyistä ratkaisuista alkaa näkyä lävitse eräs ohjelmoinnin tärkeimpiä ominaisuuksia:  pyrkimys yleisyyteen ja uudelleen käytettävyyteen.  Nämä ovat olio- ohjelmoinnin tunnusmerkkejä.

23.	Yleisiä virheitä
Mitä tässä luvussa käsitellään?
•	Käydään läpi nopeasti yleisiä "sudenkuoppia", joihin C/C++ - ohjelmoija helposti lankeaa

C- kieli tarjoaa käyttäjälleen mm. Pascalia enemmän vapauksia.  Toisaalta nämä vapaudet johtavat helposti virhetilanteisiin, joiden löytyminen saattaa olla todella vaikeata.  Seuraavassa varoitukseksi koottu muutamia "sudenkuoppia".
23.1	Kirjoitusvirheet 
Emme seuraavassa käsittele varsinaisia syntaksivirheitä, koska kääntäjä osaa niistä huomauttaa.
Kuitenkin muutama kirjoitusvirhe on ylitse muiden:
23.1.1	Unohtunut kommentin loppumerkki 
Mikäli kommentin loppumerkki unohtuu, on kaikki kommenttia kunnes sattumoisin tulee vastaan jonkin toisen kommentin loppumerkki.  Kaikki koodi tältä väliltä jää puuttumaan.  Onneksi tästä yleensä seuraa määrittelemättömiä aliohjelmia yms, mistä kääntäjä osaa huomauttaa.
Mikäli saat hirveän listan täysin käsittämättömiltä tuntuvia virheilmoituksia, saattaa syynä olla puuttuva kommentin loppumerkki.
Nykyisten ohjelmointiympäristöjen (Borland C++, MS Visual C++) värilliset syntaksin esitystavat auttavat huomattavasti poistamaan näitä virheitä.  Siis seuraa värejä ja hurraa niiden keksijöille.
23.1.2	Väärät tai puuttuvat lainausmerkit 
Myös vakiomerkkijonoja esiteltäessä merkkijonon aloittavan tai lopettavan lainausmerkin unohtaminen saa yllätyksiä aikaan.
Lisäksi tulee muistaa, että
'a'  -  kirjainvakio a (tavu 61H)
"a"  -  merkkijonovakio jossa tavut (61H ja 00) 

Lainausmerkeissäkin editorin värit auttavat!
23.2	Virheitä joista kääntäjä ei huomauta 
23.2.1	switch - lauseesta puuttuu break 
Erittäin petollinen on tilanne, jossa switch - lauseen jostakin case - osasta on break unohtunut pois.  Pääsääntöisesti jokainen case loppuu joko break tai return.
23.2.2	Ylitetään merkkijonolle varattu tila 
C- kielessä ei yleensä valvota taulukkojen indeksejä.  Tästä syystä erilaiset taulukoiden ylitykset ovat yleisiä.  Kaikkein useimmiten tämä tapahtuu merkkijonoille esim. strcpy - lauseella.
Myös dynaamisesti varatun jonon ylittäminen on vaarallista:
char *p;
p = tee_jono("Kissa!");
strcpy(p,"Kissa istuu puussa!"); /* VÄÄRIN! */ 	

23.2.3	Dynaamiset muuttujat unohtuu vapauttaa 
Dynaamisia muuttujia käytettäessä tulee muistaa, että vaikka niitä on helppo varata, pitää ne muistaa myös vapauttaa.  Esimerkiksi seuraava on väärin aliohjelmassa:
char *ali(...)
{
  char *p;
...
  p = tee_jono("Kissa!"); 	
...
  if ( f == NULL ) return VIRHE; /* !!! poistutaan vapauttamatta p:n tilaa !!! */
...
  free(p);
  return NULL;
}

Jokaisella aliohjelman kutsukerralla varataan uusi tila merkkijonolle ja mikäli kutsuja tulee riittävästi, muisti loppuu pelkkiin Kissoihin!  Tätä voidaan C++:ssa välttää ovelalla hajottimien käytöllä.
23.2.4	Osoitinta ei ole alustettu 
Hyvin tavallinen virhe on käyttää alustamatonta osoitinta:
char *p, jono[50];
strcpy(p,"Kissa");  /* VÄÄRIN! */ 	
p = jono+3;
strcpy(p,"Kissa");  /* Kelpaa */

23.2.5	Taulukoiden indeksointi 0..n- 1 
Usein unohtuu, että kun varataan taulukko
int luvut[10]; 

niin viimeinen indeksi onkin 9!
23.2.6	scanf 
Varsinainen supervaara on scanf.  Normaaleissa aliohjelmissa oikein käytettynä kääntäjä osaa varoittaa tyyppivirheestä, mikäli osoitteena esiteltyä parametria kutsutaan arvolla. scanf on kuitenkin toteutettu siten, että tyypit saavat vapaasti olla mitä tahansa ja unohtuneesta &- merkistä ei kukaan valita.
Lisäksi toinen erittäin vaarallinen tilanne syntyy, mikäli format- osassa on eri määrä parametrejä kuin varsinaisessa parametrilistassa.
Myös parametrin tyypin ja format- osassa olevan formaatin sotkeminen saattaa kaataa koko koneen.
int i;
scanf("%lf",&i); /* VÄÄRIN! */ 	

Aina kun kirjoittaa jonkin scanfin sukuisen lauseen, pitää havahtua ja tarkistaa ainakin 3 kertaa lauseen olevan kunnossa.  Tämänkin monisteen malliohjelmia kirjoitettaessa scanf on ollut kymmeniä kertoja väärin!
23.2.7	printf 
printfin kanssa on vähän vastaavia ongelmia.  Tyypin ja vastaavan format- osan erot aiheuttavat kuitenkin lähinnä vain hassuja tulosteita.
Usein myös newline merkki '\n' unohtuu rivin lopusta.  Tällöin tietysti kaikki tulosteet tulevat enempi tai vähempi sekaisin näytölle.
23.2.8	#define 
Makrot muodostavat oikeastaan kokonaan oman virheryhmänsä.  Niin paljon kuin ne auttavatkin kirjoittamista, saattavat ne tosi paljon myös sotkea ohjelmaa.  
#define b 10*10
...	
a = 5.0/b; /*  - > a==5.0  !!!! */

Erityisesti runsas sulkujen käyttö auttaa välttämään ongelmia.
23.2.9	 Funktion prototyyppi puuttuu 
Mikäli käännöksessä ei käytetä ANSI- optioita, on mahdollista kääntää ohjelman osia vaikkei funktioita olisi vielä esiteltykään.  Tällöin kääntäjä tekee omat oletuksensa funktioiden tyypeistä ja ne saattavat poiketa huomattavasti siitä, mitä kirjoittaja on tarkoittanut.
23.2.10	#include - unohtuu 
Jotkin ohjelman osat saattavat mennä käännöksestä läpi, vaikka tarvittavat #include lauseet olisivat unohtuneet pois.  Erityisesti malloc saattaa tällöin aiheuttaa ongelmia.
23.2.11	'ä' < 'a' 
Kirjainvakiot muutetaan kokonaisluvuiksi.  Järjestelmästä riippuen kirjain- tyypin arvo voi olla joka etumerkitön (unsigned) (usein hyvä asia) tai etumerkillinen (signed).  Itse merkin sisäinen esitys kummassakin tapauksessa on sama, mutta laajennettaessa merkki kokonaisluvuksi saattaa etumerkki sotkea koko homman.  Käytännössä on kyse seuraavasta:
'a'  =  0x61 = 0110 0001  - > 0000 0000 0110 0001 (unsigned)
                             0000 0000 0110 0001 (signed)
'ä'  =  0x84 = 1000 0100  - > 0000 0000 1000 0100 (unsigned)
                             1111 1111 1000 0100 (signed) 

Erityisesti tämä on muistettava käytettäessä kirjaimia indekseinä:

char c;
...
kirjaimet[c]++;  /* Lisätään kirjainten lkm. */ /* VÄÄRIN */ 	
...
kirjaimet[(unsigned char)c]++; /* OIKEIN! */
... 

23.2.12	Väärä tyypin muunnos 
Väärä tyypin muunnos saattaa myös aiheuttaa harmaita hiuksia:
#include <stdio.h>
int main(void)
{
  double d; int i;
  i = 5;
  d = i/2; 	
  printf("d = %4.2lf\n",d);
  return 0;
}

Edellinen ohjelma tulostaa d = 2.00.  Lausekkeen arvo kussakin vaiheessa on sama kuin sen laskemisessa siihen saakka tarvitun "monimutkaisimman" tyypin arvo.  Mallissa ollaan kokoajan arvossa int.  Siis myös jakolaskun tulos on int.  Vika voidaan korjata kahdella tavalla:
d = i/2.0;
d = ((double)i)/2; 

23.2.13	Pyöristys-  ja katkaisuvirheet 
Seuraava ohjelma voisi tulostaa i = 0:

#include <stdio.h>
int main(void)
{
  float d = 0.0001; int i;
  i = 10000;
  i = d*i;
  printf("i = %4d\n",i);
  return 0;
}

Miksikö?  Koska reaaliluku 0.0001 voisi sisäisenä esityksenä olla jotakin 0.000099999 ja kun tämä kerrotaan 10000:lla, tulee vähän alle 1 joka kokonaisluvuksi katkaistuna on 0!
23.2.14	Alustuksessa liian vähän pilkkuja 
Esimerkiksi tietueen ja merkkijonotaulukon alustus on kriittinen pilkkujen suhteen:
char *nimet[] = {
  "Mikko" 	
  "Pekka",
  "Matti"
}

Tästä seuraisi kahden nimen taulukko "MikkoPekka", "Matti"!
23.2.15	Palautetaan lokaalin muuttujan osoite 
Erityisesti merkkijonotyyppisiä funktioita tehtäessä pitää muistaa, ettei vahingossa palauta lokaalin muuttujan osoitetta:
char *kissa()
{
  char jono[50];
  strcpy(jono,"kissa");
  return jono;    /* VÄÄRIN! */ 	
  return "kissa"; /* OIKEIN! */
}

23.2.16	Käytetään muuttunutta muistipaikkaa 
Funktiot, jotka palauttavat merkkijonojen osoitteita, saattavat aiheuttaa yllätyksiä huolimattomasti käytettyinä:
char rivi[80],*p1,*p2; int j;
...
lue_jono(N_S(rivi));
p1 = palanen(rivi," ",&j);
...                         /* Täällä ei viitata p1:een! */
lue_jono(N_S(rivi));
p2 = palanen(rivi," ",&j);
if ( strcmp(p1,p2 ) ...     /* ON AINA p1=p2!!! */

23.2.17	Käytetään vahingossa pilkkuoperaattoria
Vaikka seuraava kutsu onkin C- kielessä laillinen, tekee se aivan muuta kuin käyttäjä olisi ajatellut:
double d;
d = 2,5;    // Laskee 2 ja 5 ja sijoittaa d = 5; 	

24.	"C- referenssi"
Mitä tässä luvussa käsitellään?
•	Pikainen "C- referenssi"

Seuraavaan on koottu muutamia C- kielen taulukoita.
24.1	Varatut sanat 
auto           break          case           char
const          continue       default        do
double         else           enum           extern
float          for            goto           if
int            long           register       return
short          signed         sizeof         static
struct         switch         typedef        union
unsigned       void           volatile       while

Lisäksi "varatuiksi sanoiksi" voidaan tavallaan laskea myöhemmin esiteltävät standardi- otsikkotiedostojen määräämät funktioiden ja makrojen nimet.
24.2	Borland- C:n eri lukutyyppien arvoalueet
 Tyyppi        |   Koko  |                 Arvoalue 
----------------------------------------------------------------------------
 unsigned char |  8 bits |                  0 -   255
 char          |  8 bits |               - 128 -   127
 enum          | 16 bits |            - 32,768 -   32,767
 unsigned int  | 16 bits |                  0 -   65,535
 short int     | 16 bits |            - 32,768 -   32,767
 int           | 16 bits |            - 32,768 -   32,767
 unsigned long | 32 bits |                  0 -   4,294,967,295
 long          | 32 bits |     - 2,147,483,648 -   2,147,483,647
 float         | 32 bits | 0 ja +/-   3.4 E- 38 -   3.4 E+38
 double        | 64 bits | 0 ja +/-  1.7 E- 308 -   1.7 E+308
 long double   | 80 bits | 0 ja +/- 3.4 E- 4932 -   1.1 E+4932

24.3	Esikääntäjän direktiivit
Seuraava tulkitaan siten, että "- merkeissä oleva osa kirjoitetaan sellaisenaan. [] merkitsevät ehdollisuutta, {} 0- n kerran esiintymää ja | vaihtoehtoa, niitä ei siis kirjoiteta.
"#define" tunnus ["("tunnuslista ")"]{symboli}       -  makron määrittely
"#undef" tunnus                                       -  määrittelyn poisto
"#include" "<" tiedostonnimi ">" |  tiedostomäärite   -  tiedoston lisääminen
"#if" vakiolauseke                                    -  käännetään jos != 0
"#elif" vakiolauseke                                  -  ELSE IF osa edelliseen 
"#else"                                               -  ehd. käänt. ELSE- osa
"#error" virheviesti                                  -  lopettaa käännöksen 
"#endif"                                              -  lopettaa ehd. käänt.
"#ifdef" tunnus                                       -  kään. jos tunnus olem.
"#if defined tunnus                                   -  synonyymi ed.
"#ifndef" tunnus                                      -  kään jos ei ole olem.
"#line" kokonaislukuliteraali [tiedostomäärite ]     -  vaihd. rivinro ja tied.

tiedostomäärite - > """tiedostonnimi"""

Valmiita makroja:
__DATE__      -  käännöspäivä muodossa Mmm dd yyyy
__FILE__      -  käännettävän tiedoston nimi
__LINE__      -  rivinumero käännettävässä tiedostossa 
__STDC__      -  1 jos kääntäjä tekee standardikoodia  

Esimerkiksi:
preceden\error.c - esimerkki virheeseen pakottamisesta 
#line 29 "error.c"
#if __STDC__ != 1
#  error Nyt tuli virhe, ei standardikääntäjä
#endif
#include <stdio.h>

#define LINTU "Käki"
int main(void)
{
  printf("Ohjelma %s, rivi %d päivä %s\n",__FILE__,__LINE__,__DATE__);
  printf(LINTU" kaukana kukkuu\n");
  return 0;
}

Jos kääntäjä on standardimoodissa, tulostuu:
Ohjelma ERROR.C, rivi 37 päivä Dec 17 1992
Käki kaukana kukkuu

muuten käännös päättyy virheilmoitukseen:
Nyt tuli virhe, ei standardikääntäjä

24.4	Operaattorit 

Operaattori           merkintä   ryhmittely
-----------------------------------------------
jälkilisäys           X++        - >
jälkivähennys         X- -   
alkio                 X[Y]
funktion kutsu        X(Y)
tietueen alkio        X.Y
osoittimen avulla     X- >Y

alkion koko tavuina   sizeof(X)  <- 
esilisäys             ++X
esivähennys           - - X
alkion osoite         &X
epäsuora osoitus      *X
etumerkki             +X
neg. etumerkki        - X
bittitason NOT        ~X
looginen NOT          !X
tyypin muunnos        (tyyppi)X

kertolasku            X * Y      - >
jakolasku             X / Y
jakojäännös           X % Y

yhteenlasku           X + Y      - >
vähennyslasku         X -  Y

siirto vasemmalle     X << Y     - >
siirto oikealle       X >> Y

pienempi              X < Y      - >
pienempi tai yhtäkuin X <= Y
suurempi              X > Y
suurempi tai yhtäkuin X >= Y

yhtäsuuri             X == Y     - >
erisuuri              X != Y

bittitason AND        X & Y      - >

bittitason XOR        X ^ Y      - >

bittitason OR         X | Y      - >

looginen AND          X && Y     - >

looginen OR           X || Y     - >

ehdollinen tulos      Z ? X : Y  <- 

sijoitus              X = Y      <- 
tulosijoitus          X *= Y
jakosijoitus          X /= Y
jakojäännössijoitus   X %= Y
summasijoitus         X += Y
erotussijoitus        X - = Y
vas.siirtosijoitus    X <<= Y
oik.siirtosijoitus    X >>= Y
bittiANDsijoitus      X &= Y
bittiXORsijoitus      X ^= Y
bittiORsijoitus       X |= Y

pilkkuoperaattori     X,Y        - >

Edellä ryhmittely tarkoittaa sitä, miten joukko perättäisiä operaattoreita tulkitaan:
X = Y  <- , eli sijoitus ryhmitellään oikealta vasemmalle
x = y = 5;   x = ( y = 5 ); 

24.5	C:n operaattoreiden sitomisjärjestys
Seuraavaan taulukkoon on koottu C:n operaattorit ja niiden "laskujärjestys" lausekkeen arvoa määrättäessä. Ylimpänä olevilla on korkein prioriteetti lauseketta tulkittaessa eli niillä on korkein presedenssi (precedence == etusija).
Operaattori                             Assosiatiivisyys
==========================================================
() [] - > .                              - - - >
! ~ ++ - -  - 1(type cast) *2 & sizeof     <- - - 
*3 / %                                  - - - >
+ - 4                                    - - - >
<< >>                                   - - - >
< <= > >=                               - - - >
== !=                                   - - - >
&                                       - - - >
^                                       - - - >
|                                       - - - >
&&                                      - - - >
||                                      - - - >
?:                                      <- - - 
= += - = *= /= %= <<= >>= &= ^= |=       <- - - 
,                                       - - - >

1 etumerkkinä
2 osoittimen edessä
3 kertolaskussa
4 vähennyslaskussa

24.5.1	Esimerkkejä
if ( 0 < x && x < 10 ) ...  <=> if ( ( 0 < x ) && ( x < 10 ) ) ...
*p++                        <=> *(p++)
*p.k                        <=> *(p.k)   ( ei siis (*p).k ) 

24.6	Erikoismerkit merkkijonoissa
Erikoismerkki       Merkitys
(escape sequence)
========================================================
\"	"
\'	'
\?	?
\\	\
\a	BEL (alarm,piippaus)
\b	BS (backscape) siirto vasemmalle
\f	FF (form feed) sivunvaihto
\n	NL (newline) rivinvaihto
\r	CR (carriage return) rivin alkuun
\t	HT (horizontal tab) siirto seuraavan
	vaakasuuntaisen tabulointimerkkiin
\v	VT (vertical tab) siirto seuraavan
	pystysuuntaisen tabulointimerkkiin
\d \dd \ddd	merkin koodi oktaalilukuna
\xh \xhh	merkin koodi heksadesimaalilukuna

24.7	Tulostuksen muotoilumerkit
printf, sprintf, fprintf - funktioiden formaattimerkkijonojen muotoilumerkit.
d,i	etumerkillinen kymmenjärjestelmän kokonaisluku 
	(Decimal, Integer)
u	etumerkitön kymmenjärjestelmän kokonaisluku (Unsigned)
o	etumerkitön oktaalijärjestelmän kokonaisluku (Octal)
x,X	etumerkitön heksadesimaalijärjestelmän kokonaisluku
	(heXadecimal)
f	reaaliluku muodossa [- ]ddd.ddd (Floating)
e,E	reaaliluku eksoponenttimuodossa [- ]d.dddeñdd (Exponent)
g,G	reaaliluku f tai eE muodossa mahdollisimman lyhyesti (General)
c	yksi merkki (Character)
s	merkkijono (String)
p	osoitin, jonka pitää olla void - tyyppinen (Pointer)
n	tämä ei tulosta mitään. Vastaavassa kohden parametri- 
	listaa täytyy olla osoitin kokonaislukuun, johon talle- 
	tetaan tämän tulostuslauseen aikana tulostettujen merk- 
	kien määrä.
%	% - merkki

Ennen muotoilumerkkiä voi olla lisäksi (seuraavassa järjestyksessä):
- 	muotoilu tasataan vasemmalle (oletus on oikealle)
+	lukuarvon eteen tulostuu aina + tai - 
_	(tyhjä) kuten edellinen, mutta + merkin tilalle
	tulostuu välilyönti	
#	vaihtoehtoinen tulostusmuoto, katso tarkemmin ANSI- C
	standardista tai C- kääntäjän manuaaleista
0	(nolla- merkki) täytemerkki välilyöntimerkin sijasta 0
	numero.numero kentän vähimmäisleveys ja reaaliluvuissa
	pisteen jälkeen tulostuvien merkkien määrä
*	vastaavassa paikassa argumenttilistassa täytyy olla
    	kokonaisluku, joka ilmaisee kentän koon
*.numero
numero.*
*.*  	kuten edellä
h,l,L 	vastaava argumentti tyyppiä short int, double tai long double

24.7.1	Esimerkkejä
preceden\printf.c - muotoiluformaatit
#include <stdio.h>
int main(void)
{
  char s[10]="Kissa";  int i=175;  double d=1.75;
  printf("  123456789012345678901234567890123456789012345678901234567890\n");
  printf("1 |%s|%d|%lf|\n",s,i,d);
  printf("2 |%10s|%10d|%10.5lf|\n",s,i,d);
  printf("3 |%- 10s|%- 10d|%- 10.5lf|\n",s,i,d);
  printf("4 |%10p|%10x|%lg|\n",s,i,d);
  printf("5 |%010s|%10X|%le|\n",s,i,d);
  printf("6 |%- 10.2s|%+10d|%+4.1lE|\n",s,i,d);
  printf("7 |%10.2s|%+010d|%1.0lg|\n",s,i,d);
  printf("8 |%*.*s|%+0*d|%*.0lg|\n",10,2,s,10,i,1,d);
  return 0;
}

  123456789012345678901234567890123456789012345678901234567890
1 |Kissa|175|1.750000|
2 |     Kissa|       175|   1.75000|
3 |Kissa     |175       |1.75000   |
4 |      FFEC|        af|1.75|
5 |     Kissa|        AF|1.750000e+00|
6 |Ki        |      +175|+1.8E+00|
7 |        Ki|+000000175|2|
8 |        Ki|+000000175|2|

24.8	Standardin mukaiset otsikkotiedostot
24.8.1	Standardit
ANSI X3.159- 1989 ja ISO/IEC 9899:1990
ANSI	= American National Standards Institute
ISO	= International Standards Organization
24.8.2	Otsikkotiedostot
 Otsikko    | Sisällys lyhyesti
----------------------------------------------------------------------------
 assert.h  	| assert debuggus makro. 
 ctype.h 	| merkkien luokitusta ja muunnoksia
 errno.h 	| virhekoodeja
 float.h 	| joukko minimi - ja maksimityylisiä vakioita reaaliluvuille
 limits.h 	| kuten edellä, mutta kokonaisluvuille
 locale.h 	| maa-  ja kielikohtaisia toimintoja ja vakioita.
 math.h 	| matemaattisia funktioita
 setjmp.h 	| määritellään tyypit funktioille longjmp ja setjmp.
 signal.h 	| määritellään tyypit funktioille signal ja raise.
 stdarg.h 	| vaihtuparametristen funktioiden tekoon makroja ja funktiota
 stddef.h 	| yleisiä makroja ja tyyppejä
 stdio.h 	| yleisiä syöttö-  ja tulostusfunktioita
 stdlib.h 	| yleisiä makroja ja funktioita
 string.h 	| merkkijonojen käsittelyyn liittyviä aliohjelmia
 time.h 	| ajan käsittelyyn liittyviä makroja, tyyppejä ja funktioita

Seuraavassa on lueteltu kunkin otsikkotiedoston alla määritellyt funktiot tai funktiomaiset makrot (f:), vakiot (v:) ja tyypit (t:)
assert.h
f: assert

ctype.h

f: isalnum        isalpha        iscntrl        isdigit        isgraph     
   islower        isprint        ispunct        isspace        isupper     
   isxdigit       tolower        toupper

errno.h
v: errno       

float.h
v: DBL_DIG        DBL_EPSILON    DBL_MANT_DIG   DBL_MAX_10_EXP DBL_MAX_EXP         
   DBL_MAX        DBL_MIN_10_EXP DBL_MIN_EXP    DBL_MIN        FLT_DIG             
   FLT_EPSILON    FLT_MANT_DIG   FLT_MAX_10_EXP FLT_MAX_EXP    FLT_MAX             
   FLT_MIN_10_EXP FLT_MIN_EXP    FLT_MIN        FLT_RADIX      FLT_ROUNDS          
   LDBL_DIG       LDBL_EPSILON   LDBL_MANT_DIG  LDBL_MAX_10_EXP     
   LDBL_MAX_EXP   LDBL_MAX       LDBL_MIN_10_EXP     
   LDBL_MIN_EXP   LDBL_MIN            

limits.h
v: CHAR_BIT       CHAR_MAX       CHAR_MIN       INT_MAX        INT_MIN     
   LONG_MAX       LONG_MIN       MB_LEN_MAX     SCHAR_MAX      SCHAR_MIN   
   SHRT_MAX       SHRT_MIN       UCHAR_MAX      UINT_MAX       ULONG_MAX   
   USHRT_MAX

locale.h
f: localeconv     setlocale

v: LC_ALL         LC_COLLATE     LC_CTYPE       LC_MONETARY    LC_NUMERIC       
   LC_TIME        NULL

t: lconv 

math.h
f: acos           asin           atan           atan2          ceil     
   cos            cosh           exp            fabs           floor    
   fmod           frexp          ldexp          log            log10    
   modf           pow            sin            sinh           sqrt     
   tan            tanh     

v: EDOM           ERANGE                 

setjmp.h
v: longjmp        setjmp

t: jmp_buf

signal.h
f: raise          signal

v: SIG_DFL        SIG_ERR        SIG_ING        SIGABRT        SIGFPE    
   SIGILL         SIGINT         SIGSEGV        SIGTERM

t: sig_atomic_t 

stdarg.h
f: va_arg         va_end         va_start

t: va_list 

stddef.h
f: offsetof       

v: NULL        

t: ptrdiff_t      size_t         wchar_t

stdio.h
f: clearerr       fclose         feof           ferror         fflush      
   fgetc          fgetpos        fgets          fopen          fprintf     
   fputc          fputs          fread          freopen        fscanf      
   fseek          fsetpos        ftell          fwrite         getc        
   getchar        gets           perror         printf         putc        
   putchar        puts           remove         rename         rewind      
   scanf          setbuf         setvbuf        sprintf        sscanf      
   tempnam        tmpfile        tmpnam         ungetc         vfprintf    
   vprintf        vsprintf    

v: _IOFBF        _IOLBF          _IONBF         BUFSIZ         EOF  
   FILENAME_MAX  FOPEN_MAX       L_tmpnam       NULL           SEEK_CUR
   SEEK_END      SEEK_SET        TMP_MAX
   stderr        stdin           stdout              

t: FILE           fpos_t         size_t

stdlib.h
f: abort          abs            atexit         atof           atoi
   atol           bsearch        calloc         div            ecvt
   exit           free           getenv         labs           ldiv
   malloc         mblen          mbtowc         mbstowcs       qsort
   rand           realloc        srand          strtod         strtol
   strtoul        system         wctomb         wcstombs

v: EXIT_FAILURE   EXIT_SUCCESS   MB_CUR_MAX     NULL           RAND_MAX     

t: div_t          ldiv_t         wchar_t

string.h
f: memchr         memcmp         memcpy         memmove        memset
   strcat         strchr         strcmp         strcoll        strcpy       
   strcspn        strerror       strlen         strncat        strncmp      
   strncpy        strpbrk        strrchr        strspn         strstr       
   strtok         strxfrm        strupr    

v: NULL

t: size_t

time.h
f: asctime        clock          ctime          difftime       gmtime      
   localtime      mktime         stime          strftime       time

v: CLOCKS_PER_SEC                NULL       

t: clock_t        size_t         time_t         tm          

Kirjallisuutta ja materiaalia
Bjarne Stroustrup: The C++ Programming Language, Third Edition - Addison-Wesley, 1997
Koskimies Kai: Pieni oliokirja, Suomen ATK-kustannus Oy, 1997
Timothy A. Budd: An Introduction to Object-Oriented Programming, Second Edition - Addison Wesley, 1997
Päivi Hietanen: C++ ja olio- ohjelmointi - Teknolit, 1996
Jesse Liberty: Opeta itsellesi C++ , Suomen atk-kustannus Jyväskylä Gummerus, 1995
Bjarne Stroustrup:  The Desing and Evolution of C++ - Addison-Wesley, 1994
Bjarne Stroustrup, Margaret A. Ellis: The Annotated C++ Reference Manual - Addison-Wesley, 1991
Herbert Schield:  Teach Yourself C++ - Osborne McGraw-Hill, 1992
Paul DiLascia:  Windows++ - Addison-Wesley, 1992
Borland: Borland C++ 5.0: Programmers guide - Borland International Inc, 1996
James O. Coplien: Advanced C++: Programming Styles and Idioms - Addison Wesley, 1992
Saumyendra Sengupta, Carl Phillip Korobkin: C++ Object-Oriented Data Structures - Springer-Verlag, 1994
Borland: Borland C++ 3.1: Programmers guide - Borland International Inc, 1992
Stephen Prata: C++ Ohjelmointi - Pagina International AB, 1992
Lappalainen Vesa, Risto Lahdelma: Olio-ohjelmointi ja C++, Luentomoniste 37, Jyväskylän Yliopisto, 1997

Muista myös ohjelmointiympäristöjen helpit!
WWW:	http://www.math.jyu.fi/~vesal/kurssit/winohj/linkkeja
http://www.hut.fi/~jkorpela/Cinfo.html  
		http://www.cerfnet.com/~mpcline/C++-FAQs-Lite/
News-ryhmät: comp.std.c++, comp.lang.c++, sfnet.atk.ohjelmointi