Dynaamiset tietorakenteet

Samankaltaiset tiedostot
Tietorakenteet ja algoritmit

Tietorakenteet ja algoritmit

Tietorakenteet ja algoritmit

Tietorakenteet ja algoritmit

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

Rakenteiset tietotyypit Moniulotteiset taulukot

Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö. Muistin käyttö C-ohjelmassa

Tietorakenteet ja algoritmit

Tietorakenteet ja algoritmit

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

Algoritmit ja tietorakenteet / HL Copyright Hannu Laine

tietueet eri tyyppisiä tietoja saman muuttujan arvoiksi

TIETORAKENTEET JA ALGORITMIT

Algoritmit 2. Luento 2 To Timo Männikkö

Tietorakenteet ja algoritmit

Muita linkattuja rakenteita

Osoitin ja viittaus C++:ssa

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

Tietueet. Tietueiden määrittely

Osoittimet ja taulukot

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

Dynaaminen muisti Rakenteiset tietotyypit

Algoritmit 2. Luento 2 Ke Timo Männikkö

Lyhyt kertaus osoittimista

Algoritmit ja tietorakenteet / HL 1 Copyright Hannu Laine. Lista. Yleistä

Merkkijono määritellään kuten muutkin taulukot, mutta tilaa on varattava yksi ylimääräinen paikka lopetusmerkille:

A TIETORAKENTEET JA ALGORITMIT

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

Loppukurssin järjestelyt

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

Algoritmit 1. Luento 3 Ti Timo Männikkö

Loppukurssin järjestelyt C:n edistyneet piirteet

1.1 Pino (stack) Koodiluonnos. Graafinen esitys ...

Moduli 4: Moniulotteiset taulukot & Bittioperaatiot

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

Algoritmit 1. Luento 4 Ke Timo Männikkö

3. Binääripuu, Java-toteutus

Osoittimet ja taulukot

Tietorakenteet ja algoritmit

18. Abstraktit tietotyypit 18.1

811312A Tietorakenteet ja algoritmit II Perustietorakenteet

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

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

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

Tietorakenteet ja algoritmit

Luokassa määriteltävät jäsenet ovat pääasiassa tietojäseniä tai aliohjelmajäseniä. Luokan määrittelyyn liittyvät varatut sanat:

Aliohjelmatyypit (2) Jakso 4 Aliohjelmien toteutus

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

Algoritmit 1. Luento 1 Ti Timo Männikkö

11/20: Konepelti auki

Tieto- ja tallennusrakenteet

5.6. C-kielen perusteet, osa 6/8, Taulukko , pva, kuvat jma

Ohjelmoinnin perusteet Y Python

Jakso 4 Aliohjelmien toteutus

Jakso 4 Aliohjelmien toteutus

Kirjoita oma versio funktioista strcpy ja strcat, jotka saavat parametrinaan kaksi merkkiosoitinta.

Sisältö. C-ohjelmointi Luento 5: Osoittimet. Keko (heap) Pino (stack) Muistinhallinta Java vs C. Prosessin rakenne

Algoritmit 2. Luento 3 Ti Timo Männikkö

Ohjelmoinnin perusteet Y Python

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

Algoritmit 2. Luento 3 Ti Timo Männikkö

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

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

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

#include <stdio.h> // io-toiminnot. //#define KM_MAILISSA int main( ){

Ohjelmoinnin perusteet Y Python

Tieto ja sen osoite (3) Jakso 3 Konekielinen ohjelmointi (TTK-91, KOKSI) Osoitinmuuttujat. Tieto ja sen osoite (5)

C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. C-ohjelma. Operaatioiden suoritusjärjestys

Aliohjelmatyypit (2) Jakso 4 Aliohjelmien toteutus

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

Luento 4 (verkkoluento 4) Aliohjelmien toteutus

Taulukot. Jukka Harju, Jukka Juslin

C-ohjelmointi: Osoittimet

Luento 4 (verkkoluento 4) Aliohjelmien toteutus

Pino S on abstrakti tietotyyppi, jolla on ainakin perusmetodit:

Tietorakenteet ja algoritmit - syksy

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Jakso 4 Aliohjelmien toteutus

Algoritmit 1. Luento 6 Ke Timo Männikkö

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

2. Perustietorakenteet

1. Mitä seuraava ohjelma tulostaa? Vastaukseksi riittää yksi rivi joka esittää tulosteen. (6 p)

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

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

Ohjelmoinnin perusteet Y Python

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

Ohjelmoinnin perusteet Y Python

7. Oliot ja viitteet 7.1

Luento 4 Aliohjelmien toteutus

Tehtävän V.1 ratkaisuehdotus Tietorakenteet, syksy 2003

Jakso 3 Konekielinen ohjelmointi (TTK-91, KOKSI)

Sisällys. 15. Lohkot. Lohkot. Lohkot

useampi ns. avain (tai vertailuavain) esim. opiskelijaa kuvaavassa alkiossa vaikkapa opintopistemäärä tai opiskelijanumero

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmoinnin perusteet Y Python

Sekvenssi: kokoelma peräkkäisiä alkioita (lineaarinen

Tietorakenteet, laskuharjoitus 3, ratkaisuja

Ohjelmoinnin perusteet Y Python

Java-kielen perusteet

Tiedostot. Tiedostot. Tiedostot. Tiedostot. Tiedostot. Tiedostot

Transkriptio:

1 Dynaamiset tietorakenteet Muuttujien sijaintipaikat ja muut ominaisuudet Kuten tiedetään, muuttujan määrittely tarkoittaa tilan varausta muuttujalle. Tämä tila voidaan varata eri paikoista. Periaatteessa muuttuja voi sijaista, joko RAM-muistissa tai rekisterissä. Usein kääntäjien optimointitoiminnat laittavat tietyt muuttujat rekisteriin. Jos muuttuja sijaitsee rekisterissä, sen käyttö on tehokasta, koska silloin tietoa ei tarvitse siirtää cpu:n ja muistin välillä. Esimerkiksi taulukon käsittelyä voi tehostaa huomattavasti se, että indeksinä käytetty kokonaisluku (tai osoitin) on rekisterissä. Ohjelmoija voi ehdottaa kääntäjälle, että se laittaisi muuttujan rekisteriin käyttämällä avainsanaa register. Tämä ei kuitenkaan takaa 100 % sesti, että muuttuja käännetyssä koodissa sitten olisi rekisterissä, koska aina ei ole mahdollista varata yhtä rekisteriä pelkästään yhden muuttujan jatkuvaan säilyttämiseen. Seuraava esimerkki valaisee avainsanan register käyttöä: int main(void) { register int i; // i on todennäköisesti rekisterissä Useimmiten kuitenkin muuttujat varataan RAM-muistista. Ohjelman käytössä oleva RAMmuisti on jaettu eri segmentteihin. Muuttuja voi sijaita RAM-muistissa seuraavilla alueilla pinossa (pinosegmentti) datasegmentissä keossa (heap, dynaaminen muisti) Se varataanko muuttuja pinosta vai datasegmentissä määräytyy pääsääntöisesti siitä missä muuttuja määritellään. Lohkojen ulkopuolella määritellyt (globaalit) muuttujat varataan datasegmentistä. Lohkojen sisällä määritellyt (paikalliset, lokaalit) muuttujat varataan pinosta. Muuttujalla on muitakin ominaisuuksia kuin sen sijaintipaikka. Muuttujan ominaisuuksiin kuuluu myös sen näkyvyys ja elinikä. Globaali muuttuja näkyy kaikkiin lohkoihin ja jopa ulos tiedostosta, jolloin siihen voidaan viitata ohjelmakoodista muissa tiedostoissa. Linkkeri hoitaa viittauksen kytkennän tässä tapauksessa oikeaan paikkaan. Lokaali muuttuja näkyy vain siinä lohkossa, jossa se on määritelty. Globaalit muuttujat ovat olemassa koko ohjelman suorituksen ajan. Niiden elinikä on ohjelman suorituksen kannalta pysyvä. Paikalliset muuttujat taas varataan automaattisesti pinosta aina siihen lohkoon tultaessa, jossa ne on määritelty. Vastaavasti niiden tila vapautetaan uusiokäyttöön lohkosta poistuttaessa. Niiden elinikä on siis lyhyempi, koska muuttuja on olemassa vain juuri sinä aikana, lohkon käskyjä suoritetaan. Useimmiten lohkolla tarkoitetaan funktiota, koska yhden funktion suoritettavat käskyt ja sen tarvitsemat paikalliset muuttujat sijaitsevat lohkon (eli sulkeiden { ) sisällä. Paikallisia muuttujia sanotaankin usein automaattimuuttujiksi, koska niiden tila varataan ja vapautetaan automaattisesti. Kuten sanottu, muuttujan ominaisuudet (sijaintipaikka, näkyvyys ja elinikä) määräytyvät sen mukaan missä muuttuja on määritelty. Näitä ominaisuuksia voidaan myös muuttaa ns. tallennusluokkamääreillä. Esimerkki 1. Tallennuslukkomääre static paikallisen muuttujan edessä muuttaa eliniän pysyväksi (ja sijaintipaikan datasegmenttiin) mutta pitää näkyvyyden ennallaan (eli rajoitettuna vain ko lohkoon).

2 Esimerkki 2. Tallennusluokkamääre static globaalin muuttujan edessä rajoittaa näkyvyyden siihen tiedostoon, jossa määrittely on tehty. 0 Tähän mennessä on puhuttu lähinnä pinossa ja datasegmentissä olevista muuttujista. Nyt siirrytään keosta (eli dynaamisesta muistista) varattaviin muuttujiin. Staattisen muistin käytön haittapuolet Ei-dynaamista muistia kutsutaan tässä esityksessä myös staattiseksi muistiksi, koska muuttujien lukumäärä, tyypit ja koot määräytyvät ohjelman käännösvaiheessa. Muuttujan tyyppi siis määrää, paljonko muistia varataan ja minkälaista tulkintaa muistialueeseen käytetään. On kuitenkin huomattava, että varsinainen tilanvaraus ja vapautus paikallisille muuttujille (ja parametreille) tapahtuu ohjelman suoritusvaiheessa ohjelmalohkon suorituksen alkaessa ja päättyessä. Staattisessa muistinvarauksessa tilanvaraus tehdään siis ohjelman omalta muistialueelta, pinosta tai datasegmentistä. Ilman dynaamisen muistin käyttöä tiedoille varattavan tilan määrä tulee määritetyksi jo ohjelmaa kirjoitettaessa. Esimerkiksi joissakin tekemissämme pinon toteutuksissa sen taulukon koko, jossa pinottavia tietoja säilytetään, määrätään kirjoittamalla taulukon määrittelyssä hakasulkeisiin luku (tai nimetty vakio). Taulukon koko tulee tällöin määrätyksi jo ohjelman käännöksen aikana. Jos alkioiden määrän yläraja on arvioitu ohjelmaa kirjoitettaessa väärin, voi käydä niin, että tila loppuu kesken ohjelman ajon aikana. Tästä saattaa olla seurauksena, että koko ohjelman ajo epäonnistuu ja siihen mennessä ohjelmalle syötettyä informaatiota ei voida hyödyntää. Tilannetta voidaan yrittää välttää varaamalla mahdollisimman iso alue muistista tiedon tallennusta varten. Tällä on taas haittana, että muistia on hukkakäytössä niissä tapauksissa, kun tietoa on vähemmän. Toinen ei-dynaamisen muistin käytön haitta on se, että varattu muisti on käytössä koko lohkon suoritusajan, vaikka tiettyä muistialuetta tarvittaisiin vain ohjelmalohkon (esimerkiksi pääohjelman) suorituksen alkuvaiheessa. Tämä haitta voidaan poistaa jakamalla ohjelma useampiin lohkoihin ja varaamalla tietyn asian käsittelyssä tarvittava muistialue tällaisen lohkon alussa. Kun lohkon suoritus päättyy, vapautetaan lohkon muuttujien tila automaattisesti. On olemassa vielä kolmaskin haittatekijä staattisten muuttujien käytössä. Datasegmentti ja pino on pieni verrattuna kekoon. Ei ole järkevää varata isoja tietorakenteita pinosta, koska pino on suhteellisen pieni alue. Pinon kokoa ei myöskään voida muuttaa ajon aikana. Lisäksi ohjelma ei voi tehdä mitään pinon ylivuototapauksessa. Sen sijaan keon koko ei ole kiinteä. Käyttöjärjestelmältä voidaan aina pyytää lisää dynaamista muistia. Lisäksi vielä ohjelma voi testata muistinvarauksen onnistumisen. Jos varaus epäonnistuu ohjelma voi tehdä toimenpiteitä tilanteen korjaamiseksi (esimerkiksi yrittää vapauttaa, jotakin muistia).. Nyt siirrytään käsittelemään varsinaista dynaamista muistinvarausta. Dynaaminen muistinvaraus Dynaamisessa muistinvarauksessa muistia varataan ohjelman suorituksen aikana erityisellä muistinvarausfunktiolla (eikä automaattisesti lohkon suorituksen alkaessa). Tällöin voidaan myös päättää, paljonko muistia varataan ja minkä tyyppisenä sitä tulkitaan. Samoin ohjelmassa voidaan vapauttaa varattu muisti, kun sitä ei enää tarvita. Kaikki muisti on periaatteessa käyttöjärjestelmän hallinnassa. Muistia pyydetään siis käyttöjärjestelmältä. Sovellusohjelmien ei kuitenkaan tarvitse suoraan käyttää käyttöjärjestelmän palvelua vaan sitä käytetään ns. kirjastofunktioiden avulla. C-kielessä tärkeimmät funktiot ovat malloc ja

3 free. Yllämainitut muistinvarausfunktiot ovat siis liityntä käyttöjärjestelmän tarjoamaan muistinvarausjärjestelmään. Useimmiten malloc funktio pyytää käyttöjärjestelmältä muistia isompina paloina ja jakaa sitä sitten eteenpäin kun malloc funktiota kutsutaan uudelleen. Muistin varaukseen on olemassa muitakin kirjasto funktioita, jotka helpottavat muistin käyttöä (calloc ja realloc) Kaikki vapaa muisti, joka ei kuulu käynnissä oleville ohjelmille tai käyttöjärjestelmälle, on käyttöjärjestelmän hallinnassa. Tätä muistia voidaan pyytää sovelluksen käyttöön ajon aikana. Sitä osaa tästä muistista, joka on tietyn sovelluksen käytössä, kutsutaan tämän sovelluksen keoksi tai dynaamiseksi muistialueeksi (dynamic memory area tai heap). Ohjelma voi pyytää käyttöjärjestelmältä muistia haluamansa määrän. Käyttöön saadulla muistialueella ei voi olla nimeä samalla tavalla kuin staattisella muuttujalla. Pääsy dynaamiselta alueelta varattuun muuttujaan tapahtuu osoittimen kautta. Kun keosta varataan muistia, saadaan vain osoite varattuun muistialueeseen. Varaaja ottaa osoitteen yleensä johonkin osoitinmuuttujaan. Varatun muistialueen tyypitys tapahtuu tyypittämällä osoitin sopivalla tavalla. Muistinvaraukseen käytettävän funktion malloc prototyyppi on muotoa: void * malloc(size_t koko); Funktion palautusarvon tyyppi on void* siksi, että malloc ei tiedä, millä tavalla käyttäjä haluaa tyypittää varatun muistialueen. Varattu muisti vapautetaan funktiolla free, jonka prototyyppi on seuraava: void free(void *mista); Esimerkki 1. Käyttäjä haluaa varata tilan dynaamiselta muistialueelta yhdelle float-luvulle, asettaa sen arvoksi 11.1 ja lopuksi tulostaa luvun. #include <stdlib.h> int main (void) { float *p; p = (float*) malloc(sizeof(float)); *p = 11.1; printf( %f, *p); free(p); return 0; Esimerkki 2. Käyttäjä haluaa varata tilan dynaamiselta muistialueelta yhdelle pisteelle ja lukea ja tulostaa pisteen arvon (käytettävissä on aikaisemmin tietueena määritelty tietotyyppi Tpiste ja funktiot void lue_piste(tpiste *pp) ja void tulosta_piste(const Tpiste *pp). #include <stdlib.h> int main (void) { Tpiste *ppiste; ppiste = (Tpiste*) malloc(sizeof(tpiste)); lue_piste(ppiste); tulosta_piste(ppiste); free(ppiste); return 0; C-kielessä taulukon nimi tarkoittaa taulukon ensimmäisen alkion osoitetta. Vastaavasti mitä tahansa osoitinta voidaan indeksoida, ja sen käyttäytyminen on samanlaista kuin taulukolla,

4 jonka alkion tyyppi on indeksoitavan osoittimen kohdetyyppi. Tämän periaatteen takia taulukon varaus dynaamiselta muistialueelta on helppoa. Varatulle alueelle osoittava osoitin vain tyypitetään siten, että sen kohdetyyppinä on taulukon alkion tyyppi. Esimerkki 3. Esimerkki varaa tilan liukulukujen taulukolle, lukee arvot taulukkoon, käyttää taulukkoa jollakin tavalla ja vapauttaa lopuksi varatun tilan. Lukujen määrä kysytään käyttäjältä ja tilaa varataan juuri oikealle määrälle alkioita. #include <stdlib.h> int main (void) { float *t int n; scanf( %d, &n); t= (float*) malloc(n * sizeof(float)); for (i = 0; i < n ; i++) scanf( %f, &t[i]);... free(t); return 0; Huomautus. C++:ssa dynaamiseen muistin varaukseen käytetään operaattoria new ja vapauttamiseen operaattoria delete. Näiden tärkeimpinä etuina funktioihin malloc ja free verrattuna on, että ne kutsuvat konstruktorifunktiota ja destruktorifunktiota varatulle alueelle. Dynaamisen muistin hyödyntämistapoja Dynaamista muistia voidaan hyödyntää monilla eri tavoilla mm. abstraktien tietotyyppien yhteydessä. Seuraavassa tarkastellaan erilaisia dynaamisen muistin hyödyntämistapoja pinon tapauksessa. Olemme itse asiassa tutustuneet jo yhteen näistä siinä yhteydessä kun toteutimme automaattisesti kasvavan pinon. Nyt myös tämä tapaus tulee uudelleen esille kertauksena. Esimerkkien tekniikat ovat sellaisenaan hyödynnettävissä abstrakteihin tietotyyppeihin yleensä ja ainakin kaikkiin säiliötyyppeihin. Pinon varaus dynaamiselta muistialueelta Edellä esitetyn periaatteen mukaisesti mikä tahansa tietotyyppi voidaan varata dynaamiselta muistialueelta, siis myös minkä tahansa abstraktin tietorakenteen tietotyyppi. Näin jo tehtiinkin edellä olevassa esimerkissä 2, jossa varattiin tila pisteelle. Samalla tavalla voidaan varata tila vaikkapa pinolle dynaamiselta muistialueelta. Olipa pinon toteutustapa mikä tahansa esitetyistä (pino taulukkona indeksointia käyttäen tai pino taulukkona osoitinta käyttäen), se voidaan varata dynaamiselta muistialueelta. Seuraava lyhyt esimerkkiohjelma lukee kolme merkkiä ja tulostaa ne päinvastaisessa järjestyksessä. Kääntämiseen käytetään dynaamiselta muistialueelta tehtävän suorituksen ajaksi varattavaa pinoa. Periaatteessa siis nyt pinon toteutus voisi olla mikä tahansa, mutta oletetaan tässä, että tietotyyppi Tchar_stack on määritelty seuraavasti: #define MAX 100 typedef char Titem; typedef struct { Titem array[max]; int top; ; // Pinon funktioiden prototyypit #include <charstack.h> int main (void) { Tchar_stack *stack; int i;

5 char mrk; stack = (Tchar_stack*) malloc(sizeof(tchar_stack)); initialize_stack(stack); for (i = 0, i < 3 ; i++) { scanf( %c, &mrk); push(stack, mrk); while(!stack_empty(stack)) { pop(stack, &mrk); printf( %c, mrk); free (stack); return 0; Tietueen sisällä olevan taulukon koko on siis nyt vakio ja sitä ei voida muuttaa ajon aikana. Tämän ratkaisun etuna aikaisempiin ratkaisuihin (staattisesti varattuihin) nähden on kuitenkin se, että pinolle varattu tila voidaan varata juuri silloin, kun sitä ruvetaan tarvitsemaan, ja tila voidaan vapauttaa heti, kun pinoa ei enää tarvita. Toinen ratkaiseva etu on se, että nyt tämä iso tietorakenne ei kuluta rajallista pinoresurssia, vaan varataan keosta, jossa tilaa on enemmän. Nyt tilanvarauksen onnistuminen voitaisiin lisäksi testata. Eräs tässä huomattava asia on, että päätös dynaamisen muistin käytöstä on nyt täysin jätetty sovellusohjelmoijan vastuulle. Sitä ei ole suunniteltu pinon luonnolliseksi ominaisuudeksi. Seuraavaksi tutustutaan ratkaisuun, jossa pinon suunnittelija suunnittelee pinon jo alun perin vain keossa käytettäväksi. Pinon käyttö kahvan (handle) avulla Nyt siis pinon suunnittelija ja toteuttaja ( komponenttiohjelmoija ) on suunnitellut pinon sellaiseksi, että 1. Pinoa on pakko käyttää dynaamisella muistialueella. 2. Pinoa on helppo käyttää dynaamisella alueella (ei tarvita mitään tietoa esim. osoittimista). 3. Pinon käyttäjä ei välttämättä edes tiedä käyttävänsä dynaamista muistia. Nyt on kysymyksessä ns. kahvakäsite (handle). Samalla on siirrytty arvosemantiikasta viitesemantiikkaan. Jotta kohdan 1 vaatimus yllä voitaisiin täyttää, ei saa olla olemassa tyyppiä (tyyppitunnusta), joka edustaa pinoa kuvaavaa tietuetta. Pinoa kuitenkin edustaa tässä siis osoitin dynaamiselle muistialueelle. Siksi tietotyyppi Tstack määritellään osoittimeksi sellaiseen tietueeseen, joka sisältää pinon ylläpitämisessä tarvittavat tiedot. Tietotyyppi Tstack määritellään siksi muodossa: #define MAX 100 typdef??? Titem; typedef struct { Titem array[max]; int top; *Tstack; Kun nyt sovellusohjelmoija tarvitsee pinoa, hän määrittelee pinomuuttujan kuten ennekin muodossa Tstack stack;

6 Nyt tulee kuitenkin varatuksi muistista pelkästään osoitin, eikä vielä pinon ylläpitoon tarvittavaa tietuetta tietokenttineen. Ennen kuin pinoa voidaan ruveta käyttämään, on tila varattava tälle tietueelle. Yllä olevan kohdan 2 mukaan tämä on tehtävä helpoksi sovellusohjelmoijalle. Tilan varaus voitaisiin tehdä jo ennestään tutussa pinon operaatiofunktiossa initialize. Tämän funktion prototyyppikin voitaisiin säilyttää ennallaan. Silloin prototyyppi olisi ja sitä kutsuttaisiin muodossa void initialize_stack(tstack *stack); initialize_stack(&stack); Kahva käsitteen yhteydessä on vakiintunut tapa, että käytetään tähän alkutoimenpiteiden tekemiseen funktiota, joissa nimessä esiintyy Create. Tämä johtuu siitä, että tämän funktion on itse asiassa luotava se varsinainen pino dynaamiselle alueelle. Lisäksi tämä funktio tehdään yleensä siten, että se palauttaa osoittimen varattuun alueeseen. Näin sen käyttö saadaan vielä helpommaksi, koska siinä ei tarvita hankalia operaattoreita kuten * tai &. Nyt Tämän funktion prototyyppi on Tstack CreateStack(void); ja sitä kutsuttaisiin muodossa stack = CreateStack(); Vastaavalla tavalla pinon käyttäjälle tehdään helppo funktio vapauttaa tila dynaamisesta muistista. Tämän funktion nimessä esiintyy usein Destroy kuvaamaan sitä että tila hävitetään dynaamisesta muistista. Pinon tapauksessa voisimme tehdä funktion, jonka prototyyppi on void DestroyStack(Tstack stack); Sitä käytettäisiin yllämainitun pinon tapauksessa muodossa DestroyStack(stack); Huomautus 1. Myös pinon muiden operaatioiden (push ja pop) käyttö yksinkertaistuu, koska niissäkään ei enää tarvita eksplisiittisesti osoittimia, koska käyttäjän pino on jo osoitin. Huomautus 2. Funktio DestroyStack tehdään käytön yksinkertaisuuden takia yleensä yllä mainitussa muodossa. Tällöin funktio ei voi asettaa pinon osoittimeksi NULL-pointeria ilmaisemaan, että pinoon ei tällä hetkellä ole varattu varsinaista aluetta. Jos pinoa käytetään Destroy-funktion kutsun jälkeen vielä uudelleen, seuraavat funktiot eivät voi testata, edustaako handle oikeaa tilaa vai onko se vapautettu. Esimerkiksi funktiossa CopyStack (DeepCopyStack tai CloneStack) ei silloin voitaisi testata onko tila jo olemassa vai ei. Ohjelmoijan joka käyttää pinoa tulee olla varovainen. Hänen tulee muistaa kutsua CreateStack-funktiota jokaiselle pinomuuttujalle ja asettaa siihen NULL-osoitin Destroyfunktiokutsun jälkeen. Huomautus 3. Tällainen kahva ajattelu on vanha ja laajalle levinnyt ajatusmalli. Esimerkiksi C-kielen tiedostoja käytetään kahvan kautta, jota nyt kutsutaan tiedostovuoksi tai tiedostoosoittimeksi. Tiedoston tilaa kuvaava tietue luodaan funktiolla fopen, joka vastaa Create funktiota. Sen jälkeen kaikille tiedostofunktioille välitetään vain tämä osoitin. Toisena esimerkkinä voisi olla Windows käyttöjärjestelmä API rajapinta, jossa erilaisia objekteja

7 käytetään kahvan (handlen) avulla. Esimerkiksi näytöllä olevaa ikkunaa käytetään ikkunakahvan avulla. Molemmissa ylläkuvatuissa ratkaisuissa on edelleen puute, että pinoon talletettavien alkioiden maksimimäärä tulee määrätyksi käännösvaiheessa. Vaikka koko pinotietue onkin varattu dynaamisesta muistista, taulukon koko on määrätty kiinteästi tietotyypin Tstack sisällä. Taulukon koko tulee siis määrätyksi jo käännösaikana. Tämä puute voidaan korjata siten, että varataan tietoalkioiden varastointipaikkana toimiva taulukko dynaamisesta muistista. Seuraavassa käsitellään tätä ratkaisua. Varataan pinon taulukko dynaamiselta alueelta Käännösaikana määräytyvän pinoon mahtuvien alkioiden maksimimäärän aiheuttama puute voidaan poistaa muuttamalla tietotyypin Tstack määrittelyä siten, että sen sisälle ei varata taulukkoa, vaan osoitin dynaamiselle alueelle, josta varataan tila taulukolle vasta ohjelman ajon aikana. Tässä tapauksessa taulukon kokoa voidaan jopa muuttaa (kasvattaa tai pienentää ajon aikana). Tällainen ratkaisu on esitetty jo aikaisemmin pinon käsittelyn yhteydessä edellisessä monisteessa. Handle ja dynaaminen taulukko yhdessä Yllämainitut ratkaisut ovat tietyllä tavalla perustyyppiratkaisuja. Niistä voidaan muodostaa myös kombinaatioita. Esimerkiksi edellä kuvattu dynaaminen taulukkoratkaisu voidaan yhdistää kahvaratkaisuun. Tällöin kahva-ajattelun tuottamat edut tässä tapauksessa ovat pienemmät, koska tietue on paljon pienempi, kun siellä ei ole mukana itse taulukkoa. Tässä tapauksessa funktiossa CreateStack olisi kaksi malloc funktion kutsua. Ensimmäisellä malloc-funktion kutsulla varattaisiin itse tietue ja toisella taulukko, jonne tietoalkiot tallennettaisiin. Vastaavasti DestroyStack-funktiossa pitäisi vapauttaa taulukon tila ensin ja vasta sitten itse tietueen tila. Segmentoitu taulukko Staattisen taulukon tapauksessa ongelmana oli taulukon kiinteä koko. Dynaamisen taulukon tapauksessa ongelmana oli tietojen siirto taulukon koon muuttamisen yhteydessä. Tätä tietojen siirron aiheuttamaa ongelmaa voidaan lieventää ratkaisulla, jossa tietueeseen laitetaan osoittimien taulukko. Yksi osoitin tässä taulukossa edustaa aina yhtä alkioiden taulukkoa dynaamisessa muistissa. Kun alkioiden taulukko tulee täyteen, varataan uusi ja viedään sen osoitin seuraavaan vapaana olevaan osoitintaulukon alkioon. Näin vältytään jo tallennettujen tietojen siirrolta muistissa. Seuraavana vaiheena tästä olisi tehdä myös tästä osoittimien taulukosta dynaaminen. Silloin kasvatuksen yhteydessä pitää siirtää vain osoittimia eikä itse alkioita. Monenlaisia muitakin ratkaisuja voidaan kehittää sovelluksen vaatimusten mukaan. Missään edellisistä ratkaisuista ei olla päästy kokonaan eroon tietojen siirrosta paikasta toiseen tilaa kasvatettaessa. Jos tyhjää tilaa löytyy riittävästi entisen taulukon perästä, saattaa käydä niin, että kopiointia ei tarvita. Tässä tarkoitetaan tilannetta, kun käytetään muistinvarausfunktiota realloc. Tämä tiedonsiirron aiheuttama ongelma voidaan poistaa käyttämällä ns. dynaamisesti linkattua rakennetta. Seuraavassa käsitellään tätä tekniikkaa. Sillä voidaan toteuttaa ratkaisu, jossa muistista on aina varattu tila vain niille alkioille, jotka jo on syötetty. Uudelle alkiolle voidaan varata tila silloin, kun sitä tarvitaan. Tällöin ylärajaa alkioiden määrälle ei ole (ylärajana on vapaan muistitilan loppuminen koneesta). Myöskään tiedon siirtoa paikasta toiseen ei koskaan tarvita.

8 Vaikka edelliset esimerkit käsittelivät pinoa, tämän uuden menetelmän käsittely aloitetaan yleisemmästä säiliöstä eli listasta. Taulukon puutteet yleisemmässä tapauksessa Ensin käsitellään taulukon puutteet yleisemmässä tapauksessa eli listan tapauksessa. tarkastellaan erikseen järjestämättömän listan ja järjestetyn listan tapauksia. Tämä tehdään tunnilla. Dynaamisesti varattu ja linkattu lista Jos lähdetään siitä, että alkiolle varataan tila dynaamiselta muistialueelta vasta silloin, kun sitä tarvitaan, ongelmaksi muodostuu se, missä säilytetään osoittimia varattuihin muistipaikkoihin. Osoittimet tarvitaan, jotta kaikkiin alkioihin olisi pääsy (dynaamiselle muistialueelle voidaan viitata vain osoittimen kautta). Jos ratkaisumalliksi otetaan osoittimien taulukko, sen koko on tiedettävä etukäteen. Tällöin tilaa alkioille ei enää kannata varata sen jälkeen, kun osoitetaulukko on täynnä, koska näihin alkioihin ei olisi enää pääsytietä. Ratkaisuna käytetään ns. linkattua rakennetta siten, että jokainen dynaamiselta alueelta varattu alkio sisältää varsinaisen datan lisäksi myös linkin (osoitteen) seuraavaan alkioon. Rakenne näyttää silloin seuraavalta: Staattinen muistialue Lista 1 Dynaaminen muistialue 7 3 Yhdellä kertaa yhdelle alkiolle ja osoittimelle varattavaa tilaa sanotaan solmuksi. Solmun tietomäärittelyssä on ongelmana, että tietotyypin sisällä on osoitinkenttä, jonka kohdetyyppi on tietotyyppi, jota juuri ollaan määrittelemässä. Tällainen ilmaus siis viittaa itse itseensä (self reference). Kääntäjä hyväksyy tässä yhteydessä itseensä viittauksen, jos käytetään ns. etukäteismäärittelyä (forward declaration). Solmun tietomäärittelyt ja muut tietomäärittelyt, joita tarvitaan dynaamisesti varatun linkatun listan muodostamiseen, ovat alla. Tapa 1. typedef struct node *Tpointer; typedef struct node { Titem item; Tpointer next; Tnode; Tapa 2. typedef struct node { Titem item; struct node *next; Tnode; typedef struct node *Tpointer; //tai typedef Tnode *Tpointer Tapa 3. (C++) struct Tnode { Titem item; Tnode *next; ; typedef Tnode *Tpointer; Dynaamisesti varatun ja linkatun listan toteutus käytännössä esitetään sellaisen lineaarisen listan avulla, jossa järjestys on syöttöjärjestys. Esimerkissä toteutetaan merkkilista, jossa

9 syötettävä merkki lisätään aina listan loppuun. Esimerkissä toteutetaan myös tällaisen listan alkioiden tulostus alusta loppuun. Muita lineaarisen listan operaatioita ei tässä yhteydessä esitetä. Esimerkki toteutetaan askel askeleelta. Liitteenä olevien ohjelmien kommenteissa on askeleet nimetty Dynamic linked linear list. Case 1, case 2 ja jne. Kahdessa ensimmäisessä esimerkissä (Case 1 ja Case 2) tehdään neljän merkin lista eri tavoilla siten, että kullekin lisäykselle kirjoitetaan omat ohjelmarivinsä. Tapauksesta Case 2 huomataan toistuvat asiat alkioiden lisäämisessä, ja niille kirjoitetaan funktio insert_to_list_end (Tapaus Case 3). Alkion lisääminen listan loppuun on huomattavasti helpompaa, jos ohjelma pitää kirjaa kulloinkin viimeisen alkion osoitteesta (tämä ei kuitenkaan ole välttämätön edellytys listan loppuun lisäämiselle). Tämän takia funktiolle insert_to_list_end välitetään parametrina listan alkuosoite ja loppuosoite. Tällainen ratkaisu ei täytä abstraktin tietotyypin vaatimusta, että tietoa (tässä siis listaa) tulee voida käsitellä yhtenä kokonaisuutena (yhdellä tietotyypillä). Asia korjataan määrittelemällä tietotyyppi Tlist, joka sisältää alkuosoitteen ja loppuosoitteen. Näin on päästy ratkaisuun Case 4 liitteessä. Listalle ei ole toteutettu kaikkia operaatiofunktioita. Lukija voi miettiä, millä tavalla muut listan operaatiofunktiot voitaisiin toteuttaa. Pino ja jono olivat listan erikoistapauksia. Seuraavassa siirrytään käsittelemään pinon ja jonon toteutusta dynaamisesti varatun ja linkatun rakenteen avulla. Pino dynaamisesti varattuna ja linkattuna rakenteena Pinossa alkio voidaan lisätä vain huipulle (listan loppuun). Jos lähtökohdaksi otettaisiin edellisessä kohdassa toteutettu linkattu lista, niin uuden alkion vienti listan loppuun kävisi yhtä kätevästi kuin edellä, koska listan viimeisen alkion osoite on tiedossa. Alkion otto olisi kuitenkin hankalaa. Otettava alkio löytyisi kyllä kätevästi viimeistä alkiota osoittavan osoittimen avulla. Ongelmana on, miten selvittää uuden, nyt huipuksi tulevan alkion osoite. Selvittäminen voitaisiin tehdä käymällä läpi kaikki alkiot alusta (pinon pohjalta) lähtien. Selkeämpi tapa on käyttää vain yhtä osoitinta, joka osoittaa aina huippualkiota. Uuden alkion osoite viedään aina huipun osoittimeen. Sitä ennen entinen huipun osoitin viedään uuden alkion next-kenttään. Alkio otetaan pois pinosta aina huippuosoittimen osoittamasta paikasta. Uudeksi huippuosoittimeksi asetetaan osoitin, joka saadaan otetun solmun next-kentästä. Tämä ratkaisu tarkoittaa sitä, että linkkien merkitys on käännetty päinvastaiseksi. Aikaisemmin esitetyssä listassa linkit esittivät alkioiden syöttöjärjestystä siten, että ensin lisätystä solmusta oli linkki toiseksi lisättyyn ja jne. Linkkiketjun viimeisenä on viimeksi lisätty alkio. Jotta pinon operaatiot saadaan tehokkaiksi, linkkien merkitys on käännetty päinvastaiseksi. Huippuosoitin osoittaa viimeksi lisättyyn solmuun. Entinen huippuosoittimen arvo on tallennettu nyt lisättävän solmun next-kenttään. Linkkiketjun lopusta löytyy solmu joka on lisätty ensimmäisenä. Liitteenä on ohjelmaratkaisu C-kielisenä ja myös oliototeutuksena C++-kielellä. Tunnilla käsitellään pinosta otto ja pinoon vienti tarkemmin. Jono dynaamisesti varattuna ja linkattuna rakenteena Jonon toteutuksen perustaksi käy hyvin aikaisemmin toteutettu listan toteutus, jossa pidettiin yllä alku ja loppuosoitetta. Alkion lisäys jonoon tehdään aina loppuun. Lisäysoperaatio on siis samanlainen, kuin toteutettu funktio insert_to_list_end. Jonosta oton periaate taas on tekniikaltaan sama kuin yllä olevan pinon toteutuksessa. Liitteenä on ohjelmaratkaisu. Tunnilla käsitellään jonosta otto ja jonoon vienti tarkemmin.