Muuttujat imperatiivisissa ohjelmointikielissä

Samankaltaiset tiedostot
815338A Ohjelmointikielten periaatteet

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Tietueet. Tietueiden määrittely

Java-kielen perusteet

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

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

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

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

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

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

Sisällys. 6. Muuttujat ja Java. Muuttujien nimeäminen. Muuttujien nimeäminen. salinovi tai syntymapaiva

6. Muuttujat ja Java 6.1

11/20: Konepelti auki

15. Ohjelmoinnin tekniikkaa 15.1

Sisällys. 6. Muuttujat ja Java. Muuttujien nimeäminen. Muuttujien nimeäminen. Muuttujien nimeäminen. Muuttujan tyypin määritys. Javan tietotyypit:

ITKP102 Ohjelmointi 1 (6 op)

7. Oliot ja viitteet 7.1

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

Java-kielen perusteet

Java-kielen perusteet

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

Taulukot. Jukka Harju, Jukka Juslin

Ohjelmoinnin perusteet Y Python

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

15. Ohjelmoinnin tekniikkaa 15.1

ITKP102 Ohjelmointi 1 (6 op)

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

7/20: Paketti kasassa ensimmäistä kertaa

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

Sisällys. 6. Muuttujat ja Java. Muuttujien nimeäminen. Muuttujien nimeäminen. salinovi tai syntymapaiva

6. Muuttujat ja Java 6.1

Tieto- ja tallennusrakenteet

Tietotyypit ja operaattorit

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

Tietorakenteet ja algoritmit

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

Rakenteiset tietotyypit Moniulotteiset taulukot

Ohjelmointi 1 Taulukot ja merkkijonot

ITKP102 Ohjelmointi 1 (6 op)

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

2) Aliohjelma, jonka toiminta perustuu sivuvaikutuksiin: aliohjelma muuttaa parametrejaan tai globaaleja muuttujia, tulostaa jotakin jne.

Kielioppia: toisin kuin Javassa

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

Harjoitustyö: virtuaalikone

Perusteet. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät Pasi Sarolahti

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

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 15.3

Tietotekniikan valintakoe

Olio-ohjelmointi Javalla

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

Sisällys. 15. Lohkot. Lohkot. Lohkot

ITKP102 Ohjelmointi 1 (6 op)

1. Esittelyt ja vakiot 1.1 Esittelyt (declarations) Ennen nimen, tunnuksen (identifier) käyttöä se on

Osoittimet ja taulukot

Dynaaminen muisti. Pasi Sarolahti Aalto University School of Electrical Engineering. C-ohjelmointi Kevät 2017.

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

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

Osoitin ja viittaus C++:ssa

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

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

811120P Diskreetit rakenteet

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

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 16.3

Ohjelmoinnin perusteet Y Python

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

Ohjelmoinnin peruskurssi Y1

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 4: Ohjelmointi, skriptaus ja Python

815338A Ohjelmointikielten periaatteet

Ohjelmoinnin perusteet Y Python

TIE448 Kääntäjätekniikka, syksy Antti-Juhani Kaijanaho. 27. lokakuuta 2009

815338A Ohjelmointikielten periaatteet

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

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

Moduli 4: Moniulotteiset taulukot & Bittioperaatiot

18. Abstraktit tietotyypit 18.1

11. Javan toistorakenteet 11.1

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

811120P Diskreetit rakenteet

Tietorakenteet. JAVA-OHJELMOINTI Osa 5: Tietorakenteita. Sisällys. Merkkijonot (String) Luokka String. Metodeja (public)

Algoritmit 1. Luento 3 Ti Timo Männikkö

12. Javan toistorakenteet 12.1

20. Javan omat luokat 20.1

Groovy. Niko Jäntti Jesper Haapalinna Group 31

Sisällys. 20. Javan omat luokat. Java API. Pakkaukset. java\lang

samalla seuraavaan puoliavaruuteen (sukupolveen), jota siivotaan harvemmin.

Sisällys. 1. Omat operaatiot. Yleistä operaatioista. Yleistä operaatioista

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

1. luento. Ohjelmointi (C) T0004 Syksy luento. 1. luento. 1. luento. 1. luento. kurssin sisältö ja tavoitteet työmuodot.

Tietojen syöttäminen ohjelmalle. Tietojen syöttäminen ohjelmalle Scanner-luokan avulla

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

lausekkeiden tapauksessa. Jotkin ohjelmointikielet on määritelty sellaisiksi,

Ruby. Tampere University of Technology Department of Pervasive Computing TIE Principles of Programming Languages

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

D-OHJELMOINTIKIELI. AA-kerho, 33. Antti Uusimäki. Arto Savolainen

1. Olio-ohjelmointi 1.1

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Ohjelmointiharjoituksia Arduino-ympäristössä

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Transkriptio:

Muuttujat imperatiivisissa ohjelmointikielissä Seuraavaksi keskitytään nykyään valta-asemassa oleviin, imperatiivisiin ohjelmointikieliin. Tässä dokumentissa perehdytään keskeiseen muuttujan käsitteeseen ja tietotyyppeihin. Esimerkeissä esiintyviä kieliä käsitellään yksityiskohtaisemmin mm. seuraavissa teoksissa: [Ker] (C-kieli), [Strou] (C++ -kieli), [Arc] (C#), [Arn] (Java), [Kor] (Pascal), [Kur] (Ada) ja [KLS] (FORTRAN). 1. Muuttujat Imperatiivisen ohjelmoinnin aivan keskeisimpiä asioita on muuttujan käsite. Esimerkiksi Edsger Dijkstra on todennut: "Kun on ymmärtänyt tavan, miten ohjelmoinnissa käsitellään muuttujia, on ymmärtänyt olennaisimman ohjelmoinnista." Kuten aiemmin on todettu, imperatiivinen ohjelmointiparadigma perustuu von Neumannin malliin tietokoneesta. Siinä muuttujat mallintavat tietokoneen muistipaikkoja; näin ollen muuttuja on itse asiassa nimi jollekin tietokoneen muistialueelle, jonka sisältö kuvaa muuttujan arvoa. Tosin muuttuja on muutakin kuin pelkkä nimi, ajatellaan esimerkiksi seuraavaa Pascal-kielen sijoituslausetta e := 2.71828; Tässä e on jonkin fyysisen muistiosoitteen nimi, 2.71828 kuvaa muuttujan arvoa, joka sijoitetaan kyseiseen muistipaikkaan lausetta suoritettaessa. Kuitenkin tietokoneen muistissa luvut säilytetään binäärisessä muodossa, joten tarvitaan ennakkotietoa muuttujan tyypistä, jotta se voidaan tulkita kyseiseksi desimaaliluvuksi. Näin ollen muuttujalla on 1. Nimi (name): tapa yksilöidä muuttuja, 2. Osoite (address): tietokoneen muistiosoite, jossa muuttujan arvo sijaitsee ja 3. Arvo (value): se data joka kullakin hetkellä on muuttujan osoitteen osoittamassa muistipaikassa, ja lisäksi siihen liittyvät seuraavat ominaisuudet eli attribuutit:

4. Tyyppi (type): muuttujan tietorakenteen nimi, 5. Näkyvyysalue (scope): se ohjelman osa, jossa muuttuja on käytettävissä, 6. Elinaika (lifetime, extent): muuttujan muistinvaraamisen ja muistinvapauttamisen välinen aika. Nimi on merkkijono, jota käytetään tunnistamaan jokin ohjelman itsenäinen kokonaisuus, esimerkiksi muuttuja. Sanaa "tunniste" (identifier) käytetään nimen synonyymina. Varhaisimmat ohjelmointikielet sallivat ainoastaan yksikirjaimisia nimiä muuttujille perintönä matemaatikkojen tavasta merkitä muuttujia matemaattisessa tekstissä. Kuitenkin jo FORTRAN luopui tästä rajoitteesta ja salli maksimissaan kuuden merkin mittaiset nimet muuttujille. Alun perin C-kielessä käytettiin nimen maksimierottelupituutena 31 ensimmäistä merkkiä, mutta nykyään tunnisteen pituutta ei rajoiteta. Jotkin C-kääntäjät voivat kuitenkin asettaa rajoituksia nimien pituudelle. FORTRAN 90 käyttää 31 merkin rajoitusta. Nykyään useimmat kielet, kuten C++ ja Java sallivat mielivaltaisen pitkät nimet. Tosin joissakin C++ -toteutuksissa saattaa rajoituksia esiintyä. Ohjelmoijan on tärkeää tietää myös, erotetaanko nimissä isot ja pienet kirjaimet. Vaikka kirjainten erottaminen heikentää kielen luettavuutta ja johtaa helpommin ohjelmointivirheisiin, useimmat moderneista kielistä, kuten Java, C++ ja C# tekevät eron isojen ja pienten kirjainten välillä. Samoin on C-kielessä. Näin ollen muuttujat Muuttuja, muuttuja, muuttuja ja muuttuja ovat kaikki eri muuttujia näissä kielissä. Sen sijaan Pascal ja FORTRAN eivät tee eroa isojen ja pienten kirjainten välillä ja näissä kyseiset muuttujan nimet viittaisivat samaan muuttujaan. Itse asiassa varhemmat FORTRANin versiot eivät sallineet pieniä kirjaimia käytettävän lainkaan muuttujan nimissä. Sittemmin tässäkin kielessä on siirrytty samaan käytäntöön kuin Pascalissa ja pienet kirjaimet tulkataan isoiksi käännösvaiheessa. Kielen kontrollirakenteita kuvaavat sanat on jotenkin erotettava muuttujista määrittelemällä ne erikoissanoiksi (special words). Tämä tehdään yleisesti kahdella tavalla: käyttämällä varattuja sanoja (reserved words) tai avainsanoja (keywords). Varattua sanaa ei voi ohjelmassa käyttää missään yhteydessä muuten kuin sille varattuun tarkoitukseen; näin ollen esimerkiksi C-, Java- tai Pascal-kielessä ei voi

määritellä if -nimistä muuttujaa. Avainsana on erikoismerkityksessä ainoastaan tietyissä yhteyksissä. Joissakin aiemmissa ohjelmointikielissä, kuten PL/I ja FORTRAN on käytetty avainsanoja. Moderneissa kielissä on kuitenkin siirrytty käytännöllisesti katsoen yksinomaan varattujen sanojen käyttöön siitä yksinkertaisesta syystä, että avainsanojen käyttö tekee mahdolliseksi erittäin hankalalukuisen ja virhealttiin koodin kirjoittamisen. Esimerkiksi FORTRAN -kielessä INTEGER tarkoittaa kokonaislukutyyppiä ja REAL liukulukutyyppiä, joten ne ovat avainsanoja. Ne eivät kuitenkaan ole varattuja ([KLS] s. 18), joten on mahdollista määritellä tämän nimiset muuttujat, esimerkiksi INTEGER REAL REAL INTEGER INTEGER = 1.23 REAL = 32 Samoin kontrollirakenteita kuvaavien sanojen käyttäminen muuttujan niminä johtaa kammottavaan ohjelmakoodiin. Muuttujan osoite on muuttujaan liittyvän fyysisen muistiosoitteen arvo, joka yleensä ilmaistaan (mikrotietokoneissa tavallisesti 32-bittisenä) heksalukuna. Muuttujan osoite ei yleensä ole staattinen, vaan samannimisen muuttujan osoite saattaa vaihdella ohjelman suorituksen aikana. Usein muuttujan osoitteesta käytetään nimitystä l-value (left value), koska muuttujan osoite on tiedettävä, kun muuttuja sijaitsee sijoituslauseen vasemmalla puolella. On myös mahdollista, että kaksi erinimistä muuttujaa viittaa samaan muistipaikkaan. Tästä ilmiöstä nimiä käytetään termiä moninimisyys (aliasing) ([Har], s. 58). Moninimisyys heikentää koodin luettavuutta ja luotettavuutta, joten nykyään sitä pidetään ei-toivottuna piirteenä ohjelmointikielessä. Mistään ohjelmointikielestä ei kuitenkaan ole onnistuttu poistamaan kokonaan moninimisyyttä. Aikoinaan FORTRANissa oli jopa erityinen mekanismi ominaisuuden toteuttamiseksi (EQUIVALENCE -lause, ks [KLS], s. 128), mikä katsottiin tarpeelliseksi muistin uudelleenkäyttämiseksi. Tapoihin muodostaa eri kielissä muuttujia viittaamaan samaan muistipaikkaan palataan vielä myöhemmin. Muuttujan tyyppi määrittelee sen arvoalueen ja minkälaisia operaatioita kyseisen tyypin muuttujalle voidaan tehdä. Esimerkiksi Java-kielessä suurin kokonaislukutyypin suurin arvo on

Integer.MAX_VALUE 2147483647 ja pienin Integer.MIN_VALUE -2147483648 Tyypin muuttujiin voidaan soveltaa perusaritmetiikan operaatioita. Muuttujan arvo on sen muistiosoitteen kulloinenkin sisältö. Yleensä tietokoneen muistiosoitteen koko on yksi tavu: tässä muistiosoitteella tarkoitetaan kuitenkin hieman abstrahoidusti sellaista muistialuetta, johon koko muuttujan data mahtuu. Esimerkiksi Javan double-tyyppinen muuttuja on kahdeksan tavun kokoinen, joten puhuttaessa tällaisen muuttujan osoitteesta, tarkoitetaan kahdeksan tavun kokoista muistialuetta, jossa säilytetään muuttujan arvoa. Usein muuttujan arvoa nimitetään r- valueksi (right value), koska sitä tarvitaan sijoituslauseen oikealla puolella. Huomaa, että päästäkseen käsiksi r-valueen, on l-value aina määritettävä ensin. Yksi keskeisimmistä muuttujiin liittyvistä käsitteistä on sidonta (binding). Sidonnalla tarkoitetaan jonkin ominaisuuden liittämistä ohjelman itsenäiseen kokonaisuuteen. Muuttujan tapauksessa se tarkoittaa yleensä tyypinsidontaa tai muistinsidontaa. Näistä ensimmäinen liittää muuttujaan jonkin tietotyypin ja jälkimmäinen muistiosoitteen (varaa eli allokoi riittävästi muistia, jotta muuttujan arvo voidaan tallentaa). Erityisesti on tärkeää tarkastella sidonta-aikaa (binding time), ts. milloin tietty sidonta tehdään. Yleisesti ottaen voidaan sanoa, että mahdollisimman varhainen sidonta lisää tehokkuutta, kun taas mahdollisimman myöhäinen sidonta joustavuutta. Ei olekaan ihme, että ohjelmointikielten kehityksen trendi on kohti yhä myöhäisempiä sidontaaikoja, kun resurssit lisääntyvät ja tehokkuus ei ole aina välttämätön kriteeri. Kaikki ennen ajoa tapahtuva sidonta staattista (static binding) ja ajonaikainen dynaamista (dynamic binding). Staattinen sidonta voidaan jakaa moneen osaan. Tutkitaan esimerkiksi C++ -kielen lauseita float desinum = 0.0; desinum = desinum + 1.1;

Tässä mahdolliset tyypit muuttujalle desinum on pitänyt sitoa jo ohjelmointikieltä suunniteltaessa ja mahdolliset merkitykset + -operaattorille kieltä määriteltäessä. Sen sijaan muuttujan desinum tyyppi ja operaattorin + merkitys jälkimmäisessä lauseessa sidotaan vasta käännösvaiheessa. Vakioiden 0.0 ja 1.1 esitystapa on sidottu kääntäjän suunnitteluvaiheessa ja lopulta muuttujan desinum arvo jälkimmäisessä lauseessa sidotaan muuttujaan dynaamisesti ajonaikaisesti lausetta suoritettaessa. Erityisesti dynaamisen ja staattisen sidonnan erottaminen ja eron ymmärtäminen on olennaista ohjelman semantiikan ymmärtämiseksi. Staattinen sidonta tapahtuu siis ennen ohjelman suoritusta eikä muutu suorituksen aikana; dynaaminen sidonta tapahtuu vasta ohjelmaa ajettaessa ja voi muuttua ohjelman suorituksen aikana. Ennen kuin muuttujaa voi ohjelmassa käyttää, siihen on liitettävä tietotyyppi. Staattisesti tämä tapahtuu liittämällä tyyppi muuttujaan joko eksplisiittisellä tai implisiittisellä esittelyllä. Eksplisiittinen esittely on ohjelmalause jossa listataan joukko muuttujan nimiä ja määritellään ne tietyn tyyppisiksi, esimerkiksi Java-kielen lause float f1,f2,f3; esittelee kolme reaalilukutyyppistä muuttujaa f1, f2 ja f3. Implisiittinen esittely liittää muuttujaan tietotyypin jonkin sopimuksen mukaan ilman erillistä esittelyä. Esimerkiksi FORTRAN -kielessä voidaan muuttuja eksplisiittisesti esitellä, mutta ilman esittelyä esiintyvä muuttuja on INTEGER-tyyppinen, mikäli sen nimi alkaa I, J, K, L, M tai N - kirjaimella; muussa tapauksessa muuttuja on REAL -tyyppinen ([KLS], kappale 4.2.1). Joissakin BASIC -kielen versioissa on vastaavanlainen ominaisuus: Mikäli muuttujan nimi loppuu merkkiin "%", muuttuja on kokonaisluku, merkkiin "$" loppuvat muuttujat ovat merkkijonoja ja kaikki muut ovat reaalilukutyyppiä. Mahdollisuus implisiittiseen esittelyyn katsotaan nykyään suuremmaksi haitaksi kuin ohjelmoijan saama mukavuushyöty. Implisiittinen esittely tapauksissa, joissa esittelyn poisjäänti on ohjelmoijan laiminlyönti, voi johtaa hankalasti löydettäviin virhetoimintoihin ohjelmassa. Useimmissa (staattista tyypinsidontaa käyttävissä) kielissä eksplisiittinen esittely on pakollinen. Kun muuttujan tyyppi sidotaan dynaamisesti, ei käytetä esittelylausetta, vaan muuttujan tyyppi sidotaan sijoituslauseen yhteydessä. Muuttujan tyyppi määrittyy siis

vasta ohjelman suorituksen aikana sijoituslausetta suoritettaessa. Dynaamista tyypinsidontaa käyttävät kielet eroavat voimakkaasti staattiseen tyypinsidontaan perustuvista kielistä. Tällaiset kielet ovat erittäin joustavia ja mahdollistavat monissa tapauksissa yksinkertaisen geneerisen ohjelmoinnin. Varhaisimpia dynaamisia kieliä olivat jo 1960 -luvun alussa kehitetyt APL ja SNOBOL, joilla on vieläkin käyttäjänsä. Useat skriptikielet, kuten JavaScript ja Python, käyttävät dynaamista tyypinsidontaa. Esimerkiksi JavaScript -kielinen lause xz = [1.5, 2.2, 3.7] aiheuttaa muuttujan xz tyypin muuttumisen lukuja sisältäväksi yksiulotteiseksi taulukoksi, ja mikäli tätä seuraa lause xz = 234 xz muuttuu numeeriseksi muuttujaksi. Saavutetusta joustavuudesta aiheutuu myös haittoja: Aiemmin mainittiin jo tehokkuuden menetys, dynaamisesti tyypitetty kieli häviää suoritusajassa yleensä melkoisesti staattisesti tyypitetyn kielen ohjelmille. Usein dynaamisesti tyypitetyt kielet ovat lisäksi tulkattavia johtuen ongelmista, joita dynaaminen tyypittäminen asettaa kääntäjälle. Toinen haitta on koodin luotettavuuden heikkeneminen, koska kääntäjä ei voi havaita ohjelmoijan tyyppivirheitä. Väärän tyyppinen muuttuja sijoituslauseen oikealla puolella aiheuttaa ainoastaan vasemman puolen muuttujan tyypinvaihdoksen. Kun muuttujan tietotyyppi on määrätty, sille voidaan varata muistista alue (osoite), johon muuttujan arvo voidaan tallentaa. Muistin varaamista kutsutaan myös allokoinniksi (allocation). Allokoinnin käänteisprosessi on varatun muistin vapauttaminen, ts. muistialueen antaminen jälleen käytettäväksi; tätä kutsutaan myös deallokoinniksi (deallocation). Muuttujan elinikä on sen käyttämän muistin varaamisen ja vapauttamisen välinen aika. Muuttujat voidaan jakaa eliniän perusteella neljään eri kategoriaan: 1. staattisiin, 2. pinodynaamisiin, 3. kekodynaamisiin (explicit heap dynamic) ja

4. implisiittisesti kekodynaamisiin (implicit heap dynamic). Yleisesti ohjelman muistialue jaetaan kolmeen osaan: 1. staattiseen (globaaliin) osaan, 2. pinomuistiin ([runtime] stack) ja 3. kekomuistiin (heap). Yleensä pino- ja kekomuisti ovat muistialueen vastakkaisissa päissä ja kasvavat toisiansa kohti seuraavan kuvion osoittamalla tavalla: Kuva. Ohjelman muistialue Staattinen muuttuja sidotaan muistosoitteeseen ennen ohjelman suoritusta ja se säilyy sidottuna tähän samaan osoitteeseen, kunnes ohjelman suoritus päättyy. Staattisten muuttujien käyttö tuo tehokkuutta ohjelmaan, mutta tekee ohjelmasta joustamattoman. Esimerkiksi rekursiivisten aliohjelmien toteuttaminen on mahdotonta käyttämällä pelkästään staattisia muuttujia. FORTRANin varhaisemmissa versioissa kaikki muuttujat olivat staattisia. Sen sijaan C-kielessä ohjelmoija voi static-määreellä määritellä muuttujan staattiseksi ([Ker], kappale 4.6). Tämä mahdollistaa esimerkiksi aliohjelman paikallisen muuttujan arvon säilymisen aliohjelmakutsujen välillä. Esimerkiksi Pascal -kielessä staattisia muuttujia ei voi määritellä. Muuttujat, joiden tyyppi on staattisesti sidottu, mutta joiden muistiosoite sidotaan ajonaikaisesti esittelylausetta suoritettaessa, ovat pinodynaamisia muuttujia. Nimitys johtuu siitä, että aina Algol 60 -kielestä lähtien sen sukuisissa kielissä paikallisten muuttujien muisti on varattu pinomuistista dynaamisesti, vaikka muuttujan tyyppi

onkin staattisesti sidottu. Tähän kategoriaan kuuluvat yleisimmin käytetyt imperatiiviset nykykielet. Juuri pinon käyttäminen muuttujien muistin varaamiseen sallii rekursiiviset aliohjelmakutsut, koska tällöin aliohjelman tietyn nimiselle muuttujalle varataan jokaista aliohjelmakutsua kohti uusi muistiosoite. Toinen etu, joka saavutetaan pinodynaamisten muuttujien käytöllä, on yhteinen muistialue kaikkien aliohjelmien paikallisille muuttujille. Dynaaminen muistinvaraus hidastaa hieman suoritusta, mutta nykyisillä resursseilla tämä ei ole olennaisesti ohjelman suoritusaikaa rajoittava tekijä. Lisäksi historiatietoja ylläpitävien muuttujien käyttäminen aliohjelmissa ei onnistu pinodynaamisilla muuttujilla vaan vaatii jonkinlaisen staattisen tai globaalin muuttujan. Kekomuistista varattavat muuttujat ovat yleisimmin (eksplisiittisesti) kekodynaamisia. Nämä varataan kekomuistista ohjelmoijan käskystä ajonaikaisesti. Kekodynaamisiin muuttujiin voidaan viitata ainoastaan osoitinmuuttujan tai viitetyypin muuttujan avulla. Tällainen muuttuja voidaan varata joko erityisen operaattorin (Java ja C++ -kielessä new) avulla tai C-kielen tapaan kutsumalla kirjastofunktiota (malloc()), joka huolehtii muistinvarauksesta. Kekodynaamisten muuttujien varaama muisti voidaan vapauttaa automaattisesti, kun muuttujaa ei enää käytetä ohjelmassa, kuten Java -kielessä tapahtuu. Tätä prosessia nimitetään roskien keruuksi (garbage collection). Monissa kielissä (esimerkiksi C ja C++) kekodynaamisten muuttujien muistia ei vapauteta automaattisesti, vaan vapauttaminen on ohjelmoijan vastuulla ja siihen on C++ - kielessä oma operaattorinsa delete ja C:ssä funktio free(). Esimerkki. Kekodynaamisen muuttujan varaaminen (ja vapauttaminen) C ja C++ - kielissä. C: C++: int *pnewint; pnewint = malloc(sizeof(int)); *pnewint = 10; free(pnewint); int *pnewint; pnewint = new int; *pnewint = 10; delete pnewint;

Mikäli muistin vapauttaminen jätetään ohjelmoijan vastuulle, on hyvin mahdollista, että ohjelman suorituksen aikana jää käyttämätöntä muistia vapauttamatta ja syntyy ns. roikkuvia osoittimia, ts. osoitinmuuttujia, jotka osoittavat jo vapautettuun muistiin. Tällaisen muistialueen käyttäminen johtaa ohjelman ennustamattomaan käyttäytymiseen. Esimerkiksi C++ koodissa int *pnewint; pnewint = new int; pnewint = 0; muistia, joka varattiin, ei voida enää vapauttaa, koska siihen osoittavaan muuttujaan sijoitettiin 0. Nyt lause delete pnewint; ohjelmassa ei tekisi mitään. Jokin muuttujan nollasta poikkeava arvo, joka ei ole varatun muistipaikan osoite kaataisi ohjelman, koska mainitussa paikassa olevaa muistia ei voisi vapauttaa. Edelleen koodissa int *pnewint, *panothernewint; pnewint = new int; *pnewint = 10; panothernewint = pnewint; delete pnewint; pnewint = 0; muuttuja panothernewint on roikkuva osoitin, koska se osoittaa paikkaan, josta muisti vapautettiin. Jos kyseisen muistipaikan arvoa käytetään kokonaislukuna, arvo on satunnainen. Implisiittisesti kekodynaamiset muuttujat sidotaan kekomuistiin vasta sijoituslauseen yhteydessä. Tällaisten muuttujien käyttö sallii hyvin joustavan ohjelmoinnin ja erittäin geneerisen koodin kirjoittamisen. Monet skriptikielet, kuten Perl ja JavaScript käsittelevät merkkijonomuuttujia ja taulukoita näin. Tyypintarkistuksen (type checking) avulla varmistetaan, että ohjelman kaikissa operaatioissa käytettävien muuttujien tyypit ovat yhteensopivat, esimerkiksi kokonaislukutyyppimuuttujaan ei saisi sijoittaa liukulukumuuttujan arvoa.

Tyypintarkistus voi olla joko staattista tai dynaamista. Mikäli tyypit tarkistetaan ajonaikaisesti, kyseessä on dynaaminen tarkistus, muuten staattinen. Kääntäjät käyttävät staattista ja tulkit dynaamista tarkistusta. Tyyppiyhteensopivuus (type compatibility) voidaan määritellä tiukemmin nimityypin yhteensopivuutena (name type compatibility) tai hieman löysemmin rakennetyypin yhteensopivuutena (structure type compatibility). Edellinen tarkoittaa, että muuttujat ovat yhteensopivaa tyyppiä ainoastaan, jos niiden määritellyt tyypit ovat samat. Rakennetyypin yhteensopivuus puolestaan toteutuu, jos muuttujien rakenne on identtinen, vaikka niiden määrittely käyttää eri nimeä. Esimerkiksi Pascal-kielisessä määrittelyssä TYPE MYINT = INTEGER; var a: INTEGER; var ma:myint; muuttujat a ja ma eivät ole nimityyppiyhteensopivat, mutta ovat rakennetyyppiyhteensopivat. Pascal -kielessä ei tarkisteta nimityypin yhteensopivuutta, sijoituslause ma := a; on laillinen ylläolevassa ohjelmassa. Standardi-Pascalissa tyypintarkistus on pääosin rakennetyypin tarkistusta, mutta joissakin tilanteissa vaaditaan nimityypin yhteensopivuutta (ks. [Seb], kappale 5.7 tai [Kor], kappale 5.2). Yleensäkin ohjelmointikielissä käytetään tyypintarkistukseen jotain mainittujen tapojen välimuotoa, koska nimityypin tarkistus on liian rajoittava ja rakennetyypin tarkistus liian hankala toteutettava, esimerkiksi C-kielessä typedef struct { int x; int y; } mystruct; typedef struct { int xx; int yy; } myotherstruct; mystruct str1; myotherstruct str2;

muuttujat str1 ja str2 ovat rakennetyyppiyhteensopivat, mutta sijoituslause str1 = str2; ei ole sallittu. Itse asiassa C-kieli käyttää muuten rakenneyhteensopivuutta paitsi tietueiden (struct) ja unionien (union) suhteen ([Ker], Appendix A 8.10). Huomaa, että C- ja C++ -kielen typedef ei oikeastaan määrittele uutta tyyppiä, vaan määrittelee ainoastaan nimen jo olemassa olevalle tyypille. Näin ollen C/C++ -kielisessä määrittelyssä typedef int myint; int i; myint mi; muuttujat i ja mi ovat nimityyppiyhteensopivat ja siten sijoituslause i = mi; on sallittu myös C++ -kielessä, joka käyttää nimityyppiyhteensopivuutta (ks. [Seb], kappale 6.14). Ohjelmointikieltä sanotaan vahvasti tyypitetyksi (strongly typed), mikäli 1. Jokaisella muuttujalla on oltava hyvin määritelty tyyppi. 2. Tyyppivirheet havaitaan aina. Yleensä vahvaa tyypitystä pidetään tavoittelemisen arvoisena piirteenä ohjelmointikielessä aina 1970 -luvulla rakenteellisen ohjelmoinnin ihanteista alkaen. Ominaisuus estää nimittäin monenlaiset ohjelmointivirheet, jotka johtuvat vääräntyyppisen muuttujan sijoittamisen tai käyttämisen parametrina aliohjelmakutsussa. Kuitenkin vain varsin harvat kielet täyttävät tämän kriteerin, jos sitä sovelletaan tiukasti. Esimerkiksi Pascal-kieli on lähes vahvasti tyypitetty, mutta siinä on mahdollisuus määritellä ns. vaihtelevia tietueita (variant records), joiden tyyppiä ei aina voi tarkistaa. C -kielessä on hieman enemmän tapauksia, joissa tyyppivirhe voi jäädä havaitsematta, joten C-kieli ei ole niin vahvasti tyypitetty kuin Pascal. Myöskään C++ ei ole vahvasti tyypitetty, vaikka tyypintarkistus on vahvempi kuin C:ssä. Adaa, Javaa ja C# -kieliä voidaan pitää vahvasti tyypitettyinä, sillä näissä kielissä tyyppivirhe voi syntyä ainoastaan niin, että ohjelmoija itse pakottaa väärän tyypin muuttujalle. Kielessä käytettävät muunnossäännöt (coercion) vaikuttavat olennaisella tavalla tyypintarkistukseen; nimittäin vahvasti tyypitetyssäkin kielessä saattaa olla esimerkiksi aritmeettisille operaatioille sääntöjä, jotka rikkovat periaatteessa tyypitystä vastaan. Esimerkiksi Javassa saadaan laskea liukulukumuuttuja ja kokonaislukumuuttuja yhteen,

jolloin kokonaisluvusta pakotetaan liukuluku ja operaatio suoritetaan. Tällaiset muunnokset heikentävät kielen luotettavuutta. Tässä mielessä Ada on luotettavampi kuin Java, koska se sisältää huomattavasti vähemmän tällaisia muunnoksia. Muuttujan näkyvyysalue (scope) on yksi tärkeimmistä käsitteistä tarkasteltaessa ohjelmointikieliä. Näkyvyysalueella tarkoitetaan niiden ohjelman lauseiden kokonaisuutta, joiden alueella muuttuja on näkyvä (visible), toisin sanoen käytettävissä. Globaalin (global) muuttujan näkyvyysalue on koko ohjelma. Ohjelmalohkon tai muun vastaavan yksikön paikalliset eli lokaalit (local) muuttujat ovat sellaisia muuttujia, jotka on määritelty kyseisessä lohkossa. Lohkon sisällä näkyvät muuttujat, joita kuitenkaan ei ole määritelty kyseisessä lohkossa, ovat lohkon eipaikallisia (nonlocal) muuttujia. Muuttujan näkyvyysalue voi määräytyä staattisesti (static scoping) ennen ohjelman suoritusta tai dynaamisesti (dynamic scoping) ohjelman suorituksen aikana. Staattinen näkyvyysalueen määräytyminen on ollut yleisin menetelmä imperatiivisissa kielissä aina sen jälkeen, kun se esiintyi ensi kertaa Algol 60 -kielessä. Nimensä mukaisesti staattista näkyvyysaluetta käyttävien kielten muuttujien näkyvyysalueet määräytyvät jo ennen ohjelman suoritusta ohjelman rakenteen perusteella. Ohjelman rakenteet voivat olla toisilleen alisteisia, yleensä tämä tarkoittaa ohjelmakoodissa määrittelyjen (ja siten myös näkyvyysalueiden) sisäkkäisyyttä. Useimmissa ohjelmointikielissä aliohjelmilla on oma näkyvyysalueensa; samoin nykyisissä kielissä on useimmiten mahdollista muodostaa uusia näkyvyysalueita määrittelemällä ohjelmalohkoja (blocks). Ohjelmalohkon ja kootun lauseen (compound statement) ero on siinä, että lohkon sisällä voidaan esitellä uusia muuttujia, kun kootun lauseen sisällä tämä ei ole mahdollista. Esimerkiksi Pascal -kielessä voidaan määritellä koottuja lauseita keräämällä niitä begin - end parin sisään, mutta muuttujia ei niissä voi määritellä. Siten koodi (muuttujat a ja b ovat aiemmin esitelty) begin var x:integer; a := a+b; end;

on Pascalissa virheellinen. Sen sijaan C, C++ ja Java -kielissä sulkujen { ja } sisällä olevat lauseet muodostavat lohkon ja muuttujia voidaan niissä esitellä. Kaikissa kielissä muuttuja saadaan nykyään esitellä missä kohdassa lohkoa tahansa, mutta C-kielen aiemmissa versioissa esittelyt oli aina sijoitettava lohkon alkuun. Staattisesta näkyvyysalueen määräytymisestä seuraa, että mikäli sisemmissä näkyvyysalueissa esitellään samannimisiä muuttujia kuin jo on alueen sisältävässä lohkossa olemassa, on ulomman alueen muuttuja piilotettava sisemmässä lohkossa, jotta esiteltyä muuttujaa voidaan käyttää. Esimerkiksi C-kielisessä ohjelmassa int i = 100; int ki = 35; { int i = 10; printf("lohkon i = %d\n", i); i = ki; printf("lohkon i = %d\n", i); } printf("ulompi i = %d\n", i); Tulostuu Lohkon i = 10 Lohkon i = 35 Ulompi i = 100 Lohkon sisällä siis aiemmin esitelty muuttuja i on piilotettu ja lohkossa esitelty muuttuja i näkyvissä. Kun lohkosta poistutaan, on jälleen aiemmin esitelty muuttuja i näkyvissä. Joissakin kielissä, kuten Pascalissa, sallitaan myös aliohjelmien sisäkkäisyys, jolloin havaitaan sama ilmiö, vaikka Pascalissa ei voikaan muodostaa lohkoja mielivaltaisesti. Esimerkiksi Pascal -ohjelmassa

program main; var x:integer; procedure A; var x:integer; procedure B; begin {scope B...} end; begin {scope A...} end; procedure C; var x: integer; procedure D; begin{scope D...} end; procedure E; var x:integer; var y:integer; begin {scope E...} end; begin {scope C...} end; begin {scope main...} end. aliohjelma A sisältää aliohjelman B ja aliohjelma C aliohjelmat D ja E. Tämä vaikuttaa paitsi muuttujien, myös aliohjelmien näkyvyyteen. Näin ollen pääohjelman rungosta voi kutsua aliohjemia A ja C, mutta ei aliohjelmia B, D ja E. Muuttujien näkyvyysalueet (tässä x(main) tarkoittaa pääohjelmassa esiteltyä muuttujaa x ja x(a) aliohjelmassa A esiteltyä muuttujaa x jne) ovat aliohjelmittain seuraavat:

main: x(main) A: x(a), x(main) piilotettu B: x(a), x(main) piilotettu C: x(c), x(main) piilotettu D: x(c), x(main) piilotettu E: y(e), x(e), x(c) piilotettu, x(main) piilotettu Hyvin harvat ohjelmointikielet käyttävät dynaamista näkyvyysalueen määräytyvyyttä. Tällaisia kieliä ovat APL, SNOBOL ja LISP -kielen varhaisimmat versiot. Perl sallii myös dynaamisesti määräytyvän näkyvyysalueen muuttujien määrittelyn. Dynaaminen näkyvyysalueen määräytyminen perustuu aliohjelmien suoritusjärjestykseen eikä aliohjelmien rakenteelliseen sijaintiin ohjelmakokonaisuudessa. Näin ollen aktiivisen aliohjelman muuttujat ovat näkyvissä kaikille aliohjelmille, joita kutsutaan kyseisen aliohjelman käynnistämisen jälkeen. Tällöin voi luonnollisesti sattua samannimisten muuttujien törmäyksiä, staattinen tyypintarkistus on mahdoton, joten se on tehtävä ohjelman suorituksen aikaisesti. Oletetaan että C käyttäisi dynaamista näkyvyysalueen määräytymistä (näin ei tietenkään asia oikeasti ole). Silloin ohjelma

int x; void fun_1() { } void fun_2() { } int main() { } printf("%d\n", x); int x = 10; fun_1(); x = 5; fun_2(); return 0; tulostaisi 10, koska paikallinen muuttuja x piilottaisi globaalin muuttujan x. Kun käytetään staattista näkyvyysalueen määräytymistä, ohjelma tulostaa 5, koska aliohjelman fun_2 paikallinen muuttuja x on näkyvissä ainoastaan kyseisessä aliohjelmassa. Dynaaminen näkyvyysalueen määräytyminen aiheuttaa monenlaisia ongelmia: Ohjelman luotettavuus heikkenee, koska aliohjelmien paikallisia muuttujia ei voi suojella niiden ulkopuolista muuttamista vastaan. Edelleen koodin luettavuus huononee, koska muuttujien määräytyminen perustuu suoritusjärjestykseen. Etu tässä näkyvyysalueen määräytymisessä on aliohjelmien tiedonvälityksen helpottuminen. Nykyisin katsotaan haittojen olevan huomattavasti suuremmat, joten juuri mikään moderneista kielistä ei määrää näkyvyysalueita dynaamisesti. Maarit Harsun kirjan ([Har]) kolmas luku sisältää myös yllä mainittuja asioita.

2. Tietotyypit Jokainen tietokoneohjelma manipuloi dataa jollakin tavalla; data ohjelman sisällä esitetään tietorakenteiden avulla. Ohjelman logiikka koostuu algoritmeista, joten algoritmit ja tietorakenteet ovat ohjelmien perusrakennusaineita, tämän on Niklaus Wirth sisällyttänyt jopa kirjansa "Algorithms+Data Structures = Programs" nimeen. Näin ollen tietotyyppi (data type) on keskeinen käsite ohjelmoinnissa, koska se luokittelee ohjelman datan. Tietotyypeistä ja tyypin tarkistuksesta on puhuttu jo edellä. Tässä käsitellään ja luokitellaan ohjelmointikielissä esiintyviä tietotyyppejä sekä perehdytään hieman eri tapoihin implementoida niitä. Tietotyyppi voidaan määritellä joukoksi arvoja, joihin liittyy joukko näihin arvoihin sovellettavia operaatioita. Tietotyyppejä käsitellään myös Maarit Harsun kirjan ([Har]) luvussa 4. Tietotyyppejä, joiden määrittelemiseen ei käytetä muita tietotyyppejä, sanotaan primitiiviksi tietotyypeiksi (primitive data types). Lähes jokaisessa ohjelmointikielessä määritellään joukko primitiivisiä kielen mukana tulevia primitiivisiä tietotyyppejä. Käsitteenä primitiivinen tietotyyppi muistuttaa läheisesti Loudenin (ks. [Lou] s. 158) käyttämää yksinkertaisen (simple) tietotyypin käsitettä. Louden määrittelee yksinkertaisen tietotyypin koskemaan kuitenkin sellaisia tietotyyppejä, joilla ei ole muuta rakennetta kuin sisäänrakennettu aritmeettinen tai peräkkäinen rakenne. Tyypillisesti primitiiviset tyypit jaetaan numeeriseen, loogiseen ja merkkitietoon. Useissa varhaisissa ohjelmointikielissä ainoat primitiiviset tietotyypit olivat numeerisia. Kaikkein yleisin numeerinen primitiivinen tietotyyppi on kokonaislukutyyppi (integer). Ohjelmointikielestä ja ympäristöstä riippuen kokonaisluvun pituus voi vaihdella; nykyään yleisin (ja esimerkiksi int Java -kielessä spesifioituna, [Arn] kappale 5.5) on 32 bitin mittainen luku. Lisäksi yleensä voidaan määritellä pitempi kokonaisluku (Javassa ja C:ssä long) sekä lyhempi kokonaisluku (Javassa ja C:ssä short). C-kielessä voidaan myös käyttää eripituisia etumerkittömiä kokonaislukutyyppejä (unsigned int jne). Normaalisti korkein bitti ilmaisee luvun etumerkin (1 tarkoittaa negatiivista lukua), ja yleisimmin negatiiviset luvut tallennetaan ns. kahden komplementtina. Tällöin. luvun merkki vaihdetaan tekemällä sille looginen komplementti ja lisäämällä siihen luku 1.

Esimerkiksi 8-bittisten kokonaislukujen ollessa kyseessä, luku -12 esitettäisiin seuraavasti: 12 = 00001100, looginen komplementti = 11110011 joten -12 = 11110011 + 1 = 11110100 Liukulukutyyppi (floating-point type) esittää reaalilukuja (likimääräisesti) tietokoneessa. On luonnollisesti lukuja, joita ei voi tarkasti esittää millään valittavalla tietokoneen käyttämällä merkintätavalla. Liukulukutyypin luvut esitetään nykyisin useimmiten IEEE:n suosittamaa standardia 754 käyttämällä. Siinä 32-bittinen (monissa kielissä float) liukuluku esitetään tieteellisestä merkintätavasta tutussa muodossa (1+mantissa)*2 eksponentti, missä mantissa on välillä [0,1). Ylin bitti on etumerkki (1 tarkoittaa negatiivista lukua), kahdeksan seuraavaa bittiä on varattu esittämään eksponenttia ja loput 23 bittiä mantissaa. Luku esitetään aina binäärisessä muodossa ja eksponentti esitetään poikkeamana luvusta 127, joten esimerkiksi luvulle 0,46875 = 15/32 saataisiin esitys 0,4687510 = 15/8 *2-2 = (1+ 7/8)* 2-2. Siten eksponentti = 127-2 = 125 ja mantissa on tällöin 7/8 = 0.1112, joten saadaan esitys Merkki(+) Eksponentti (125) Mantissa (0.111) 0 01111101 111000000000000000000000 Kaksinkertaisen tarkkuuden liukuluvuille on vastaavan kaltainen esitysmuoto, siinä käytetään mantissalle 52 bittiä ja eksponentille 11 bittiä, poikkeaman arvo on 1023. Joissakin ympäristöissä käytetään lisäksi desimaalityypin esitystä, jolloin tietyn mittaisille desimaaliluvuille saadaan tarkka esitys. Desimaalilukuesitys rajoittaa esitettävien lukujen kokoa, mutta tämä esitysmuoto on käytössä ainakin C# -kielessä, jossa sen pituus on 64 bittiä. Lukujen esittämistä tietokoneessa on käsitelty aiemmin diskreettien rakenteiden kurssilla. Looginen tietotyyppi (boolean, logical type) on - ainakin periaatteessa - tietotyypeistä yksinkertaisin, sillä sen tarvitsee sisältää vain kaksi arvoa, tosi ja epätosi. Loogisen tietotyypin esitystapa vaihtelee kielestä toiseen varsin paljon, esimerkiksi C-kielessä mikä tahansa nollasta poikkeava lukuarvo tulkitaan todeksi ja nolla epätodeksi. Yleensä kuitenkin kehittyneemmissä kielissä loogisella tietotyypillä on mahdollisuus saada arvot

true ja false. Myös C-kielessä on standardista C99 lähtien määritelty looginen tietotyyppi (bool), joka voi saada arvot true ja false. Nämä ovat kuitenkin kokonaislukutyyppiä ja niiden arvot ovat 1 ja 0. Merkkitieto esitetään tietokoneen sisäisesti numeerisina koodeina. Vanhastaan yleisin koodauskäytäntö on ollut ASCII (American Standard Code for Information Interchange), jossa yleisimmin esiintyvät merkit esitetään luvuilla 0-127. Tämä tapa on muistinkäytön suhteen tehokas, sillä merkit saadaan mahtuvaan yhteen tavuun, ja vielä ylin bitti jää vapaaksi. Tätä bittiä on käytetty muodostamaan erilaisia laajennuksia standardimerkistöön, jolloin käytettävä laajennus riippuu ympäristöstä. Esimerkiksi se, sisältääkö käytettävä lisämerkistö skandinaavisia kirjaimia, on tällainen seikka. ASCII - merkinnän peruja on se seikka, että useimmissa varhemmissa kielissä merkkityypin muuttuja (esimerkiksi C:ssä char) on ollut yleisimmin, mutta ei välttämättä, yhden tavun eli kahdeksan bitin mittainen. Nykyään tilansäästö ei kuitenkaan ole niin tarpeellista ja lisäksi tarve käyttää yhä laajempaa merkistöä kasvaa koko ajan. Näin ollen uudemmat kielet, kuten Java ja C#, käyttävät merkkitietotyypin esittämiseen 16 - bittistä Unicode-merkistöä. Merkkijonotyyppi (character string type) ei useimmiten ole primitiivinen, koska se määritellään merkkitietotyypin avulla. Se on kuitenkin monessa kielessä valmiiksi määriteltyjen perustietotyyppien joukossa, joten käsitellään se tässä yhteydessä. Periaatteessa olisi mahdollista määritellä merkkijonotyyppi omana primitiivisenä tyyppinään; näin ei useimmissa kielissä tehdä vaan käytetään yksiulotteista, merkkien muodostamaa taulukkoa. Poikkeuksia ovat FORTRAN (version 77 jälkeiset versiot) ja BASIC, joissa merkkijonot ovat primitiivisiä. Merkkijonoille toteutetaan yleensä joitakin perusoperaatioita, joita ovat mm. osajonoon viittaaminen, merkkijonojen katenointi, viittaaminen tietyssä kohdassa esiintyvään merkkiin jne. C-kielessä merkkijono on merkkiin '\0' päättyvä yksiulotteinen merkkitaulukko. Tätä muotoa voidaan käyttää myös C++:ssa, vaikka tässä kielessä onkin suositeltavampaa käyttää standardikirjaston string -luokkaa. Merkkijono-operaatiot hoidetaan C-kielessä käyttämällä kirjastofunktioita, esimerkiksi merkkijonojen vertailu funktioilla strcmp() ja strncmp(), merkkijonojen katenointi funktioilla strcat() ja strncat() sekä tietyn merkin

(strchr()) tai merkkijonon(strstr()) etsiminen merkkijonosta. Pascal -kielessäkin merkkijonot ovat merkkien muodostamia taulukoita (ns. pakattuja taulukoita); Pascalissa merkkijonot ovat staattisia pituudeltaan ja ainoastaan samanpituisten merkkijonojen järjestystä voidaan vertailla relaatio-operaattoreilla. Standardi-Pascalista puuttuvat lisäksi merkkijonojen yhdistely ja pilkkomismahdollisuudet, mitä voidaan pitää melkoisena puutteena. Pascalista onkin useita toteutuksia, joissa merkkijonojen esitysmuotoa ja käsittelyä on parannettu. Adassa (kuten myös FORTRANissa) on vastaavan kaltainen määrämittaisuuden vaatimus merkkijonoille. Ada tarjoaa mahdollisuuden katenoida merkkijonoja käyttämällä & -operaattoria: JONO1 := JONO1 & JONO2; FORTRANissa merkkijonojen katenaatio-operaattori on //. Lisäksi FORTRANin merkkijonojen vertailu sallii eripituiset merkkijonot. Tällöin lyhempää merkkijonoa käsitellään kuin se olisi täytetty tyhjillä merkeillä samanmittaiseksi kuin pitempi jono. Staattisen pituuden merkkijonot ovat aina täynnä merkkejä: jos lyhempi merkkijono sijoitetaan pitempään, loppu täytetään tyhjillä merkeillä. C -tyyppiset merkkijonot ovat pituudeltaan dynaamisia, mutta rajoitettuja: merkkejä voi olla mielivaltainen määrä, mutta sitä rajoittaa taulukolle varattu tila. C -merkkijonon lopun osoittaa aina merkki '\0'. Muuten merkkijonon pituutta ei pidetä yllä. Monissa kielissä merkkijonot voivat olla vaihtelevan pituisia ilman ylärajaa; tällöin sanotaan että kielessä merkkijonot ovat pituudeltaan dynaamisia. Java -kielen toteutuksessa merkkijonot ovat suoraan Object -luokasta periytyvän String -luokan ilmentymiä, joten merkkijonotyyppi on (jossakin mielessä) primitiivinen tyyppi. Oikeastaan merkkijono ei Javassa ole lainkaan kieleen sisäänrakennettu tyyppi, vaan luokka määritellään java.lang -paketissa, joka sisältyy automaattisesti kaikkiin Javalla kirjoitettuihin ohjelmiin. Javan merkkijonot ovat vakioita, ts. merkkijonon merkkejä ei voi muuttaa muuten kuin luomalla uusi merkkijono. Muokattavat merkkijonot ovat Javassa StringBuffer -luokan ilmentymiä. Javassa, kuten C++ -kielessäkin sen string - luokalle, on varsin laaja kokoelma valmiita metodeja merkkijonojen käsittelyyn.

Ordinaalityyppi (ordinal type) on tietotyyppi, jonka arvot voidaan yhdistää positiivisiin kokonaislukuihin luonnollisella tavalla. Useat kielet sallivat käyttäjän määrittelemiä ordinaalityyppejä; nämä eivät täytä primitiivisen tyypin määritelmää, mutta ovat kuitenkin yksinkertaisia tietotyyppejä. Käyttäjän määrittelemiä ordinaalityyppejä ovat luetellut tyypit (enumeration types) ja rajoitetut tyypit (subrange type). Luetellun tyypin muuttujien arvot määritellään luettelemalla symbolisiksi vakioiksi. Lueteltuun tyyppiin liittyy järjestys, joten luetellun tyypin arvoja voidaan vertailla. Esimerkiksi viikonpäivät voitaisiin esittää lueteltuna tyyppinä Pascal -kielellä seuraavasti: TYPE paiva = (su, ma, ti, ke, tor, pe, la); Sama C-kielellä olisi: enum paiva {su, ma, ti, ke, tor, pe, la}; Vertailu tämän tyypin muuttujien välillä tehtäisiin Pascalissa var ps,pt:paiva; begin end. ps:=su; pt:=tor; if pt > ps then begin end; ja C -kielessä: writeln('torstai sunnuntain jälkeen!'); enum paiva x = su, y = tor; if(x < y) printf("sunnuntai ennen torstaita\n"); C++ -kielessä sama koodi toimisi, mutta muuttujien esittelystä voitaisiin myös jättää enum pois. Useimmiten ei ole mahdollista määritellä lueteltua tyyppiä, joka sisältäisi

arvona sellaisen merkkijonovakion, joka esiintyy jo toisessa luetellussa tyypissä. Näin ylläolevaan Pascal -esimerkkikoodiin ei voi lisätä uutta tyyppiä TYPE bile_paiva = (ke, pe, la); eikä C-koodiin enum bile_paiva {ke, pe, la}; Adassa puolestaan tämä on mahdollista; tällaisessa tapauksessa voidaan merkitä, minkä tyypin arvosta on kysymys (ks. [Kur], kappale 4.3). Näin ollen Adassa voitaisiin kirjoittaa type PAIVA is (su, ma, ti, ke, tor, pe, la); type BILE_PAIVA is (ke, pe, la); ja viitata PAIVA -tyypin keskiviikkoon seuraavasti: PS: PAIVA; PS := PAIVA'(ke); Varhaisemmissa kielissä lueteltuja tyyppejä ei ollut ja ohjelmoijat joutuivat yleisesti käyttämään kokonaislukuarvoja merkitsemään tällaisia tyyppejä. Voitaisiin sopia, että 1 tarkoittaa sunnuntaita, 2 maanantaita jne. Tällaisella tavalla on kuitenkin useita varjopuolia. Ensiksikin koodin luettavuus kärsii, koska koodin lukija joutuu koko ajan pitämään mielessään, mitä eri arvot tarkoittavat. Sen sijaan määritellyt tyypit ovat helposti tulkittavissa. Lisäksi koodin luotettavuus kärsii: Kahden eri tyypin muuttujat voivat erehdyksessä sotkeutua toisiinsa ja lisäksi muuttujilla voidaan tehdä aritmeettisia operaatioita, jotka johtavat rajojen ylitykseen. Esimerkiksi yllä lauantai saisi arvon 7, johon voidaan lisätä luku 1, mutta 8 ei tarkoita mitään päivää. Tällaiset virhemahdollisuudet poistuvat mikäli kielessä tarkistetaan lueteltujen tyyppien yhteensopivuus. Näin tehdään esimerkiksi Pascalissa, mutta C-kielessä luetellut tyypit ovat tavallisia kokonaislukuja, joihin voidaan soveltaa kokonaislukujen operaatioita. Siten ylläolevissa esimerkeissä var ps,pt:paiva; begin ps:=10;

on Pascal -koodissa virhe, mutta C -koodissa enum paiva x; x = 10; sallitaan. Silti C-kielessäkin lueteltujen tyyppien käyttö parantaa kodin luettavuutta ja jonkin verran myös luotettavuutta. C++-kielessä suoritetaan myös luetelluille tyypeille voimakkaampi tyypintarkistus, joten yllämainittu C-kielinen koodi on virheellistä C++ - koodia. Java-kielessä ei voinut alun perin määritellä lueteltua tyyppiä, minkä takia ohjelmissa käytettiin usein monia nimettyjä vakioita. Tämä puute korjattiin Javan myöhempiin versioihin: kieleen lisättiin tyyppi enum (käytössä kielen versiosta 1.5 lähtien). Tämän tietotyypin muuttuja voi olla joukko esimääriteltyjä vakioita, esimerkiksi seuraavasti: public enum Paiva { } SUNNUNTAI, MAANANTAI, TIISTAI, KESKIVIIKKO, TORSTAI, PERJANTAI, LAUANTAI Javan enum-tyyppi on monipuolisempi kuin aiemmin mainituissa kielissä. Javassa vakiot ovat olioita, joille voidaan määritellä mitä tahansa luokkaan liitettäviä ominaisuuksia. Javan luetellun tyypin vakioita ei voi vertailla suoraan operaattorilla <, vaan siihen on käytettävä metodia compareto esimerkiksi seuraavasti: if( (Paiva.KESKIVIIKKO).compareTo(Paiva.MAANANTAI) < 0) else System.out.println("Keskiviikko tulee ennen maanantaita."); System.out.println("Maanantai tulee ennen keskiviikkoa."); Rajoitettu tyyppi on jonkin ordinaalityypin peräkkäisten arvojen osajono. Rajoitetun tyypin muuttujat esiintyivät ensimmäistä kertaa Pascal -kielessä ja niitä voidaan käyttää myös Pascaliin pohjautuvassa Adassa. Pascalissa voidaan määritellä minkä tahansa ordinaalityypin, myös käyttäjän määrittelemän, rajoitettu tyyppi antamalla ala- ja yläraja seuraavasti

TYPE allesata = 1..100; pienetkirjaimet = 'a'..'z'; arkipaivat = ma..la; Rajoitetun tyypin käyttäminen lisää luotettavuutta ainakin siinä tapauksessa, että kääntäjä tarkistaa virheelliset operaatiot. Muissa yleisissä kielissä Pascalin ja Adan lisäksi ei rajoitettua tyyppiä käytetä. Seuraavaksi siirrytään käsittelemään ohjelmointikieliin toteutettuja rakenteellisia tietotyyppejä, joita ovat taulukot, tietueet ja unionit. Lisäksi Pascal -kieleen on toteutettu joukkotyyppi. Rakenteellinen tietotyyppi koostuu yhdestä tai useammasta yksinkertaista tai rakenteista tyyppiä olevasta komponentista. Taulukot ovat epäilemättä yleisimmin käytettyjä tietorakenteita ohjelmoinnissa. Taulukko muodostuu kiinteästä määrästä samaa tyyppiä olevia tietoalkioita, jotka sijaitsevat peräkkäin yhtenäisessä muistialueessa. Taulukon käyttö on tästä syystä tehokasta, koska minkä tahansa sen alkion muistiosoite voidaan suoraan laskea, kunhan vain tunnetaan taulukon alkuosoite. Taulukon alkiot voivat olla mitä tahansa tietotyyppiä, joko primitiivistä, kielessä määriteltyä tai ohjelmassa määriteltyä. Taulukon alkioihin viitataan taulukon nimellä ja alkion indeksillä taulukossa. Indeksi on useimmiten positiivinen kokonaisluku, mutta joissakin kielissä se voi mikä tahansa ordinaalityypin arvo. Esimerkiksi C-kielessä, jossa taulukon indeksit alkavat nollasta, koodi int lukutaulu[50]; lukutaulu[10] = 34; esittelee 50 -paikkaisen kokonaislukutaulukon ja sijoittaa sen yhdenteentoista paikkaan luvun 34. Samoin Javassa ja C++:ssa taulukon indeksit alkavat nollasta. Vastaava tehtäisiin Pascal -kielessä seuraavasti

VAR lukutaulu: ARRAY [0..49] OF INTEGER; begin lukutaulu[10] := 34; ja FORTRANissa INTEGER LTAULU(0:49) LTAULU(10) = 34; Näissä kielissä (tosin FORTRANissa vasta versiosta 77 alkaen, sitä varhemmissa versioissa taulukon indeksit alkavat aina luvusta 1) voidaan taulukon ylä- ja alarajat antaa esittelyn yhteydessä. Pascalissa voidaan myös käyttää indeksointiin muitakin ordinaalityypin arvoja ([Kor], kappale 7.2), esimerkiksi TYPE paiva = (su,ma,ti,ke,tor,pe,la); VAR tokataulu: ARRAY [ti..pe] OF INTEGER; begin tokataulu[ke] := 22; Taulukot voidaan jakaa neljään tyyppiin taulukon indeksien rajojen sidonnan ja taulukon muistin allokoinnin tapahtuma-ajan perusteella. Staattisten taulukoiden (static arrays) rajat sidotaan staattisesti, samoin taulukon vaatima muisti varataan staattisesti. Tämän tyypin taulukot ovat suoritusajan suhteen tehokkaimpia käyttää, koska ne eivät vaadi sidontaa eivätkä muistinvarausta ohjelman suorituksen aikana. Kiinteiden pinodynaamisten taulukoiden (fixed stack-dynamic arrays) rajat sidotaan staattisesti, mutta muisti varataan ajonaikaisesti pinomuistista. Tässä tapauksessa muistin käyttö on tehokkaampaa kuin staattisten taulukoiden tapauksessa. Pinodynaamisten taulukoiden (stack-dynamic arrays) rajat sidotaan dynaamisesti ja muisti varataan pinomuistista. Molemmat pysyvät vakioina taulukon elinajan. Tällaisten käyttö lisää joustavuutta, koska taulukon kokoa ei tarvitse tietää etukäteen. Kekodynaamisten taulukoiden (heap-dynamic array ) rajat sidotaan dynaamisesti ja taulukon muisti varataan dynaamisesti kekomuistista. Taulukon rajat ja varattu muisti voivat muuttua sen elinaikana. Näin ollen tämä on kaikkein joustavin tyyppi. FORTRANissa (ennen versiota 90) kaikki muuttujat varataan staattisesti. Tämä koskee myös taulukoita, jotka siten ovat FORTRANissa aina staattisia. C-kielessä taulukko, samoin kuin muuttujakin, voidaan esitellä static-määreellä staattiseksi. Yleensä C:n ja

C++:n funktioissa määritellyt taulukot ovat kiinteitä pinodynaamisia taulukoita, koska niiden rajat sidotaan käännösaikana, mutta niille varataan muisti pinomuistista suorituksen aikana. Pinodynaamiset taulukot olivat ennen harvinaisia yleisimmissä imperatiivisissa ohjelmointikielissä: Ada-kielessä on alusta lähtien voinut määritellä pinodynaamisen taulukon declare-lohkoa. AR_LEN := 25; declare AR: array(1..ar_len) of INTEGER; begin end; Tällöin muuttujaan AR_LEN voidaan syöttää jokin arvo ja taulukko varataan dynaamisesti declare-lohkoon tultaessa. Muisti vapautetaan jälleen poistuttaessa lohkosta. Yleensä rajojen dynaamisen sidonnan yhteydessä on käytetty kekodynaamisia taulukoita. C- ja C++-kielten uusimmissa standardeissa sallitaan kuitenkin pinodynaamiset taulukot. Siten seuraavan kaltainen C-ohjelmakoodi on korrekti: int koko = 0; scanf("%d",&koko); int taulukko[koko]; taulukko[koko-1] = 123; int i; for(i = 0; i < koko; i++){ printf("taulukko[%d] = %d\n",i,taulukko[i]); } Javassa, samoin kuin C#:ssa taulukot ovat olioita ja siten kaikki taulukot ovat kekodynaamisia. Uuden taulukon luominen tapahtuu joko käyttämällä new - operaattoria tai luettelemalla taulukon alkiot; tässäkin tapauksessa luodaan kekodynaaminen taulukko. Esimerkiksi Javassa int[] lukutaulu = new int[50]; C-kielessä kekodynaaminen taulukko varataan, samoin kuin muukin dynaamisesti varattava muisti, käyttämällä malloc-funktiota: int *lukutaulu; lukutaulu = malloc(50*sizeof(int));

lukutaulu[10] = 34; free(lukutaulu); Huomaa, että C-kielessä taulukko muuttujana on osoitin taulukon alkiotyyppiin; siksi lukutaulu esitellään osoittimena, mutta taulukon alkioihin voidaan viitata hakasulkeita käyttämällä. Huomaa myös, että C:ssä (samoin kuin C++ -kielessä) dynaamisesti varattava muisti on ohjelmoijan vapautettava itse. Pascal-kielessä oli alkujaan se hankala ominaisuus, että taulukon indeksirajat olivat osa taulukon tyyppiä: näin ollen oli esimerkiksi mahdotonta kirjoittaa aliohjelmaa, joka olisi parametrinaan ottanut erikokoisia taulukoita. Tämä kierrettiin ottamalla käyttöön ns. taulukkomallit (conformant arrays) ks esimerkiksi [Kor, s. 135]. Tällä mallilla voitiin antaa aliohjelmalle parametrina taulukko, jonka indeksit ovat jonkin ordinaalityypin rajoitettuja tyyppejä. Esimerkiksi PROCEDURE laskesumma(var summa: INTEGER; taulu: ARRAY [alaraja..ylaraja:integer] OF INTEGER); VAR ind:integer; BEGIN END; summa := 0; FOR ind:= alaraja TO ylaraja DO summa := summa + taulu[ind]; jolloin pääohjelmassa voidaan kutsua aliohjelmaa seuraavasti: VAR luvut: ARRAY[1..50] OF INTEGER; kokosumma: INTEGER; laskesumma(kokosumma,luvut); Tätä toteutusta eivät kaikki kääntäjät tue. Yleisemmin tämä voidaan nykyään toteuttaa ns. avoimien taulukoiden avulla (esimerkiksi Delphissä); tässä aliohjelmalle annetaan parametriksi vain taulukon tyyppi ja sen indeksien oletetaan alkavan nollasta. Korkein indeksi saadaan kutsumalla funktiota HIGH(). Tällöin ylläolevan aliohjelman koodi tulisi muotoon

PROCEDURE laskesumma(var summa:integer; taulu: ARRAY OF INTEGER); VAR ind:integer; BEGIN END; summa := 0; FOR ind:= 0 TO HIGH(taulu) DO summa := summa + taulu[ind]; Taulukot voivat olla myös moniulotteisia, ts. tarvitaan useampia indeksejä viittaamaan taulukon alkioihin. Yleensä tämä tarkoittaa sitä että taulukon ensimmäisen indeksin (dimension) alkiot ovat (n-1) -ulotteisia taulukoita jne. Esimerkiksi Pascal -kielen määrittely ARRAY[1..6,3..21,0..3] OF INTEGER on täsmälleen sama kuin ARRAY[1..6] OF ARRAY[3..21] OF ARRAY[0..3] OF INTEGER Yleisesti ohjelmointikielissä ei aseteta ylärajaa taulukon dimensioille; FORTRAN on poikkeus tästä. Alunperin FORTRANissa sai käyttää korkeintaan kolmiulotteisia taulukoita ja FORTRAN 77 salli seitsenulotteiset taulukot. Sittemmin ulottuvuuksien lukumäärää on vielä nostettu. Pascalissa ei ole mahdollista alustaa taulukkoa sen esittelyn yhteydessä. Monissa kielissä tämä on kuitenkin mahdollista. Esimerkiksi FORTRANissa voidaan DATA - lauseella antaa taulukoille (sekä muuttujille ja merkkijonoille) alkuarvoja, esimerkiksi REAL REAALILUVUT(4) DATA REAALILUVUT/1.1,2.5,99.3,12.2234/ alustaa taulukon luetelluilla arvoilla. Sekä C, C++, C# että Java -kielessä on mahdollista esitellä ja alustaa taulukko luettelemalla sen alkiot ilman sen dimensioiden määrittelemistä, esimerkiksi int vektori[] = {2,4,6,8};

luo samalla nelipaikkaisen taulukon. Tämä lisää luonnollisesti ohjelmoijan mukavuutta, mutta heikentää koodin luotettavuutta, koska taulukon koko määräytyy automaattisesti eikä välttämättä huomata onko esimerkiksi jokin alkio jäänyt vahingossa pois tai kirjoitettu kahdesti. Sama esittely voitaisiin luonnollisesti tehdä myös int vektori[4] = {2,4,6,8}; Tämä tapa on sikäli turvallisempi, että kääntäjä huomaa, mikäli alustuslukuja on liikaa. Kaksi- ja useampiulotteinen taulukko voidaan C/C++ -kielessä alustaa luettelemalla ainoastaan niin, että ainoastaan yksi dimensio jätetään vapaaksi: int matriisi[][4] = {{1,2,3,4},{2,3,4,5}}; Javassa sen sijaan esittelyt int matriisi[][] = {{1,2,3,4},{2,3,4,5}}; int[][] matriisi2 = {{3,2,1,4},{5,3,4,2}}; on sallittu. C# -kielessä voidaan myös esitellä ja alustaa useampiulotteiset taulukot täysin luettelemalla, mutta tällöin on kirjoitettava int[,] matriisi = {{1,2,3,4},{2,3,4,5}}; Lisäksi Javassa taataan se, että taulukko on alustettu oletusarvoilla, esimerkiksi numeeriset taulukot arvoilla 0 ja oliotaulukot arvoilla null, vaikka ohjelmoija ei kirjoittaisi alustuskoodia taulukolle. Tätä ei taata esimerkiksi C-kielessä, jossa alustamaton taulukko sisältää satunnaista dataa. Yleensä ohjelmointikieleen sinänsä ei ole sisällytetty juuri taulukko-operaatioita, ts. sellaisia operaatioita, jotka käsittelisivät taulukkoa itsenäisenä yksikkönä. Tavallisesti nämä on toteutettu kirjastofunktioina tai sisällytetty luokkakirjastoihin. FORTRAN 90 sisältää kuitenkin taulukko-operaatioita, esimerkiksi taulukkojen summan (alkioittain). Adassa on mahdollista viitata taulukon osiin indeksirajoilla ja näin käsitellä esimerkiksi matriisin rivejä tai sarakkeita vektoreina. Laajin kokoelma kieleen rakennettuja vektorija matriisioperaatioita on eittämättä APL-kielessä, joka onkin pääasiassa suunniteltu