Helsingin yliopisto / Tietojenkäsittelytieteen laitos / 581258-1 Johdatus ohjelmointiin
Copyright © 1997 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

1.1 Tietokoneita ja algoritmeja

(Muutettu viimeksi 12.1.1998)

Tietokone ja ohjelma

Tietokone on rakennettu "osaamaan" yksinkertaisia operaatioita, jotka käsittelevät tietokoneen komponenteissa olevia bittien jonoja. Tuollaista operaatiojoukkoa kutsutaan konekieleksi. Operaatiot itsekin esitetään tietokoneen muistissa bittijonoina.

Konekielinen ohjelmointi (yleensä ns. "assemblerilla") on hyvin kömpelöä ja virhealtista. Käytännössä nykyään lähes aina ohjelmoidaan ns. lausekielillä. Niitä käyttäen ohjelmoija välttyy koneen sisäisen rakenteen ajattelemiselta. Ohjelmista saadaan lausekielellä ohjelmoiden myös siirrettäviä: ne ovat käytettävissä kaikissa tietokoneissa, joille ko. lausekieli on toteutettu.

Lausekielten ajattelutapa on konekieltä paljon ihmisläheisempi: jo ensimmäiset lausekielet mahdollistivat matematiikasta tuttujen laskutoimitusten kirjoittamisen; voitiin kirjoittaa vaikkapa A+B*C sen sijaan, että olisi itse kerrottu, mistä koneen osasta minne bittijonoja siirrellään ja minkä bittijonojen välillä käynnistetään peruslaskutoimituksia. Sittemmin lausekieliin on kehitelty monenlaisia ajatusten järjestämisen välineitä: nimettyjä aliohjelmia ja aliohjelmakirjastoja, lohkorakennetta, olioita ja luokkia, ...

Jotta tietokone saataisiin suorittamaan lausekielellä laadittuja ohjelmia, lausekieliset ohjelmat on tavalla tai toisella kuitenkin muokattava konekielisiksi - tietokonehan ei muuta "osaa" kuin vain konemaisesti suorittaa muistissaan olevaa konekielisten operaatioiden jonoa. Tuollaista muokkaamista kieleltä toiselle kutsutaan kääntämiseksi.

Lausekielisen ohjelman kääntäminen konekielelle on aika työlästä ja mekaanistakin puuhaa, joten se sopii mainiosti tietokoneelle: ohjelmaa, jonka syöttötietoina on algoritmi lausekielisenä ja tulostietoina vastaava algoritmi vaikkapa konekielisenä, kutsutaan kääntäjäksi (compiler).


                                      syöttötiedot
                                      omalle algoritmille 
                        
                                           |
                                           V

   oma algoritmi                      OMA ALGORITMI
   lausekielellä  --> KÄÄNTÄJÄ  -->   KONEKIELELLÄ

                                           |
                                           V

                                      oman algoritmin
                                      tulostiedot


Kääntäjä usein ohjelmoidaan itsekin lausekielellä. Miten se itse saadaan käännettyä konekieliseksi? Tietokonehan ei muunlaisia algoritmeja osaa suorittaa!

Algoritmi ja sen tila

Algoritmisen ohjelmoinnin peruskäsitteitä ovat muuttuja (variable) ja sijoitusoperaatio (assignment operation).

Muuttuja tarkoittaa matematiikassa jotakin melko syvällistä: esimerkiksi ilmauksessa a+b=b+a väitetään vaikkapa minkä tahansa lukuparin summan olevan riippumaton laskentajärjestyksestä. Tilastotieteessä muuttuja voi tarkoittaa "selittävää tekijää"; esimerkiksi myytyjen jäätelöiden määrä voisi olla muuttujana hukkumiskuolemien määrää selitettäessä.

Algoritmisessa ohjelmoinnissa muuttuja on paljon konkreettisempi asia: muuttuja on arvon säilytyspaikka, "laatikko, jonka kyljessä on nimi". Laatikon sisältöa voi tutkia ja sisältöä voi muuttaa. Uuden sisällön asettamista laatikkoon kutsutaan sijoittamiseksi, sijoitusoperaatioksi tai sijoituslauseeksi.

Sijoitus korvaa muuttujan vanhan sisällön uudella. Uusi arvo voi olla jonkin laskutoimituksen, ns. lausekkeen (expression) arvo. Tuo lauseke voi sisältää vakioita, muuttujien arvoja ja laskutoimituksia.

Sijoitusoperaation ilmauksena voidaan käyttää vaikkapa merkkijonoja "<-" tai ":=". Valitettavasti eräissä ohjelmointikielissä (mm. Javassa!) sijoitusoperaation merkkinä käytetään yhtäsuuruusmerkkiä "=". Näissä kielissä loogiseen vertailuun joudutaan siten käyttämään matematiikan kielestä poikkeavia ilmauksia, esimerkiksi "==".

Esimerkkejä (huom. tämä ei ole Javaa!):

     eka := 7;
     toka := 9 * eka;
     kolm := (eka + toka) * 100;
     eka := eka + 1;
Huom: Sijoitusoperaatio kannattaa heti alussa opetella lukemaan "saa arvokseen", ei "on", vaikka ohjelmointikielessä Javan tapaan käytettäisiin yhtäsuuruusmerkkiä sijoitusoperaation ilmauksena! Erityisen hyvin tuo viimeinen esimerkki osoittaa, miksi.
     eka := eka + 1
Ilmaus "eka on eka plus yksi" on looginen epätotuus. Sitävastoin "eka saa arvokseen eka plus yksi" ilmaisee, mistä on kysymys: ekan vanhaan arvoon lisätään yksi ja saatu summa sijoitetaan muuttujan eka uudeksi arvoksi.

Algoritmia siis suoritetaan ajassa: "ensin tehdään sitä, sitten tätä". Algoritmin muuttujien arvot kullakin ajan hetkellä muodostavat algoritmin ns. tilan (state). Ja oikeastaan kaikki tietokoneohjelmat ovat lopulta vain algoritmeja, jotka etenevät tilasta toiseen. Jos ohjelmassa näyttäisi olevan jotakin "viisautta", kyse on siitä, että ohjelman laatija on osannut rakentaa algoritmilleen sellaisen tilojen ketjun, joka ohjelman käyttäjästä vaikuttaa viisaalta.

Edellisen esimerkin tilat:


tila:     |__?____|  |__?____|  |__?____| 
            eka        toka       kolm

     eka := 7;

tila:     |__7____|  |__?____|  |__?____| 
            eka        toka       kolm

     toka := 9 * eka;

tila:     |__7____|  |__63___|  |__?____| 
            eka        toka       kolm

     kolm := (eka + toka) * 100;

tila:     |__7____|  |__63___|  |__7000_| 
            eka        toka       kolm

     eka := eka + 1;

tila:     |__8____|  |__63___|  |__7000_| 
            eka        toka       kolm


Algoritmi liitetään ympäristöönsä ns. syöttö- ja tulostusoperaatioin. Syöttöoperaation eli lukemisen seurauksena jokin muuttuja saa arvon ohjelman käyttäjältä tai vaikkapa jostakin tiedostosta. Lukeminen siis muuttaa ohjelman tilaa. Tulostusoperaatio, kirjoittaminen puolestaan laskee jonkin lausekkeen arvon ja siirtää tuloksen jollekin tulostuslaitteelle. Tällöin ohjelman tila ei muutu. (Huomaa analogia sijoitusoperaation vasempaan ja oikeaan puoleen.)

Esimerkki (huom. tämä ei ole Javaa!):

     lue(eka);
     lue(toka);
     kolmas := eka + toka;
     kirjoita(kolmas);

Peräkkäin kirjoitetut operaatiot suoritetaan peräkkäin - "vasemmalta oikealle, ylhäältä alaspäin". Alkeisoperaatioiden (sijoittamisia, lukemisia, kirjoittamisia) suorittamista ohjataan rakenteisin operaatioin. Perusesimerkkejä ovat valinta ja toisto (huom. tämä ei ole Javaa!):
    lue(luku);

    if (luku=7)             // valinta
      kirjoita("hip hei");
    else
      kirjoita("töttöröö");

    lue(luku);

    while (luku < 100) {    // toisto
      kirjoita(luku * luku);
      luku := luku + 1;
    }

Mitä seuraava algoritmi tekee? Simuloi ja opi jotakin! (Huom. tämä ei ole Javaa!)
    lue(luku);
    while (luku < 100) {    // toisto
      kirjoita(luku * luku);
      luku := luku - 1;
    }

Huom: Rakenteiset operaatiot ovat "rakenteisia" siksi, että niiden rakenneosina on muita lauseita. Noita rakenneosia voidaan kutsua alialgoritmeiksi: "Toistolause toistaa alialgoritmia..."

Lady Ada Lovelace, lordi Byronin tytär, keksi valinnan ja toiston 1800-luvun lopulla. Hän laati ohjelmia Charles Babbagen suunnittelemaan mekaaniseen tietokoneeseen, jossa ohjelmat suoritettiin suoraan reikäkorttipakalta. Lady Ada keksi täydentää konekieltä operaatioilla, jotka jostakin ehdosta riippuen hyppäävät joidenkin korttien ohi tai jotka siirtävät jo ohitetun korttipakan osan uudelleen luettavaksi!

Ehdollisuus - algoritmin tilasta riippuva toiminnan ohjaus - on keskeistä algoritmeja laadittaessa. Ilman ehdollisuutta algoritmi toimisi samoin kaikilla syöttötiedoilla! Alialgoritmien nimeäminen ja nimettyjen alialgoritmien kutsuminen on myös tärkeä väline; kerran ohjelmoitua alialgoritmia ei tarvitse kirjoittaa uudelleen ja uudelleen.

Ajatusten järjestämistä ja turvallisuuden tavoittelua

Teoriassa algoritmien laatimiseen ei tarvittaisi muita kuin edelläluetellut välineet. Itse asiassa vieläkin vähemmällä selvittäisiin: jopa konekielellä tai ns. Turingin koneella (matemaattinen algoritmimalli) voitaisiin ohjelmoida maailman kaikki mahdolliset algoritmit.

Käytännössä ohjelmat kuitenkin ovat niin suuria ja monimutkaisia, että algoritmin laatijan ajatusten hallintaan tarvitaan apuvälineita. Jollakin tavoin on voitava rajoittaa kerralla ajateltavien asioiden määrä niin pieneksi, että ajatusten voima vielä riittää oikean toimintalogiikan laatimiseen ja ohjelmointivirheiden välttämiseen.

Jo edellä mainitut valinta ja toisto sekä alialgoritmien nimeäminen ovat välineitä ohjelmointiongelman jäsentelyyn. Muita ovat mm. ns. lohkorakenne, oliot, luokat, kirjastot, ...

"Oikean" ohjelman tila on tyypillisesti hyvin suuri, ts. muuttujia on hyvin paljon. Kuitenkin useimmilla muuttujilla on merkitystä vain jossakin ohjelman osassa. Jäsentelyn välineet yleensä antavat keinoja myös muuttujien ja ohjelman muun kaluston käytettävyyden säätelyyn, ns. nimien näkyvyyden säätelyyn.

Rakenteiset lauseet ja lohkorakenne liittyvät ns. osittavaan ongelmanratkaisuun (stepwise refinement, "top down" -suunnittelu) ja ns. rakenteiseen ohjelmointiin (Algol, Pascal, ...).

Oliot, niiden luokat ja luokkien hierarkia puolestaan ovat ns. olio-ohjelmoinnin perusvälineitä. Ne täydentävät rakenteisen ohjelmoinnin ajatteluvälineitä toisaalta kokoavalla ongelmanratkaisulla (abstraktit tietotyypit, abstraktit koneet, "bottom up" -suunnittelu), toisaalta ongelmamaailman mallinnusvälineillä ("Missu on kissa, kissa on imettäväinen, imettäväinen on selkärankainen, selkärankainen kuuluu 'eläinkuntaan'") (Modula, C++, Java, ...)

Tietokoneessa kaikki tiedot, muuttujien arvot, esitetään bittijonoina ja koneen kannalta bittijono on vain bittijono. Mutta ohjelmoijan kannalta jotkin bittijonot esittävät kokonaislukuja, jotkin desimaalilukuja, jotkin merkkejä, jotkin totuusarvoja, jotkin merkkijonoja, jotkin kokonaislukumatriiseja, ... Ohjelmoijan kannalta siis muuttujien arvoilla ja samalla itse muuttujilla on jokin tyyppi (type). Vanhanaikaisilla ohjelmointikielillä ohjelmoidessa tyypillinen virhe oli vahingossa käsitellä vaikkapa desimaaliluvun bittiesitystä kokonaisluvun tavoin, kokonaislukua totuusarvon tavoin jne.

Tuollaisia virheitä oli vaikea välttää, mutta sitten keksittiin hyvä ajatus: Miksei panna tietokonetta tarkistamaan, että arvoja käytetään ohjelmoijan tarkoittamalla tavalla. Juuri tuollainen mekaaninen tarkistustyöhän sopii vallan erinomaisesti juuri koneelle.

Edellytys oli, että ohjelmoija ilmoittaa ennalta, mihin aikoo mitäkin muuttujaa käyttää. Kieliin otettiin mukaan muuttujien määrittelyt: esitellään ennalta, millaiseen tarkoitukseen kutakin muuttujaa käytetään. Sitten ohjelmointijärjestelmä (kääntäjä ja käännetty ohjelma) pyrkii tarkastamaan, ettei ohjelmoija yritä sijoittaa muuttujalle vääräntyyppistä arvoa. Tätä nimitetään vahvaksi tyypitykseksi.


Takaisin luvun 1 sisällysluetteloon.