Suppea johdatus Pascal-ohjelmointiin

Jorma Sajaniemi
Martti Karjalainen

Joensuun yliopisto
Epsilon ry
1986

ISBN 951-696-531-8

Copyright © 1985, 1986 Jorma Sajaniemi, Martti Karjalainen

Joensuu 1986

ESIPUHE

Nyt käsillä oleva kirjanen on tarkoitettu ohjelmoinnin ja samalla Pascal-kielen perusteiden opiskeluun. Kirjasta voi tietenkin käyttää sinälläänkin ilman muita apuvälineitä, mutta oppiminen edistyy kuitenkin parhaiten silloin, kun käytettävissä on Pascal-kielellä varustettu tietokone sekä asiantunteva opettaja. Tietokoneiden käyttöön liittyy lukematon määrä pieniä, mutta kuitenkin kiusallisia yksityiskohtia, jotka ratkeavat nopeasti vain alansa hallitsevan opettajan opastuksella.

Algoritmisen ohjelmoinnin periaatteita esitettäessä on kirjasessa luotettu kokonaisten ohjelmien ilmaisuvoimaan. Siksi kirjasessa on sivumäärään nähden verraten runsaasti täydellisiä ohjelmia, jotka valaisevat kulloinkin käsiteltävänä olevaa ohjelmoinnin rakennetta. Valitussa esitystavassa on erityisesti pyritty välttämään monissa oppikirjoissa noudatettua käytäntöä aloittaa ohjelmoinnin esittely ohjelmointikielen lausekerakenteen yksityiskohdista. Samalla on saavutettu pedagogisesti hedelmällinen lähtökohta, jossa opetus ja ongelmien ratkaisu voivat alusta lähtien perustua kokonaisten ja toimivien ohjelmien käyttöön.

Jatkossa käsitellään Pascal-kielestä vain eräs osa, josta käytetään nimeä PienoisPascal. Tämä osa ei missään tapauksessa ole olennaisesti vaikeampi kuin esimerkiksi kouluopetuksessa paljon käytetty BASIC. Päinvastoin, PienoisPascal on valittu Pascal-kielestä jättämällä pois monimutkaiset ja siten ohjelmoinnin perusteiden opiskelua vaikeuttavat piirteet. Mikäli ohjelmointitaitoa halutaan tämän kirjasen jälkeen vielä kehittää, on luonnollinen jatko siirtyä johonkin täydellisempään Pascal-oppikirjaan. BASIC-kielen opiskeluun verrattuna tämä on parempi vaihtoehto, koska Pascal on näistä kahdesta kielestä täydellisempi ja johdonmukaisempi ja antaa siten paremman pohjan kaikelle myöhemmälle ohjelmoinnille.

PienoisPascal tarjoaa uuden vaihtoehdon ohjelmoinnin alkeisopetukseen. Eräänä tarkoituksena onkin ollut edesauttaa Pascal-kielen käyttöönottoa kouluopetuksessa. Tätä kautta koululaiset voisivat saada entistä tukevamman pohjan atk-alan jatkoharrastukselleen.

Joensuussa helmikuussa 1985

Tekijät

Kirjasen toinen laitos on ulkoasultaan täysin uudistunut. Sisällölliset muutokset ovat vähäisiä, lähinnä havaittujen pikkuvirheiden korjauksia. Uudesta taitosta johtuen sivunumerointi on muuttunut, mutta ensimmäistä laitosta voidaan hyvin käyttää uuden rinnalla.

Teoksen verkkoversio eroaa toisesta painoksesta vain joiltakin sanamuodoiltaan, jotka johtuvat siitä, että verkkoversiossa ei ole sivunumeroita ja tekstin jakautuminen sivuille riippuu kulloinkin käytettävästä laite- ja ohjelmistoympäristöstä.

SISÄLLYSLUETTELO

ESIPUHE

1. JOHDANTO

2. TIETOKONE TIETOJENKÄSITTELIJÄNÄ

3. OHJELMOINTI PASCAL-KIELELLÄ

3.1 Ongelma ja algoritmi
3.2 Algoritmisen kielen peruskäsitteitä
3.3 Tiedon syöttö ja tulostus
3.4 Tiedon käsittely ja välitulosten säilyttäminen
3.5 Päättelyiden tekeminen
3.6 Toimintojen toistaminen
3.7 Tietojen koostaminen komponenteista
3.8 Lausekkeet

HARJOITUSTEHTÄVIEN RATKAISUT

Liite 1: Syntaksikaaviot
Liite 2: Pascalin sanasymbolit
Liite 3: Ennaltaesitellyt funktiot
Liite 4: Syntaksin välikesymbolit

1. JOHDANTO

Tietokoneet ovat elektroniikan kehittymisen ja halpenemisen myötä nousseet merkittäviksi apuvälineiksi lähes kaikessa inhimillisessä toiminnassa. Pelkkä tietokonelaitteisto on kuitenkin sinällään täysin hyödytön; toimiakseen se tarvitsee ohjelmia, jotka ohjaavat tietokoneen toimintaa ja saavat koneen täten toimimaan halutulla tavalla. Niinpä tietokoneiden hyödyntämisen kynnyskysymykseksi onkin noussut kysymys ohjelmista: kuinka niitä saadaan tehtyä riittävän halvalla, nopeasti ja hyvin, ja kuinka niistä saadaan luotettavia?

Tämän kirjasen tarkoitus on perehdyttää lukijaa tietokoneohjelmien laatimiseen eli ohjelmointiin. Jotta tietokone voisi suorittaa ohjelmia, ne on kirjoitettava jollain erityisellä ohjelmointikielellä, joita on käytössä useita satoja erilaisia. Ohjelmointikielillä on kuitenkin paljon yhteisiä piirteitä, joiden perusteella niitä voidaan ryhmitellä eri luokkiin. Jatkossa tarkastellaan ohjelmointia erityisesti algoritmisten kielten kannalta. Näissä kielissä ohjelmat muodostuvat yksityiskohtaisista ohjeista, jotka kuvaavat, kuinka haluttu tulos muodostetaan askel askeleelta.

Algoritmisilla kielillä on yhteinen perusta ajateltaessa niitä yksittäisiä askelia, joista ohjelmat muodostuvat sekä näiden askelien yhdistelyperiaatteita. Tämä perusta voidaan esittää irrallaankin, jolloin puhutaan algoritmien yleisistä muodostusperiaatteista. Tässä kirjasessa näitä periaatteita käsitellään kuitenkin tiukasti Pascal-ohjelmointikielen yhteydessä. Näin lukijalla on mahdollisuus välittömästi kokeilla eri rakenteiden toimintaa tietokoneella.

Pascal-ohjelmointikielen historia alkaa 60-luvun loppupuolelta. Silloin Niklaus Wirth kehitti Pascal-kielen lähtien sellaisista, nykyisin vähäisessä käytössä olevista kielistä kuin Algol-60, Algol-W ja Euler. Kieltä kehittäessään Wirth piti erityisesti mielessä pedagogiset näkökohdat. Niinpä syntynyt kieli sopi erityisesti ohjelmoinnin opiskeluun. Kielen ensimmäinen toteutus valmistui 1970 ja kieli julkaistiin 1971.

Ensimmäisen version koekäyttö johti joihinkin muutoksiin, ja vuonna 1973 julkaistiin uuden version kuvaus. Tämä versio muodostui käytännössä usean vuoden ajaksi kielen standardiksi ja sille perustuen Pascal on toteutettu useille tietokoneille. Version kuvaus löytyy esimerkiksi Kathleen Jensenin ja Niklaus Wirthin yhteisestä kirjasta /1/.

Vaikka Pascal täyttikin tässä vaiheessa hyvin paikkansa opetuskielenä, sen käyttökelpoisuudessa todellisessa ohjelmointityössä oli toivomisen varaa. Niinpä kansainvälinen standardointijärjestö ISO laatiessaan Pascal-kielelle standardia laajensi kieltä eräin osin, erityisesti proseduurien ja funktioiden parametrien kohdalla. Pascal-standardi /2/ valmistui vuonna 1983. Vastaavia kielen laajennuksia oli tätä ennen jo otettu mukaan kielen toteutuksiin eri tietokoneilla. Koska standardia ei silloin kuitenkaan vielä ollut, laajennukset poikkeavat jonkin verran toisistaan.

Jensenin ja Wirthin kirja muodosti käytännössä Pascal-kielen standardin ennen ISO:n laatimaa varsinaista standardia. Siten tietokoneiden esittelylehtisistä löytyvä sanonta "standardin mukainen Pascal" saattaa tarkoittaa kumpaa tahansa edellä mainituista.

Pascal yleistyi voimakkaasti opetuskielenä 70- ja 80-lukujen vaihteessa, jolloin sen käyttö korkeakoulujen ja yliopistojen opetuskielenä laajeni kaikkialla. Niinpä Suomessakin Pascal on käytössä lähes kaikkien yliopistojen ensimmäiseksi opetettavana ohjelmointikielenä. Sen sijaan Pascal-kielen käyttö koulujen opetuksessa on vielä vähäistä. Tämä johtuu osittain opettajista, joiden atk-tiedot ovat vielä varsin heiveröisiä. Osasyynä on myös koulujen määrärahojen niukkuus, jolloin hankitaan helposti halpoja laitteita, joissa hintaan sisältyy yleensä jokin muu kieli kuin Pascal.

Nyt käsillä olevassa kirjasessa käytetään ISO-standardin mukaisesta Pascal-kielestä erotettua osaa, josta käytetään jatkossa nimeä PienoisPascal. PienoisPascal sisältää Pascalkielestä sellaisen joukon piirteitä, joilla voi hyvin tehdä monia käytännöllisiä ohjelmia ja joiden hallitseminen antaa tuntuman algoritmisesta ohjelmoinnista. Piirteitä valittaessa on erityisesti painotettu yksinkertaisuutta: monimutkaiset piirteet on karsittu pois, elleivät ne ole ohjelmien kirjoittamisen kannalta aivan oleellisia.

Täydelliseen Pascal-kieleen verrattuna PienoisPascal-kielestä puuttuvat mm. tietueet, osoittimet, joukot, tiedostojen käsittely yleisessä tapauksessa, käyttäjän itsensä esittelemät funktiot ja proseduurit sekä hyppylause. Eräät näistä piirteistä ovat ensiarvoisen tärkeitä laadittaessa muita kuin aivan yksinkertaisia ohjelmia. Ne on kuitenkin jouduttu valitettavasti jättämään pois, jotta esitys saataisiin pidettyä riittävän suppeana. Jokaisen Pascal-kieltä käyttökielekseen ajattelevan onkin syytä jatkaa tämän kirjasen jälkeen koko Pascal-kielen opiskelemista.

Koska PienoisPascal on Pascal-kielen osajoukko, ovat kaikki PienoisPascal-ohjelmat samalla myös oikeita Pascal-ohjelmia. PienoisPascal-kielessä oletetaan, että aakkosjärjestyksessä peräkkäiset kirjaimet ovat peräkkäisiä myös tietokoneen sisäisessä merkkiesityksessä. Varsinainen Pascal-standardi ei tätä kuitenkaan vaadi, joten eräät esimerkkiohjelmat eivät välttämättä toimi kaikilla laitteistoilla. Käytännössä valtaosa laitteista käyttää ASCII-koodia, joka on PienoisPascalkielen oletuksen mukainen.

Kirjasen luku 2 antaa yleiskuvan tietokoneesta tietojenkäsittelijänä. Luvun tarkoituksena on toimia yleisenä johdatuksena tietokoneen rakenteeseen ja ohjelmien merkitykseen, jotta nämä seikat selvenisivät myös sellaisille lukijoille, joilla ei ole etukäteistietoja tietokoneista. Samalla luku 2 kiinnittää eräitä jatkossa tarvittavia termejä.

Kolmas luku käsittelee sitten varsinaista ohjelmointia ja PienoisPascal-kieltä. Lähes kaikkien alakohtien lopussa on harjoitustehtäviä, joiden tekeminen on ensiarvoisen tärkeää haluttaessa oppia ohjelmoimaan. Ohjelmointi on samanlainen taito kuin polkupyörällä ajaminen tai kutominen: sen oppiminen ei onnistu kirjoja lukemalla, vaan ainoastaan harjoittelemalla. Kerran kunnolla opittua taitoa on sitten helppo soveltaa aina uusissa tilanteissa. Harjoitustehtävistä oppii eniten, jos omia ratkaisujaan voi kokeilla tietokoneella. Sellaisia lukijoita varten, joilla ei ole kokeilumahdollisuutta, on kirjasen loppuun liitetty harjoitustehtävien esimerkkiratkaisut.

Kunkin alakohdan lopussa on sanastoluettelo, joka sisältää kohdan tärkeimmät termit ja niiden englanninkieliset vastineet. Luetteloilla on kaksi tarkoitusta. Ensinnäkin ne muodostavat lyhyen yhteenvedon keskeisistä käsitteistä. Lukijaa kehoitetaankin kertauksenomaisesti miettimään kunkin termin kohdalla, mitä kyseinen termi tarkoittaa. Toisaalta sanastoluettelot antavat yhteyden atk-maailman valtakieleen, englantiin. Alan kirjallisuutta on suomeksi julkaistu hyvin vähän, ja jo vaatimatonkin perehtyminen ohjelmointiin ja sitä kautta tietotekniikkaan vaatii englanninkielen hallintaa. Käytettävät suomennokset perustuvat Tietojenkäsittelyliiton julkaisemiin sanakirjoihin /3/ ja /4/.

Kirjallisuutta:

/1/ Kathleen Jensen, Niklaus Wirth: PASCAL User Manual and Report. Springer-Verlag, 1975.

/2/ Programming Languages - Pascal, ISO 7185-1983. (sama kuin British Standards Institution BS 6192:1982).

/3/ Lassi Juutinen, Kai Koskimies, Kari-Jouko Räihä, Jorma Sajaniemi: Pascal-ohjelmointikielen englantilais suomalainen sanasto. Tietojenkäsittelyliitto ry, Ota paino, 1982.

/4/ Atk-sanakirja. Tietojenkäsittelyliiton julkaisu n:o 42, Kouvolan Kirjapaino, 1980.

2. TIETOKONE TIETOJENKÄSITTELIJÄNÄ

Tietojenkäsittelyn lähtökohtana on tieto. Tietoa voidaan tarkastella monesta näkökulmasta. Toisaalta tiedolla tarkoitetaan yksittäistä tietoalkiota (data), joka on esitetty jossain sovitussa muodossa jollakin tietovälineellä. Esimerkkinä tietovälineestä olkoon äänilevy, jolle tieto on talletettu epätasaisen uran muodossa. Toisaalta tiedolla tarkoitetaan sitä ihmismielessä syntyvää käsitystä (informaatio), joka syntyy ihmisen havainnoidessa ympäristöään. Tästä on esimerkkinä se käsitys musiikillisesta teoksesta, joka syntyy ihmisen aivoissa kuunneltaessa äänilevyä.

Tietojenkäsittelyssä tietoa tarkastellaan tavallisesti datamielessä. On kuitenkin aina muistettava, että tietojenkäsittelyn perimmäisenä tarkoituksena on antaa ihmisille tai laitteille näiden päätöksentekoa avustavaa ja toimintaa ohjaavaa informaatiota. Käytännössä tämä merkitsee sitä, että tiedon eri olemukset sekoittuvat helposti. Onkin aina pidettävä mielessä ero tietokoneen käsittelemien tietoalkioiden ja ihmisen niihin liittämien tulkintojen välillä.

Esimerkkinä voidaan tarkastella lukua 24. Ihmiselle tämä luku edustaa vuorokauden tuntien lukumäärää tai jossain toisessa yhteydessä vaikkapa tietyn henkilön ikää. Tietokoneen kannalta kyseessä on kuitenkin vain kokonaisluku, jota voidaan käsitellä kuten muitakin kokonaislukuja. Luku voidaan esimerkiksi korottaa kolmanteen potenssiin - toimenpide, joka ei ole järkevä tehtäväksi iän tapauksessa. Luvun 24 tulkitseminen tavanomaiseksi kokonaisluvuksi on lisäksi vain yksi tietokoneen suhtautumistavoista. Eräissä yhteyksissä kone käsittelee tätä lukua kahtena erillisenä merkkinä, nimittäin merkkeinä '2' ja '4', joilla ei ole mitään muuta tekemistä keskenään kuin se, että ne sattuvat esiintymään peräkkäin.

Tietojenkäsittelyä voidaan tehdä periaatteessa kahdella eri tavalla: käsin tai automaattisesti. Käsin suoritettavalla tietojenkäsittelyllä on pitkät perinteet, jotka ovat johtaneet moniin tuttuihin apuvälineisiin. Tietoja on talletettu esimerkiksi kirkonkirjoihin, kalentereihin, oppikirjoihin, lukujärjestyksiin, puhelinluetteloihin ja logaritmitaulukoihin. Näiden käsittelyssä on käytetty lukemista, kirjoittamista käsin ja koneella, monistamista, painamista ja laskemista käsin tai yksinkertaisilla apuvälineillä. Näitä kaikkia toimintoja on ohjannut ja pääosin suorittanut ihminen.

Automaattisella tietojenkäsittelyllä tarkoitetaan edellä kuvattujen toimintojen suorittamista tietokoneen avulla. Periaatteessa automaattinen tietojenkäsittely ei siis tuo mitään uutta ihmisen mahdollisuuksiin. Käytännössä tietokoneiden nopeus ja kyky selvitä virheittä monivaiheisista toiminnoista kuitenkin lisäävät toimintamahdollisuuksia.

Esimerkkinä voidaan ottaa suurta laskentamäärää vaativa optimointi, jonka avulla suuressa sahalaitoksessa kukin tukki voidaan sahata yksilöllisesti edullisimmalla tavalla. Tietenkin on mahdollista palkata suuri joukko matemaatikkoja laskemaan kullekin tukille paras sahaustapa. Laskenta olisi kuitenkin niin hidasta, ettei yhden päivän aikana pystyttäisi sahaamaan montakaan tukkia. Sen sijaan tietokoneella vastaava laskenta pystytään suorittamaan siinä ajassa, mikä kuluu tukin siirtymiseen mittauskohdasta sahauslinjan alkuun.

Edellä on käynyt ilmi, että tietokone pystyy varastoimaan ja käsittelemään tietoa. Tässä tarvittavat tietokoneen pääosat ovat keskusyksikkö ja siihen liittyvät oheislaitteet. Seuraava kuva havainnollistaa tietokoneen rakennetta.

Keskusyksikkö koostuu suorittimesta (eli prosessorista) sekä keskusmuistista. Sen tehtävä on huolehtia varsinaisesta tietojenkäsittelystä. Keskusmuistissa säilytetään kulloinkin suoritettavana olevaa ohjelmaa ja sen tarvitsemia tietoja. Suoritin ohjaa tietokoneen toimintaa ja tekee kaikki tarvittavat toimenpiteet.

Oheislaitteita käytetään tietojen syöttöön, tulostukseen sekä varastointiin. Syöttölaitteista yleisin on näppäimistö, jonka avulla voidaan antaa tietoja keskusyksikön käsiteltäväksi. Tulostuslaitteita ovat esimerkiksi näyttöruutu, kirjoittimet ja kuvien tekemiseen tarkoitetut piirturit. Tietojen varastointiin käytetään levyjä, levykkeitä ja magneettinauhoja, joilla kaikilla tietoa voidaan säilyttää teoriassa rajattoman kauan.

Tiedon säilytykseen on siis käytettävissä toisaalta erilaisia oheislaitteiden käsittelemiä tietovälineitä ja toisaalta keskusmuisti. Keskusmuistista suoritin saa tarvitsemansa tiedot nopeammin kuin oheislaitteilta. Siksi kulloinkin tarvittavat tiedot säilytetään keskusmuistissa. Tietojen pysyvä taltioiminen tehdään kuitenkin oheislaitteille, sillä keskusmuistin talletuskapasiteetti on hyvin rajallinen ja muistin sisältö ei säily sähkövirran katketessa.

Erilaiset tiedonsiirtoväylät yhdistävät oheislaitteet keskusyksikköön. Väylät ovat käytännössä erilaisia kaapeleita, joiden kautta kulkevien sähköisten signaalien laatu ja käyttötapa on tarkoin määrätty.

Tietokoneen toimintaa ohjaa ohjelma, joka koostuu yksinkertaisista konekäskyistä. Ohjelma on tavallisesti talletettu jonkin oheislaitteen tietovälineelle, esimerkiksi levykeasemassa sijaitsevalle levykkeelle. Ohjelma luetaan tietovälineeltä keskusmuistiin suorittamista varten. Suoritin tutkii ohjelmaa käsky kerrallaan ja suorittaa kunkin käskyn tarkoittaman toimenpiteen.

Käsitellessään tietoja suoritin käyttää tiedoista sisäistä esitystä, joka perustuu pelkkien nollien ja ykkösten käyttöön. Tietoja syötettäessä ja tuloksia tarkasteltaessa voidaan kuitenkin käyttää ihmisen ymmärtämää esitystä, koska ohjelmat suorittavat automaattisesti muunnokset esitystavasta toiseen.

Ohjelman suoritus on tietenkin voitava käynnistää jollakin tavalla. Tämä tehdään antamalla käynnistyskomento näppäimistöltä. Komennon tulkitsee käyttöjärjestelmä, joka on tietokoneen toiminnasta vastaava erityisohjelma. Käyttöjärjestelmä huolehtii monista tavanomaisista tehtävistä kuten muiden ohjelmien lukemisesta keskusmuistiin ja oheislaitteiden yksityiskohtaisesta ohjaamisesta.

Esimerkkinä ohjelman suorittamisesta tarkastellaan tilannetta, missä halutaan saada selville tiedot koulun eri oppilaiden viimeisimmistä todistuksista. Ohjelma käynnistetään antamalla käyttöjärjestelmälle komento, jonka osana on kyseisen ohjelman nimi. Tällöin suoritin lukee ohjelman keskusmuistiin ja aloittaa sen suorituksen. Ohjelma tulostaa näyttöruudulle sopivan juonnon, esimerkiksi


        Todistustietojen kirjoittaminen.
        Anna oppilaan nimi: 

ja jää odottamaan näppäimistöltä syötettävää nimeä. Kun nimi on annettu, ohjelma vertaa annettua nimeä levykkeelle aiemmin talletettuihin tietoihin. Jos nimeä ei levykkeeltä löydy, ohjelma antaa tästä ilmoituksen näyttöön. Muuten ohjelma lukee oppilaan tiedot levykkeeltä ja kirjoittaa ne näyttöön. Nyt ohjelma voi päättää toimintansa, jolloin suoritus jatkuu käyttöjärjestelmän toimesta. Tämä puolestaan pyytää käyttäjää antamaan komennon, joka kuvaa seuraavana suoritettavaksi halutun tehtävän.

Suorittimen ohjaamisessa käytettävät konekäskyt ovat hyvin yksinkertaisia ja suorittavat kukin vain erittäin alkeellisen toimenpiteen. Niinpä konekielisten ohjelmien kirjoittaminen on hankalaa. Asia on ratkaistu kehittämällä korkeamman tason ohjelmointikieliä, joista nykyisin on enimmäkseen käytössä nk. lausekielet.

Lausekielissä tietokoneen tehtäväksi aiotut toimenpiteet ilmaistaan lauseina, jotka ovat monipuolisempia kuin yksittäiset konekäskyt ja jotka ovat lähempänä ihmisen ajattelutapaa. Yleisesti käytössä olevia lausekieliä ovat esimerkiksi Pascal, COBOL, FORTRAN ja BASIC. Nämä poikkeavat toisistaan niin tarkoitetun sovellusalueensa kuin monipuolisuutensa ja yleisten ominaisuuksiensa suhteen. Pyrittäessä vielä kauemmaksi tietokoneen toiminnasta ja lähemmäksi ihmisten tarpeita on kehitetty nk. korkean tason lausekieliä. Niiden avulla määritellään ratkaistava ongelma, mutta jätetään tietokonejärjestelmän huoleksi selvittää se, miten ratkaisuun päästään.

Koska suoritin ei kykene suorittamaan muita kuin konekielisiä, konekäskyille perustuvia ohjelmia, on lausekielten tapauksessa käytettävä jotain apuohjelmaa, jotta varsinainen lausekielellä kirjoitettu ohjelma voitaisiin suorittaa. Tällaisia apuohjelmia on periaatteessa kahta lajia: kääntäjiä ja tulkkeja.

Kääntäjällä tarkoitetaan sellaista (konekielistä) ohjelmaa, joka muuttaa lausekielisen ohjelman vastaavaksi konekieliseksi ohjelmaksi. Siten lausekielisen ohjelman suoritusta on edellettävä ohjelman käännös konekieliseksi. Jos sama ohjelma suoritetaan useaan kertaan, riittää tietenkin yksi käännös. Sama käännetty versio voidaan aina tarvittaessa lukea keskusmuistiin.

Tulkkia käytettäessä ei lausekielistä ohjelmaa varsinaisesti käännetä, se korkeintaan muunnetaan johonkin helposti käsiteltävään sisäiseen esitykseen, joka on kuitenkin lähempänä lausekielistä kuin konekielistä ohjelmaa. Tulkki on konekielinen ohjelma ja siten välittömästi suorituskelpoinen. Se aloittaa toimintansa tekemällä edelläkuvatun muunnoksen suoritettavaksi aiotulle ohjelmalle. Sen jälkeen tulkki tutkii ohjelmaa lause lauseelta ja tekee kunkin lauseen kohdalla sen tarkoittamat toimenpiteet.

Tulkin käyttäminen on kääntämiseen verrattuna hitaampaa, koska ohjelmassa suoritetaan sama lause yleensä useita kertoja, jolloin tulkki joutuu selvittämään lauseen merkityksen yhtä monta kertaa. Käännöksen tapauksessa tämä tehdään vain kerran (käännösvaiheessa) ja varsinaisessa suorituksessa toteutetaan valmiiksi konekielisiä käskyjä. Toisaalta virhetilanteissa tulkki voi antaa parempia selityksiä virheen tapahtumiskohdasta ja sen syistä, koska tulkilla on käytettävissään alkuperäinen lausekielinen ohjelma.

Kääntämisen ja tulkinnan eroja havainnollistaa seuraava kuvio. Siinä vinolaatikot kuvaavat oheislaitteilla sijaitsevia tietoja ja suorakaiteet ohjelmien suoritusta. Nuolet kuvaavat tietojen siirtoa oheislaitteilta ohjelmien käyttöön. Käytännössä toiminta voi poiketa kuvion ilmaisemasta, koska eri vaiheet on usein yhdistetty.

Sanastoa:
 
tieto data, information
tietojenkäsittely data processing
 
tietokone computer
keskusyksikkö central processing unit, CPU
keskusmuisti main storage, memory
suoritin processor
tietoväylä data bus
oheislaite peripheral device
näppäimistö keyboard
näyttöruutu display unit
kirjoitin printer
piirturi plotter
tietoväline data carrier, data medium
levy disk
levyke diskette
magneettinauha magnetic tape
 
ohjelma program
konekäsky machine instruction
konekieli machine language
lause statement
lausekieli high level language
käyttöjärjestelmä operating system
komento command
juonto prompt
kääntäjä compiler
tulkki interpreter
suorittaa execute

3. OHJELMOINTI PASCAL-KIELELLÄ

Tässä luvussa esitetään ohjelmoinnin tärkeimpiä periaatteita. Esimerkkiohjelmat kirjoitetaan Pascal-kielellä, josta tarvittava osa esitetään yksityiskohtaisen tarkasti. On kuitenkin muistettava, että käytettävä kieli on vain osa Pascal-kielestä eikä sisällä kaikkia Pascal-kielen tärkeitä rakenteita.

3.1 Ongelma ja algoritmi

Ohjelmointityön lähtökohtana on ongelma, joka on ratkaistava. Ongelma voi olla esimerkiksi koulun yhden oppilaan todistustietojen kirjoittaminen tai sinin laskeminen annetusta kulmasta.

Ongelman käsittelemisen ensimmäinen vaihe on täsmentää ongelma kaikilta yksityiskohdiltaan. Erityisesti tämä tarkoittaa ongelmaan liittyvien syöttö- ja tulostietojen täsmällistä selvittämistä. Edellä mainitussa todistustietojen tulostuksessa tulee ensimmäiseksi tarkastella syöttötietoja eli syötteitä. Näiden osalta on esimerkiksi päätettävä, ilmaistaanko haluttu oppilas nimen, henkilötunnuksen vaiko jonkin koulussa muuten käytettävän tunnuksen avulla. Edelleen on päätettävä, onko tarkoitus tulostaa kerralla yhden vaiko useamman oppilaan tiedot. Vastaavasti tulostietojen eli tulosteiden osalta on päätettävä, mitkä tiedot kustakin pyydetystä oppilaasta tulostetaan ja missä muodossa ne esitetään.

Ennen varsinaisen ohjelman kirjoittamista on myös selvitettävä, mitä toiminta-askelia ratkaisun tulee sisältää. Tällaista ongelman ratkaisemiseksi tarvittavien yksittäisten toimenpiteiden kuvausta kutsutaan algoritmiksi.

Algoritmi voi olla yksinkertainen tai monimutkainen riippuen ongelman laadusta. Todistustietoja käsittelevän esimerkkiongelman tapauksessa algoritmi on helppo laatia ja se sisältää seuraavat vaiheet:

1. Tulostetaan ohjelman toimintaa kuvaava lyhyt selitys. 2. Pyydetään käsiteltävän oppilaan nimi ja luetaan se. 3. Käydään läpi levykkeellä olevat oppilaiden tiedot. 4. Jos pyydetyn oppilaan tiedot löytyivät levykkeeltä, niin tulostetaan ne, muuten tulostetaan ilmoitus löytymättömyydestä.

Toisen esimerkkiongelman eli annetun kulman sinin laskeminen on jo selvästi vaikeampi ongelma. Laskentamenetelmän eli algoritmin keksiminen edellyttää jonkinasteisia matematiikan tietoja. Algoritmin laatiminen on kuitenkin ehdoton edellytys varsinaisen ohjelman tekemiselle, sillä ilman algoritmia ei ole käsitystä ongelman ratkaisun muodostamisesta.

Algoritmin kirjoittamiseen ei käytetä mitään erityiskieltä tai merkintätapaa, vaan normaali suomenkieli riittää. Algoritmiahan ei ole tarkoitus antaa sellaisenaan tietokoneen suoritettavaksi. Se on vain apuväline siirryttäessä ongelmasta ohjelmaan.

Yksinkertaista algoritmia ei yleensä tarvitse edes kirjoittaa paperille, koska tällöin algoritmin voi koko ohjelmoinnin ajan pitää vain mielessään. Jos algoritmi kuitenkin sisältää monimutkaisia tai vaikeasti käsitettäviä vaiheita, se on syytä kirjoittaa muistiin. Samoin pitkä algoritmi tulee kirjoittaa paperille, koska ihmisen kyky muistaa perättäisten askelten toiminnat on hyvin rajallinen. Ihmismuistin kykyä rajoittavat vielä ohjelmaa kirjoitettaessa esiintyvät, yksityiskohtiin liittyvät ongelmat, jotka vievät ajatuksen pois kokonaisuudesta.

Aivan yksinkertaisia algoritmeja lukuunottamatta tulee ennen varsinaisen ohjelman kirjoittamista vakuuttautua algoritmin oikeellisuudesta. On siis osoitettava, että algoritmi tekee sen, mitä siltä odotetaan. Koska jatkossa paneudutaan vain ohjelmoinnin alkeisiin, tulevat algoritmitkin olemaan niin yksinkertaisia, ettei varsinaisia oikeellisuustodistuksia tarvita.

Edellisen perusteella voidaan ohjelmointiin sisällyttää seuraavat vaiheet:

1. Ongelman täsmällinen selvittäminen 2. Algoritmin suunnittelu 3. Algoritmin oikeellisuuden toteaminen 4. Algoritmin muuntaminen ohjelmaksi

Käytännössä näitä vaiheita joudutaan usein toistamaan. Laadittu algoritmi ei ehkä ole tarpeeksi yksityiskohtainen ohjelman tekemistä varten, jolloin joudutaan palaamaan kohdasta neljä kohtaan kaksi. Toinen uudelleenaloittamisen syy voi olla syntyneen ohjelman hitaus: sama ongelma voidaan tietenkin ratkaista usealla eri tavalla, joita vastaavat ohjelmat voivat käyttää suoritusaikaa ja muistitilaa aivan eri määriä. Algoritmia parantamalla voidaan siten päästä nopeampaan ohjelmaan, jonka käyttö on siten miellyttävämpää.

Edellä kuvattu ohjelmoinnin vaihejako kuvastaa käytännön työelämää ajatellen itse asiassa vasta ohjelman elinkaaren alkuosan vaiheita. Varsin tärkeä osuus on lisäksi ensin testaus- ja myöhemmin ylläpitovaiheella. Testauksessa ohjelmasta pyritään löytämään ja poistamaan siinä vielä mahdollisesti olevat virheet. Ylläpidossa taas ohjelmaa korjataan vastaamaan muuttuvia käyttöolosuhteita. Nyt käsillä olevan suppean esityksen yhteydessä ei kuitenkaan ole mahdollista paneutua syvällisesti näihin ohjelmoinnin osa-alueisiin.

Sanastoa:
 
ongelma problem
algoritmi algorithm
 
syöte input
tuloste output
 
oikeellisuus correctness
testaus testing
ylläpito maintenance

3.2 Algoritmisen kielen peruskäsitteitä

Tässä kohdassa esitetään eräitä Pascal-ohjelmien peruskäsitteitä, joita tarvitaan myöhemmin jatkuvasti. Ohjelmien laatimiseen ja kielen hienouksiin ei vielä puututa.

Tarkastellaan ensimmäisenä täydellistä Pascal-ohjelmaa, joka tulostaa näyttöön tekstin TERVE. Mitään muuta ohjelma ei tee.


program tervehdys (output);                  
begin                                        
    (* Tämä ohjelma tulostaa tekstin TERVE *)
    writeln('TERVE')                         
end.                                         

Ohjelman ensimmäisenä rivinä on otsikko, joka sisältää ohjelman nimen. Tässä tapauksessa ohjelman nimenä on siis tervehdys. Lisäksi otsikossa esiintyvä tunnus output ilmaisee, että ohjelma suorittaa tulostusta näyttöruudulle. Ohjelman varsinaisen suoritettavan osan aloittaa sana begin ja sen päättää sana end. Ohjelmatekstin loppumisen ilmoittaa viimeinen piste.

Suoritettavan osan ensimmäinen rivi, joka alkaa merkkijonolla (* Tämä ohjelma ..., on pelkkä selite. Sillä ei ole mitään merkitystä varsinaisen ohjelman kannalta vaan se on kirjoitettu vain, jotta lukijan olisi helpompi ymmärtää ohjelman tarkoitus ja toiminta. Seuraava rivi sisältääkin sitten tulostuslauseen, joka aiheuttaa tekstin TERVE tulostamisen.

Pascal-ohjelma koostuu tekstialkioista, joiden luonne on ymmärrettävä, jotta pystyttäisiin kirjoittamaan virheettömiä ohjelmia. Ohjelmatekstin pääosan muodostavat tunnukset, sanasymbolit ja erikoissymbolit.

Tunnukset ovat ohjelmoijan antamia nimiä ohjelmassa esiintyville olioille. Yleensä ohjelmassa esitellään kukin tunnus ja sen merkitys erikseen. Eräät tunnukset ovat ennaltaesiteltyjä. Tämä merkitsee sitä, että niillä on valmiiksi tietty merkitys, joka voidaan kuitenkin kumota antamalla tunnukselle ohjelmassa toinen merkitys. Tällainen ennaltaesitelty tunnus on output edellisessä ohjelmassa. Ennaltaesiteltyjä tunnuksia lukuunottamatta tunnukset voidaan valita vapaasti. Ainoastaan niiden muoto on rajoitettu: tunnuksen tulee koostua numeroista sekä kirjaimista ja ensimmäisenä on oltava kirjain. Suuri kirjain ja vastaava pieni kirjain katsotaan samaksi. Siten esimerkiksi tunnus Korkeus on sama kuin tunnus korkeus tai KORKEUS.

Sanasymbolit ovat tunnusten kaltaisia, mutta niillä on kiinteä merkitys. Tällaisia ovat esimerkiksi symbolit begin ja end. Näille ei siis voi antaa ohjelmassa uuttaa merkitystä. Liitteessä 2 on lueteltu Pascalin sanasymbolit.

Erikoissymbolit ovat yksittäisiä erikoismerkkejä tai kahden merkin muodostamia ryhmiä. Esimerkkiohjelmassa tällaisia ovat mm. alkusulku, loppusulku, puolipiste ja piste. Erikoissymboleiden käytölle on hyvin tarkat säännöt, joita on ehdottomasti noudatettava.

Ohjelmaan kirjoitettavat selitteet eivät vaikuta millään tavalla ohjelman suoritukseen. Niiden tarkoitus on avustaa ohjelman lukijaa ohjelman toiminnan ymmärtämisessä. Siksi ohjelmaa vastaavan algoritmin vaiheet tulisi ilmaista ohjelmassa olevilla selitteillä. Näiden lisäksi tulee selitteillä kuvata ohjelmassa esiteltävät tunnukset ongelman kannalta. Jatkossa tullaan näkemään, että esittelyt sisältävät ohjelman kannalta kaiken tarpeellisen tiedon, mutta yhteys alkuperäiseen ongelmaan ei ole aina riittävän selvä ilman selitteiden käyttöä.

Ohjelman alkuun on lisäksi syytä laittaa selite, jossa kerrotaan ohjelman tarkoitus, tekijä ja laatimisen päivämäärä. Mikäli ohjelmaan myöhemmin tehdään muutoksia, jatketaan tätä selitettä lisäämällä muutoksen tekijän nimi ja muutoksen päivämäärä.

Selite aloitetaan erikoissymbolilla (* ja lopetetaan erikoissymbolilla *). Koska nämä ovat kahden merkin muodostamia symboleita, tulee merkit kirjoittaa yhteen. Selitteen aloittamiseen ja lopettamiseen voidaan myös käyttää symboleita { ja }.

Edellä on monesti viitattu niihin yksityiskohtaisiin sääntöihin, joiden mukaisesti Pascal-ohjelmat on kirjoitettava. Tätä ohjelmien ulkoasuun liittyvää säännöstöä kutsutaan kielen syntaksiksi ja se kertoo, millaiset ovat kääntäjän tai tulkin mielestä virheettömiä ohjelmia ja siten suorituskelpoisia. Miten suoritus sitten etenee, riippuu tietenkin kielen eri rakenteiden merkityksestä. Kunkin rakenteen merkitystä sanotaan sen semantiikaksi. Syntaktisesti virheetön ohjelma voi olla semanttisesti virheellinen, jolloin virhe ilmenee vasta suoritusvaiheessa. Esimerkkinä olkoon lauseke a/b (siis luku a jaettuna luvulla b). Tämä on syntaktisesti virheetön lauseke. Sen suoritus johtaa kuitenkin semanttiseen virheeseen, jos b:llä on arvo 0, sillä jakolaskun tulos on nollalla jaettaessa määrittelemätön.

PienoisPascalin syntaksi annetaan jatkossa kahdella eri tavalla. Luvussa 3 käytetään erityisiä syntaktisia sääntöjä ja liitteessä 1 syntaksikaavioita. Näillä molemmilla tavoilla ilmaistaan täsmälleen sama asia. Henkilökohtaisten mieltymysten mukaan jokainen voi valita itselleen sopivimman tavan.

Syntaktisista säännöistä olkoon esimerkkinä tunnuksen määrittelevät säännöt.


tunnus = kirjain { kirjain | numero } .

kirjain = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |
            "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |
            "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" .

numero = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" .

Tässä tunnus, kirjain ja numero ovat nk. välikesymboleja, jotka eivät sellaisenaan esiinny ohjelmassa ja joille kullekin on pisteeseen päättyvä sääntö. Sääntö kuvaa sen, millaiset eri mahdollisuudet välikesymbolilla on. Esimerkiksi numero voi olla mikä tahansa yksittäisistä numeromerkeistä 0, 1, ..., 9.

Säännöissä välikesymbolit esiintyvät sellaisenaan. Päätesymbolit eli sellaiset symbolit, jotka esiintyvät lopullisessa ohjelmassa, ympäröidään lainausmerkein. Lisäksi säännöissä esiintyy metasymboleja eli symboleja, jotka kuuluvat sääntöön eivätkä kuvattavaan ohjelmanosaan. Metasymbolit ovat seuraavat:

| erottaa eri vaihtoehtoja
{ x } x voi esiintyä nolla tai useampia kertoja
[ x ] x voi esiintyä kerran tai olla esiintymättä
( x | y ) joko x tai y

Edellisiä sääntöjä käyttämällä saadaan aikaan esimerkiksi tunnus kemia2a muodostamalla ensimmäisen säännön avulla ensin välikesymboleista koostuva jono

kirjain kirjain kirjain kirjain kirjain numero kirjain

ja soveltamalla tämän jälkeen toista sääntöä useasti (kerran kullekin kirjain-välikesymbolille) ja kolmatta sääntöä kerran (välikesymbolille numero). Näin saadaan haluttu tunnus kemia2a.

Koko ohjelma kuvataan seuraavalla syntaktisella säännöllä:


ohjelma = ohjelman-otsikko ";" ohjelman-lohko "." .

Verrattaessa tätä sääntöä aiempaan esimerkkiohjelmaan huomataan, että sääntö on kirjoitettu yhdelle riville vaikka esimerkkiohjelma on usean rivin mittainen. Ohjelman otsikko ja tätä seuraava puolipiste on kirjoitettu ensimmäiselle riville. Ohjelman lohko ja ohjelman lopettava piste käsittävät puolestaan useita rivejä.

Pascal-kieli ei itse asiassa millään lailla rajoita ohjelman sijoittelemista riveille vaan uusi rivi voidaan aloittaa milloin tahansa. Kielen kannalta uuden rivin aloittaminen vastaa tavanomaista sanaväliä, joka sekin saa olla kuinka pitkä tahansa.

On kuitenkin selvää, että ohjelmaa kirjoitettaessa tulee pyrkiä selkeyteen ja johdonmukaisuuteen. Vaikka seuraava ohjelma toimii aivan samoin kuin edellinenkin, on siihen tutustuminen kuitenkin paljon hankalampaa.


                    program tervehdys(                 
output                                          );begin
    (* Tämä                                  ohjelma   
tulostaa tekstin                                       
                        TERVE *) writeln('TERVE'       
                           )                           
                    end  .                             

Jatkossa annettavat esimerkit ovat samalla esimerkkejä ohjelman ulkoasun muotoilemisesta. Niissä käytetään porrastusta kuvaamaan sisäkkäisiä rakenteita. Toisin sanoen kunkin rakenteen alirakenteet alkavat sisemmältä kohdalta riviä. Samoin rakenteiden pääosat sijoitetaan systemaattisella tavalla.

Ohjelman otsikko sisältää tunnuksen, joka on ohjelman nimi. Lisäksi ohjelman otsikossa on yleensä sulkujen ympäröimä tunnuslista, joka ilmaisee sen, sisältääkö ohjelma tietojen syöttöä ja tulostusta:


ohjelman-otsikko = "program" tunnus [ "(" ohjelman-parametrit ")" ] .

ohjelman-parametrit = tunnuslista .

tunnuslista = tunnus { "," tunnus } .

Ohjelman lohko sisältää määrittely- ja esittelyosia sekä lauseosan. Lauseosa puolestaan sisältää ohjelman toiminnan kuvaavat lauseet. Esimerkkiohjelmassa lauseita oli vain yksi, nimittäin tulostuslause, joka on erikoistapaus syntaktisissa säännöissä esiintyvästä proseduurilauseesta. Jos lauseita on useita, ne erotetaan toisistaan puolipistein.


ohjelman-lohko = lohko .

lohko = vakionmäärittelyosa tyypinmäärittelyosa muuttujien-esittelyosa lauseosa .

lauseosa = yhdyslause .

yhdyslause = "begin" lausejono "end" .

lausejono = lause { ";" lause } .

lause = yksinkertainen-lause | rakenteinen-lause .

yksinkertainen-lause = tyhjä-lause | sijoituslause | proseduurilause.

Ohjelmissa tarvitaan usein vakioita eli kiinteitä arvoja, jotka eivät muutu ohjelman suorituksen aikana. Tällaisille vakioille kannattaa antaa nimi: vakioon viittaaminen on helpompaa ja selvempää, ja lisäksi tarkoitetun arvon mahdollisesti muuttuessa on muutoksen tekeminen ohjelmaan nopeampaa ja helpompaa.

Tarkastellaan esimerkkinä vakioiden nimeämisestä teatterin lipunmyyntiä avustavan ohjelman suunnittelua. Jos teatterissa on 20 tuoliriviä ja kussakin rivissä 20 tuolia, niin ohjelmassa tarvitaan arvoa 20 varmasti useassa kohdassa. Ohjelmaa tarkastelemalla ei kuitenkaan ole aina selvää, milloin luku 20 tarkoittaa rivien määrää ja milloin taas tuolien määrää yhdellä rivillä. Jos teatteria uudistetaan lisäämällä yksi uusi tuolirivi, niin vastaavan muutoksen tekeminen ohjelmaan on vaikeaa. Onhan nyt muutettava osa luvun 20 esiintymisistä luvuksi 21 ja osa on puolestaan jätettävä ennalleen. Muutos on kuitenkin helppo, jos ohjelmaan on jo alun pitäen otettu kaksi vakiota RivienLkm ja TuolienLkm, jotka molemmat on määritelty vakioksi 20. Nyt muutoksen tekemiseen riittää toisen määrittelyn muuttaminen.

Toisena esimerkkinä voidaan ottaa ohjelma, joka tulostaa päivien lukumäärän yhdessä, kahdessa, kolmessa ja neljässä vuodessa. Yhden vuoden päivien lukumäärä on otettu vakioksi, josta muut lukumäärät saadaan kertolaskulla. Pascal-kielessä kertolaskua merkitään tähdellä (*) joka on aina merkittävä kertojan ja kerrottavan väliin.


program paivat (output);                                  
(*                                                        
        Vuosien muuttaminen päiviksi                      
        30.11.84   Matti Meikäläinen                      
*)                                                        
const vuosi = 365;                                        
begin                                                     
    writeln('Yhdessä  vuodessa on ', vuosi, ' päivää.');  
    writeln('Kahdessa vuodessa on ', 2*vuosi, ' päivää.');
    writeln('Kolmessa vuodessa on ', 3*vuosi, ' päivää.');
    writeln('Neljässä vuodessa on ', 4*vuosi, ' päivää.');
end.                                                      

Kukin tulostuslause aiheuttaa yhden rivin tulostumisen näyttöön. Rivit koostuvat osista, jotka erotetaan tulostuslauseessa toisistaan pilkulla (pilkku ei tulostu näyttöön). Tulostettavina osina esiintyy heittomerkeillä ympäröityjä tekstejä, jotka tulostuvat näyttöön sellaisinaan, sekä lausekkeita, joiden arvo lasketaan ennen varsinaista tulostamista. Esimerkkiohjelman suorituksesta syntyy siten seuraava tulostus:


        Yhdessä  vuodessa on        365 päivää.
        Kahdessa vuodessa on        730 päivää.
        Kolmessa vuodessa on       1095 päivää.
        Neljässä vuodessa on       1460 päivää.

Esimerkistä näkyy yksi vakionmäärittely. Niitä voi sisältyä vakionmäärittelyosaan useitakin seuraavan syntaksin mukaisesti.


vakionmäärittelyosa = [ "const" vakionmäärittely ";"
                        { vakionmäärittely ";" } ] .

vakionmäärittely = tunnus "=" vakio .

Edellisissä säännöissä on esiintynyt joukko välikesymboleja, joille ei ole vielä annettu syntaktisia sääntöjä. Nämä tulevat esille myöhemmin, kun kyseisiä rakenteita käsitellään tarkemmin. Tässä esitetään enää tyhjän lauseen sääntö.


tyhjä-lause = .

Tyhjä lause ei sisällä mitään eikä sen suoritus aiheuta mitään toimintaa. Tyhjä lause on olemassa lähinnä sitä varten, että ohjelmien syntaktisen ulkoasun suhteen olisi eräitä vapauksia. Esimerkiksi lausejonon syntaktinen sääntö ilmaisee, että lausejonoon kuuluvat lauseet on erotettava toisistaan puolipistein. Siten jonon viimeisen lauseen perässä ei enää olekaan puolipistettä. Kuitenkin edellisessä esimerkissä viimeisen tulostuslauseen perässä on puolipiste! Tämä on selitettävissä siten, että esimerkin viimeinen tulostuslause ei olekaan lausejonon viimeinen lause vaan toiseksi viimeinen. Viimeinen lause on nimittäin tyhjä lause, joka ei tietenkään näy mistään muusta kuin sitä edeltävästä puolipisteestä.

Syntaktisissa säännöissä tullaan myöhemmin näkemään välikesymboleja, jotka tuntuvat tarpeettomilta välivaiheilta pyrittäessä päätesymboleihin. Nämä ovat mukana sen takia, että käytettävät säännöt on muodostettu Pascal-standardin säännöistä jättämällä tiettyjä vaihtoehtoja pois. Niinpä mukana on nyt sääntöjä, joissa on vain yksi "vaihtoehto".

Harjoitustehtäviä:

1. Laadi ohjelma, joka tulostaa seuraavan aforismin:


        Henkilölle, jonka ainoa työkalu on vasara,
        jokainen ongelma näyttää naulalta.
                      E. W. Dijkstra

2. Villen viikkoraha on seitsemän markkaa ynnä kaksi markkaa kultakin sellaiselta päivältä, jona hän on itse sijannut sänkynsä. Laadi ohjelma, joka tulostaa Villen viikkorahan eri mahdollisuudet.

Sanastoa:
 
otsikko heading
selite comment
lohko block
parametri parameter
lista list
yhdyslause compound statement
 
tekstialkio lexical token
tunnus identifier
sanasymboli word symbol
erikoissymboli special symbol
 
esitellä declare
ennaltaesitelty predeclared
määritellä define
vakio constant
 
syntaksi syntax
semantiikka semantics
välikesymboli non-terminal symbol
päätesymboli terminal symbol
metasymboli metasymbol

3.3 Tiedon syöttö ja tulostus

Edellä on ollut esimerkkejä ohjelmista, jotka suorittavat tulostusta. Koska näillä ohjelmilla ei ole mitään syöttötietoja, ne toimivat välttämättä jokaisella suorituskerrallaan täsmälleen samalla tavalla. Niinpä ne tulostavat aina täsmälleen samat rivit. Käytännössä tällaisia ohjelmia tarvitaan hyvin harvoin. Normaalisti ohjelmat ottavat joitakin syötteitä, jotka sitten määräävät ohjelman jatkotoiminnan ja sen tuottamat tulosteet.

Jotta ohjelma voisi tarkastella saamiaan syötteitä, sen tulee sijoittaa nämä talteen johonkin sopivaan paikkaan. Tällaisina talletuspaikkoina käytetään muuttujia, jotka esitellään ohjelmassa erityisessä muuttujien esittelyosassa. Muuttuja on vakion kaltainen siinä suhteessa, että muuttujalla on tunnus. Se kuitenkin eroaa vakiosta siinä suhteessa, että muuttujalla ei ole kiinteää arvoa, vaan sen arvo voi vaihdella ohjelman suorituksen kuluessa. Muuttujalle voidaan antaa uusi arvo esimerkiksi lukemalla se syöttötietona. Alunperin muuttujan arvo on määrittelemätön.

Tarkastellaan esimerkkinä ohjelmaa, joka laskee kauppiaalle tavaran liikevaihtoverollisen hinnan, kun kauppias antaa syöttötietona verottoman hinnan. Ohjelma perustuu tiedolle, että liikevaihtovero on 19.05% verottomasta hinnasta.


program liikevaihtovero (input, output);                   
(*                                                         
        Liikevaihtoveron laskeminen                        
        30.11.84   Matti Meikäläinen                       
*)                                                         
                                                           
const LvvKerroin = 0.1905;                                 
                                                           
(* Esitellään muuttuja VerotonHinta.                       
   Ennaltaesitelty tunnus real tarkoittaa, että            
   muuttujan arvo voi olla mikä tahansa desimaaliluku.     
*)                                                         
var VerotonHinta : real;                                   
                                                           
begin                                                      
    writeln('Liikevaihtoverollisen hinnan laskeminen.');   
    writeln('Anna veroton hinta:');                        
                                                           
    (* Nyt on näyttöön tulostunut verotonta hintaa pyytävä 
       teksti. Syötettävä luku luetaan readln-lauseella    
       muuttujan VerotonHinta arvoksi.                     
    *)                                                     
    readln(VerotonHinta);                                  
                                                           
    (* Lopuksi tulostetaan veroton hinta, lv-vero ja       
       verollinen hinta.                                   
    *)                                                     
    writeln('Veroton hinta    ', VerotonHinta : 8 : 2);    
    writeln('Liikevaihtovero  ',                           
      LvvKerroin * VerotonHinta : 8 : 2);                  
    writeln('Verollinen hinta ',                           
      (1 + LvvKerroin) * VerotonHinta : 8 : 2)             
end.                                                       

Seuraavassa on esimerkki ohjelman toiminnasta. Ohjelman tulostamat tiedot näkyvät sellaisinaan, ohjelman käyttäjän syöttämät tiedot näkyvät alleviivattuina.


        Liikevaihtoverollisen hinnan laskeminen.
        Anna veroton hinta:
        49.50
        Veroton hinta       49.50
        Liikevaihtovero      9.43
        Verollinen hinta    58.93

Kuten esimerkkiohjelmasta käy ilmi, kirjoitetaan muuttujien esittelyosa vakioiden määrittelyosan ja lauseosan väliin. Muuttujien esittelyosa alkaa sanasymbolilla var, jota seuraa joukko puolipisteeseen päättyviä muuttujien esittelyjä seuraavien sääntöjen mukaisesti.


muuttujien-esittelyosa = [ "var" muuttujien-esittely ";"
                           { muuttujien-esittely ";" } ] .

muuttujien-esittely = tunnuslista ":" tyyppi-ilmaus .

tyyppi-ilmaus = tyypin-tunnus | tyyppikuvaus .

tyypin-tunnus = tunnus .

Muuttujien esittelyssä annetaan lista pilkuilla toisistaan erotettuja tunnuksia sekä näiden perässä kaksoispisteellä erotettuna esiteltävien muuttujien tyyppi. Tyyppejä käsitellään tarkemmin seuraavassa kohdassa. Tässä yhteydessä riittävät tyypit integer ja real. Tyyppiä integer oleva muuttuja voi saada arvokseen kokonaislukuja (esimerkiksi 0, 58, -170). Tyyppiä real oleva muuttuja voi saada arvokseen desimaalilukuja, joita tavataan tietojenkäsittelyssä kutsua reaaliluvuiksi. Reaaliluvuissa käytetään desimaalipilkun sijasta pistettä. Esimerkiksi 3.14159 on tyyppiä real oleva luku.

Esimerkkinä syntaktisesti virheettömästä muuttujien esittelyosasta olkoon seuraava:


var TuntiPalkka : real;                    
    NormaaliTunnit, YlityoTunnit : integer;
    yhteensa : real;                       

Esimerkkiohjelman tulostuslauseissa on käytetty uutta muotoa VerotonHinta:8:2. Tämä tarkoittaa, että reaalimuuttujan VerotonHinta arvo halutaan tulostettavaksi kahdeksalla merkillä siten, että desimaalipisteen perässä on kaksi numeroa. Tulostettavan luvun ollessa lyhyempi kuin kahdeksan numeroa tulostuu alkuun tarvittava määrä tyhjää.

Vastaavasti voidaan kokonaisluvun yhteydessä käyttää muotoa NormaaliTunnit:5 tarkoittamaan, että asianomainen luku on tulostettava viidellä merkillä. Jos luku on niin suuri, että se vaatii enemmän merkkejä, niin silloin käytetään niin monta kuin tarvitaan. Mikäli tulostuksessa käytettävien merkkien määrää ei ole ilmaistu ohjelmassa (kuten oli laita aiemmissa esimerkeissä), niin määrä on jokin kiinteä luku, joka voi vaihdella eri tietokoneilla.

Tulostuslauseissa esiintynyt tunnus writeln on ennaltaesitelty proseduurin tunnus. Writeln on siis proseduuri, mikä tarkoittaa, että siihen liittyy tietty toimintakokonaisuus. Proseduuri writeln tulostaa yhdelle riville ne alkiot, jotka ovat sulkujen ympäröiminä tulostuslauseessa. Samankaltainen on proseduuri write, joka myös tulostaa annetut alkiot, mutta joka ei suorita siirtymistä uuden rivin alkuun. Siten syöttötietojen pyyntö voidaan suorittaa proseduurilla write, jolloin tietoja pyytävä teksti ja syötettävä tieto tulevat näytössä samalle riville.

Otetaan esimerkkinä ohjelma, joka laskee työntekijän palkan, kun syöttötietoina annetaan tuntipalkka, normaalipalkkaiset työtunnit, 50%:lla korotetut ylityötunnit ja 100%:lla korotetut ylityötunnit. Ohjelmassa on varustauduttu siihen, että korotusprosentit voivat tulevaisuudessa muuttua.


program palkka (input, output);                              
(*                                                           
        Palkan laskeminen                                    
        30.11.84   Matti Meikäläinen                         
*)                                                           
                                                             
const                                                        
    pros1 = 50;   (* Ensimmäinen korotusprosentti *)         
    pros2 = 100;  (* Toinen korotusprosentti *)              
                                                             
var tp : real; (* Tuntipalkka *)                             
    NormTunnit, Pros1Tunnit, Pros2Tunnit : integer;          
                                                             
begin                                                        
    writeln('Palkan laskeminen.');                           
                                                             
    write('Anna tuntipalkka: '); readln(tp);                 
    write('Anna normaalitunnit: '); readln(NormTunnit);      
    write('Anna ', pros1 : 3, '%:lla korotetut tunnit: ');   
    readln(Pros1Tunnit);                                     
    write('Anna ', pros2 : 3, '%:lla korotetut tunnit: ');   
    readln(Pros2Tunnit);                                     
                                                             
    writeln('Normaalitunnit    ', NormTunnit : 3,            
      '*', tp : 7 : 2, '        ',                           
      NormTunnit * tp : 7 : 2);                              
    writeln(pros1 : 3, '%:lla korotetut', Pros1Tunnit : 3,   
      '                ',                                    
      Pros1Tunnit * tp * (100 + pros1) / 100 : 7 : 2);       
    writeln(pros2 : 3, '%:lla korotetut', Pros2Tunnit : 3,   
      '                ',                                    
      Pros2Tunnit * tp * (100 + pros2) / 100 : 7 : 2);       
    writeln('                                   ----------');
    writeln('Yhteensä                             ',         
      NormTunnit * tp +                                      
      (Pros1Tunnit * tp * (100 + pros1) / 100) +             
      (Pros2Tunnit * tp * (100 + pros2) / 100) : 7 : 2)      
end.                                                         

Ohjelman suoritus antaa seuraavanlaisen tuloksen.


        Palkan laskeminen.
        Anna tuntipalkka: 23.50
        Anna normaalitunnit: 37
        Anna  50%:lla korotetut tunnit: 8
        Anna 100%:lla korotetut tunnit: 3
        Normaalitunnit     37*  23.50         869.50
         50%:lla korotetut  8                 282.00
        100%:lla korotetut  3                 141.00
	                                   ----------
        Yhteensä                             1292.50

Alkion tulostuksessa voidaan käytettävien merkkien määrä ilmaista myös heittomerkein ympäröidyn tekstin kohdalla. Tällaiselle on käyttöä esimerkiksi tulostettaessa riville pitkä tyhjä tila muodolla ' ':20. Kaksoispisteen perässä saa olla muukin lauseke kuin pelkkä vakioarvo, siis esimerkiksi muuttuja. Tätä käytetään hyväksi seuraavassa ohjelmassa, joka tulostaa kaksi neliötä halutun matkan päähän toisistaan.


program neliot (input, output);                        
(*                                                     
        Neliöiden tulostaminen.                        
        30.11.84   Matti Meikäläinen                   
*)                                                     
                                                       
var vali : integer; (* Tulostettavien neliöiden väli *)
                                                       
begin                                                  
    writeln('Kahden neliön tulostaminen.'); writeln;   
    write('Anna neliöiden välimatka: '); readln(vali); 
    writeln;                                           
                                                       
    writeln('***', ' ' : vali, '***');                 
    writeln('* *', ' ' : vali, '* *');                 
    writeln('***', ' ' : vali, '***')                  
end.                                                   

Ohjelmassa on käytetty proseduuria writeln ilman tulostettavia alkioita. Tällöin proseduuri tulostaa tyhjän rivin. Ohjelman suoritus antaa esimerkiksi seuraavan tuloksen.


        Kahden neliön tulostaminen.

Anna neliöiden välimatka: 7

*** *** * * * * *** ***

Proseduuri readln lukee arvot pyydetyille muuttujille ja jättää syöttörivin loppuosan huomiotta. Jos myös loppuosa sisältää tarpeellisia tietoja, voidaan alkuosa lukea proseduurilla read. Se toimii muuten kuten readln, mutta jättää syöttörivin loppuosan koskematta. Loppuosa voidaan sitten lukea tarvittaessa myöhemmin.

Syöttö- ja tulostuslauseiden syntaktiset säännöt ovat seuraavat:


proseduurilause = proseduurin-tunnus ( read-parametrilista |
                    readln-parametrilista | write-parametrilista |
                    writeln-parametrilista ) .

proseduurin-tunnus = tunnus .

read-parametrilista = "(" muuttujaviittaus { "," muuttujaviittaus } ")" .

readln-parametrilista = [ "(" muuttujaviittaus { "," muuttujaviittaus } ")" ] .

muuttujaviittaus = kokonainen-muuttuja | komponenttimuuttuja .

kokonainen-muuttuja = muuttujan-tunnus .

muuttujan-tunnus = tunnus .

write-parametrilista = "(" kirjoitusparametri { "," kirjoitusparametri } ")" .

writeln-parametrilista = [ "(" kirjoitusparametri { "," kirjoitusparametri } ")" ] .

kirjoitusparametri = lauseke [ ":" lauseke [ ":" lauseke ] ] .

Säännöistä ilmenee, että proseduureilla readln ja writeln voi olla tyhjä parametrilista. Ne voivat siis esiintyä ohjelmassa sellaisenaan ilman parametreja (eli sulkumerkkien sisässä lueteltuja alkioita). Sen sijaan proseduureilla read ja write tulee olla ainakin yksi luettava tai tulostettava alkio.

Harjoitustehtäviä:

1. Oletetaan, että vuotuinen inflaatio on 6%. Laadi ohjelma, joka lukee syöttötietona rahasumman ja tulostaa vastaavan summan yhden ja kahden vuoden inflaatiokorjauksella.

2. Pekan viikkoraha on 4 markkaa, jonka lisäksi hän saa jokaisesta roskien ulosviennistä 30 penniä ja jokaisesta tiskaamisesta 80 penniä. Näin kertyneestä summasta Pekka saa käteen 70% ja loput on pantava pankkiin. Laadi ohjelma, joka tulostaa viikkorahan tiedot kun sille annetaan syötteenä roskien ulosvientikerrat ja tiskaamiskerrat.

3. Laadi ohjelma, joka tulostaa seuraavan kompassiruusun syöttötietona annetun matkan päähän vasemmasta laidasta.


          P

L + I

E

Sanastoa:
 
muuttuja variable
tyyppi type
 
proseduuri procedure

3.4 Tiedon käsittely ja välitulosten säilyttäminen

Palkanlaskentaesimerkisssä jouduttiin samat laskutoimitukset suorittamaan useaan kertaan, kun korotettujen tuntien palkka tulostettiin sinällään ja toisaalta summattiin kokonaispalkkaan. Tällainen useassa paikassa tarvittava arvo voidaan sijoittaa muuttujan arvoksi käyttämällä sijoituslausetta. Esimerkiksi


Palkka := NormTunnit * tp

sijoittaa muuttujalle Palkka sijoitusmerkin := oikella puolella olevan lausekkeen arvon, siis tuntipalkan kerrottuna normaalipalkkaisten tuntien määrällä. Muuttujan arvoa voidaan tämän jälkeen muuttaa joko uudella sijoituslauseella tai lukemalla muuttujalle uusi arvo syöttölauseella.

Sijoituslauseessa sama muuttuja voi esiintyä sekä sijoitusmerkin vasemmalla että sen oikealla puolella. Sijoituksen oikealla puolella muuttuja tarkoittaa vanhaa arvoaan eli sitä arvoa, joka muuttujalla oli sijoituslauseen suorituksen alkaessa.

Muuttujan Palkka arvoa voidaan nyt kasvattaa vaikkapa "hyvän miehen lisällä", jonka oletetaan olevan 100 markkaa, seuraavasti:


Palkka := Palkka + 100

Sijoituslauseen syntaksi on lyhyesti seuraava:


sijoituslause = muuttujaviittaus ":=" lauseke .

Palkanlaskuesimerkki voidaan nyt kirjoittaa seuraavana, aiempaa selvempänä ohjelmana.


program palkka (input, output);                           
(*                                                        
      Palkan laskeminen. Toinen versio.                   
      30.11.84   Matti Meikäläinen                        
      13.12.84   Heikki Heikäläinen                       
*)                                                        
                                                          
const                                                     
    pros1 = 50;   (* Ensimmäinen korotusprosentti *)      
    pros2 = 100;  (* Toinen korotusprosentti *)           
                                                          
var tp,             (* Tuntipalkka *)                     
    kp,             (* Korotetun osan palkka *)           
    palkka : real;  (* Kokonaispalkka *)                  
    NormTunnit, Pros1Tunnit, Pros2Tunnit : integer;       
                                                          
begin                                                     
    writeln('Palkan laskeminen.');                        
                                                          
    write('Anna tuntipalkka: '); readln(tp);              
    write('Anna normaalitunnit: '); readln(NormTunnit);   
    write('Anna ', pros1 : 3, '%:lla korotetut tunnit: ');
    readln(Pros1Tunnit);                                  
    write('Anna ', pros2 : 3, '%:lla korotetut tunnit: ');
    readln(Pros2Tunnit);                                  
                                                          
    palkka := NormTunnit * tp; (* Normaalituntien osuus *)
    writeln('Normaalitunnit    ', NormTunnit : 3,         
      '*', tp : 7 : 2, '        ', palkka : 7 : 2);       
                                                          
    kp :=  Pros1Tunnit * tp * (100 + pros1) / 100;        
    palkka := palkka + kp;                                
    writeln(pros1 : 3, '%:lla korotetut', Pros1Tunnit : 3,
      ' ' : 16, kp : 7 : 2);                              
                                                          
    kp :=  Pros2Tunnit * tp * (100 + pros2) / 100;        
    palkka := palkka + kp;                                
    writeln(pros2 : 3, '%:lla korotetut', Pros2Tunnit : 3,
      ' ' : 16, kp : 7 : 2);                              
                                                          
    writeln('----------' : 45);                           
    writeln('Yhteensä', ' ' : 29, palkka : 7 : 2)         
end.                                                      

Kahden muuttujan arvon vaihtaminen vaatii yhden apupaikan ja kolme sijoituslausetta. Jos muuttujat a, b ja apu ovat kaikki samaa tyyppiä, voidaan muuttujien a ja b arvot vaihtaa tallettamalla ensin a:n arvo apumuuttujaan, sijoittamalla b:n arvo a:n arvoksi ja lopuksi ottamalla apupaikkaan talletettu a:n alkuperäinen arvo b:n arvoksi. Seuraavassa esimerkissä seurataan muuttujien a, b ja apu arvojen kehitystä, kun niillä oletetaan aluksi olevan arvot 15, 28 ja 167.


                (*  a    b   apu  *)
                (*  15   28  167  *)
apu := a;       (*  15   28   15  *)
a   := b;       (*  28   28   15  *)
b   := apu;     (*  28   15   15  *)

Sijoituslauseessa on huolehdittava, että sijoitettavan arvon tyyppi on yhteensopiva sijoituksen vasemmalla puolella olevan muuttujan tyypin kanssa. Aiemmin esillä olleiden tyyppien integer ja real kohdalla tämä merkitsee seuraavaa. Tyyppiä integer oleva arvo voidaan sijoittaa tyyppiä real olevaan muuttujaan (koska jokainen kokonaisluku on samalla myös reaaliluku), mutta ei päinvastoin.

Olkoon esimerkiksi annettu seuraavat muuttujien esittelyt:


var i, j : integer;
    r, s : real;   

Näillä muuttujilla voidaan suorittaa seuraavat sijoitukset:


r := s;           (* molemmilla puolilla real *)   
r := 19.75;       (* molemmilla puolilla real *)   
r := 20;          (* real := integer *)            
r := i;           (* real := integer *)            
r := i * s;                                        
i := 39;          (* molemmilla puolilla integer *)
i := j + 3;                                        

Sen sijaan seuraavat sijoitukset ovat virheellisiä:

i := s;           (* Väärin: integer := real *)
i := 19.75;       (* Väärin: integer := real *)

Tyyppiä real oleva arvo voidaan sijoittaa tyyppiä integer olevaan muuttujaan muuttamalla sen tyyppi käyttäen joko pyöristystä tai katkaisua. Siten seuraavat sijoitukset ovat sallittuja:


i := round(19.75);      (* pyöristys, ts. i := 20 *)            
i := trunc(19.75);      (* katkaisu,  ts. i := 19 *)            
i := round(s + 0.4999); (* lähes puolikkaan lisäys ja pyöristys,
                           siis pyöristys ylöspäin *)           

Tässä round ja trunc ovat ennaltaesiteltyjä funktioita. Funktio on proseduurin kaltainen siinä suhteessa, että funktioon liittyy tietty toimintakokonaisuus. Lisäksi funktio palauttaa jonkin arvon, joten funktioita voidaan käyttää lausekkeissa. Funktioilla round ja trunc on yksi kutsuparametri, nimittäin pyöristettävä tai katkaistava arvo. Kuten seuraavista säännöistä ilmenee, on eräillä funktioilla useita parametreja. Toisilla taas ei ole yhtään parametria, jolloin funktiota käytetään kirjoittamalla pelkästään sen tunnus.


funktioilmaus = funktion-tunnus [ kutsuparametrilista ] .

funktion-tunnus = tunnus .

kutsuparametrilista = "(" kutsuparametri { "," kutsuparametri } ")" .

kutsuparametri = lauseke .

Edellä on käsitelty kahta ennaltamääriteltyä tyyppiä: integer ja real. Sekä real- että integer-tyyppisiä arvoja voidaan käyttää tavanomaisessa laskennassa, kunhan muistetaan huolehtia tyyppien yhteensopivuudesta. Reaalilukujen sisäisestä esityksestä johtuen niillä suoritettava laskenta ei kuitenkaan ole aivan tarkkaa, vaan tulos saattaa hieman poiketa matemaattisesti oikeasta arvosta. Sen sijaan kokonaisluvuilla suoritettava laskenta antaa aina täsmälleen oikean tuloksen.

Seuraavaksi tarkastellaan kahta muuta ennaltamääriteltyä tyyppiä: Boolean ja char. Näitä tyyppejä olevilla arvoilla ei voi suorittaa normaalia laskentaa, vaan niiden käyttöalueet ovat aivan muualla.

Ennaltamääriteltyyn tyyppiin Boolean kuuluvia arvoja on kaksi: false ja true. Näitä kutsutaan totuusarvoiksi ja käytetään ilmaisemaan sitä, onko jokin ehto voimassa vai ei. Esimerkiksi SyottoVirhe saattaisi olla tyypin Boolean muuttuja, joka ilmaisee, onko ohjelma löytänyt syöttötiedoistaan virheitä.

Totuusarvot syntyvät useimmiten vertailun tuloksena. Esimerkiksi lausekkeen


lkm = 0

arvo on true, jos muuttujan lkm arvo on nolla, ja muuten lausekkeen arvo on false. Vertailuissa voi esiintyä operaattorin = lisäksi operaattorit <, >, <= (pienempi tai yhtäsuuri kuin), >= ja <> (erisuuri kuin).

Totuusarvoja voidaan yhdistää mm. operaattoreilla and ja or. Näiden merkitys vastaa täsmälleen näiden sanojen tavanomaista merkitystä. Niinpä muotoa a and b olevan lausekkeen arvo on true, mikäli sekä a että b ovat arvoltaan true. Sen sijaan jos joko a tai b ei ole true, on lausekkeen arvo false. Vastaavasti muotoa a or b olevan lausekkeen arvo on true, jos joko a tai b (tai molemmat) on true.

Esimerkiksi lausekkeen


(Pros1tunnit = 0) and (Pros2Tunnit = 0)

arvo on true, jos sekä muuttujan Pros1Tunnit että muuttujan Pros2Tunnit arvo on nolla, ja muuten se on false. Lauseke ilmaisee siis sen, onko ylitöitä tehty lainkaan. Tässä esimerkissä vertailtavat arvot ovat tyyppiä integer ja vertailujen tulokset totuusarvoja, jotka voidaan yhdistää operaattorilla and. Seuraavassa lausekkeessa toinen yhdistettävistä totuusarvoista saadaan vertailun avulla, toinen saadaan tyypin Boolean muuttujasta.


SyottoVirhe or (lkm <> 0)

Lausekkeen arvo on true, jos joko SyottoVirhe on true tai muuttujan lkm arvo ei ole nolla. Totuusarvojen käytöstä tulee esimerkkejä myöhemmin.

Ennaltamääritellyn tyypin char arvoja ovat yksittäiset merkit (esimerkiksi kirjaimet ja numerot). Seuraavassa ohjelmassa esitellään kaksi tyypin char muuttujaa, annetaan niille arvot ja tulostetaan rivi


        --h--?--


program merkit (input, output);                       
                                                      
var c, merkki : char;                                 
                                                      
begin                                                 
    c := 'h';       (* c:n arvoksi kirjain h *)       
    merkki := '?';  (* merkin arvoksi kysymysmerkki *)
                                                      
    writeln('--', c, '--', merkki, '--')              
end.                                                  

Seuraavan esimerkin ohjelma pyytää ohjelman käyttäjän nimikirjaimet ja tulostaa ne kehystettyinä. Kehystyksessä käytettävä merkki annetaan myös syötteenä seuraavan tulostusesimerkin mukaisesti.


        Nimikirjaimiesi kehystys.

Anna nimikirjaimesi (2 kpl): mh Anna kehystyksessä käytettävä merkki: %

%%%%%% % mh % %%%%%%


program kehystys (input, output);                                  
(*                                                                 
        Nimikirjainten kehystys.                                   
        30.11.84   Matti Meikäläinen                               
*)                                                                 
                                                                   
var etu, suku,     (* nimikirjaimet *)                             
    kehys : char;  (* kehystysmerkki *)                            
                                                                   
begin                                                              
    writeln('Nimikirjaimiesi kehystys.'); writeln;                 
    write('Anna nimikirjaimesi (2 kpl): '); readln(etu, suku);     
    write('Anna kehystyksessä käytettävä merkki: '); readln(kehys);
    writeln;                                                       
                                                                   
    writeln(kehys, kehys, kehys, kehys, kehys, kehys);             
    writeln(kehys, ' ', etu, suku, kehys : 2);                     
    writeln(kehys, kehys, kehys, kehys, kehys, kehys)              
end.                                                               

Ohjelmassa voidaan määritellä myös omia tyyppejä. Seuraava ohjelma laskee syöttenä annettavaan kellonaikaan mennessä keskiyöstä kuluneet sekunnit, ja siinä määritellään kolme osavälityyppiä. Ensimmäisenä määritellään tyyppi tunnit osaväliksi 0..23. Tämä tarkoittaa sitä, että tyyppiä tunnit olevan muuttujan (esimerkissä muuttuja tunti) arvo voi olla kokonaisluku väliltä nollasta kahteenkymmeneenkolmeen. Juuri tällä välillähän kellosta luettavat tunnit aina ovat.


program aika (input, output);                                
(*                                                           
        Vuorokaudesta kuluneiden sekuntien laskeminen.       
        30.11.84   Matti Meikäläinen                         
*)                                                           
                                                             
type                                                         
    tunnit = 0..23;      (* tunnit kellossa *)               
    minuutit = 0..59;    (* minuutit kellossa *)             
    sekunnit = 0..59;    (* sekunnit kellossa *)             
                                                             
var tunti : tunnit;    (* käsiteltävä *)                     
    min : minuutit;    (* kellon-     *)                     
    sek : sekunnit;    (* aika        *)                     
                                                             
begin                                                        
    writeln('Sekuntien laskeminen.'); writeln;               
    write('Anna kellonaika (tu min sek , esim. 13 30 5): '); 
    readln(tunti, min, sek);                                 
    writeln;                                                 
                                                             
    writeln ('Kellon ollessa ', tunti : 2, '.', min : 2, '.',
      sek : 2, ' on vuorokaudesta');                         
    writeln('kulunut ',                                      
      sek + (60 * (min + 60 * tunti)) : 6, ' sekuntia.')     
end.                                                         

Ohjelman suoritus antaa seuraavanlaisen tuloksen.


        Sekuntien laskeminen.

Anna kellonaika (tu min sek , esim. 13 30 5): 9 35 47

Kellon ollessa 9.35.47 on vuorokaudesta kulunut 34547 sekuntia.

Jos muuttujan tunti tyypiksi olisi esitelty integer, ohjelma toimisi oikeilla syötteillä samoin kuin edellä oleva ohjelma. Sen sijaan osavälityyppi tunnit antaa Pascal-kääntäjälle mahdollisuuden sisällyttää vastaavaan konekieliseen ohjelmaan tarkistuksen, että muuttujalle tunti syötettävä arvo todella on määritellyllä välillä.

Osavälityypin käyttö antaa siten mahdollisuuden automaattisiin tarkistuksiin. Samalla se antaa kääntäjälle mahdollisuuden säästää tietokoneen muistitilaa, koska esimerkiksi välillä 0..23 olevat luvut voidaan tallettaa pienempään tilaan kuin mielivaltaiset kokonaisluvut. Kolmas merkitys osavälityypille tulee myöhemmin esille taulukoiden yhteydessä.

Tyypit määritellään ohjelmassa vakioiden ja muuttujien välissä. Tyypinmäärittelyosan ja osavälityypin syntaktiset säännöt ovat seuraavat:


tyypinmäärittelyosa = [ "type" tyypinmäärittely ";"
                        { tyypinmäärittely ";" } ] .

tyypinmäärittely = tunnus "=" tyyppi-ilmaus .

tyyppikuvaus = järjestetyn-tyypin-kuvaus | rakenteisen-tyypin-kuvaus.

järjestetyn-tyypin-kuvaus = lueteltu-tyyppi | osavälityyppi .

osavälityyppi = vakio ".." vakio .

Ohjelmassa voidaan määritellä myös lueteltuja tyyppejä. Tällöin määrittelyssä luetellaan kaikki ne arvot, jotka ovat tätä tyyppiä ja jotka voivat siten olla kyseistä tyyppiä olevien muuttujien arvoina. Lueteltavat arvot ovat muodoltaan tunnuksia seuraavan syntaksin mukaisesti:


lueteltu-tyyppi = "(" tunnuslista ")" .

Esimerkkinä tarkastellaan seuraavia määrittelyjä ja esittelyjä:


type                                               
    viikonpaiva = (maanantai, tiistai, keskiviikko,
      torstai, perjantai, lauantai, sunnuntai);    
    tyopaiva = maanantai..perjantai;               
    maa = (hertta, ruutu, risti, pata);            
                                                   
var tanaan, huomenna : viikonpaiva;                
    PoytakortinMaa : maa;                          

Luetellun tyypin alkioilla ei tietenkään voida suorittaa laskentaa. Sen sijaan niihin voidaan kohdistaa ennaltaesitellyt funktiot succ ja pred, joilla on yksi parametri. Succ palauttaa arvonaan seuraavan arvon ja pred edellisen arvon tyypin määritelleessä listassa. Edellisten määrittelyjen ja esittelyjen voimassaollessa voidaan siten suorittaa seuraavat sijoitukset.


tanaan := tiistai;        (* muuttujalle tanaan arvoksi tiistai *)
huomenna := succ(tanaan); (* eli huomenna := keskiviikko *)       

Luetellun tyypin alkioita voidaan vertailla samoin kuin lukujakin, ja niillä on määrittelystä ilmenevä järjestys. Niinpä edellisten sijoitusten jälkeen lausekkeen


huomenna < tiistai

arvo on false.

Edellisestä esimerkistä ilmenee, että osavälityyppi voi olla osa luetellusta tyypistä. Sitä tyyppiä, josta osaväli on otettu, kutsutaan osavälityypin isäntätyypiksi. Edellä siis tyypin tyopaiva isäntätyyppi on viikonpaiva. Isäntätyyppinä voi myös olla integer tai char (tai Boolean).

Tähän mennessä tunnuksia on käytetty nimeämään vakioita, tyyppejä, lueteltujen tyyppien alkioita ja muuttujia. PienoisPascalissa voidaan yhdellä tunnuksella nimetä vain yksi olio sekaannuksien välttämiseksi. (Varsinaisessa Pascalissa on mahdollista antaa tunnukselle useita merkityksiä, joista kulloinkin on voimassa vain yksi.) Erityisesti kahdella luetellulla tyypillä ei voi olla samaa alkiota. Niinpä seuraavassa on virhe.

type
    viikonpaiva = (maa, tii, kes, tor, per, lau, sun);
    vapaapaiva  = (lau, sun); (* Väärin *)

Koska tunnukset lau ja sun ovat tyypin viikonpaiva alkioiden tunnuksia, niitä ei saa käyttää muiden lueteltujen tyyppien alkioiden tunnuksina. Jos tällainen käyttö nimittäin olisi sallittua, niin silloinhan voitaisiin määritellä vaikkapa seuraavasti:

type
    viikonpaiva = (maa, tii, kes, tor, per, lau, sun);
    vapaapaiva  = (sun, lau); (* Tämä on siis väärin *)

Nyt olisi mahdotonta sanoa, kumpi on pienempi arvo, lau vai sun. Koska esimerkissä kuitenkin on tarkoitus sanoa, että vapaapäiviä ovat eräät viikonpäivät, käy asia luonnollisella tavalla käyttämällä osaväliä:


type                                                  
    viikonpaiva = (maa, tii, kes, tor, per, lau, sun);
    vapaapaiva  = lau..sun; (* Oikein *)              

Tyyppi vapaapaiva tulee siis määritellä isäntätyypin viikonpaiva osaväliksi sen sijaan, että yritettäisiin määritellä virheellisten esimerkkien tapaan kokonaan uusi tyyppi, jossa esiintyy samoja alkioita kuin tyypissä viikonpaiva.

Syöttölauseissa esiintyvien muuttujien tyyppi saa olla real, integer, char tai näiden osavälityyppi. Tulostettavien arvojen tyyppinä saa olla real, integer, char, Boolean tai merkkijono. Sen sijaan lueteltua tyyppiä olevia arvoja ei voi lukea eikä kirjoittaa syöttö- ja tulostuslauseilla. Tosin eräissä Pascaltoteutuksissa tämäkin on tehty mahdolliseksi.

Yhteenvetona eri tyypeistä voidaan todeta, että

Harjoitustehtäviä:

1. Pankki maksaa kolmen vuoden talletukselle korkoa 11%, ja korko lisätään pääomaan vuosittain. Laadi ohjelma, joka saa syöttötietona talletettavan rahasumman ja joka tulostaa koron sekä kertyneen pääoman kunkin vuoden lopussa.

2. Suutari on erikoistunut valmistamaan lapikkaita, joiden kokonumerot vaihtelevat välillä 21-47. Lapikkaan sisäpohjan pituus on pienimmässä koossa 14 cm ja kasvaa 0.5 cm aina yhtä numeroa kohti. Siten suurimpaan lapikkaaseen mahtuu 27 cm pitkä jalka. Lapikasparin hinta riippuu koosta seuraavasti:
koot 21-29 230 mk
koot 30-39 240 mk
koot 40-47 250 mk
Laadi ohjelma, jonka avulla suutari saa selville lapikasparin hinnan ja sisäpohjan pituuden antamalla syötteenä kokonumeron. Normaalihinnan lisäksi ohjelman on tulostettava tuttavilta veloitettava hinta, joka on 70 mk normaalihintaa alempi.

3. Rullaverhotehdas ostaa verhokankaat kokonaisina pakkoina, joissa kussakin on kangasta 31 metriä. Laadi ohjelma, joka saa syötteenä valmistettavien rullaverhojen määrän ja yhteen verhoon käytettävän kankaan määrän. Ohjelman on tulostettava tieto siitä, kuinka monta verhoa yhdestä pakasta saadaan ja kuinka monta pakkaa tarvitaan.

4. Laadi ohjelma, joka lukee kaksidesimaalisen desimaalipilkkua käyttävän luvun ja tulostaa sen desimaalipisteellä. Siis syötteestä 315,06 tulee tulostua 315.06. Ohje: syöte on luettava kahtena kokonaislukuna ja niiden välissä olevana tyypin char arvona.

Sanastoa:
 
sijoitus assignment
yhteensopiva compatible
pyöristää round
katkaista truncate
funktio function
 
järjestetty tyyppi ordinal type
 
totuusarvo truth value
tosi true
epätosi false
vertailuoperaattori relational operator
 
osaväli subrange
isäntätyyppi host type
 
lueteltu tyyppi enumerated type
seuraaja successor
edeltäjä predecessor

3.5 Päättelyiden tekeminen

Kaikille aikaisemmin käsitellyille ohjelmille on ollut tunnusomaista, että kussakin ohjelmassa on suoritettu syötteistä riippumatta aina samat toimenpiteet samassa järjestyksessä. Ohjelman tuottama tulostus on voinut vaihdella, mutta se on saatu aikaan aina samoilla toiminnoilla. Käytännössä on kuitenkin usein tilanteita, joissa ohjelmassa on pystyttävä tekemään päättelyitä ja valitsemaan niiden perusteella suoritettavaksi keskenään vaihtoehtoisia toimenpiteitä.

Päättelyiden tekemiseen on Pascal-kielessä useita mahdollisuuksia. Tarkastellaan aluksi tilannetta, jossa halutaan valita kahden toimintavaihtoehdon kesken. Otetaan esimerkiksi ohjelma, joka syötteenä saamansa vuosiluvun perusteella ilmoittaa, onko kyseinen vuosi karkausvuosi vai ei. Molemmat valinnaiset toimenpiteet ovat tässä tapauksessa tulostuslauseita: toinen lause ilmoittaa, että vuosi on karkausvuosi ja toinen vastaavasti, että vuosi ei ole karkausvuosi.

Esimerkkitapauksessa tarvitaan kahden toimintavaihtoehdon valintaa varten totuusarvoinen lauseke, joka tuottaa arvon true karkausvuodelle ja arvon false muille vuosille. Karkausvuoden ilmaiseva totuusarvoinen lauseke perustuu yksinkertaiseen jaollisuussääntöön: jokainen 400:lla jaollinen vuosiluku on karkausvuosi ja lisäksi neljällä jaollisista vuosiluvuista ne, jotka eivät ole jaollisia 100:lla. Jaollisuus puolestaan ilmenee jakojäännöksestä: luku a on jaollinen luvulla b, jos a jaettuna luvulla b tuottaa jakojäännökseksi nollan.

Kahden tyyppiä integer olevan luvun osamäärän ja jakojäännöksen esittämiseen on Pascal-kielessä operaatiot div ja mod, joiden kummankin tulos on tyyppiä integer:

a div b     osamäärän kokonaisosa eli trunc(a/b)
a mod b     jakojäännös jaettaessa positiivinen luku a positiivisella luvulla b

Seuraavassa joitakin esimerkkejä näistä operaatioista:


1984 mod 4          tulos:   0
1984 div 4                 496
1985 mod 4                   1
1985 div 4                 496

Karkausvuoden jaollisuussääntö voidaan siten esittää seuraavana totuusarvoisena lausekkeena operaatiota mod käyttäen (huomaa sulkumerkkien käyttö lausekkeen rakenteen selkeyttämisessä):


(vuosi mod 400 = 0) or                      
((vuosi mod 4 = 0) and (vuosi mod 100 <> 0))

Tätä käyttäen saadaan seuraava ohjelma:


program karkausvuosi (input, output);                        
(*                                                           
        Karkausvuoden ilmoittaminen.                         
        30.11.84 Matti Meikäläinen                           
*)                                                           
                                                             
var vuosi : integer;                                         
                                                             
begin                                                        
    writeln('Karkausvuoden ilmoittaminen.'); writeln;        
                                                             
    write('Anna vuosiluku: '); readln(vuosi);                
                                                             
    if (vuosi mod 400 = 0) or                                
       ((vuosi mod 4 = 0) and (vuosi mod 100 <> 0))          
    then writeln('Vuosi ', vuosi : 4, ' on karkausvuosi')    
    else writeln('Vuosi ', vuosi : 4, ' ei ole karkausvuosi')
end.                                                         

Seuraavassa on esimerkkejä ohjelman suorituksista erilaisilla vuosiluvuilla. Niistä voidaan samalla havaita tärkeä periaate ohjelmien testaamiseen. Ohjelmassa mahdollisesti olevien virheiden löytämiseksi tulisi testiaineistoksi valita syötteitä, joilla tutkitaan ohjelman toiminta jokaisessa päättelytilanteessa. Tässä tapauksessa saadaan jaollisuussäännön perusteella neljä testattavaa tapausta:


        Karkausvuoden ilmoittaminen.

Anna vuosiluku: 1984 Vuosi 1984 on karkausvuosi


        Karkausvuoden ilmoittaminen.

Anna vuosiluku: 1985 Vuosi 1985 ei ole karkausvuosi


        Karkausvuoden ilmoittaminen.

Anna vuosiluku: 1900 Vuosi 1900 ei ole karkausvuosi


        Karkausvuoden ilmoittaminen.

Anna vuosiluku: 2000 Vuosi 2000 on karkausvuosi

Ohjelmasta karkausvuosi havaitaan, että päättely esitetään sanasymbolilla if alkavana lauserakenteena. Symbolia if seuraavan totuusarvoisen lausekkeen perusteella tulee suoritettavaksi symbolin then jäljessä oleva lause silloin, kun lausekkeen arvo on true. Muulloin suoritetaan symbolin else jäljessä oleva lause. Päättelyssä käytettyä lauserakennetta kutsutaan ehtolauseeksi. Se kuuluu Pascal-kielessä ns. rakenteisiin lauseisiin eli lauseisiin, jotka sisältävät muita lauseita:


rakenteinen-lause = yhdyslause | ehdollinen-lause | toistolause .

ehdollinen-lause = ehtolause | valintalause .

ehtolause = "if" Boolen-lauseke "then" lause [ else-osa ] .

Boolen-lauseke = lauseke .

else-osa = "else" lause .

Ehtolauseen syntaksista ilmenee, että else-osa voi myös puuttua. Jättämällä else-osa pois saadaan aikaan päättely, jossa totuusarvoisen lausekkeen arvolla true suoritetaan symbolia then seuraava lause, ja arvolla false ei suoriteta mitään toimenpidettä. Näin voidaan ohjelmoida päättely, jossa valitaan tietyn toimenpiteen suorittamisen ja suorittamatta jättämisen välillä.

Tarkastellaan seuraavaksi esimerkkinä päättelyn ohjelmoinnista aiemmin esitetyn ohjelman liikevaihtovero täydentämistä niin, että siinä varaudutaan myös virheellisten syötteiden käsittelyyn. Esimerkki valaisee samalla ns. puolustautuvaa ohjelmointia, mikä on yleisnimitys yksinkertaiselle ohjelmointimenettelylle ohjelmien luotettavuuden parantamiseksi. Menettelyn perusideana on saada ohjelma "puolustautumaan" epäkelpoja syötteitä vastaan sisällyttämällä siihen virhetarkistukset kaikille syötteille. Tarkistuksien avulla voidaan parantaa ohjelman tulosten luotettavuutta ja suojella samalla ohjelmaa käyttäjän virheiltä ja väärinkäytöksiltä. Tarkistukset voidaan ohjelmoida päättelyinä, joissa virheellisistä syötteistä ensinnäkin huomautetaan käyttäjälle ja toiseksi suoritetaan tilanteesta riippuen esim. jokin seuraavista toimenpiteistä:

Ohjelman liikevaihtovero ainoa syöte on tuotteen veroton hinta. Se esiintyy ohjelmassa tyyppiä real olevana muuttujana. Pascal-järjestelmä hyväksyy siten syötteeksi vain reaalilukuja, joten ohjelmassa voidaan rajoittua reaalilukuarvoisten syötteiden virhetarkistuksiin. Voidaan perustellusti vaatia, että kelvollisen hinnan tulee olla vähintään 0 mk. Siten ohjelmassa tulee huomauttaa käyttäjälle negatiivisesta syötteestä. Korjaustoimenpiteenä voidaan negatiivinen hinta muuttaa positiiviseksi jatkokäsittelyä varten.


program liikevaihtovero (input, output);                             
(*                                                                   
        Liikevaihtoveron laskeminen. Toinen versio.                  
        30.11.84   Matti Meikäläinen                                 
        13.12.84   Heikki Heikäläinen                                
*)                                                                   
                                                                     
const LvvKerroin = 0.1905;                                           
                                                                     
var VerotonHinta : real;                                             
                                                                     
begin                                                                
    writeln('Liikevaihtoverollisen hinnan laskeminen.');             
    writeln;                                                         
                                                                     
    write('Anna veroton hinta: '); readln(VerotonHinta);             
                                                                     
    (* Syötteiden virhetarkistus: *)                                 
                                                                     
    if VerotonHinta < 0 then                                         
       begin                                                         
       writeln('Hinta ', VerotonHinta : 8 : 2, ' on NEGATIIVINEN?'); 
       VerotonHinta := -VerotonHinta;                                
       writeln('Käytetään sen sijaan hintaa ', VerotonHinta : 8 : 2);
       writeln                                                       
       end;                                                          
                                                                     
    (* Tulostus: veroton hinta, vero ja verollinen hinta *)          
                                                                     
    writeln('Veroton hinta    ', VerotonHinta : 8 : 2);              
    writeln('Liikevaihtovero  ',                                     
      LvvKerroin * VerotonHinta : 8 : 2);                            
    writeln('Verollinen hinta ',                                     
      (1 + LvvKerroin) * VerotonHinta : 8 : 2)                       
end.                                                                 

Seuraavassa on esimerkki ohjelman suorituksesta virheellisellä syötteellä:


        Liikevaihtoverollisen hinnan laskeminen.

Anna veroton hinta: -198.70 Hinta -198.70 on NEGATIIVINEN? Käytetään sen sijaan hintaa 198.70

Veroton hinta 198.70 Liikevaihtovero 37.85 Verollinen hinta 236.55

Esimerkkiohjelmasta nähdään, että virhetarkistukseen liittyvä päättely on ohjelmoitu ilman else-osaa olevalla ehtolauseella. Näin on voitu menetellä, koska vain virheelliset syötteet vaativat lisäkäsittelyä ja koska liikevaihtoveron laskenta etenee virhetarkistuksen jälkeen täsmälleen samalla tavalla sekä alun perin virheettömille että "virheettömiksi" muutetuille syötteille. Samoin esimerkistä nähdään, että käyttämällä ehtolauseen sisällä yhdyslausetta voidaan päättelyyn sisältyvä toiminto koostaa itse asiassa monista Pascal-kielen lauseista.

Usein päättelyä tarvitaan tilanteessa, jossa valinta halutaan suorittaa useamman kuin kahden toimintavaihtoehdon kesken. Otetaan esimerkiksi vaikkapa kotimaahan lähetettävän paketin postimaksun laskeminen. Vuoden 1985 postimaksutaulukon mukaan maksut määräytyivät seuraavasti:

Paino enintään kg Maksu mk
1 9.00
3 12.00
5 14.00
jokaiselta seuraavalta 5 kg:lta tai sen osalta 6.00
Enimmäispaino 20 kg

Pakettimaksun suuruuden laskemisessa on taulukossa kaikkiaan neljä vaihtoehtoa paketin painon mukaan. Viimeisen ryhmän maksu voidaan laskea yhtenäisellä tavalla tutkimalla, montako 6 mk:n lisämaksua tarvitaan.

Koska Pascal-kielen ehtolauseessa voidaan sekä then-osassa että else-osassa käyttää vapaasti muita lauseita (mm. ehtolauseita), voidaan monivalintapäättely ohjelmoida sisäkkäisten ehtolauseiden avulla. Ensimmäinen ehto ja siihen liittyvä then-osa muodostetaan vastaamaan ensimmäistä vaihtoehtoa (enintään 1 kg:n pakettia). Kaikki seuraavat vaihtoehdot tutkitaan tämän ehtolauseen else-osassa. Else-osa muodostetaan myös ehtolauseesta: lauseen then-osa muodostetaan vastaamaan toista vaihtoehtoa (enintään 3 kg:n pakettia), ja lauseen else-osassa tutkitaan loput jäljelle jäävät vaihtoehdot. Tätä menettelyä sovelletaan toistuvasti niin kauan kuin vaihtoehtoja on jäljellä.

Monivalintapäättelyn ohjelmointi edellä kerrotulla tavalla tuottaa esimerkkitapauksessa seuraavan ohjelman:


program pakettimaksu (input, output);                            
(*                                                               
        Kotimaan pakettimaksun laskeminen.                       
        15.1.85   Matti Meikäläinen                              
*)                                                               
                                                                 
var Paino, Maksu : real; (* paketin paino, maksu *)              
                                                                 
begin                                                            
    writeln('Kotimaan pakettimaksun laskeminen.');               
    writeln;                                                     
                                                                 
    write('Anna paketin kg-paino (0.001-20.0): '); readln(Paino);
                                                                 
    if (Paino <= 0.0) or (Paino > 20.0) then                     
       writeln(Paino : 6 : 3, ' kg ei kelpaa')                   
    else                                                         
       begin (* paketin paino on kelvollinen *)                  
       if      Paino <= 1.0 then Maksu := 9                      
       else if Paino <= 3.0 then Maksu := 12                     
       else if Paino <= 5.0 then Maksu := 14                     
       else Maksu := 14 + 6 * trunc((Paino - 0.001) / 5);        
                                                                 
       writeln('Paketin paino ', Paino : 6 : 3, ' kg');          
       writeln('Pakettimaksu ', Maksu : 6 : 2, '  mk')           
       end                                                       
end.                                                             

Seuraavassa on esimerkkejä ohjelman suorituksista eri painoisille paketeille. Erilaisia syötteitä ei kuitenkaan ole riittävästi ohjelman testaamista ajatellen, koska käytettävissä oleva tila ei tässä mahdollista laajojen testiaineistojen käyttöä.


        Kotimaan pakettimaksun laskeminen.

Anna paketin kg-paino (0.001-20.0): 1 Paketin paino 1.000 kg Pakettimaksu 9.00 mk


        Kotimaan pakettimaksun laskeminen.

Anna paketin kg-paino (0.001-20.0): 1.001 Paketin paino 1.001 kg Pakettimaksu 12.00 mk


        Kotimaan pakettimaksun laskeminen.

Anna paketin kg-paino (0.001-20.0): 20.0 Paketin paino 20.000 kg Pakettimaksu 32.00 mk

Ohjelmasta pakettimaksu havaitaan, että ehtolauseet on syytä kirjoittaa porrastetusti niin, että lukijan on helppo havaita ja erottaa toisistaan päättelyyn kuuluvat osat. Esimerkkiohjelmassa porrastusta on käytetty ensinnäkin siten, että samaan ehtolauseeseen kuuluvina on epäkelvon paketin ja kelvollisen paketin käsittelyosat sijoitettu sisennettynä samalle saraketasolle. Postimaksutaulukon havainnollisuutta on haluttu noudattaa myös ohjelmassa, ja siksi eri painoisten pakettien käsittelyosat ovat samalla saraketasolla, vaikka ne itse asiassa muodostavat monitasoisen sisäkkäisen ehtolauseen.

Sisäkkäisten ehtolauseiden käyttöön Pascal-kielessä liittyy hieman harmillinen else-osan moniselitteisyysongelma. Tarkastellaan tilannetta esimerkin avulla. Kielen syntaksin perusteella ei ole selvää, kumpaan ehtolauseeseen seuraavassa esimerkissä oleva else-osa liittyy (olkoon Paino vaikkapa pakettimaksuohjelman mukainen muuttuja):


if Paino > 0                                  
then if Paino < 20                            
then writeln('Paino >  0  ja  Paino <  20')   
else writeln('Paino <= 0  vai Paino >= 20 ??')

Else-osan tulostuslauseessa oleva teksti kuvaa molempia ajateltavissa olevia mahdollisuuksia. Moniselitteisyys ilmenee siis silloin, kun ohjelmassa esiintyy useita sisäkkäisiä ehtolauseita, joista joku on ilman else-osaa. Tilanne on kielen määrittelyssä ratkaistu siten, että else-osa liittyy aina lähinnä edellä olevaan then-osaan. Edellisen esimerkin oikea tulkinta on siis:


if Paino > 0                                          
   then if Paino < 20                                 
           then writeln('Paino >  0  ja  Paino <  20')
           else writeln('Paino >= 20')                

Monivalintapäättelyjen tekemiseen on Pascal-kielessä ehtolauseen lisäksi myös ns. valintalause. Sitä voidaan käyttää erityisesti silloin, kun päättelyssä on useita rinnakkaisia toimintavaihtoehtoja, jotka voidaan valita yksittäisten vakioarvojen perusteella. Valintalauseella on selvä yhtäläisyys ehtolauseeseen: molemmissa valitaan toimintavaihtoehto lausekkeella, jonka eri arvoja vastaten toiminnat ohjelmoidaan. Ehtolauseessa on rajoituttu vain totuusarvoiseen lausekkeeseen ja siten valintaa ohjaaviin vakioihin true ja false. Valintalauseessa voidaan käyttää myös muun tyyppisiä lausekkeita, jolloin lausekkeen mahdollisista arvoista riippuen päättelyssä voi olla suurikin määrä keskenään vaihtoehtoisia toimenpiteitä.

Tarkastellaan esimerkkinä valintalauseen käytöstä ohjelmaa, joka tarkistaa sille annetun päivämäärän järkevyyden. Oletetaan, että päiväyksen vuosiluku saa olla mikä tahansa kokonaisluku. Kuukauden tulee olla välillä 1-12 oleva kokonaisluku. Päivän kelpoisuus riippuu kuukaudesta: useimmissa kuukausissa kelpaa päiväksi välillä 1-31 oleva kokonaisluku, mutta joissakin kuukausissa on päivän ylärajana 30 ja helmikuussa joko 28 tai 29. Juuri päivän kelpoisuuden tarkistuksessa voidaan käyttää monivalintapäättelyä: kun kuukauden numero on havaittu kelvolliseksi, sitä käytetään valintalausekkeena, jonka arvoja 1-12 vastaten ohjelmoidaan kuukausikohtainen päivän virhetarkistus.


program paivays (input, output);                                
(*                                                              
        Päiväyksen järkevyyden tarkistus.                       
        15.1.85   Matti Meikäläinen                             
*)                                                              
                                                                
var pp, kk, vv : integer; (* päivä, kuukausi, vuosi *)          
    ppVirhe, kkVirhe : Boolean; (* virheilmaisimet  *)          
                                                                
begin                                                           
    writeln('Päiväyksen järkevyystarkistus.');                  
    writeln;                                                    
                                                                
    write('Anna pvm (pv kk v,  esim. 15 10 1984): ');           
    readln(pp, kk, vv);                                         
                                                                
    (* Päivän ja kuukauden yleinen kelvollisuus: *)             
    ppVirhe := (pp < 1) or (pp > 31);                           
    kkVirhe := (kk < 1) or (kk > 12);                           
                                                                
    (* Päivän kelvollisuuden täsmennys kuukauden mukaan: *)     
    if not kkVirhe then                                         
       case kk of                                               
            1,3,5,7,8,10,12:                                    
               ; (* Ei muuta tehtävää, siis tyhjä lause *)      
            4,6,9,11:                                           
               ppVirhe := ppVirhe or (pp > 30);                 
            2:                                                  
               if (vv mod 400 = 0) or                           
                  ((vv mod 4 = 0) and (vv mod 100 <> 0))        
               then ppVirhe := ppVirhe or (pp > 29)             
               else ppVirhe := ppVirhe or (pp > 28);            
       end; (* case *)                                          
                                                                
    (* Tarkistukset tehty, kerrotaan tulos: *)                  
    write('Päiväys ', pp : 2, ' ', kk : 2, ' ', vv : 4);        
    if not (ppVirhe or kkVirhe) then                            
        writeln(' on kelvollinen')                              
    else                                                        
        begin                                                   
        writeln(' ei ole kelvollinen:');                        
        if kkVirhe then writeln(kk : 2, '. kuukausi ei kelpaa');
        if ppVirhe then writeln(pp : 2, '. päivä ei kelpaa')    
        end                                                     
end.                                                            

Ohjelmasta paivays nähdään, että valintalause alkaa sanasymbolilla case, jota seuraa valintalauseke. Kukin toimintavaihtoehto ohjelmoidaan yhdeksi Pascal-kielen lauseeksi, jonka eteen sijoitetaan lauseeseen liittyvien valintavakioiden luettelo kaksoispisteellä lauseesta erotettuna. Valintalauseen yksityiskohtainen syntaksi on seuraava:


valintalause = "case" valintalauseke "of" valintalistan-alkio
                 { ";" valintalistan-alkio } [ ";" ] "end" .

valintalauseke = lauseke .

valintalistan-alkio = valintavakiolista ":" lause .

valintavakiolista = valintavakio { "," valintavakio } .

valintavakio = vakio .

Seuraavassa on joitakin esimerkkejä ohjelman suorituksista erilaisilla päiväyksillä.


        Päiväyksen järkevyystarkistus.

Anna pvm (pv kk v, esim. 15 10 1984): 15 10 1984 Päiväys 15 10 1984 on kelvollinen


        Päiväyksen järkevyystarkistus.

Anna pvm (pv kk v, esim. 15 10 1984): 29 2 1984 Päiväys 29 2 1984 on kelvollinen


        Päiväyksen järkevyystarkistus.

Anna pvm (pv kk v, esim. 15 10 1984): 29 2 1985 Päiväys 29 2 1985 ei ole kelvollinen: 29. päivä ei kelpaa

Valintalauseen lisäksi ohjelmasta paivays havaitaan, kuinka sijoituslausetta voidaan käyttää myös totuusarvoisten välitulosten tallettamiseen. Esimerkiksi päivän virheilmaisimena on tyyppiä Boolean oleva muuttuja ppVirhe, ja päivän alustava kelpoisuusehto talletetaan muuttujaan ppVirhe sijoituksella


ppVirhe := (pp < 1) or (pp > 31)

Ohjelman totuusarvoisissa lausekkeissa esiintyy uutena operaattorina sanasymboli not. Nimensä mukaisesti sen soveltaminen jäljessä seuraavaan totuusarvoiseen lausekkeeseen tuottaa päinvastaisen totuusarvon eli

        not false = true          not true = false

Valintalauseen käytössä on huomattava, että ohjelmassa on ennen valintalauseen suorittamista huolehdittava siitä, että valintalausekkeella voi olla vain jokin niistä arvoista, joille on valintavakio. Esimerkiksi päiväyksen tarkistusohjelmassa on juuri ennen valintalausetta varmistettu ehtolauseen avulla, että valintalausekkeena oleva kuukauden arvo on aina välillä 1-12, koska vain näille arvoille on olemassa toimintavaihtoehdot.

Muodostamalla valintalauseen toimenpidevaihtoehto yhdyslauseesta voidaan toimenpide koostaa useista lauseista samaan tapaan kuin ehtolauseessakin.

Valintalausekkeen tulee olla tyypiltään jotain järjestettyä tyyppiä (esim. integer, char, lueteltu tyyppi tai osavälityyppi). Erityisesti on siis huomattava, että tyyppiä real oleva lauseke ei ole sallittu valintalausekkeena.

Esimerkiksi pakettimaksuohjelman monivalintapäättelyä ei voitaisi muuttaa suoraan valintalauseeksi, koska valintalauseke (paketin paino) tulisi olemaan tyyppiä real. Jos paketin paino sen sijaan esitettäisiin aina kokonaislukuna grammoiksi tulkittuna, voitaisiin päättely ohjelmoida periaatteessa valintalauseella. Lopputuloksena oleva ohjelma olisi kuitenkin työläs kirjoittaa ja verraten epähavainnollinenkin: esimerkiksi painoluokkaan 1-3 kg kuuluvan paketin käsittelyä varten olisi valintavakioluettelossa oltava 2000 lukua (luvut 1001, 1002, ..., 3000). Päättelyitä ohjelmoitaessa on siis syytä harkita, saavutetaanko parempi lopputulos ehtolausetta vai valintalausetta käyttäen.

Harjoitustehtäviä

1. Tekstissä kerrottu karkausvuoden määräytymisperiaate noudattaa gregoriaanista ajanlaskua. Se on kuitenkin ollut käytössä Suomessa ja Ruotsissa vasta vuodesta 1753 lähtien. Sitä ennen noudatettiin juliaanista ajanlaskua, jossa karkausvuosi oli jokainen neljällä jaollinen vuosiluku. Tee ajanlaskun historiallisen kehityksen mukainen karkausvuosiohjelma, joka ilmoittaa karkausvuodet juliaanisen ajanlaskun mukaan vuotta 1753 edeltävältä ajalta ja gregoriaanisen kalenterin mukaan vuodesta 1753 lähtien.

2. Tee ohjelma, joka laskee verotettavasta varallisuudesta perittävän veron suuruuden oheisen varallisuusveroasteikon mukaisesti. Ohjelman syötteenä on henkilön markkamääräinen verotettava varallisuus ja tulostuksena kyseisestä varallisuudesta perittävä vero.

Verotettava varallisuus mk Vero alarajan kohdalla mk Vero alarajan ylittävästä varallisuudesta %
259000 - 359000 120 0.8
359000 - 502000 920 1.0
502000 - 721000 2350 1.3
721000 - 1077000 5197 1.5
1077000 - 10537 1.7

3. Tee ohjelma, joka ilmoittaa syötteenä saamansa päiväyksen mukaisen päivän järjestysnumeron vuoden alusta lukien. Esimerkiksi päiväyksestä 16 2 1985 tulisi ilmoittaa, että kyseessä on 47. päivä vuonna 1985.

Sanastoa:
 
rakenteinen lause structured statement
ehdollinen lause conditional statement
ehtolause if statement
valintalause case statement
 
moniselitteinen ambiguous
puolustautuva ohjelmointi defensive programming mointi

3.6 Toimintojen toistaminen

Tietojenkäsittelyä sovelletaan usein tilanteissa, joissa tietyt toimenpiteet on toistettava useita kertoja peräkkäin. Esimerkiksi palkanmaksutietojen, laskujen tai osoitetarrojen kirjoittaminen toistetaan peräkkäin samanlaisena toimenpidekokonaisuutena kunkin palkansaajan, laskutettavan asiakkaan tai osoitteen kohdalla.

Toimintojen toistaminen on voitu ilmaista toistaiseksi käsiteltyjen ohjelmointirakenteiden avulla ainoastaan siten, että toistettavia lauseita on kirjoitettu ohjelmaan peräkkäin toiston mukainen määrä. Esimerkiksi alakohdassa 3.2 on ohjelma, joka ilmoittaa päivien lukumäärän yhdessä, kahdessa, kolmessa ja neljässä vuodessa. Halutun tuloksen aikaansaamiseksi ohjelmaan on kirjoitettu peräkkäin neljä lähes samanlaista tulostuslausetta, joissa ainoastaan vuosien lukumäärä on muuttuvana tietona.

Algoritmiset ohjelmointikielet on kuitenkin varustettu nimen omaan toiston esittämiseen suunnitelluilla rakenteilla. Kielten perusrakenteisiin kuuluu erityinen toistolause, jossa ilmaistaan toistettava toimenpide, toistokertojen määrä ja toiston ohjaukseen liittyviä lisätietoja. Pascal-kielessä tätä rakennetta kutsutaan askeltavaksi toistolauseeksi. Sen avulla voidaan edellä mainittu alakohdan 3.2 ohjelma esittää seuraavassa muodossa:


program paivat (output);                                          
(*                                                                
        Vuosien muuttaminen päiviksi. Toinen versio.              
        30.11.84   Matti Meikäläinen                              
        30.01.85   Heikki Heikäläinen                             
*)                                                                
                                                                  
const vuosi = 365; (* yhden vuoden päivien lkm   *)               
                                                                  
var   i : 1..4;    (* vuosien lukumäärän laskuri *)               
                                                                  
begin                                                             
    for i := 1 to 4 do                                            
        writeln(i : 1, ' vuodessa on ', i * vuosi : 4, ' päivää.')
end.                                                              

Ohjelman suoritus tuottaa seuraavan tulostuksen:


        1 vuodessa on  365 päivää.
        2 vuodessa on  730 päivää.
        3 vuodessa on 1095 päivää.
        4 vuodessa on 1460 päivää.

Askeltava toistolause aloitetaan sanasymbolilla for. Sitä seuraava muuttuja (esimerkissä i) on toiston askelmuuttuja, ja arvot 1 ja 4 vastaavasti askelmuttujan alku- ja loppuarvot. Sanasymbolin do jälkeen olevaa lausetta toistetaan esimerkissä siten, että ensimmäisellä toistokerralla askelmuuttujan arvona on alkuarvo (eli 1), toisella kerralla alkuarvoa suuruusjärjestyksessä seuraava arvo (siis 2) jne. Viimeinen toistokerta on silloin, kun askelmuuttujan arvona on loppuarvo (eli esimerkissä 4). Syntaksin yksityiskohdat ilmenevät seuraavista säännöistä:


toistolause = loppuehtoinen-toistolause |
                alkuehtoinen-toistolause |
                askeltava-toistolause .

askeltava-toistolause = "for" askelmuuttuja ":=" alkuarvo ( "to" | "downto" ) loppuarvo "do" lause .

askelmuuttuja = kokonainen-muuttuja .

alkuarvo = lauseke .

loppuarvo = lauseke .

Toistokertojen kokonaismäärä määräytyy askeltavassa toistolauseessa siis askelmuuttujan alku- ja loppuarvojen perusteella. Kun askelmuuttuja lisäksi on samassa asemassa kuin ohjelman muutkin muuttujat, voidaan toistettavassa lauseessa käyttää hyväksi askelmuuttujan arvoa. Syntaksisäännöistä nähdään myös, että alku- ja loppuarvot ovat itse asiassa lausekkeita. Ne voidaan siis muodostaa myös muista kuin pelkistä vakioista. Askelmuuttujan ja alku- sekä loppuarvolausekkeiden tulee olla keskenään samaa järjestettyä tyyppiä (esim. integer, char, osavälityyppi tai lueteltu tyyppi).

Tarkastellaan seuraavaksi ohjelmaa, jolla havainnollistetaan vuotuisen liikevaihdon kehittymistä tulostamalla kunkin vuosineljänneksen liikevaihdon suuruutta kuvaava vaakapylväs. Kukin pylväs koostetaan kirjoittamalla peräkkäin pylvään pituutta vastaava määrä tähtimerkkejä (*). Piirtäminen voidaan siis toteuttaa askeltavalla toistolauseella, jossa askelmuuttujan alkuarvona on 1 ja loppuarvona pylvään pituutta kuvaava lauseke. Toistokertojen lukumäärä määräytyy ohjelmassa siten syötteenä annettujen lukujen perusteella.


program histogrammi (input, output);                     
(*                                                       
        Liikevaihdon tulostus histogrammina.             
        15.1.85   Matti Meikäläinen                      
*)                                                       
const pisin = 40;         (* Pisin pylväs *)             
                                                         
var mk1, mk2, mk3, mk4,   (* Vuosineljännekset  *)       
    max : real;           (* Suurin edellisistä *)       
    i   : integer;        (* Kierroslaskuri     *)       
                                                         
begin                                                    
    writeln('Liikevaihdon graafinen esitys.'); writeln;  
    writeln('Anna  liikevaihdot');                       
    write('1. vuosineljännes: '); readln(mk1);           
    write('2. vuosineljännes: '); readln(mk2);           
    write('3. vuosineljännes: '); readln(mk3);           
    write('4. vuosineljännes: '); readln(mk4);           
    writeln;                                             
                                                         
    (* Etsitään suurin: *)                               
    max := mk1;                                          
    if max < mk2 then max := mk2;                        
    if max < mk3 then max := mk3;                        
    if max < mk4 then max := mk4;                        
                                                         
    (* Tulostetaan vuosineljännes kerrallaan: *)         
                                                         
    write('1. vuosineljännes: ');                        
    for i := 1 to round(mk1 / max * pisin) do write('*');
    writeln;                                             
                                                         
    write('2. vuosineljännes: ');                        
    for i := 1 to round(mk2 / max * pisin) do write('*');
    writeln;                                             
                                                         
    write('3. vuosineljännes: ');                        
    for i := 1 to round(mk3 / max * pisin) do write('*');
    writeln;                                             
                                                         
    write('4. vuosineljännes: ');                        
    for i := 1 to round(mk4 / max * pisin) do write('*');
    writeln                                              
end.                                                     

Piirtämistä varten pylväiden pituudet on normeerattu niin, että pisin pylväs koostuu aina 40 tähtimerkistä. Kunkin pylvään tähtimerkkien määräksi saadaan siten pylvästä vastaavan liikevaihdon ja suurimman liikevaihdon suhteen mukainen osuus 40 merkistä. Esimerkistä huomataan samalla, että toistettavassa lauseessa ei tietenkään aina tarvitse käyttää hyväksi askelmuuttujan arvoa (tässä tapauksessa askelmuuttujaa käytetään pelkkänä toiston kierroslaskurina).

Esimerkkiohjelmassa käy askelmuuttuja i läpi suuruusjärjestyksessä kaikki nousevat arvot alkuarvosta loppuarvoon. Askeltava toistolause antaa kuitenkin myös mahdollisuuden toiston suorittamiseen siten, että askelmuuttuja käy läpi suuruusjärjestyksessä laskevat arvot alkuarvosta loppuarvoon. Edellinen askellustapa ilmaistaan alku- ja loppuarvojen väliin sijoitettavalla sanasymbolilla to ja jälkimmäinen puolestaan symbolilla downto.

Seuraavassa on esimerkki ohjelman histogrammi tulostuksesta:


        Liikevaihdon graafinen esitys.

Anna liikevaihdot 1. vuosineljännes: 120000 2. vuosineljännes: 150000 3. vuosineljännes: 80000 4. vuosineljännes: 140000

1. vuosineljännes: ******************************** 2. vuosineljännes: **************************************** 3. vuosineljännes: ********************* 4. vuosineljännes: *************************************

Askelmuuttujan arvoja koskien on Pascal-kielen määrittelyssä erikseen mainittu, että askelmuuttujan arvo on toistolauseen päättymisen jälkeen määrittelemätön. Ohjelmoija ei siten voi toistolauseen jälkeistä ohjelman osaa suunnitellessaan olettaa, että askelmuuttujan arvoksi olisi "jäänyt" toiston päätyttyä viimeinen käytetty arvo (eli loppuarvo).

Muodostamalla toistettava lause yhdyslauseesta voidaan toistoon sisällyttää hyvinkin laajoja toimenpidekokonaisuuksia. Mikään ei lisäksi tietenkään estä sijoittamasta toistolausetta myös toisen toistolauseen sisään.

Esimerkkinä yhdyslauseen ja sisäkkäisten toistolauseiden käytöstä tehdään aakkosmerkkejä a-z kirjoittava ohjelma, jolla voidaan testata laitteiston toimivuutta tekstin tulostamisessa. Ensimmäiselle riville kirjoitetaan peräkkäin merkit abc...xyz. Jotta kukin rivi erottuisi selvästi edellisistä, aloitetaan seuraava rivi aina edellisen rivin viimeisellä merkillä. Toiselle riville kirjoitetaan siis merkit zab...wxy, kolmannelle riville vastaavasti yza...vwx ja viimeiselle riville bcd...yza. Kirjoitus muodostaa siten merkkiketjun, joka kiertyy seuraavalla rivillä aina yhden merkkipaikan verran oikealle, kunnes koko ketju on läpikäyty.

Algoritmia suunniteltaessa huomataan välittömästi, että toiminta perustuu toistoon, jossa seuraavan rivin alkuun siirtyvä merkki (siirtomerkki) käy läpi laskevassa aakkosjärjestyksessä merkit zyx...cba. Kunkin rivin alkuun kirjoitetaan ensin nousevassa aakkosjärjestyksessä siirtomerkkiä seuraavat merkit merkkiin z saakka. Rivin loppuun kirjoitetaan sitten nousevassa aakkosjärjestyksessä merkit alkaen merkistä a ja loppuen siirtomerkkiin. Toiminnan algoritminen hahmotelma on siis seuraava:

        Käy läpi laskevassa järjestyksessä siirtomerkit z-a:
            Kirjoita yksi rivi:
            - ensin siirtomerkin jälkeiset merkit merkkiin z asti
            - sitten merkit a:sta alkaen siirtomerkkiin asti
            - lopuksi siirrytään seuraavalle riville


program kirjoitustesti (output);                                
(*                                                              
        Aakkosten kirjoitustesti.                               
        30.01.85   Matti Meikäläinen                            
*)                                                              
                                                                
const Alku  = 'a';   (* testin alkuaakkonen  *)                 
      Loppu = 'z';   (* testin loppuaakkonen *)                 
                                                                
var   merkki,        (* kirjoitettava merkki *)                 
      siirto : char; (* siirtokohdan  merkki *)                 
                                                                
begin                                                           
    writeln('Aakkosten ', Alku, '-', Loppu, ' kirjoitustesti.');
    writeln;                                                    
                                                                
    (* Käy läpi järjestyksessä siirtomerkit Loppu..Alku: *)     
    for siirto := Loppu downto Alku do                          
        (* Kirjoita yksi rivi: *)                               
        begin                                                   
        for merkki := succ(siirto) to Loppu do write(merkki);   
        for merkki := Alku to siirto do write(merkki);          
        writeln                                                 
        end (* for, siirto *)                                   
end.                                                            

Ohjelman tulostus on seuraava:


        Aakkosten a-z kirjoitustesti.

abcdefghijklmnopqrstuvwxyz zabcdefghijklmnopqrstuvwxy yzabcdefghijklmnopqrstuvwx xyzabcdefghijklmnopqrstuvw wxyzabcdefghijklmnopqrstuv vwxyzabcdefghijklmnopqrstu uvwxyzabcdefghijklmnopqrst tuvwxyzabcdefghijklmnopqrs stuvwxyzabcdefghijklmnopqr rstuvwxyzabcdefghijklmnopq qrstuvwxyzabcdefghijklmnop pqrstuvwxyzabcdefghijklmno opqrstuvwxyzabcdefghijklmn nopqrstuvwxyzabcdefghijklm mnopqrstuvwxyzabcdefghijkl lmnopqrstuvwxyzabcdefghijk klmnopqrstuvwxyzabcdefghij jklmnopqrstuvwxyzabcdefghi ijklmnopqrstuvwxyzabcdefgh hijklmnopqrstuvwxyzabcdefg ghijklmnopqrstuvwxyzabcdef fghijklmnopqrstuvwxyzabcde efghijklmnopqrstuvwxyzabcd defghijklmnopqrstuvwxyzabc cdefghijklmnopqrstuvwxyzab bcdefghijklmnopqrstuvwxyza

Toisinaan askelmuuttujan alku- ja loppuarvot saattavat olla sellaiset, että toistettavaa lausetta ei suoriteta kertaakaan. Askellustavasta riippuen tämä tilanne syntyy seuraavissa tapauksissa:

        alkuarvo > loppuarvo      (askellustapana to)
        alkuarvo < loppuarvo      (askellustapana downto)

Edellisessä esimerkkiohjelmassa havaitaan tilanne, jossa toistettavaa lausetta ei suoriteta kertaakaan. Kun uloimmassa toistolauseessa askelmuuttujan siirto arvona on ensimmäisellä toistokerralla Loppu, niin toistolauseessa


for merkki := succ(siirto) to Loppu do write(merkki)

on askelmuuttujan merkki alkuarvona succ(Loppu) ja loppuarvona Loppu. Alkuarvo on siis suurempi kuin loppuarvo, joten lausetta write(merkki) ei suoriteta kertaakaan.

Askeltavalle toistolauseelle on tunnusomaista toiston ennustettavuus. Ensinnäkin askelmuuttujan arvon muuttaminen toistettavassa lauseessa on nimenomaisesti kielletty. Lisäksi askelmuuttujan alku- ja loppuarvot lasketaan aina erikseen ennen varsinaisen toiston aloittamista. Siten näissä lausekkeissa esiintyvien muuttujien arvojen muuttaminen toistettavassa lauseessa ei enää vaikuta toistokertojen määrään. Askelmuuttujalle toiston kuluessa asetettavat arvot määräytyvät siksi täysin kiinteästi alku- ja loppuarvolausekkeiden perusteella ilman, että niihin voitaisiin enää mitenkään vaikuttaa toistettavassa lauseessa.

Toimintojen toistamista tarvitaan kuitenkin usein tilanteissa, joissa toistokertojen määrää on voitava ohjata vapaammin kuin askeltavan toistolauseen kaltaisessa rakenteessa. Tällainen tarve syntyy esimerkiksi silloin, kun halutaan tehdä keskiarvon laskeva ohjelma tilanteeseen, jossa laskennnan syötteinä olevien lukujen määrää ei ennalta tiedetä. Kun oletetaan, että keskiarvo lasketaan positiivisista luvuista, voidaan syötteiden loppuminen ilmaista negatiivisella luvulla. Keskiarvo saadaan selville yksinkertaisesti laskemalla syötteenä annettavien (positiivisten) lukujen summa ja jakamalla se lukujen määrällä. Keskiarvon laskemisen algoritmiksi saadaan siis:

        Aseta lukujen lkm ja summa nollaksi
        Toista toimenpiteitä
            - lue seuraava luku
            - jos luku >= 0  niin aseta
                  summa := summa + luku
                  lkm := lkm + 1
        kunnes luku < 0
        Jos lkm > 0 niin keskiarvo on  summa / lkm

Toiston loppuminen määräytyy algoritmissa vasta toistettavien toimenpiteiden yhteydessä (kohdassa "kunnes luku < 0"), joten askeltava toistolause ei sovellu algoritmin toteutukseen. Useiden algoritmisten kielten tapaan myös Pascal sisältää tällaisiinkin tilanteisiin soveltuvan toistorakenteen. Keskiarvoalgoritmin toteutuksessa voidaan käyttää ns. loppuehtoista toistolausetta, jolloin ohjelmaksi saadaan seuraava:


program keskiarvo (input, output);                          
(*                                                          
        Positiivisten lukujen keskiarvon laskeminen.        
        30.01.85   Matti Meikäläinen                        
*)                                                          
                                                            
var luku,            (* Kukin luku    *)                    
    summa : real;    (* Lukujen summa *)                    
    lkm   : integer; (* Lukujen määrä *)                    
                                                            
begin                                                       
    writeln('Positiivisten lukujen keskiarvon laskeminen.');
    writeln;                                                
                                                            
    summa := 0;                                             
    lkm   := 0;                                             
                                                            
    writeln('Anna luvut (lopuksi negatiivinen luku):');     
    repeat                                                  
        write(lkm + 1 : 2, '. luku: ');                     
        readln(luku);                                       
        if luku >= 0 then                                   
           begin                                            
           summa := summa + luku;                           
           lkm := lkm + 1                                   
           end (* if *)                                     
    until luku < 0;                                         
                                                            
    writeln('------------------------------');              
    writeln('Lukuja       ', lkm : 10, '    kpl');          
    if lkm > 0 then                                         
         begin                                              
         writeln('Kokonaissumma ', summa : 12 : 2);         
         writeln('Keskiarvo     ', summa / lkm : 12 : 2)    
         end                                                
    else writeln('Keskiarvo määrittelemätön')               
end.                                                        

Seuraavassa on esimerkki ohjelman keskiarvo tulostuksesta:


        Positiivisten lukujen keskiarvon laskeminen.

Anna luvut (lopuksi negatiivinen luku): 1. luku: 7.75 2. luku: 8.25 3. luku: 9 4. luku: 5 5. luku: -1 ------------------------------ Lukuja 4 kpl Kokonaissumma 30.00 Keskiarvo 7.50

Loppuehtoinen toistolause vastaa toiminnaltaan suoraan edellä tarkastellun keskiarvolaskennan algoritmin toistorakennetta. Sanasymbolien repeat ja until välissä olevia lauseita suoritetaan toistuvasti, kunnes symbolia until seuraavan totuusarvoisen lausekkeen (eli loppuehtolausekkeen) arvoksi saadaan true.


loppuehtoinen-toistolause = "repeat" lausejono
                              "until" Boolen-lauseke .

Loppuehtolausekkeen arvo lasketaan joka toistokerralla sen jälkeen, kun lausejonon lauseet on suoritettu. Tästä on tietenkin seurauksena, että lauseet suoritetaan varmasti ainakin kerran. Sen sijaan toistokertojen määrällä ei ole mitään kiinteätä ylärajaa. Ohjelmassa on siten huolehdittava siitä, että toisto todella joskus päättyy (eli lausekkeen arvoksi saadaan joskus true). Siksi loppuehtolauseke sisältää yleensä aina vähintään yhden muuttujan, jonka arvoa muutetaan toistettavissa lauseissa.

Pascal-kieleen kuuluu myös alkuehtoinen toistolause, joka muistuttaa melko paljon loppuehtoista toistolausetta:


alkuehtoinen-toistolause = "while" Boolen-lauseke "do" lause .

Sanasymbolia do seuraavaa lausetta toistetaan niin kauan kuin symbolia while seuraavan totuusarvoisen lausekkeen arvona säilyy true. Lausekkeen arvo lasketaan joka toistokerralla ennen toistettavan lauseen suoritusta, ja toisto päättyy, mikäli lausekkeen arvoksi saadaan false.

Alkuehtoisella toistolauseella ja loppuehtoisella toistolauseella on ilmeinen yhteys. Muotoa

        "while" Boolen-lauseke "do" lause

oleva toistolause tarkoittaa samaa kuin seuraava lauserakenne:

        "if" Boolen-lauseke "then"
             "repeat" lause "until" "not" "(" Boolen-lauseke ")"

Alkuehtoisen toistolauseen tarkoituksena on tarjota käyttöön ensinnäkin toistorakenne, jossa toistettavan lauseen suorituskertojen lukumäärä voi vaihdella vapaasti nollasta ylöspäin. Loppuehtoisessa toistolauseessa on toistokertoja aina vähintään yksi, kun taas alkuehtoiseen toistolauseeseen kuuluvaa lausetta ei välttämättä suoriteta kertaakaan. Lisäksi ohjelman luettavuuden ja selkeyden vuoksi on toisinaan parempi muotoilla toistorakenne käyttämällä toistoa koskevaa alkuehtoa kuin sitä seuraavaa loppuehtoa. Alkuehtohan ilmaisee nimenomaisesti toiston jatkumistilanteen, kun taas loppuehdosta nähdään havainnollisimmin toiston päättymistilanne.

Tarkastellaan seuraavaksi ohjelmaa, jolla voidaan laskea jonkin joukon lukumäärässä tapahtuvaan muutokseen kuluva aika. Laskenta perustuu tietoihin joukon lukumääristä alku- ja lopputilanteessa sekä yhdessä vuodessa tapahtuvaan prosentuaaliseen muutokseen. Ohjelmalla voidaan hakea vastausta mm. seuraavan kaltaisiin kysymyksiin:

Ohjelman toiminta perustuu toistorakenteeseen, jossa lukumäärään lasketaan yhden vuoden muutos edellyttäen, että lukumäärän loppuarvoa ei ole vielä saavutettu. Algoritmisesti toiminta voidaan hahmotella seuraavasti:

        Alussa lukumääräksi alkumäärä ja kuluneet vuodet nollaksi
        Niin kauan kuin loppuarvoa ei ole saavutettu:
             Laske yhden vuoden aiheuttamat muutokset:
                 - lukumäärän vuosimuutos
                 - vuosien määrän lisäys yhdellä

Algoritmista havaitaan, että toiston loppuminen ratkeaa vasta toistettavissa toimenpiteissä. Askeltava toistolause ei siis sovellu tähän tapaukseen. Myöskään loppuehtoinen toistolause ei ole tässä paras mahdollinen: saattaahan olla, että loppuarvo on jo heti saavutettu, jolloin vuosimuutosta ei tarvitse laskea kertaakaan. Sen sijaan alkuehtoinen toistolause soveltuu hyvin ratkaisun toteutukseen, koska siinä toiston jatkumisehto tutkitaan joka toistokerralla ennen toiston aloittamista.

Myös ohjelman syötteiden lukeminen on usein tarkoituksenmukaista järjestää toistolauseella. Heti lukemisen jälkeen tarkistetaan syötteiden kelvollisuus ja virheen tapauksessa pyydetään uudet tiedot. Jos syötteissä on virheitä, ne joudutaan pyytämään uudelleen mahdollisesti moneenkin kertaan. Toiston loppuminen ratkeaa vasta toistettavissa toimenpiteissä, joten askeltava toistolause ei sovellu tähän tarkoitukseen. Koska syötteet on pyydettävä varmasti ainakin kerran, myöskään alkuehtoinen toistolause ei ole erityisen sovelias. Syötetietojen lukeminen onkin järkevintä perustaa loppuehtoisen toistolauseen käyttöön.

Joukon lukumäärämuutoksia laskevassa ohjelmassa tarvitaan siis sekä alkuehtoista että loppuehtoista toistolausetta. Alkuehtoisen avulla järjestetään varsinainen lukumäärämuutosten laskemisosa ja loppuehtoisen avulla syötteiden lukeminen ja virhetarkistukset. Kelvollisiksi hyväksytään seuraavat ehdot täyttävät syötetiedot:


program LukumaaranMuutos (input, output);                 
(*                                                        
        Joukon lukumäärämuutoksen ajan laskeminen.        
        15.02.85   Matti Meikäläinen                      
*)                                                        
                                                          
var Alussa,               (* Lukumäärä alussa         *)  
    Lopussa,              (* Lukumäärä lopussa        *)  
    Vuodet     : integer; (* Muutokseen kuluva aika   *)  
    Maara,                (* Vuotuinen lkm-tilanne    *)  
    Pros,                 (* Vuotuinen muutospros.    *)  
    Kerroin    : real;    (* Vuotuinen muutoskerroin  *)  
    SyotteetOK : Boolean; (* Syötteiden virheilmaisin *)  
                                                          
begin                                                     
    writeln('Joukon lukumäärämuutoksen ajan laskeminen.');
    writeln;                                              
                                                          
    writeln('Anna lähtötiedot');                          
    repeat                                                
        write('Lukumäärä alussa    : '); readln(Alussa);  
        write('Vuotuinen muutos (%): '); readln(Pros);    
        write('Lukumäärä lopussa   : '); readln(Lopussa); 
        SyotteetOK := ((Alussa > 0) and (Lopussa > 0))    
          and (((Pros > 0) and (Alussa <= Lopussa)) or    
               ((Pros < 0) and (Alussa >= Lopussa)));     
        if not SyotteetOK then                            
            begin                                         
            writeln;                                      
            writeln('Epäkelvot tiedot, anna uudet')       
            end                                           
    until SyotteetOK;                                     
                                                          
    Kerroin := 1 + Pros / 100;                            
                                                          
    (* Lukumäärän ja kuluneiden vuosien alustus: *)       
    Maara   := Alussa;                                    
    Vuodet  := 0;                                         
                                                          
    (* Niin kauan kuin loppuarvoa ei ole saavutettu: *)   
    while ((Pros > 0) and (Maara < Lopussa)) or           
          ((Pros < 0) and (Maara > Lopussa)) do           
        (* Laske yhden vuoden aiheuttamat muutokset: *)   
        begin                                             
        Maara  := Maara * Kerroin;                        
        Vuodet := Vuodet + 1                              
        end; (* while *)                                  
                                                          
    writeln;                                              
    writeln('Muutos määrästä ', Alussa : 1, ' määrään ',  
      Lopussa : 1, ' kestää ', Vuodet : 1, ' vuotta,');   
    writeln('kun muutos on ', Pros : 4 : 2, ' % vuodessa')
end.                                                      

Seuraavassa esimerkissä ohjelmalla on selvitetty, kauanko kestää 480000 asukkaan kaupungin asukasluvun vähentyminen 100000 asukkaalla, kun asukkaiden määrä vähenee vuosittain 2 %. Esimerkissä on ensin annettu vahingossa virheellinen muuttumistieto (lukumäärä ei voi vähentyä, mikäli vuotuinen muutos on positiivinen). Ohjelma on siksi pyytänyt käyttäjältä uudet lähtötiedot.


        Joukon lukumäärämuutoksen ajan laskeminen.

Anna lähtötiedot Lukumäärä alussa : 480 Vuotuinen muutos (%): 2 Lukumäärä lopussa : 380

Epäkelvot tiedot, anna uudet Lukumäärä alussa : 480 Vuotuinen muutos (%): -2 Lukumäärä lopussa : 380

Muutos määrästä 480 määrään 380 kestää 12 vuotta, kun muutos on -2.00 % vuodessa

Koska toiston päättyminen sekä alkuehtoisessa että loppuehtoisessa toistolauseessa ratkeaa vasta toistettavassa toimenpidekokonaisuudessa, on mahdollista muodostaa ohjelmia, joissa toisto ei pääty koskaan. Tavallisesti tällöin on kyse ohjelmointivirheestä, jonka seurauksena toiston lopettava ehto ei voi koskaan tulla voimaan.

Toiston päättymättömyys ei kuitenkaan tarkoita sitä, etteikö toisto olisi mitenkään keskeytettävissä. Tietokoneet on yleensä aina varustettu järjestelmällä, jonka avulla käyttäjä voi keskeyttää käynnistämänsä ohjelman suorituksen milloin tahansa. Keskeyttäminen saadaan aikaan esimerkiksi painamalla tiettyjä näppäimiä tai erityistä keskeytyskytkintä. Tietokoneen toiminnan saa tietenkin pysähtymään myös virtakytkimestä. Sen käyttö ikuiseen toistoon jääneen ohjelman pysäyttämiseen ei kuitenkaan ole tavallisesti suositeltavaa.

Esimerkkinä päättymättömästä toistosta tarkastellaan ohjelmaa, jolla lasketaan toistuvasti Pascal-kielen operaatioiden mod ja div tuloksia erilaisilla operandeilla. Ohjelma perustetaan tässä ikuiselle toistolle vain siksi, että kävisi havainnollisesti ilmi, millainen ohjelmarakenne voi aiheuttaa päättymättömän toiston. Ohjelman tulee pyytää kerta toisensa jälkeen syötteitä ja antaa niitä vastaavat mod- ja divoperaatioiden tulokset. Ikuinen toisto syntyy esimerkiksi alkuehtoisessa toistolauseessa silloin, kun symbolia while seuraavan totuusarvoisen lausekkeen arvoksi saadaan aina true. Jos lausekkeessa on vain vakio-operandeja, sen tulos on aina vakio. Siten esimerkiksi seuraavien lausekkeiden arvon laskeminen tuottaa aina arvon true:


1 > 0    
9 = 9    
'b' < 'k'
not false
true     

Valitsemalla näistä yksinkertaisin (eli viimeinen vaihtoehto) voidaan ohjelma esittää seuraavasti:


program ModDivHarjoitus (input, output);                      
(*                                                            
        Operaatioiden mod ja div koekäyttö.                   
        15.02.85   Matti Meikäläinen                          
*)                                                            
                                                              
var a, b : integer; (* Luvut mod- ja div-operandeiksi *)      
                                                              
begin                                                         
    writeln('Operaatioiden mod ja div koekäyttö.');           
    writeln;                                                  
                                                              
    writeln('Ohjelma pyytää toistuvasti kaksi kokonaislukua');
    writeln('operaatioiden mod ja div operandeiksi.');        
    writeln('HUOM: ohjelma pysähtyy vain pakkokeinoin!');     
    writeln;                                                  
                                                              
    (* Toista ikuisesti toimenpiteitä: *)                     
    while true do                                             
        begin                                                 
        write('Anna kaksi kokonaislukua: '); readln(a, b);    
        writeln(a : 1, ' div ', b : 1, '  =  ', a div b : 1); 
        write(a : 1, ' mod ', b : 1, '  =  ');                
        if b > 0 then writeln(a mod b : 1)                    
        else writeln('määrittelemätön')                       
        end                                                   
end.                                                          

Ikuinen toisto ei kuitenkaan edellytä aina vakio-operandeja. Tahaton ikuinen toisto syntyy tyypillisesti esimerkiksi seuraavanlaisessa tilanteessa.


i := j;               (* jos j:n arvo on esim. -1        *)
while i <> 0 do       (* niin i ei tule koskaan nollaksi *)
    begin             (* ja toisto ei pääty koskaan      *)
    ....                                                   
    i := i - 1                                             
    end;                                                   

Seuraavassa on esimerkki yllä olevan ohjelman tulostuksesta. Ohjelma on pysäytetty esimerkissä siinä vaiheessa, kun se pyytää kolmatta lukuparia. Koska pakkopysäytys tehdään jollakin käytettävästä laitteesta täysin riippuvalla tavalla, tässä on esitetty vain periaatteellinen pysäytyskohta merkitsemällä syöteriville asiaan liittyvä huomautus.


        Operaatioiden mod ja div koekäyttö.

Ohjelma pyytää toistuvasti kaksi kokonaislukua operaatioiden mod ja div operandeiksi. HUOM: ohjelma pysähtyy vain pakkokeinoin!

Anna kaksi kokonaislukua: 25 9 25 div 9 = 2 25 mod 9 = 7 Anna kaksi kokonaislukua: 25 -9 25 div -9 = -2 25 mod -9 = määrittelemätön Anna kaksi kokonaislukua: ** Pakkopysäytys tehty tässä **

Seuraavassa on vielä kertauksena joitakin yleisohjeita toimintojen toistamisen ohjelmointiin:

Harjoitustehtäviä:

1. Tee ohjelma, joka saa syötteinä alkupääoman, korkoprosentin ja talletusvuosien lukumäärän sekä tulostaa pääoman määrän kunkin vuoden lopussa koko talletusajalta.

2. Tee ohjelma, joka tulostaa merkkineliön. Ohjelmalle annetaan syötteinä neliön pohjamerkki ja neliön sivun pituus. Kun merkitään sivun pituutta n:llä, niin merkkineliö käsittää n riviä, joista jokainen sisältää n kpl syötteenä saatua merkkiä. Ota ohjelmaa tehdessäsi huomioon, että neliön sivun pituudella on käytännössä aina jokin tulostuslaitteesta johtuva yläraja.

3. Kahden hengen tikkupelissä asetetaan pöydälle kasa tikkuja. Kumpikin pelaaja ottaa tikkukasasta vuorotellen joko yhden, kaksi tai kolme tikkua. Pelin häviää se, joka joutuu ottamaan viimeisen tikun. Tee tikkupeliä mahdollisimman taitavasti pelaava ohjelma. Ohjelmalle annetaan syötteenä tikkujen määrä ja tieto pelin aloittajasta (joko ohjelma tai tietokoneen käyttäjä).

Sanastoa:
 
toistolause repetitive statement
 
askeltava toistolause for statement
askelmuuttuja control variable
alkuehtoinen toistolause while statement
loppuehtoinen toistolause repeat statement

3.7 Tietojen koostaminen komponenteista

Alakohdassa 3.6 on esimerkkiohjelma, joka tulostaa yrityksen liikevaihdon neljännesvuosittaisen kehittymisen graafisena pylväikkönä. Ohjelman loppuosassa kiinnittyy huomio peräkkäisten lauseiden ryhmään, joka toistuu useaan kertaan lähes samanlaisena. Ainoa ero näiden ryhmien välillä on käsiteltävä muuttuja, joka ilmoittaa kulloisenkin vuosineljänneksen liikevaihdon.

Muunnetaan seuraavaksi kyseinen ohjelma tapaukseen, jossa tarkastelu suoritetaankin kuukausittain eikä neljännesvuosittain. Mikäli ohjelma muodostettaisiin samoin kuin aiemmin, toistuisivat ohjelman lauseet kahteentoista kertaan. Ottamalla käyttöön uusi käsite, taulukko, voidaan ohjelmaa kuitenkin jopa lyhentää.

Taulukolla tarkoitetaan kokoelmaa samaa tyyppiä olevia alkioita eli taulukon komponentteja. Esimerkissä näitä ovat kuukausittaiset liikevaihdot, jotka ovat kaikki reaalilukuja. Taulukolla on tunnus, johon viittaaminen tarkoittaa koko taulukkoa eli kaikkien komponenttien muodostamaa kokonaisuutta. Yksittäisillä komponenteilla on viittaamista varten niin kutsuttu indeksi. Esimerkissä taulukon tunnukseksi voidaan valita vaikkapa Liikevaihto ja sillä on 12 komponenttia, yksi kutakin kuukautta varten. Komponenttien indekseiksi on luonnollista valita kuukausien numerot 1-12. Tällainen taulukko voidaan esitellä seuraavasti:


Liikevaihto : array [1..12] of real

Esittely kertoo, että muuttuja Liikevaihto on taulukko, joka koostuu tyyppiä real olevista komponenteista, joiden indekseinä esiintyvät osavälityypin 1..12 mahdolliset arvot. Taulukkoa voidaan nyt havainnollistaa seuraavalla kuvalla. Ruudut edustavat taulukon komponentteja ja kunkin ruudun yläpuolelle on merkitty vastaava indeksi.

                1   2   3   4   5   6   7   8   9   10  11  12
              ________________________________________________
              |   |   |   |   |   |   |   |   |   |   |   |   |
Liikevaihto   |   |   |   |   |   |   |   |   |   |   |   |   |
              |___|___|___|___|___|___|___|___|___|___|___|___|
Taulukon komponentteihin viitataan ilmauksella, jossa taulukon nimeä seuraa hakasulkujen sisällä oleva indeksi. Tällainen komponentti käyttäytyy aivan kuten tavalliset muuttujatkin. Esimerkkinä olkoon seuraava sijoituslause:


Liikevaihto[3] := 9

Sijoituslause antaa arvon yhdeksän sille komponentille, jonka indeksi on kolme. Tilanne on siis sijoituslauseen jälkeen seuraava:

                1   2   3   4   5   6   7   8   9   10  11  12
              ________________________________________________
              |   |   |   |   |   |   |   |   |   |   |   |   |
Liikevaihto   |   |   | 9 |   |   |   |   |   |   |   |   |   |
              |___|___|___|___|___|___|___|___|___|___|___|___|
Tarkastellaan nyt kuukausittaisen liikevaihdon esittävää ohjelmaa. Ohjelma alkaa toistolauseella, jossa taulukon kullekin komponentille luetaan syöttötiedoista arvo. Arvot voitaisiin lukea missä järjestyksessä tahansa, mutta tietojen syöttäjän kannalta on tietenkin tavanomainen kuukausijärjestys luonnollisin. Kun kaikki kuukausiarvot on luettu, niistä voidaan etsiä suurin. Tämä tehdään ottamalla ensin tammikuun liikevaihto muuttujaan max, joka kuvaa tähän mennessä löydettyä suurinta arvoa. Sitten käydään taulukon muut komponentit läpi, ja mikäli löytyy aiempaa suurempi arvo, se otetaan muuttujan max arvoksi. Lopuksi tulostetaan pylväät.


program histogrammi (input, output);                         
(*                                                           
        Liikevaihdon tulostus histogrammina. Toinen versio.  
        15.1.85   Matti Meikäläinen                          
        28.1.85   Heikki Heikäläinen                         
*)                                                           
                                                             
const pisin = 40;         (* Pisin pylväs *)                 
                                                             
var Liikevaihto : array [1..12] of real; (* Kuukausittain *) 
    max : real;           (* Suurin edellisistä *)           
    kk,                   (* Käsiteltävä kuukausi *)         
    i : integer;          (* Kierroslaskuri *)               
                                                             
begin                                                        
    writeln('Liikevaihdon graafinen esitys.'); writeln;      
    for kk := 1 to 12 do                                     
        begin                                                
        write('Anna ', kk : 2, '. kuun liikevaihto: ');      
        readln(Liikevaihto[kk])                              
        end;                                                 
    writeln;                                                 
                                                             
    (* Etsitään suurin: *)                                   
    max := Liikevaihto[1];                                   
    for kk := 2 to 12 do                                     
        if max < Liikevaihto[kk] then max := Liikevaihto[kk];
                                                             
    (* Tulostetaan kuukausittain: *)                         
    for kk := 1 to 12 do                                     
        begin                                                
        write(kk : 2, '. kuukausi: ');                       
        for i := 1 to round(Liikevaihto[kk] / max * pisin)   
            do write('*');                                   
        writeln                                              
        end                                                  
end.                                                         

Ohjelman tulostuksesta on esimerkkinä seuraava:


        Liikevaihdon graafinen esitys.

Anna 1. kuun liikevaihto: 3785 Anna 2. kuun liikevaihto: 4674 Anna 3. kuun liikevaihto: 5895 Anna 4. kuun liikevaihto: 6974 Anna 5. kuun liikevaihto: 5130 Anna 6. kuun liikevaihto: 3605 Anna 7. kuun liikevaihto: 3720 Anna 8. kuun liikevaihto: 4257 Anna 9. kuun liikevaihto: 4348 Anna 10. kuun liikevaihto: 5543 Anna 11. kuun liikevaihto: 6635 Anna 12. kuun liikevaihto: 4720

1. kuukausi: ********************** 2. kuukausi: *************************** 3. kuukausi: ********************************** 4. kuukausi: **************************************** 5. kuukausi: ***************************** 6. kuukausi: ********************* 7. kuukausi: ********************* 8. kuukausi: ************************ 9. kuukausi: ************************* 10. kuukausi: ******************************** 11. kuukausi: ************************************** 12. kuukausi: ***************************

Tarkastellaan seuraavaksi taulukkotyypin syntaksia.


taulukkotyyppi = "array" "[ indeksityyppi { "," indeksityyppi } "]"
                   "of" komponenttityyppi .

indeksityyppi = järjestetty-tyyppi .

järjestetty-tyyppi = järjestetyn-tyypin-kuvaus | järjestetyn-tyypin-tunnus .

järjestetyn-tyypin-tunnus = tyypin-tunnus .

komponenttityyppi = tyyppi-ilmaus .

Syntaksista ilmenee, että komponenttien tyyppi saa olla mikä tahansa. Sen sijaan indeksin tyypille on rajoituksia. Ensinnäkin indeksin tyypin tulee olla järjestetty tyyppi ja toiseksi tyyppi integer on kielletty, jottei taulukossa voisi olla äärettömän monta komponenttia. Niinpä indeksin kelvollisia tyyppejä ovat char, Boolean ja luetellut tyypit, näiden osavälityypit sekä tyypin integer osavälityypit. Esimerkissä indeksin tyyppi oli 1..12 eli eräs kokonaislukujen osaväli.

Syntaksista näkyy myös, että taulukolle voidaan antaa useita indeksityyppejä. Tällaista taulukkoa kutsutaan moniulotteiseksi taulukoksi. Tarkastellaan esimerkkinä tammipelin pelilautaa, joka koostuu 64 ruudusta. Nämä ruudut sijaitsevat kahdeksassa rivissä seuraavasti:

            ________________
        1   |_|_|_|_|_|_|_|_|
        2   |_|_|_|_|_|_|_|_|
        3   |_|_|_|_|_|_|_|_|
        4   |_|_|_|_|_|_|_|_|
        5   |_|_|_|_|_|_|_|_|
        6   |_|_|_|_|_|_|_|_|
        7   |_|_|_|_|_|_|_|_|
        8   |_|_|_|_|_|_|_|_|
             a b c d e f g h

Kuhunkin ruutuun on nyt luonnollista viitata kahdella indeksillä, ruudun rivin ilmoittavalla numerolla sekä sarakkeen ilmoittavalla kirjaimella. Esimerkiksi vasemmassa ylänurkassa oleva ruutu on 1,a. Pascal-kielessä tällainen taulukkotyyppi voidaan määritellä seuraavasti:


type                                        
    ruutu = (Tyhja, Valkea, ValkeaKunkku,   
             Musta, MustaKunkku);           
                                            
    lauta = array [1..8, 'a'..'h'] of ruutu;

Taulukkotyypillä lauta on siis kaksi indeksiä, joista ensimmäinen voi vaihdella välillä 1-8 ja toinen on tyypin char osavälityyppiä ja voi vaihdella välillä a-h. Taulukon komponentit ovat lueteltua tyyppiä ruutu, jossa on arvot eri tilanteita varten. Nyt voidaan esitellä edellä kuvattua muotoa oleva taulukkomuuttuja peli:


var peli : lauta;

Moniulotteisen taulukon yksittäisiin komponentteihin viitataan kuten yksiulotteisenkin tapauksessa paitsi, että pilkuilla toisistaan erotettuja indeksejä on useampia. Niinpä seuraava sijoitus ilmaisee, että laudan oikeaan ylänurkkaan edennyt valkea nappula kruunataan kuninkaaksi.


peli[1, 'h'] := ValkeaKunkku

Komponenttien viittaamisen syntaksi on seuraava:


komponenttimuuttuja = indeksoitu-muuttuja .

indeksoitu-muuttuja = taulukkomuuttuja "[" indeksilauseke { "," indeksilauseke } "]" .

taulukkomuuttuja = muuttujaviittaus .

indeksilauseke = lauseke .

Edellä kuvattuja rakenteita käyttäen on laadittu seuraava ohjelma. Se saa syötteenään laudalla olevien nappuloiden sijainnit ja tulostaa laudan siistissä muodossa. Tulostuksessa on valkean nappulan merkki 0, mustan @ ja kuninkaalla on lisäksi kruunu (w). Ohjelmassa ei määritellä tyyppiä lauta erikseen, vaan se on liitetty muuttujan peli esittelyyn. Toisaalta on määritelty uudet tyypit, joita käytetään indeksityyppeinä. Nämä muutokset eivät vaikuta millään tavalla syntyvän taulukkomuuttujan muotoon tai siihen viittaamiseen.

Taulukon komponenttien alkuarvo on tavanomaisten muuttujien tapaan määrittelemätön. Tämän takia ohjelman toiminta alkaa taulukon alustuksella, jossa kullekin komponentille annetaan alkuarvoksi Tyhja. Kustakin laudalla olevasta nappulasta ohjelma saa syötteenä nappulan lajin ja sijainnin, joiden avulla ohjelma sijoittaa tiedon nappulan lajista sijainnin antamaan komponenttiin. Mikäli samaan pelilaudan ruutuun ilmoitetaan useampia nappuloita, niistä jää voimaan viimeinen.

Kun pelitilanne on saatu taulukkoon, ohjelma tulostaa pelilaudan siistissä muodossa. Pelilaudan kukin ruuturivi on tulostettu kolmena tulostusrivinä. Siksi ohjelmassa on käytetty seuraavaa muotoa olevaa sisäkkäisten silmukoiden rakennetta:


for ri := 1 to 8 do             (* Kutakin ruuturiviä kohti *)
    for i := 1 to 3 do          (* kolme tulostusriviä, joilla
                                   kullakin on käytävä läpi *)
        for sa := 'a' to 'h' do (* kaikki sarakkeet, joilla   
                                   kullakin on merkit .?. sekä
                                   ruutuja rajoittava ! *)    
            (* Tulosta merkit *)                              

Alin tulostettava rivi saa erikoishuomion, koska se muodostaa samalla pelilaudan alakehyksen. Muissa kohdissa vaihdetaan pelilaudan ruudun pohjavärin ilmaisevan muuttujan pohja arvoa aina siirryttäessä uuden ruudun alueelle. Alimmalla rivillä tätä ei tehdä, jolloin syntyy toivottu vaikutus.


program tammi (input, output);                                     
(*                                                                 
        Tammipelin tilanteen tulostus.                             
        30.11.84   Matti Meikäläinen                               
*)                                                                 
                                                                   
type                                                               
    ruutu = (Tyhja, Valkea, ValkeaKunkku,                          
             Musta, MustaKunkku);                                  
    rivi = 1..8;                                                   
    sarake = 'a'..'h';                                             
                                                                   
var peli : array [rivi, sarake] of ruutu;                          
    ru : ruutu;         (* Tulostettava ruutu *)                   
    ri : rivi;                                                     
    sa : sarake;                                                   
    laji,               (* Nappulan laji *)                        
    pohja : char;       (* Ruudun pohjaväri *)                     
    i : integer;                                                   
                                                                   
begin                                                              
    writeln('Tammipelin tilanteen tulostus.'); writeln;            
                                                                   
    (* Pelitilanteen alustus, kaikki ruudut tyhjiksi: *)           
    for ri := 1 to 8 do                                            
        for sa := 'a' to 'h' do                                    
            peli[ri, sa] := Tyhja;                                 
                                                                   
    (* Pelitilanteen lukeminen: *)                                 
    writeln('Anna nappuloiden lajit ja sijainnit, kun');           
    writeln('v = valkea sotilas      V = valkea kuningas');        
    writeln('m = musta sotilas       M = musta kuningas');         
    writeln('Esimerkiksi: V4e');                                   
    writeln('Lopeta antamalla jokin muu laji kuin v, V, m tai M.');
                                                                   
    repeat                                                         
        write('Anna nappula: '); readln(laji, ri, sa);             
        if      laji = 'v' then peli[ri, sa] := Valkea             
        else if laji = 'V' then peli[ri, sa] := ValkeaKunkku       
        else if laji = 'm' then peli[ri, sa] := Musta              
        else if laji = 'M' then peli[ri, sa] := MustaKunkku        
    until (laji <> 'v') and (laji <> 'V') and                      
          (laji <> 'm') and (laji <> 'M');                         
    writeln;                                                       
                                                                   
    (* Pelitilanteen tulostaminen: *)                              
    pohja := ' ';    (* Vasen yläkulma on vaalea ruutu *)          
    for i := 1 to 33 do write('.'); writeln; (* Yläkehys *)        
    for ri := 1 to 8 do                                            
        begin (* Tulosta yksi lautarivi *)                         
        (* Lautarivin tulostuksessa kolme tulostusriviä: *)        
        for i := 1 to 3 do                                         
            begin (* Yksi tulostusrivi *)                          
            write('!');   (* Pystyviiva ruutujen eteen *)          
            for sa := 'a' to 'h' do                                
                begin                                              
                (* Yhden ruudun osuus tältä tulostusriviltä: *)    
                ru := peli[ri, sa];  (* Tämä ruutu *)              
                write(pohja);                                      
                case i of  (* Tulostusrivin numerosta riippuen *)  
                     1: if (ru = ValkeaKunkku) or  (* Kruunu ? *)  
                           (ru = MustaKunkku)                      
                        then write('w') else write(pohja);         
                     2: case ru of                                 
                             Tyhja:                write(pohja);   
                             Valkea, ValkeaKunkku: write('O');     
                             Musta, MustaKunkku:   write('@')      
                        end;                                       
                     3: write(pohja)                               
                end; (* case i *)                                  
                write(pohja, '!');                                 
                if (ri <> 8) or (i <> 3) then (* Ei alin rivi *)   
                    if pohja = ' ' (* Vaihda pohjaväri *)          
                        then pohja := '.' else pohja := ' '        
                end; (* for sa *)                                  
            writeln                                                
            end; (* for i, yksi tulostusrivi *)                    
        if pohja = ' ' then pohja := '.' else pohja := ' '         
        end  (* for ri, pelilaudan yksi rivi *)                    
end.                                                               

Seuraavassa on esimerkki ohjelman tulostamasta pelilaudasta. Laudalla on kolme valkeaa nappulaa, joista yksi on kuningas, ja samoin kolme mustaa nappulaa, joista kuninkaita on kaksi.


             .................................
             !   !...!   !.w.!   !...!   !...!
             !   !...!   !.O.!   !...!   !...!
             !   !...!   !...!   !...!   !...!
             !...!   !...!   !...!   !...!   !
             !...!   !...!   !...!   !...!   !
             !...!   !...!   !...!   !...!   !
             !   !...!   !...!   !...!   !...!
             !   !...!   !...!   !.@.!   !...!
             !   !...!   !...!   !...!   !...!
             !...!   !.w.!   !...!   !...!   !
             !...!   !.@.!   !...!   !...!   !
             !...!   !...!   !...!   !...!   !
             !   !...!   !...!   !...!   !...!
             !   !...!   !...!   !...!   !...!
             !   !...!   !...!   !...!   !...!
             !...!   !...!   !...!   !...!   !
             !...!   !...!   !.O.!   !...!   !
             !...!   !...!   !...!   !...!   !
             !   !...!   !.w.!   !...!   !...!
             !   !...!   !.@.!   !...!   !...!
             !   !...!   !...!   !...!   !...!
             !...!   !...!   !...!   !...!   !
             !...!   !...!   !...!   !.O.!   !
             !...!...!...!...!...!...!...!...!

Tämä tulostus syntyy seuraavilla syötteillä.


        Tammipelin tilanteen tulostus.

Anna nappuloiden lajit ja sijainnit, kun v = valkea sotilas V = valkea kuningas m = musta sotilas M = musta kuningas Esimerkiksi: V4e Lopeta antamalla jokin muu laji kuin v, V, m tai M. Anna nappula: v6e Anna nappula: v8g Anna nappula: V1d Anna nappula: m3f Anna nappula: M4c Anna nappula: M7d Anna nappula: a1a

Taulukon komponenttien lukumäärä voidaan määritellä kuinka suureksi tahansa. Erityisesti moniulotteisen taulukon tapauksessa komponenttien määrä kasvaa helposti erittäin suureksi. Jos indeksityypit ovat esimerkiksi 1..20 ja 5..34, niin taulukossa on kaikkiaan 20*30 eli 600 komponenttia. Näille kaikille on järjestelmän tietenkin varattava tilaa muistista.

Taulukkotyypin määrittelyssä voidaan järjestelmälle ilmoittaa, että taulukko on syytä tallettaa tiivistetyssä muodossa. Tällöin järjestelmä sulloo komponentit mahdollisimman lähelle toisiaan, jolloin muistitilaa säästyy. Toisaalta komponenttien käsittely saattaa hidastua, koska viitatun komponentin löytäminen voi vaatia ylimääräisiä toimenpiteitä. Toive tällaisen sisäisen esitysmuodon käyttämisestä ilmoitetaan lisäämällä taulukkotyypin kuvauksen eteen sanasymboli packed seuraavan syntaksin mukaisesti.


rakenteisen-tyypin-kuvaus = [ "packed" ]
                              pakkaamaton-rakenteinen-tyyppi .

pakkaamaton-rakenteinen-tyyppi = taulukkotyyppi .

Pakattuja ja pakkaamattomia taulukkoja ja niiden komponentteja käsitellään aivan samoin tavoin.

Sellaista pakattua taulukkotyyppiä, jonka komponenttityyppi on char ja jonka ainoa indeksityyppi on arvosta 1 alkava osaväli, kutsutaan merkkijonotyypiksi. Merkkijonojen käsittelyssä on sallittua tehdä eräitä toimenpiteitä, jotka eivät ole sallittuja muille taulukoille.

Ensinnäkin merkkijonolle voidaan sijoittaa arvo siten, että sijoitettavana arvona on heittomerkeillä ympäröity vakiojono, jossa on oltava niin monta merkkiä kuin taulukossa on komponentteja.

Toiseksi kahta samanpituista merkkijonoa voidaan vertailla tavanomaisilla vertailuoperaattoreilla. Merkkijonojen järjestys on normaali aakkosjärjestys. Esimerkiksi jono 'laude' on pienempi kuin 'lauta', koska niiden kolme ensimmäistä kirjainta ovat samat, mutta neljännen kohdalla kirjain d edeltää aakkosissa kirjainta t.

Kolmanneksi merkkijono voidaan tulostaa yhtenä kokonaisuutena ilman, että tarvitsisi tulostaa erikseen siihen kuuluvat merkit peräkkäin yksi kerrallaan. Näitä kaikkia piirteitä käytetään hyväksi seuraavassa ohjelmassa. Se saa syötteenään valtion nimen ja tulostaa tiedon siitä, miten tämä nimi suhtautuu aakkosjärjestyksessä nimeen Finland.


program ValtionNimi (input, output);            
(*                                              
        Valtion nimen vertailu Finlandiin.      
        15.1.85   Matti Meikäläinen             
*)                                              
                                                
const pit = 10;         (* Jonojen pituus *)    
                                                
var Suomi, muu : packed array [1..pit] of char; 
    i : 1..pit;                                 
                                                
begin                                           
    writeln('Valtion nimen vertailu.'); writeln;
    write(' ' : 23);                            
    for i := 1 to pit do write('*'); writeln;   
    write('Anna nimi ', pit : 2, ' merkillä: ');
    for i := 1 to pit do read(muu[i]);          
    writeln;                                    
                                                
    Suomi := 'Finland   ';                      
    write('Aakkosjärjestys on ');               
    if Suomi < muu                              
        then writeln(Suomi, muu : pit + 1)      
        else writeln(muu, Suomi : pit + 1)      
end.                                            

Esimerkkinä ohjelman tulostuksesta olkoon seuraava:


        Valtion nimen vertailu.

********** Anna nimi 10 merkillä: Norway

Aakkosjärjestys on Finland Norway

Ohjelmassa aiheuttaa harmia merkkijonon kiinteä pituus, mikä on huomioitava erityisesti syötteitä käsiteltäessä. Koska ohjelma lukee 10 merkkiä, on myös syötteen oltava näin pitkä. Ohjelma tosin tulostaa syöttörivin yläpuolelle oikean pituuden osoittavan määrän tähtiä, jottei ohjelman käyttäjän tarvitsisi laskea syöttämiensä merkkien määrää.

Edellä kuvattu ratkaisu ei kuitenkaan ole käytännöllinen. Kieleen kuuluukin ennaltaesitelty funktio eoln (lyhennys sanoista end of line), jolla ei ole yhtään kutsuparametria. Sen palauttama totuusarvo ilmoittaa, onko syöttörivi jo loppu vai ei. Esimerkkinä tarkastellaan edellistä ohjelmaa, joka tällä kertaa ottaa molemmat nimet syötteinä. Nimeä muodostaessaan ohjelma käy toistolauseessa läpi merkkijonon kaikki komponentit ja lukee komponentille arvon syötteestä, mutta jos rivi on jo loppu, ottaa arvoksi välilyöntimerkin eli tyhjeen.


program nimet (input, output);                              
(*                                                          
        Nimien vertailu.                                    
        15.1.85   Matti Meikäläinen                         
*)                                                          
                                                            
const pit = 30;         (* Jonojen pituus *)                
                                                            
var maa1, maa2 : packed array [1..pit] of char;             
    i : 1..pit;                                             
                                                            
begin                                                       
    writeln('Nimien vertailu.'); writeln;                   
                                                            
    write('Anna 1. nimi enintään ', pit : 2, ' merkillä: ');
    for i := 1 to pit do                                    
        if eoln then maa1[i] := ' ' (* Loppuun tyhjää *)    
                else read(maa1[i]); (* Seuraava merkki *)   
    readln; (* Siirrytään seuraavalle riville *)            
                                                            
    write('Anna 2. nimi enintään ', pit : 2, ' merkillä: ');
    for i := 1 to pit do                                    
        if eoln then maa2[i] := ' '                         
                else read(maa2[i]);                         
    writeln;                                                
                                                            
    writeln('Aakkosjärjestys on:');                         
    if maa1 < maa2                                          
        then begin writeln(maa1); writeln(maa2) end         
        else begin writeln(maa2); writeln(maa1) end         
end.                                                        

Esimerkki ohjelman tulostuksesta:


        Nimien vertailu.

Anna 1. nimi enintään 30 merkillä: Suomi Anna 2. nimi enintään 30 merkillä: Ruotsi

Aakkosjärjestys on: Ruotsi Suomi

Seuraavasta vakiomerkkijonojen syntaksista ilmenee, että jos jonoon halutaan kirjoittaa heittomerkki, se on kirjoitettava kahtena. Yksinäinen heittomerkki tulkitaan nimittäin merkkijonon päättäväksi merkiksi.


merkkijono = "'" jonoalkio { jonoalkio } "'" .

jonoalkio = heittomerkin-kuva | jonomerkki .

heittomerkin-kuva = "''" .

jonomerkki = mikä-tahansa-järjestelmän-tuntema-merkki .

Seuraavassa on joitakin esimerkkejä merkkijonojen esityksistä ja niiden tarkoittamista jonoista.

'Jono'                          Jono
'Tarkk''ampujankatu 6'          Tarkk'ampujankatu 6
'''pohatta'''                   'pohatta'
''''                            '

Tarkastellaan lopuksi esimerkkiä, jossa lajitellaan syötteenä annettava nimiluettelo. Kukin nimi koostuu kahdesta osasta: etu- ja sukunimestä, jotka molemmat ovat merkkijonoja. Tällaisia nimiä varten esitellään kaksiulotteinen taulukko, jonka ensimmäinen indeksi kertoo, monennestako nimestä on kysymys. Toinen indeksi ilmoittaa, onko kyseessä etu- vai sukunimi. Käytettävä tietorakenne on siis seuraavanlainen:


        etu                     suku
     _______________________________________________
    |                       |                       |
  1 | Jorma                 | Sajaniemi             |
    |_______________________|_______________________|
    |                       |                       |
  2 | Martti                | Karjalainen           |
    |_______________________|_______________________|
    |                       |                       |
  3 |                       |                       |
    |_______________________|_______________________|
    |                       |                       |
  . .          .            .           .           .
  . .          .            .           .           .
  . .          .            .           .           .
    |                       |                       |
    |_______________________|_______________________|
    |                       |                       |
100 |                       |                       |
    |_______________________|_______________________| 

Lajittelu suoritetaan vertaamalla aluksi ensimmäistä nimeä kaikkiin muihin. Mikäli muualta löytyy aakkosjärjestyksessä pienempi nimi, niin sen paikkaa vaihdetaan ensimmäisenä olevan kanssa. Kun kaikki vertailut on tehty, on ensimmäisenä oleva varmasti oikeassa paikassa. Tämän jälkeen verrataan toisena olevaa kaikkiin sen perässä oleviin vastaavalla tavalla. Tämän vaiheen jälkeen sekä ensimmäinen että toinen nimi ovat oikeilla paikoillaan. Näin jatkaen saadaan koko taulukko järjestykseen.

Käytetty lajittelumenetelmä ei ole kovin tehokas. Tarvittavien vertailujen määrä nousee taulukon koon kasvaessa suureksi, ja ohjelmasta tulee hidas. Yksinkertaisissa sovelluksissa tämä on kuitenkin riittävä menetelmä.

Ennen lajittelua ohjelma kysyy käyttäjältä lajitteluperustetta. Lajittelu voidaan tehdä joko etu- tai sukunimen mukaan. Se voidaan kuitenkin suorittaa joka tapauksessa täsmälleen samalla osalla ohjelmaa. Lajitteluperuste talletetaan nimittäin muuttujaan, jota käytetään viitattaessa taulukon komponentteihin. Jos muuttujan arvo on esimerkiksi suku, niin jokainen viittaus kohdistuu taulukon sukunimiin ja lajittelu tapahtuu näiden mukaisesti.


program lajittelu (input, output);                          
(*                                                          
        Nimiluettelon lajittelu.                            
        15.1.85   Matti Meikäläinen                         
*)                                                          
                                                            
const                                                       
    pit = 30;         (* Jonojen pituus *)                  
    max = 100;        (* Luettelon maksimipituus *)         
                                                            
type                                                        
    jono = packed array [1..pit] of char;  (* Kukin jono *) 
    NimiLaji = (etu, suku);   (* Mikä nimi kyseessä *)      
                                                            
var luettelo : array [1..max, NimiLaji] of jono;            
    apu : jono;                                            
    lkm,               (* Luettelon todellinen pituus *)    
    i, j : integer;                                         
    loppu : Boolean;   (* Syöttötietojen loppuminen *)      
    c : char;                                               
    laji : NimiLaji;   (* Lajitteluperuste *)               
                                                            
begin                                                       
    writeln('Nimiluettelon lajittelu.'); writeln;           
                                                            
    (* Nimiluettelon lukeminen: *)                          
    lkm := 1; (* Seuraavaksi luettavan numero *)            
    repeat                                                  
        write('Anna etunimi (lopuksi tyhjä): ');            
        for i := 1 to pit do                                
            if eoln then luettelo[lkm, etu][i] := ' '       
                    else read(luettelo[lkm, etu][i]);       
        readln;                                             
        loppu := luettelo[lkm, etu][1] = ' '; (* Ei etu-    
                                 nimeä, siis syöte loppu * )
        if not loppu then                                   
            begin (* Etunimi ei ollut tyhjä, jatketaan *)   
            write('Anna sukunimi               : ');        
            for i:= 1 to pit do                             
                if eoln then luettelo[lkm, suku][i] := ' '  
                        else read(luettelo[lkm, suku][i]);  
            readln;                                         
            lkm := lkm + 1                                  
            end                                             
    until loppu;                                            
    lkm := lkm - 1; (* Viimeinen olemassaoleva *)           
                                                            
    (* Lajitteluperusteen selvittäminen: *)                 
    repeat                                                  
        write('Minkä mukaan lajitellaan, etu- (e) vai suku',
            'nimen (s): ');                                 
        readln(c);                                          
    until (c = 'e') or (c = 's');                           
    if c = 'e' then laji := etu else laji := suku;          
                                                            
    (* Nimiluettelon lajittelu: *)                          
    for i := 1 to lkm - 1 do                                
        for j := i + 1 to lkm do                            
            if luettelo[i, laji] > luettelo[j, laji] then   
                begin (* Väärä järjestys, vaihdetaan *)     
                apu := luettelo[i, etu];                    
                luettelo[i, etu] := luettelo[j, etu];       
                luettelo[j, etu] := apu;                    
                apu := luettelo[i, suku];                   
                luettelo[i, suku] := luettelo[j, suku];     
                luettelo[j, suku] := apu                    
                end;                                        
                                                            
    (* Nimiluettelon tulostus: *)                           
    writeln;                                                
    for i := 1 to lkm do                                    
        writeln(luettelo[i, etu], luettelo[i,suku])         
end.                                                        

Seuraavassa on esimerkki ohjelman tulostuksesta.


        Nimiluettelon lajittelu.

Anna etunimi (lopuksi tyhjä): Jorma Anna sukunimi : Sajaniemi Anna etunimi (lopuksi tyhjä): Martti Anna sukunimi : Karjalainen Anna etunimi (lopuksi tyhjä): Aku Anna sukunimi : Ankka Anna etunimi (lopuksi tyhjä): Hannu Anna sukunimi : Hanhi Anna etunimi (lopuksi tyhjä): Minkä mukaan lajitellaan, etu- (e) vai sukunimen (s): s

Aku Ankka Hannu Hanhi Martti Karjalainen Jorma Sajaniemi

Taulukoiden osalta on vielä paikallaan muuan varoitus: taulukoita ei pidä käyttää, jos niitä ei tarvitse. Jos on esimerkiksi laskettava lukujoukon keskiarvo, niin ei ole mitään mieltä lukea näitä ensin taulukkoon ja sitten suorittaa keskiarvoa varten tarvittava lukujen yhteenlasku. Paljon järkevämpää on laskea summaa sitä mukaan kuin lukuja syötetään. Tällöin tullaan toimeen kahdella muuttujalla: toinen summaa varten ja toinen lukujen lukumäärän laskemista varten. Ennen taulukon käyttöönottoa onkin aina mietittävä, onko taulukko todella tarpeellinen vai ei.

Harjoitustehtäviä:

1. Hiihtokilpailuissa on kilpailijat numeroitu sadasta alkaen. Laadi ohjelma, joka saa syötteenä kilpailijoiden ajat lähtöeli numerojärjestyksessä. Tämän jälkeen ohjelman tulee tulostaa kullekin kilpailijalle suhteellinen aika eli se, kuinka paljon kyseinen kilpailija on hävinnyt voittajalle.

2. Ratkaise kohdan 3.5 harjoitustehtävä 2 käyttäen taulukkoa, johon ohjelman alussa talletetaan em. tehtävästä ilmenevät verotusta kuvaavat tiedot ja josta sitten etsitään toistorakenteen avulla syötteen tapausta kuvaavat verotustiedot.

3. Muuta ohjelma histogrammi käyttämään tulosteissaan kuukausien numeroiden sijasta niiden nimiä. Talleta nimet taulukkoon ohjelman alussa.

Sanastoa:
 
taulukko array
komponentti component
indeksi index
moniulotteinen multi-dimensional
pakattu packed
merkkijono string
 
alustaa initialize
lajitella sort

3.8 Lausekkeet

Edellä on lausekkeita käytetty vapaasti kiinnittämättä huomiota niiden yksityiskohtaiseen syntaksiin ja erikoissääntöihin. Tässä kohdassa vihdoin esitetään lausekkeiden käyttö yksityiskohtaisesti.

Lausekkeen esiintyminen ohjelmassa tarkoittaa aina, että tarkoituksena on laskea lausekkeen arvo. Yksinkertaisimmassa tapauksessa lausekkeen muodostaa yksi muuttujaviittaus tai etumerkitön vakio. Yleisesti tarkastellen lauseke voi sisältää myös funktioilmauksia ja näitä kaikkia yhdistäviä operaattoreita.

Operaattoreiden kesken vallitsee sidontajärjestys, mikä tarkoittaa sitä, että operattoreiden suoritusjärjestys ei välttämättä ole vasemmalta oikealle. Esimerkiksi lausekkeen 20+3*2 arvo on 26 eli kertolasku suoritetaan ennen yhteenlaskua. Tätä laskujärjestystä voidaan muuttaa tavanomaiseen tapaan käyttämällä sulkuja. Niinpä lausekkeen (20+3)*2 arvo on 46.

Lausekkeiden syntaksissa on operaattoreiden sidontajärjestyksen esittämistä varten otettu käyttöön välikesymbolit vertailuoperaattori, yhteenlaskuoperaattori ja kertolaskuoperaattori.


lauseke = yksinkertainen-lauseke [ vertailuoperaattori
            yksinkertainen-lauseke ] .

yksinkertainen-lauseke = [ etumerkki ] termi { yhteenlaskuoperaattori termi } .

termi = tekijä { kertolaskuoperaattori tekijä } .

tekijä = muuttujaviittaus | etumerkitön-vakio | funktioilmaus | "(" lauseke ")" | "not" tekijä .

vertailuoperaattori = "=" | "<>" | "<" | ">" | "<=" | ">=" .

yhteenlaskuoperaattori = "+" | "-" | "or" .

kertolaskuoperaattori = "*" | "/" | "div" | "mod" | "and" .

etumerkki = "+" | "-" .

Tarkastellaan esimerkkinä lauseketta 20+3*2. Edellisiä sääntöjä käyttämällä tämä lauseke saadaan aikaan lähtien välikesymbolista lauseke seuraavan taulukon mukaisesti. Taulukossa säännöt on ajateltu numeroiduksi yhdestä kahdeksaan.

Nro Saatava jono
1 yksinkertainen-lauseke
2 termi yhteenlaskuoperaattori termi
3 tekijä yhteenlaskuoperaattori termi
4 etumerkitön-vakio yhteenlaskuoperaattori termi
6 ym. 20 + termi
3 20 + tekijä kertolaskuoperaattori tekijä
4, 4 20 + etumerkitön-vakio kertolaskuoperaattori etumerkitön-vakio
7 ym. 20 + 3 * 2

Esimerkissä ilmestyy operaattori + ennen kertolaskuoperaattoria. Toisin sanoen yhteenlaskun kannalta lausekkeen osa 3*2 on toinen operandi, jonka arvo on selvitettävä ennen yhteenlaskun suorittamista.

Lausekkeen kuvaavista säännöistä seuraa, että operaattorien sidontajärjestys on seuraava:

Lisäksi yhtäsitovat operaattorit suoritetaan vasemmalta oikealle. Siten seuraavat lausekkeet tarkoittavat aina pareittain samaa.


a + b * c  
a + (b * c)

a < b + 10  
a < (b + 10)

(a <= 0) and (b = 2*c)    
(a <= 0) and (b = (2 * c))

a / b / c                            
(a / b) / c       eli     a / (b * c)

Sen sijaan seuraavassa muodossa sulut ovat pakolliset, koska suluton muoto tarkoittaa jotain aivan muuta (ja virheellistä):


(a < 0)  and  (b < 0)

Edellä olleessa lausekkeiden syntaksissa esimerkiksi kertolaskuoperaattorit näyttävät olevan kaikki samassa asemassa ja siten keskenään vaihtoehtoisia. Kielen semantiikka asettaa kuitenkin rajoituksia operaattoreiden käsittelemien arvojen tyypeille. Niinpä operaattori div kohdistuu kahteen kokonaislukuun, kun taas operaattori and kohdistuu kahteen totuusarvoon. Vastaavasti käsite yhteenlaskuoperaattori on otettu syntaksiin sidontajärjestyksen takia ja eri operaattorien sallitut käyttöyhteydet riippuvat lausekkeissa esiintyvien muuttujien ja arvojen tyypeistä.

Seuraavat taulukot kuvaavat eri operaattoreiden ominaisuudet. Kustakin operaattorista annetaan lyhyt kuvaus ja kerrotaan operaattorin tarvitsemien operandien, ts. operaattorin käyttämien arvojen, mahdolliset tyypit ja tuloksen tyyppi. Operandin tyyppi saa tietenkin olla myös taulukossa esiintyvän tyypin osaväli. On huomattava, että tuloksen tyyppi riippuu eräissä tapauksissa operandien tyypeistä. Niinpä yhteenlaskun tulos on tyyppiä integer, jos molemmat yhteenlaskettavat ovat tätä tyyppiä, mutta tuloksen tyyppi on real, jos edes toinen yhteenlaskettavista on tyyppiä real.

KAKSIOPERANDISET ARITMEETTISET OPERAATTORIT

Operaattori Selitys Operandit Tulos
+ yhteenlasku integer tai real 1)
- vähennyslasku integer tai real 1)
* kertolasku integer tai real 1)
/ jakolasku integer tai real real
div katkaiseva jakolasku integer integer
mod jakojäännöksen kokonaisosa (positiivisille luvuille) integer integer

1) integer, jos molemmat operandit tyyppiä integer real muuten

YKSIOPERANDISET ARITMEETTISET OPERAATTORIT (ETUMERKIT)

Operaattori Selitys Operandit Tulos
+ ei vaikutusta integer integer
real real
- merkinvaihto integer integer
real real

Boolen operaattorit muodostavat operandeinaan olevista tyypin Boolean arvoista tuloksen, joka on myös tyyppiä Boolean. Boolen operaattorit ovat seuraavat.

KAKSIOPERANDISET BOOLEN OPERAATTORIT

Operaattori Selitys Operandit Tulos
or looginen tai Boolean Boolean
and looginen ja Boolean Boolean

YKSIOPERANDINEN BOOLEN OPERAATTORI

Operaattori Selitys Operandit Tulos
not looginen ei Boolean Boolean

Boolen operaattorien merkitys selviää seuraavista totuustauluista.

          a     b    |   a or b
        ------------------------
        false false  |   false
        false true   |   true
        true  false  |   true
        true  true   |   true

a b | a and b ------------------------ false false | false false true | false true false | false true true | true

a | not a ----------------- false | true true | false

Vertailuoperaattoreiden operandien on oltava vertailukelpoisia. Tämä toteutuu, jos ne ovat joko samaa tyyppiä tai sitten toinen vertailtavista on tyyppiä real ja toinen tyyppiä integer.

VERTAILUOPERAATTORIT

Operaattori Selitys Operandit Tulos
= yhtäsuuri kuin integer tai real Boolean
char Boolean
Boolean Boolean
jokin lueteltu tyyppi Boolean
merkkijono Boolean
<> eri kuin -"- -"-
< pienempi kuin -"- -"-
> suurempi kuin -"- -"-
<= pienempi tai yhtäsuuri kuin -"- -"-
>= suurempi tai yhtäsuuri kuin -"- -"-

Verrattaessa totuusarvoja on voimassa false < true.

Lausekkeista on enää käsittelemättä vakiot, jotka ovat pääosin itsensäselittäviä. Ainoastaan reaalilukujen esitys kaipaa tarkastelua.

Aiemmin on todettu, että reaaliluvuissa käytetään desimaalipilkun sijasta pistettä. Tämän lisäksi luvun loppuun voidaan myös lisätä e-kirjain ja mahdollisella etumerkillä varustettu kokonaisluku. Merkintä tarkoittaa sitä kymmenen potenssia, jolla reaaliluku on kerrottava. Siis luku 15e2 tarkoittaa lukua 1500.0 (= 15 * 100). Vastaavasti 4.7e-3 tarkoittaa lukua 0.0047 (= 4.7 * 0.001).

Vakioiden syntaktiset säännöt ovat seuraavat:


vakio = [ etumerkki ] (etumerkitön-luku | vakion-tunnus ) |
          merkkijono .

etumerkitön-luku = etumerkitön-kokonaisluku | etumerkitön-reaaliluku .

etumerkitön-kokonaisluku = numerojono .

numerojono = numero { numero } .

etumerkitön-reaaliluku = etumerkitön-kokonaisluku "." jaososa [ "e" suuruusluokkakerroin ] | etumerkitön-kokonaisluku "e" suuruusluokkakerroin .

jaososa = numerojono .

suuruusluokkakerroin = etumerkkinen-kokonaisluku .

etumerkkinen-kokonaisluku = [ etumerkki ] etumerkitön-kokonaisluku .

vakion-tunnus = tunnus .

etumerkitön-vakio = etumerkitön-luku | merkkijono | vakion-tunnus .

Lausekkeissa voidaan käyttää ennaltaesiteltyjä funktioita, jotka on lueteltu liitteessä 3.

Sanastoa:
 
lauseke expression
termi term
tekijä factor
 
operaattori operator
operandi operand
sidontajärjestys operator precedence
 
vertailuoperaattori relational operator
yhteenlaskuoperaattori adding operator
kertolaskuoperaattori multiplying operator
etumerkki sign
 
etumerkitön unsigned
suuruusluokkakerroin scale factor

HARJOITUSTEHTÄVIEN RATKAISUT

Seuraavassa on esimerkkiratkaisut tekstissä annettuihin harjoitustehtäviin. On muistettava, että sama ongelma voidaan ratkaista monella eri tavalla, eivätkä eri tavat ole välttämättä toistaan huonompia. Esimerkkiratkaisut on tehty käyttämällä aina hyväksi vain sitä osaa kielestä, joka on kyseisen tehtävän antamiskohtaan mennessä esitetty.

Tehtävä 3.2.1


program aforismi (output);                                
(*                                                        
        Aforismin tulostaminen                            
        30.11.84 Matti Meikäläinen                        
*)                                                        
begin                                                     
    writeln('Henkilölle, jonka ainoa työkalu on vasara,');
    writeln('jokainen ongelma näyttää naulalta.');        
    writeln('              E. W. Dijkstra')               
end.                                                      

Tulostus:


Henkilölle, jonka ainoa työkalu on vasara,
jokainen ongelma näyttää naulalta.
              E. W. Dijkstra

Tehtävä 3.2.2


program viikkoraha (output);                               
(*                                                         
        Kokonaisviikkorahan tulostaminen                   
        30.11.84   Matti Meikäläinen                       
*)                                                         
                                                           
const                                                      
    perus = 7; (* Viikkorahan perusosa *)                  
    lisa = 2;  (* Sängynsijauksen antama lisä *)           
                                                           
begin                                                      
    writeln('Viikkoraha on ');                             
    writeln(perus, ' mk jos 0 sängynsijausta.');           
    writeln(perus + lisa, ' mk jos 1 sängynsijaus.');      
    writeln(perus + 2 * lisa, ' mk jos 2 sängynsijausta.');
    writeln(perus + 3 * lisa, ' mk jos 3 sängynsijausta.');
    writeln(perus + 4 * lisa, ' mk jos 4 sängynsijausta.');
    writeln(perus + 5 * lisa, ' mk jos 5 sängynsijausta.');
    writeln(perus + 6 * lisa, ' mk jos 6 sängynsijausta.');
    writeln(perus + 7 * lisa, ' mk jos 7 sängynsijausta.') 
end.                                                       

Tulostus:


Viikkoraha on
         7 mk jos 0 sängynsijausta.
         9 mk jos 1 sängynsijaus.
        11 mk jos 2 sängynsijausta.
        13 mk jos 3 sängynsijausta.
        15 mk jos 4 sängynsijausta.
        17 mk jos 5 sängynsijausta.
        19 mk jos 6 sängynsijausta.
        21 mk jos 7 sängynsijausta.

Tehtävä 3.3.1


program inflaatio (input, output);                            
(*                                                            
        Inflaation vaikutuksen laskeminen                     
        30.11.84   Matti Meikäläinen                          
*)                                                            
                                                              
const Vuosikerroin = 1.06; (* Inflaation vuotuinen vaikutus *)
                                                              
var summa : real;                                             
                                                              
begin                                                         
    writeln('Inflaation vaikutuksen laskeminen.');            
    write('Anna alkuperäinen summa: '); readln(summa);        
    writeln;                                                  
                                                              
    writeln('Inflaatioprosentti: ',                           
      (Vuosikerroin - 1) * 100 : 3 : 1, '%');                 
                                                              
    writeln('Alkuperäinen summa:         ', summa : 8 : 2);   
    writeln('Yhden vuoden inflaatiolla:  ',                   
      Vuosikerroin * summa : 8 : 2);                          
    writeln('Kahden vuoden inflaatiolla: ',                   
      Vuosikerroin * Vuosikerroin * summa : 8 : 2)            
end.                                                          

Esimerkki tulostuksesta:


Inflaation vaikutuksen laskeminen.
Anna alkuperäinen summa: 200

Inflaatioprosentti: 6.0% Alkuperäinen summa: 200.00 Yhden vuoden inflaatiolla: 212.00 Kahden vuoden inflaatiolla: 224.72

Tehtävä 3.3.2


program PekanViikkoraha (input, output);                           
(*                                                                 
        Pekan viikkorahan laskeminen                               
        30.11.84   Matti Meikäläinen                               
*)                                                                 
                                                                   
const                                                              
    perus  = 4.0; (* Viikkorahan perusosa     *)                   
    roskis = 0.3; (* Roskien ulosviennin lisä *)                   
    tiski  = 0.8; (* Tiskauksen lisä          *)                   
    pankki = 0.3; (* Pankkiin pantava osa     *)                   
                                                                   
var RoskaKerrat, TiskiKerrat : integer;                            
                                                                   
begin                                                              
    writeln('Viikkorahan laskeminen.'); writeln;                   
                                                                   
    write('Anna roskienvientikertojen lkm: '); readln(RoskaKerrat);
    write('Anna tiskauskertojen lkm: '); readln(TiskiKerrat);      
    writeln;                                                       
                                                                   
    writeln('Viikkorahan perusosa: ', ' ' : 14, perus : 6 : 2);    
    writeln('Roskien ulosvienti:   ', RoskaKerrat : 2, ' * ',      
      roskis : 4 : 2, ' ' : 5, RoskaKerrat * roskis : 6 : 2);      
    writeln('Tiskaus:              ', TiskiKerrat : 2, ' * ',      
      tiski : 4 : 2, ' ' : 5, TiskiKerrat * tiski : 6 : 2);        
    writeln('                      ', ' ' : 13, '--------');       
    writeln('Yhteensä:             ', ' ' : 14,                    
      perus + RoskaKerrat * roskis + TiskiKerrat * tiski : 6 : 2); 
    writeln;                                                       
    writeln('Käteen:               ', ' ' : 14,                    
      (perus + RoskaKerrat * roskis + TiskiKerrat * tiski) *       
      (1 - pankki) : 6 : 2);                                       
    writeln('Pankkiin:             ', ' ' : 14,                    
      (perus + RoskaKerrat * roskis + TiskiKerrat * tiski) *       
      pankki : 6 : 2)                                              
end.                                                               

Esimerkki tulostuksesta:


Viikkorahan laskeminen.

Anna roskienvientikertojen lkm: 13 Anna tiskauskertojen lkm: 6

Viikkorahan perusosa: 4.00 Roskien ulosvienti: 13 * 0.30 3.90 Tiskaus: 6 * 0.80 4.80 -------- Yhteensä: 12.70

Käteen: 8.89 Pankkiin: 3.81

Tehtävä 3.3.3


program kompassiruusu (input, output);                            
(*                                                                
        Kompassiruusun tulostaminen.                              
        30.11.84   Matti Meikäläinen                              
        7.2.86     Nipa Naapuri (länsi ja itä oikeille paikoille) 
*)                                                                
                                                                  
var siirto : integer; (* Siirtymä vasemmasta laidasta *)          
                                                                  
begin                                                             
    writeln('Kompassiruusun tulostaminen.'); writeln;             
    write('Anna etäisyys vasemmasta laidasta: '); readln(siirto); 
    writeln;                                                      
                                                                  
    writeln('P' : siirto + 3);     (* 3 = jonon 'P' pituus (1)  *)
    writeln;                       (*     + keskitys (2)        *)
    writeln('L + I' : siirto + 5); (* 5 = jonon pituus *)         
    writeln;                                                      
    writeln('E' : siirto + 3)                                     
end.                                                              

Esimerkkejä tulostuksesta:


Kompassiruusun tulostaminen.

Anna etäisyys vasemmasta laidasta: 11

P

L + I

E


Kompassiruusun tulostaminen.

Anna etäisyys vasemmasta laidasta: 0

P

L + I

E

Tehtävä 3.4.1


program talletus (input, output);                                    
(*                                                                   
        Kolmen vuoden talletuksen kehittyminen                       
        30.11.84   Matti Meikäläinen                                 
*)                                                                   
                                                                     
const pros = 0.11; (* Vuotuinen korko *)                             
                                                                     
var paaoma,        (* Talletus lisättynä koroilla *)                 
    korko : real;  (* Kulloisenkin vuoden korko   *)                 
                                                                     
begin                                                                
    writeln('Kolmivuotisen tilin laskeminen.'); writeln;             
    write('Anna talletettava summa: '); readln(paaoma);              
    writeln;                                                         
                                                                     
    writeln('Pääoma alussa            ', ' ' : 10, paaoma : 10 : 2); 
                                                                     
    (* Ensimmäinen vuosi *)                                          
    korko := pros * paaoma;                                          
    paaoma := paaoma + korko;                                        
    writeln('1. vuoden korko          ', korko : 10 : 2);            
    writeln('Pääoma 1. vuoden lopussa ', ' ' : 10 , paaoma : 10 : 2);
                                                                     
    (* Toinen vuosi *)                                               
    korko := pros * paaoma;                                          
    paaoma := paaoma + korko;                                        
    writeln('2. vuoden korko          ', korko : 10 : 2);            
    writeln('Pääoma 2. vuoden lopussa ', ' ' : 10 , paaoma : 10 : 2);
                                                                     
    (* Kolmas vuosi *)                                               
    korko := pros * paaoma;                                          
    paaoma := paaoma + korko;                                        
    writeln('3. vuoden korko          ', korko : 10 : 2);            
    writeln('Pääoma 3. vuoden lopussa ', ' ' : 10 , paaoma : 10 : 2) 
end.                                                                 

Esimerkki tulostuksesta:


Kolmivuotisen tilin laskeminen.

Anna talletettava summa: 10000

Pääoma alussa 10000.00 1. vuoden korko 1100.00 Pääoma 1. vuoden lopussa 11100.00 2. vuoden korko 1221.00 Pääoma 2. vuoden lopussa 12321.00 3. vuoden korko 1355.31 Pääoma 3. vuoden lopussa 13676.31

Tehtävä 3.4.2


program lapikkaat (input, output);                               
(*                                                               
        Lapikkaiden tietojen tulostaminen                        
        30.11.84   Matti Meikäläinen                             
*)                                                               
                                                                 
const                                                            
    Min       = 21;    (* Pienin kokonumero                *)    
    Max       = 47;    (* Suurin kokonumero                *)    
    MinPohja  = 14.0;  (* Pienimmän pohjan pituus          *)    
    PohjaLisa = 0.5;   (* Pohjan pituuskasvu numeroa kohti *)    
    MinHinta  = 230;   (* Kokoluokan 21-29 hinta           *)    
    AleMk     = 70;    (* Tuttaville annettava alennus     *)    
                                                                 
type                                                             
    numero = Min..Max; (* Mahdolliset kokonumerot *)             
                                                                 
var koko : numero;     (* Tarkasteltava kokonumero   *)          
    hinta : integer;   (* Lapikasparin normaalihinta *)          
                                                                 
begin                                                            
    writeln('Lapikastiedot.'); writeln;                          
    write('Anna kokonumero: '); readln(koko);                    
    writeln;                                                     
                                                                 
    hinta := Minhinta + 10 * trunc((koko - 20) / 10);            
    writeln('Lapikkaan n:o ', koko : 2,                          
      ' pituus on ', MinPohja + PohjaLisa * (koko - Min) : 4 : 1,
      ' cm.');                                                   
    writeln('Parin hinta on ', hinta : 3,                        
      ' mk (tuttaville ', hinta - AleMk : 3, ' mk).')            
end.                                                             

Esimerkki tulostuksesta:


Lapikastiedot.

Anna kokonumero: 37

Lapikkaan n:o 37 pituus on 22.0 cm. Parin hinta on 240 mk (tuttaville 170 mk).

Tehtävä 3.4.3


program verhot (input, output);                                     
(*                                                                  
        Verhokangasmenekin laskeminen                               
        30.11.84   Matti Meikäläinen                                
*)                                                                  
                                                                    
const                                                               
    pakka = 31.0;        (* Pakan pituus *)                         
                                                                    
var pituus  : real;    (* Valmistettavien verhojen pituus *)        
    verhoja,           (* Valmistettavien lukumäärä       *)        
    pakasta : integer; (* Yhdestä pakasta saatavien määrä *)        
                                                                    
begin                                                               
    writeln('Verhokankaan menekki.'); writeln;                      
    write('Anna verhojen pituus: '); readln(pituus);                
    write('Anna verhojen lukumäärä: '); readln(verhoja);            
    writeln;                                                        
                                                                    
    pakasta := trunc(pakka / pituus);                               
    writeln(pakka : 4 : 1, ' m:n pakasta tulee ',                   
      pituus : 4 : 1, ' m:n verhoja ', pakasta : 2, ' kappaletta.');
    writeln('Tehtäessä ', verhoja : 4, ' verhoa tarvitaan siten ',  
      round(verhoja / pakasta + 0.4999) : 4, ' pakkaa.')            
end.                                                                

Esimerkki tulostuksesta:


Verhokankaan menekki.

Anna verhojen pituus: 3.2 Anna verhojen lukumäärä: 147

31.0 m:n pakasta tulee 3.2 m:n verhoja 9 kappaletta. Tehtäessä 147 verhoa tarvitaan siten 17 pakkaa.

Tehtävä 3.4.4


program PilkkuPiste (input, output);                         
(*                                                           
        Pilkun hyväksyminen syötteessä                       
        30.11.84   Matti Meikäläinen                         
*)                                                           
                                                             
type pilkku = ','..',';   (* Osaväli, johon kuuluu vain yksi 
                             arvo, nimittäin merkki pilkku *)
                                                             
var KokonaisOsa, DesimaaliOsa : integer;                     
    merkki : pilkku;                                         
                                                             
begin                                                        
    writeln('Anna desimaaliluku käyttäen pilkkua');          
    write('ja kahta desimaalia (esim: 315,06): ');           
    readln(KokonaisOsa, merkki, DesimaaliOsa);               
                                                             
    writeln('Luku oli: ',                                    
      KokonaisOsa + (DesimaaliOsa / 100) : 9 : 2)            
end.                                                         

Esimerkki tulostuksesta:


Anna desimaaliluku käyttäen pilkkua
ja kahta desimaalia (esim: 315,06): 142,73
Luku oli:    142.73

Tehtävä 3.5.1


program karkausvuosi (input, output);                        
(*                                                           
        Karkausvuoden ilmoittaminen juliaanisessa            
        tai gregoriaanisessa ajanlaskussa.                   
        15.1.85 Matti Meikäläinen                            
*)                                                           
                                                             
const Rajavuosi = 1753;       (* ajanlaskun muutosvuosi *)   
                                                             
var Vuosi : integer;          (* vuosiluku              *)   
    OnKarkausvuosi : Boolean; (* karkausvuoden ilmaisin *)   
                                                             
begin                                                        
    writeln('Karkausvuoden ilmoittaminen.'); writeln;        
                                                             
    write('Anna vuosiluku: '); readln(Vuosi);                
                                                             
    if Vuosi < Rajavuosi then                                
        begin (* juliaaninen ajanlasku *)                    
        OnKarkausvuosi := Vuosi mod 4 = 0;                   
        write('Juliaaninen kalenteri: ')                     
        end                                                  
    else                                                     
        begin (* gregoriaaninen ajanlasku *)                 
        OnKarkausvuosi := (Vuosi mod 400 = 0) or             
          ((Vuosi mod 4 = 0) and (Vuosi mod 100 <> 0));      
        write('Gregoriaaninen kalenteri: ')                  
        end;                                                 
                                                             
    if OnKarkausvuosi                                        
    then writeln('vuosi ', Vuosi : 4, ' on karkausvuosi')    
    else writeln('vuosi ', Vuosi : 4, ' ei ole karkausvuosi')
end.                                                         

Esimerkki tulostuksesta:


Karkausvuoden ilmoittaminen.

Anna vuosiluku: 1700 Juliaaninen kalenteri: vuosi 1700 on karkausvuosi

Tehtävä 3.5.2


program varallisuusvero (input, output);                     
(*                                                           
        Varallisuusveron laskeminen.                         
        30.1.85   Matti Meikäläinen                          
*)                                                           
                                                             
var mkVarat,         (* varallisuus markkoina     *)         
    mkVero : real;   (* varallisuusvero markkoina *)         
                                                             
begin                                                        
    writeln('Varallisuusveron laskeminen.');                 
    writeln;                                                 
                                                             
    write('Anna vuoden 1984 verotettava varallisuus (mk): ');
    readln(mkVarat);                                         
                                                             
    (* Varallisuusvero vuoden 1984 asteikon mukaan: *)       
    if      mkVarat <  259000 then                           
         mkVero := 0                                         
    else if mkVarat <  359000 then                           
         mkVero :=   120 + 0.008 * (mkVarat -  259000)       
    else if mkVarat <  502000 then                           
         mkVero :=   920 + 0.01  * (mkVarat -  359000)       
    else if mkVarat <  721000 then                           
         mkVero :=  2350 + 0.013 * (mkVarat -  502000)       
    else if mkVarat < 1077000 then                           
         mkVero :=  5197 + 0.015 * (mkVarat -  721000)       
    else mkVero := 10537 + 0.017 * (mkVarat - 1077000);      
                                                             
    writeln;                                                 
    writeln('Verotettava varallisuus       ',                
      mkVarat : 12 : 2, ' mk');                              
    writeln('Varallisuusvero vuodelta 1984 ',                
      mkVero  : 12 : 2, ' mk')                               
end.                                                         

Esimerkki tulostuksesta:


Varallisuusveron laskeminen.

Anna vuoden 1984 verotettava varallisuus (mk): 360000 Verotettava varallisuus 360000.00 mk Varallisuusvero vuodelta 1984 930.00 mk

Tehtävä 3.5.3


program PaivanNumero (input, output);                            
(*                                                               
        Päivän järjestysnumeron ilmoittaminen.                   
        15.1.85   Matti Meikäläinen                              
*)                                                               
                                                                 
var pp, kk, vv : integer; (* päivä, kuukausi, vuosi *)           
    ppNumero : 1..366;    (* päivän järjestysnumero *)           
    OnKarkausvuosi,       (* karkausvuoden ilmaisin *)           
    ppVirhe, kkVirhe : Boolean; (* virheilmaisimet  *)           
                                                                 
begin                                                            
    writeln('Päivän järjestysnumeron ilmoittaminen.');           
    writeln;                                                     
                                                                 
    write('Anna pvm (pv kk v,  esim. 15 10 1984): ');            
    readln(pp, kk, vv);                                          
                                                                 
    OnKarkausvuosi := (vv mod 400 = 0) or                        
      ((vv mod 4 = 0) and (vv mod 100 <> 0));                    
                                                                 
    (* Päiväyksen kelvollisuuden tarkistaminen: *)               
    ppVirhe := (pp < 1) or (pp > 31);                            
    kkVirhe := (kk < 1) or (kk > 12);                            
    if not kkVirhe then                                          
       case kk of                                                
            1,3,5,7,8,10,12:                                     
               ; (* Ei muuta tehtävää, siis tyhjä lause *)       
            4,6,9,11:                                            
               ppVirhe := ppVirhe or (pp > 30);                  
            2:                                                   
               if OnKarkausvuosi                                 
               then ppVirhe := ppVirhe or (pp > 29)              
               else ppVirhe := ppVirhe or (pp > 28);             
       end; (* case *)                                          

    (* Päivän järjestysnumeron laskeminen: *)                    
    if not (kkVirhe or ppVirhe) then                             
       begin                                                     
       ppNumero := pp; (* Päiväyksen päivä lisättynä  *)         
       case kk of      (* täysien kuukausien päivillä *)         
            1: ;                                                 
            2: ppNumero := ppNumero + 31;                        
            3: ppNumero := ppNumero + 59;  (* 31+28 *)           
            4: ppNumero := ppNumero + 90;  (* 31+28+31 *)        
            5: ppNumero := ppNumero + 120; (* ed. + 30 *)        
            6: ppNumero := ppNumero + 151; (* ed. + 31 *)        
            7: ppNumero := ppNumero + 181; (* ed. + 30 *)        
            8: ppNumero := ppNumero + 212; (* ed. + 31 *)        
            9: ppNumero := ppNumero + 243; (* ed. + 31 *)        
           10: ppNumero := ppNumero + 273; (* ed. + 30 *)        
           11: ppNumero := ppNumero + 304; (* ed. + 31 *)        
           12: ppNumero := ppNumero + 334; (* ed. + 30 *)        
       end; (* case *)                                           
       if OnKarkausvuosi and (kk > 2) then ppNumero := ppNumero+1
       end; (* if *)                                             
                                                                 
    (* Tulostus: *)                                              
    if not (ppVirhe or kkVirhe) then                             
        writeln(pp : 1, '/', kk : 1, ' on ', ppNumero : 1,       
          '. päivä vuonna ', vv : 1)                             
    else                                                         
        begin                                                    
        writeln('Päiväys ', pp : 1, ' ', kk : 1, ' ', vv : 1,    
          ' ei ole kelvollinen:');                               
        if kkVirhe then writeln(kk : 1, '. kuukausi ei kelpaa'); 
        if ppVirhe then writeln(pp : 1, '. päivä ei kelpaa')     
        end                                                      
end.                                                             

Esimerkkejä tulostuksesta:


Päivän järjestysnumeron ilmoittaminen.

Anna pvm (pv kk v, esim. 15 10 1984): 31 12 1985 31/12 on 365. päivä vuonna 1985


Päivän järjestysnumeron ilmoittaminen.

Anna pvm (pv kk v, esim. 15 10 1984): 31 12 1984 31/12 on 366. päivä vuonna 1984

Tehtävä 3.6.1


program PaaomanKasvu (input, output);                              
(*                                                                 
        Talletuspääoman kasvu.                                     
        28.02.85   Matti Meikäläinen                               
*)                                                                 
                                                                   
var Paaoma,               (* Vuotuinen pääoma         *)           
    Pros,                 (* Talletuskorko            *)           
    Kerroin    : real;    (* Vuotuinen pääomakerroin  *)           
    Vuodet,               (* Talletusaika             *)           
    i          : integer; (* Talletusvuosilaskuri     *)           
    SyotteetOK : Boolean; (* Syötteiden virheilmaisin *)           
                                                                   
begin                                                              
    writeln('Talletuspääoman kasvun laskeminen.');                 
    writeln;                                                       
                                                                   
    writeln('Anna lähtötiedot');                                   
    repeat                                                         
        write('Alkupääoma (mk)  : '); readln(Paaoma);              
        write('Talletuskorko (%): '); readln(Pros);                
        write('Talletusaika (v) : '); readln(Vuodet);              
        SyotteetOK := (Paaoma > 0) and (Pros > 0) and (Vuodet > 0);
        if not SyotteetOK then                                     
            begin                                                  
            writeln;                                               
            writeln('Epäkelvot tiedot, anna uudet')                
            end                                                    
    until SyotteetOK;                                              
    writeln;                                                       
                                                                   
    Kerroin := 1 + Pros / 100;                                     
                                                                   
    writeln('Korko ', Pros : 5 : 2, ' %, alkupääoma     ',         
      Paaoma : 12 : 2);                                            
    (* Laske ja tulosta pääoma vuosittain talletusajalta: *)       
    for i := 1 to Vuodet do                                        
        begin                                                      
        Paaoma := Paaoma * Kerroin;                                
        writeln('Talletus ', i : 2, '. vuoden kuluttua: ',         
          Paaoma : 12 : 2)                                         
        end                                                        
end.                                                               

Esimerkki tulostuksesta:


Talletuspääoman kasvun laskeminen.

Anna lähtötiedot Alkupääoma (mk) : 10000 Talletuskorko (%): 8.5 Talletusaika (v) : 4

Korko 8.50 %, alkupääoma 10000.00 Talletus 1. vuoden kuluttua: 10850.00 Talletus 2. vuoden kuluttua: 11772.25 Talletus 3. vuoden kuluttua: 12772.89 Talletus 4. vuoden kuluttua: 13858.59

Tehtävä 3.6.2


program MerkkiNelio (input, output);                    
(*                                                      
        Merkkineliön piirtäminen.                       
        15.02.85   Matti Meikäläinen                    
*)                                                      
                                                        
const MaxSivu = 78;       (* Sivun pituuden yläraja   *)
                                                        
var Merkki     : char;    (* Neliön pohjamerkki       *)
    Sivu,                 (* Sivun pituus             *)
    i, j       : integer; (* Sivujen pituuslaskurit   *)
                                                        
begin                                                   
    writeln('Merkkineliön piirtäminen.');               
    writeln;                                            
                                                        
    writeln('Anna lähtötiedot');                        
    write('Neliön pohjamerkki : '); readln(Merkki);     
    repeat                                              
        write('Neliön sivun pituus: '); readln(Sivu);   
        if (Sivu < 1) or (Sivu > MaxSivu) then          
            begin                                       
            writeln;                                    
            writeln('Epäkelpo pituus, anna uusi')       
            end                                         
    until (Sivu >= 1) and (Sivu <= MaxSivu);            
    writeln;                                            
                                                        
    (* Piirrä neliö: *)                                 
    for i := 1 to Sivu do                               
        begin                                           
        for j := 1 to Sivu do write(Merkki);            
        writeln                                         
        end                                             
end.                                                    

Esimerkki tulostuksesta:


Merkkineliön piirtäminen.

Anna lähtötiedot Neliön pohjamerkki : + Neliön sivun pituus: 79

Epäkelpo pituus, anna uusi Neliön sivun pituus: 5

+++++ +++++ +++++ +++++ +++++

Tehtävä 3.6.3


program Tikkupeli (input, output);                                 
(*                                                                 
        Kahden pelaajan tikkupeli.                                 
        15.02.85   Matti Meikäläinen                               
*)                                                                 
                                                                   
type Pelaaja = (Kone, Kalle); (* Pelin kaksi pelaajaa   *)         
                                                                   
var  Vuorossa   : Pelaaja;  (* Vuorossa oleva pelaaja   *)         
     Vastaus    : char;     (* kyllä/ei  -vastaus       *)         
     Kasassa,               (* Pelitikkujen määrä       *)         
     Kasasta    : integer;  (* Pelaajan ottamat tikut   *)         
                                                                   
begin                                                              
    writeln('** Tikkupeli kahdelle pelaajalle **'); writeln;       
    writeln('Peliin otetaan kasa tikkuja. Pelaajat ottavat');      
    writeln('vuorotellen  yksi,  kaksi  tai  kolme tikkua.');      
    writeln('Viimeisen tikun ottanut häviää pelin.');              
    writeln;                                                      

    writeln('Anna lähtötiedot');                                   
    repeat                                                         
        write('Tikkujen määrä pelin alussa: '); readln(Kasassa);   
        if Kasassa < 1 then writeln('Tikkuja oltava ainakin yksi') 
    until Kasassa >= 1;                                            
    repeat                                                         
        write('Aloitatko  (k=kyllä,  e=ei): ');readln(Vastaus)     
    until (Vastaus = 'k') or (Vastaus = 'e');                      
    writeln;                                                       
                                                                   
    if Vastaus = 'k' then Vuorossa := Kalle else Vuorossa := Kone; 
                                                                   
    (* Pelaajat ottavat vuorotellen tikkuja, kunnes ne loppuvat: *)
    repeat                                                         
        case Vuorossa of                                           
             Kalle: begin                                          
                    repeat                                         
                        write('Kasassa ', Kasassa : 2, ' tikkua. ',
                          'Montako otat: ');                       
                        readln(Kasasta);                           
                    until (Kasasta <= 3) and (Kasasta >= 1) and    
                          (Kasasta <= Kasassa);                    
                    Vuorossa := Kone                               
                    end;                                           
             Kone:  begin                                          
                    (* Optimaalinen pelitapa: Kalle häviää, jos  *)
                    (* hänelle saadaan jäämään 4*n+1 tikkua      *)
                    case Kasassa mod 4 of                          
                         0:   Kasasta := 3;                        
                         1,2: Kasasta := 1;                        
                         3:   Kasasta := 2;                        
                    end; (* case *)                                
                    writeln('Kasassa ', Kasassa : 2, ' tikkua, ',  
                      'otan niistä ', Kasasta : 3, ' kpl.');       
                    Vuorossa := Kalle                              
                    end                                            
        end; (* case *)                                            
        Kasassa := Kasassa - Kasasta                               
    until Kasassa = 0;                                             
                                                                   
    (* Tikut lopussa; peli on päättynyt ja julistetaan voittaja: *)
    writeln;                                                       
    writeln('** Kasassa 0 tikkua, peli on päättynyt **');          
    if Vuorossa = Kalle then                                       
         writeln('Sinä voitit, onneksi olkoon!')                   
    else                                                           
         begin                                                     
         writeln('Sinä hävisit tällä kerralla, valitan.');         
         writeln('Älä silti murehdi, voithan yrittää uudelleen.')  
         end                                                       
end.                                                               

Esimerkki tulostuksesta:


** Tikkupeli kahdelle pelaajalle **

Peliin otetaan kasa tikkuja. Pelaajat ottavat vuorotellen yksi, kaksi tai kolme tikkua. Viimeisen tikun ottanut häviää pelin.

Anna lähtötiedot Tikkujen määrä pelin alussa: 0 Tikkuja oltava ainakin yksi Tikkujen määrä pelin alussa: 8 Aloitatko (k=kyllä, e=ei): joo Aloitatko (k=kyllä, e=ei): k

Kasassa 8 tikkua. Montako otat: 3 Kasassa 5 tikkua, otan niistä 1 kpl. Kasassa 4 tikkua. Montako otat: 1 Kasassa 3 tikkua, otan niistä 2 kpl. Kasassa 1 tikkua. Montako otat: 0 Kasassa 1 tikkua. Montako otat: 1

** Kasassa 0 tikkua, peli on päättynyt ** Sinä hävisit tällä kerralla, valitan. Älä silti murehdi, voithan yrittää uudelleen.

Tehtävä 3.7.1


program tulospalvelu (input, output);                               
(*                                                                  
        Hiihtokilpailun tulospalvelu.                               
        15.1.85   Matti Meikäläinen                                 
*)                                                                  
                                                                    
const                                                               
    Min = 100;          (* Ensimmäisen kilpailijan numero *)        
    Max = 300;          (* Suurin mahdollinen numero      *)        
    ikuisuus = 1000000; (* Tosi pitkä aika                *)        
                                                                    
var aika : array [Min..Max] of real; (* Kilpailijoiden ajat *)      
    voitto,             (* Voittajan aika                       *)  
    sek  : real;        (* Syöttöä varten                       *)  
    minu : integer;     (*      -"-                             *)  
    suurin,             (* Suurin käytössä oleva kilpailunumero *)  
    i    : Min..Max;                                                
                                                                    
begin                                                               
    writeln('Suhteellisten aikojen tulostus.'); writeln;            
                                                                    
    (* Kilpailijoiden aikojen lukeminen: *)                         
    writeln('Anna ajat muodossa min sek.osat (esim. 63 14.9).');    
    writeln('Anna lopuksi aika 0 0');                               
    suurin := Min; (* Seuraavaksi käsiteltävän kilpailijan numero *)
    voitto := ikuisuus;   (* Paras tähän mennessä löydetyistä *)    
    repeat                                                          
        write('Anna kilpailijan n:ro', suurin : 4, ' aika: ');      
        readln(minu, sek);                                          
        sek := minu * 60 + sek; (* Aika sekunteina *)               
        if (sek < voitto) and (sek <> 0) then voitto := sek;        
        aika[suurin] := sek;                                        
        suurin := suurin + 1                                        
    until sek = 0;                                                  
    suurin := suurin - 2;                                           
                                                                    
    (* Tulostus: *)                                                 
    writeln; writeln('Kilpailija       Aika    Suht. aika');        
    for i := Min to suurin do                                       
        writeln(i : 7, trunc(aika[i] / 60) : 8,                     
          aika[i] - 60 * trunc(aika[i] / 60) : 6 : 2,               
          trunc((aika[i] - voitto) / 60) : 8,                       
          aika[i] - voitto - 60 * trunc((aika[i] - voitto) / 60)    
          : 6 : 2)                                                  
end.                                                                

Esimerkki tulostuksesta:


Suhteellisten aikojen tulostus.

Anna ajat muodossa min sek.osat (esim. 63 14.9). Anna lopuksi aika 0 0 Anna kilpailijan n:ro 100 aika: 32 14.3 Anna kilpailijan n:ro 101 aika: 28 59.7 Anna kilpailijan n:ro 102 aika: 29 3.8 Anna kilpailijan n:ro 103 aika: 36 0 Anna kilpailijan n:ro 104 aika: 30 15.1 Anna kilpailijan n:ro 105 aika: 0 0

Kilpailija Aika Suht. aika 100 32 14.30 3 14.60 101 28 59.70 0 0.00 102 29 3.80 0 4.10 103 36 0.00 7 0.30 104 30 15.10 1 15.40

Tehtävä 3.7.2


program varallisuusvero (input, output);                           
(*                                                                 
        Varallisuusveron laskeminen. Toinen versio.                
        30.1.85   Matti Meikäläinen                                
        6.2.85    Heikki Heikäläinen                               
*)                                                                 
                                                                   
const                                                              
    luokkia = 7;          (* verotusluokkien lukumäärä        *)   
    IsoRaha = 1000000000; (* suurempi kuin kenenkään omaisuus *)   
                                                                   
type tiedot = (alaraja, vero, pros); (* verotusluokan tiedot  *)   
                                                                   
var mkVarat,         (* varallisuus markkoina     *)               
    mkVero  : real;  (* varallisuusvero markkoina *)               
    verotus : array [1..luokkia, tiedot] of real;                  
    i       : 1..luokkia;                                          
                                                                   
begin                                                              
    (* Verotustietotaulukon alustus: *)                            
    verotus[1, alaraja] := 0;                                      
    verotus[1, vero] := 0;            verotus[1, pros] := 0;       
    verotus[2, alaraja] := 259000;                                 
    verotus[2, vero] := 120;          verotus[2, pros] := 0.008;   
    verotus[3, alaraja] := 359000;                                 
    verotus[3, vero] := 920;          verotus[3, pros] := 0.01;    
    verotus[4, alaraja] := 502000;                                 
    verotus[4, vero] := 2350;         verotus[4, pros] := 0.013;   
    verotus[5, alaraja] := 721000;                                 
    verotus[5, vero] := 5197;         verotus[5, pros] := 0.015;   
    verotus[6, alaraja] := 1077000;                                
    verotus[6, vero] := 10537;        verotus[6, pros] := 0.017;   
    verotus[7, alaraja] := IsoRaha; (* Ohjelmarakenteen takia *)   
                                                                   
    writeln('Varallisuusveron laskeminen.'); writeln;              
                                                                   
    write('Anna vuoden 1984 verotettava varallisuus (mk): ');      
    readln(mkVarat);                                               
                                                                   
    (* Varallisuusvero vuoden 1984 asteikon mukaan: *)             
    i := 1;                                                        
    while mkVarat >= verotus[i, alaraja] do i := i + 1;            
    if i > 1 then i := i - 1; (* i=1 vain negatiivisile varoille *)
    mkVero := verotus[i, vero] +                                   
      verotus[i, pros] * (mkVarat - verotus[i, alaraja]);          
                                                                   
    writeln;                                                       
    writeln('Verotettava varallisuus       ',                      
      mkVarat : 12 : 2, ' mk');                                    
    writeln('Varallisuusvero vuodelta 1984 ',                      
      mkVero  : 12 : 2, ' mk')                                     
end.                                                               

Esimerkkejä tulostuksesta:


Varallisuusveron laskeminen.

Anna vuoden 1984 verotettava varallisuus (mk): 120000

Verotettava varallisuus 120000.00 mk Varallisuusvero vuodelta 1984 0.00 mk


Varallisuusveron laskeminen.

Anna vuoden 1984 verotettava varallisuus (mk): 360000

Verotettava varallisuus 360000.00 mk Varallisuusvero vuodelta 1984 930.00 mk


Varallisuusveron laskeminen.

Anna vuoden 1984 verotettava varallisuus (mk): 1087000

Verotettava varallisuus 1087000.00 mk Varallisuusvero vuodelta 1984 10707.00 mk

Tehtävä 3.7.3


program histogrammi (input, output);                                 
(*                                                                   
        Liikevaihdon tulostus histogrammina. Kolmas versio.          
        15.1.85   Matti Meikäläinen                                  
        28.1.85   Heikki Heikäläinen                                 
        6.2.85    Tapani Teikäläinen                                 
*)                                                                   
                                                                     
const pisin = 40;         (* Pisin pylväs *)                         
                                                                     
var Liikevaihto : array [1..12] of real; (* Kuukausittain *)         
    max : real;            (* Suurin edellisistä          *)         
    kk,                    (* Käsiteltävä kuukausi        *)         
    i   : integer;         (* Kierroslaskuri              *)         
    kuu : array [1..12] of (* Kuukausien nimet            *)         
            packed array [1..7] of char;                             
                                                                     
begin                                                                
    (* Kuukausien nimitaulukon alustus: *)                           
    kuu[1] := 'Tammi  '; kuu[2] := 'Helmi  '; kuu[3] := 'Maalis ';   
    kuu[4] := 'Huhti  '; kuu[5] := 'Touko  '; kuu[6] := 'Kesä   ';   
    kuu[7] := 'Heinä  '; kuu[8] := 'Elo    '; kuu[9] := 'Syys   ';   
    kuu[10]:= 'Loka   '; kuu[11]:= 'Marras '; kuu[12]:= 'Joulu  ';   
                                                                     
    (* Kuukausitietojen lukeminen: *)                                
    writeln('Liikevaihdon graafinen esitys.'); writeln;              
    for kk := 1 to 12 do                                             
        begin                                                        
        write('Anna ');                                              
        i := 1;                                                      
        while kuu[kk][i] <> ' ' do (* Kuun nimessä vielä kirjaimia *)
            begin write(kuu[kk][i]); i := i + 1 end;                 
        write('kuun', ' ' : 8 - i, 'liikevaihto: ');                 
        readln(Liikevaihto[kk])                                      
        end;                                                         
    writeln;                                                         
                                                                     
    (* Etsitään suurin: *)                                           
    max := Liikevaihto[1];                                           
    for kk := 2 to 12 do                                             
        if max < Liikevaihto[kk] then max := Liikevaihto[kk];        
                                                                     
    (* Tulostetaan kuukausittain: *)                                 
    for kk := 1 to 12 do                                             
        begin                                                        
        write(kuu[kk] : 6, ': ');                                    
        for i := 1 to round(Liikevaihto[kk] / max * pisin) do        
            write('*');                                              
        writeln                                                      
        end                                                          
end.                                                                 

Esimerkki tulostuksesta:


Liikevaihdon graafinen esitys.

Anna Tammikuun liikevaihto: 100 Anna Helmikuun liikevaihto: 95 Anna Maaliskuun liikevaihto: 90 Anna Huhtikuun liikevaihto: 85 Anna Toukokuun liikevaihto: 80 Anna Kesäkuun liikevaihto: 75 Anna Heinäkuun liikevaihto: 70 Anna Elokuun liikevaihto: 65 Anna Syyskuun liikevaihto: 60 Anna Lokakuun liikevaihto: 55 Anna Marraskuun liikevaihto: 70 Anna Joulukuun liikevaihto: 90

Tammi : **************************************** Helmi : ************************************** Maalis: ************************************ Huhti : ********************************** Touko : ******************************** Kesä : ****************************** Heinä : **************************** Elo : ************************** Syys : ************************ Loka : ********************** Marras: **************************** Joulu : ************************************

Liite 1: Syntaksikaaviot

Alla on esitetty PienoisPascalin syntaksi graafisesti syntaksikaavioiden avulla. Tarkastellaan aluksi kaavioiden ja syntaksisääntöjen vastaavuutta. Esimerkiksi ohjelman otsikon syntaksi sekä sääntöjen että kaavion avulla esitettynä näyttää seuraavalta:


ohjelman-otsikko = "program" tunnus [ "(" ohjelman-parametrit ")" ] .

ohjelman-parametrit = tunnuslista .

tunnuslista = tunnus { "," tunnus } .

Kaaviota seurataan nuolten mukaisissa suunnissa kaavion vasemmasta laidasta alkaen. Etenemissuunnasta loivassa kulmassa (kork. 45 astetta) erkanevat viivat ilmaisevat syntaksin vaihtoehtoisia jatkumismahdollisuuksia. Samojen symbolien moninkertaiset esiintymät ilmaistaan kaaviossa muodostamalla silmukka, jossa symbolien järjestys ilmenee nuolen suunnasta. Ympyröihin tai pyöreäreunaisiin laatikoihin kirjoitetaan symbolit, joita ei enää tarkenneta ja joiden on siis esiinnyttävä sellaisenaan. Toisissa kaavioissa edelleen tarkennettavat välikesymbolit kirjoitetaan suorakulmaisiin laatikoihin.

Kaavioesityksessä tarvitaan yleensä vain osa sääntöesityksen välikesymboleista, koska yhteen kaavioon mahtuu tavallisesti useita sääntöjä vastaava kokonaisuus. Edellisessä kaaviossa esiintyy tunnus ainoana lisätarkennusta vaativana symbolina. Sen syntaksi voidaan esittää seuraavasti (symboleja kirjain ja numero ei enää ole tarkennettu, koska niiden oletetaan olevan lukijan tiedossa):


tunnus = kirjain { kirjain | numero } .

Syntaksikaaviot:

Liite 2: Pascalin sanasymbolit

Seuraava luettelo sisältää Pascalin sanasymbolit. Sanasymbolit ovat tunnusten kaltaisia symboleita, joilla on kiinteä merkitys. Niille ei siis voi antaa ohjelmassa uutta merkitystä.

Luettelossa on Pascalin kaikki sanasymbolit, siis myös sellaiset, jotka eivät esiinny PienoisPascalissa.

and             end             nil             set
array           file            not             then
begin           for             of              to
case            function        or              type
const           goto            packed          until
div             if              procedure       var
do              in              program         while
downto          label           record          with
else            mod             repeat

Liite 3: Ennaltaesitellyt funktiot

Seuraava luettelo sisältää PienoisPascalin ennaltaesitellyt funktiot ja kuvauksen niiden palauttamista tuloksista. Täydelliseen Pascaliin kuuluu myös muita, seuraavaan luetteloon sisältymättömiä ennaltaesiteltyjä funktioita.

Seuraavien funktioiden kutsuparametrit voivat olla joko tyyppiä integer tai real. Funktioiden palauttama tulos on tyyppiä real paitsi funktioilla abs ja sqr, joiden palauttama tulos on samaa tyyppiä kuin kutsuparametri.
 
abs(x) luvun x itseisarvo
sqr(x) luvun x neliö (eli toinen potenssi)
sin(x) radiaaneina ilmaistun kulman x sini
cos(x) radiaaneina ilmaistun kulman x kosini
exp(x) luku 2.71828 potenssiin x
ln(x) luvun x luonnollinen logaritmi
sqrt(x) luvun x neliöjuuri
arctan(x) radiaaneina ilmaistun kulman x arkustangentti
 
Seuraavien funktioiden kutsuparametri on tyyppiä real ja tulos tyyppiä integer.
 
round(x) luku x oikein pyöristettynä
trunc(x) luku x katkaistuna nollaa kohti
 
Seuraavien funktioiden kutsuparametri on jotain järjestettyä tyyppiä ja tulos on samaa tyyppiä paitsi funktiolla ord, jonka tulos on tyyppiä integer.
 
succ(x) arvoa x seuraava arvo
pred(x) arvoa x edeltävä arvo
ord(x) arvon x järjestysnumero tyyppinsä sisällä
 
Seuraavan funktion kutsuparametri on tyyppiä integer ja tulos tyyppiä char.
 
chr(x) se merkki 'a', jolle ord('a') = x
 
Seuraavan funktion kutsuparametri on tyyppiä integer ja tulos tyyppiä Boolean.
 
odd(x) true, jos x on pariton, false muuten
 
Seuraavalla funktiolla ei ole kutsuparametreja ja sen tulos on tyyppiä Boolean.
 
eoln true, jos syöterivi on loppu, false muuten

Liite 4: Syntaksin välikesymbolit

Seuraava luettelo sisältää PienoisPascalin syntaktisissa säännöissä esiintyvät välikesymbolit akkosjärjestyksessä.

    alkuarvo
    alkuehtoinen-toistolause
    askelmuuttuja
    askeltava-toistolause

Boolen-lauseke

ehdollinen-lause ehtolause else-osa etumerkitön-kokonaisluku etumerkitön-luku etumerkitön-reaaliluku etumerkitön-vakio etumerkki etumerkkinen-kokonaisluku

funktioilmaus funktion-tunnus

heittomerkin-kuva

indeksilauseke indeksityyppi indeksoitu-muuttuja

jaososa jonoalkio jonomerkki järjestetty-tyyppi järjestetyn-tyypin-kuvaus järjestetyn-tyypin-tunnus

kertolaskuoperaattori kirjain kirjoitusparametri kokonainen-muuttuja komponenttimuuttuja komponenttityyppi kutsuparametri kutsuparametrilista

lause lausejono lauseke lauseosa lohko loppuarvo loppuehtoinen-toistolause lueteltu-tyyppi

merkkijono muuttujan-tunnus muuttujaviittaus muuttujien-esittely muuttujien-esittelyosa

numero numerojono

ohjelma ohjelman-lohko ohjelman-otsikko ohjelman-parametrit osavälityyppi

pakkaamaton-rakenteinen-tyyppi proseduurilause proseduurin-tunnus

rakenteinen-lause rakenteisen-tyypin-kuvaus read-parametrilista readln-parametrilista

sijoituslause suuruusluokkakerroin

taulukkomuuttuja taulukkotyyppi tekijä termi toistolause tunnus tunnuslista tyhjä-lause tyypin-tunnus tyypinmäärittely tyypinmäärittelyosa tyyppi-ilmaus tyyppikuvaus

vakio vakion-tunnus vakionmäärittely vakionmäärittelyosa valintalause valintalauseke valintalistan-alkio valintavakio valintavakiolista vertailuoperaattori

write-parametrilista writeln-parametrilista

yhdyslause yhteenlaskuoperaattori yksinkertainen-lause yksinkertainen-lauseke


Jorma.Sajaniemi@Joensuu.Fi