815338A Ohjelmointikielten periaatteet 2015-2016. Harjoitus 5 Vastaukset Harjoituksen aiheena ovat aliohjelmat ja abstraktit tietotyypit sekä olio-ohjelmointi. Tehtävät tehdään C-, C++- ja Java-kielillä. Ellei koneellasi ole kääntäjää, voit suorittaa ohjelman osoitteessa https://ideone.com/ tai http://www.tutorialspoint.com/codingground.htm. C++-koodia voi suorittaa myös osoitteessa http://cpp.sh/. Tehtävä 1. C -kielessä voidaan saada pinon sisältö näkymään (väärin)käyttämällä printf - funktiota. Kun funktiota kutsutaan ilman parametreja seuraavasti: printf("pinossa on nyt:\n%p\n%p\n%p\n%p\n"); saadaan pinosta 16 ylintä tavua heksamuodossa näkyviin. Kirjoita C-kielinen funktio #include <stdio.h> #include <string.h> void stack_fun(char *strparam) { char mjono[8]; printf("pinossa:\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n" ); strcpy(mjono,strparam); printf("pinossa:\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n" ); ja kutsu tätä jokin merkkijono parametrina. Etsi merkkijonosi merkkien ASCII -koodit taulukosta. Löydätkö merkkejä pinosta? Mitä tapahtuu, jos parametrimerkkijono on pitempi kuin 7 merkkiä? Mitä seuraamuksia tällä voi olla (muista että myös paluuosoitetta säilytetään pinossa)? Vastaus. Harjoituksen yhteyteen linkitetyssä tiedostossa T1_Pinomuisti.c on ohjelma, jonka pääohjelmassa kutsutaan funktiota call_fun(), joka sisältää paikallisen muuttujana funktiolle stack_fun() välitettävän merkkitaulukon. Taulukon koko on määritelty vakiona PUSKURIN_KOKO, jonka arvo on ohjelmassa nyt 8. Taulukkoon kopioidaan pieniä a- kirjaimia, joiden ASCII-koodi heksalukuna on 61. Pinomuistin tulostuksesta huomataan, että koodit näkyvät pinossa, josta on varattu tila funktion paikalliselle merkkitaulukolle. Koska funktio strcpy kopioi merkkejä, kunnes lähdemerkkijonosta löytyy lopettava nollamerkki, lähdemerkkijonon merkkejä kopioidaan pinomuistiin mahdollisesti varatun tilan yli. Kun merkkejä tulee riittävästi, kopioidaan pinossa säilytettävän paluuosoitteen päälle, mikä johtaa ohjelman kaatumiseen. Kun käänsin ohjelman gcc-kääntäjällä, ohjelma kaatui, kun PUSKURIN_KOKO oli 17. Arvo voi vaihdella kääntäjäkohtaisesti ja kääntäjän asetusten mukaan. Harjoitusluokassa ohjelma kaatui jo, kun PUSKURIN_KOKO oli 9, mutta kun funktioon stack_fun() lisättiin 2-paikkainen taulukko paikalliseksi muuttujaksi, arvoa voitiin kasvattaa huomattavasti ennen ohjelman
kaatumista. Tämä johtuu tavasta, jolla kääntäjä varaa pinosta muistia paikallisille muuttujille. Testaa asiaa omalla kääntäjälläsi. Tehtävä 2. C++- kielessä (toisin kuin C:ssä) funktioita voidaan ylikuormittaa. Lisäksi kääntäjä saattaa tehdä funktion parametreille automaattisia tyypinmuunnoksia. Testaa ensin seuraavaa funktiota kutsumalla sitä pääohjelmasta ainakin seuraavan tyyppisillä parametreilla: double, float, int, short int ja char. Ovatko nämä kaikki kutsut sallittuja? #include <iostream> using namespace std; void tulosta(double x){ cout << "Doublen tulostus:"<< endl; cout << "Parametri = " << x << endl; Kirjoita sitten funktiosta samannimiset versiot, joissa parametrin tyyppi on doublen asemasta int ja short int. Voidaanko näin menetellä? Jos voidaan, niin tutki mitä funktiota kutsutaan milläkin parametrilla. Vastaus. Funktiota voidaan kutsua kaikilla tehtävässä mainituilla parametrityypeillä. Tällöin tapahtuu parametrin automaattinen tyypinmuunnos double-tyyppiseksi. Tiedostossa T2_CppOverloading.cpp, joka on linkitetty harjoituksen yhteyteen, on kirjoitettu funktiosta ylikuormitettu versio kaikille mainituille parametrityypeille. Ajettaessa ohjelmaa huomataan, että kutsu ohjautuu tyypinmukaiselle versiolle funktiosta. Kun funktiosta jätetään ainoastaan versio, jonka parametri on char-tyyppinen, huomataan että tässäkin tapauksessa funktiota voidaan kutsua kaikilla ohjelman parametrityypeillä. Nyt kutsussa tapahtuu automaattinen kaventava tyypinmuunnos, jollaisia on yleensä syytä varoa. Tehtävä 3. Kirjoita C++ -kielellä geneerinen aliohjelma, joka laskee mitä tahansa numeerista tietoa sisältävän taulukon alkioiden summan. Testaa aliohjelmaasi int- ja double-tyyppisillä taulukoilla. Ohje: Katso mallia luentojen geneerisestä lajittelufunktiosta. Vastaus. Harjoituksen yhteyteen linkitettyyn tiedostoon T3_CppGenerics.cpp on toteutettu vaadittu funktio. Huomaa template-mekanismin ja tyyppiparametrin käyttö.
Tehtävä 4. Kuten muistetaan, pino on tietorakenne, joka säilöö alkioita LIFOperiaatteella (Last In First Out). Pinoon viimeksi laitettu alkio otetaan siis ensimmäiseksi pois. Pino voidaan toteuttaa taulukkoa käyttämällä C++ -luokkana, jonka runko on seuraava: class Pino { private: int *pino_osoitin; int koko; int huippu; ; Pino (int pinonkoko); ~Pino (); void push(int luku); void pop( ); int top( ); bool empty( ); Pino ::Pino (int pinonkoko) { pino_osoitin = new int [pinonkoko]; koko = pinonkoko; huippu = -1; Pino ::~Pino () { delete [] pino_osoitin; void Pino ::push(int luku) { /* metodin runko */ void Pino ::pop( ) { /* metodin runko */ int Pino ::top( ) { /* metodin runko */ bool Pino ::empty( ) { /* metodin runko */ Tämä runko on harjoituksen yhteyteen linkitetyssä tiedostossa pino.cpp. (Yleensä C++luokat laaditaan niin, että luokan määrittely on omassa otsikkotiedostossaan ja metodien toteutukset omassa lähdekooditiedostossaan, mutta yksinkertaisuuden vuoksi kirjoitetaan nyt kaikki koodi yhteen tiedostoon.) Täydennä metodien rungot ja testaa luokkaasi ohjelmassa. Huomaa: Tässä versiossa pinon on tarkoitus toimia siten, että päällimmäinen alkio saadaan kutsumalla funktiota top, kutsu ei poista alkiota pinosta, vaan se poistetaan kutsumalla metodia pop, joka ei palauta mitään. Metodin empty avulla voidaan kysyä onko pino tyhjä. Vastaus. Vaadittu ohjelma on linkitetyssä tiedostossa T4_pino.cpp. Kommentteja on seuraavan tehtävän vastauksen yhteydessä.
Tehtävä 5. Kirjoita Java-versio tehtävän 1 pinosta. Vertaa toteutuksia. Java-kielisen ohjelman runko on harjoituksen yhteyteen linkitetyssä tiedostossa pino.java. Vastaus. Vaadittu ohjelma on linkitetyssä tiedostossa T5_pino.java. Luokan koodi on varsin paljon C++-koodin kaltainen. Seuraavia muodollisia eroja voidaan havaita: C++-luokan määrittely sisältää tyypillisesti vain jäsenmuuttujat ja metodien esittelyt. Metodien toteutukset kirjoitetaan luokan määrittelystä erilleen. Javassa myös metodien toteutukset kuuluvat luokan määrittelyyn. Javassa jokaiselle jäsenmuuttujalle ja metodille kirjoitetaan näkyvyysmääre, C++:ssa näkyvyysmääre koskee kaikkia jäseniä, kunnes uusi määre kohdataan. C++:ssa luokkaan kirjoitetaan tavallisesti hajotin, niin tässäkin tapauksessa, jossa sitä tarvitaan vapauttamaan kekodynaamisesti varattu taulukko. Javassa luokan vastaavaa, finalize-metodia tarvitsee vain harvoin toteuttaa. C++-version pääohjelmassa pino voidaan luoda paikalliseksi muuttujaksi, kun Java-ohjelmassa olio on aina varattava kekomuistista. Tehtävä 6. C++ -ohjelmassasi on seuraavat luokkien määrittelyt: #include <iostream> using namespace std; class Base { virtual void metodi() = 0; ; class Sub : public Base { void metodi(); ; void Sub::metodi() { cout << "Olen Sub-luokan metodi." << endl; ja seuraavat funktiot: void kutsu(base b){ void kutsu(base &b){ void kutsu(base *b){ Mikä tai mitkä näistä funktioiden määrittelyistä ovat sallittuja? Miksi? Tee ohjelma, johon jätetään sallittujen funktioiden määrittelyt ja niitä kutsutaan pääohjelmasta käyttäen
parametrina pääohjelmassa luotua Sub-luokan oliota. Lisää funktioihin luokan metodin kutsu ja tarkkaile ohjelman toimintaa. Vastaus. Vaadittu ohjelma on linkitetyssä tiedostossa T6_CppAbstrClass.cpp. Luokan Base metodi on puhtaasti virtuaalinen (eli abstrakti metodi). Siten luokastakin tulee abstrakti, eikä siitä voi luoda oliota. Näin ollen funktio void kutsu(base b){ ei ole mahdollinen, koska sen parametri on arvoparametri, jolloin parametri on metodin paikallinen muuttuja, johon todellinen parametri kopioidaan. Luokasta Base ei kuitenkaan voi tehdä oliota, joten koodia ei voi kääntää. Sen sijaan viiteparametri ja osoitinparametri voi viitata jonkin Base-luokan aliluokan olioon. Aliluokka voi olla sellainen, josta on mahdollista luoda olioita, joten kaksi muuta funktiota ovat sallittuja. Esimerkkikoodin pääohjelmassa luodaan Sub-luokan olio paikallisena muuttujana. Huomaa, että viiteparametrisen funktion kutsussa parametri annetaan oliona, kun taas osoitinparametrisen funktion kutsuun annetaan olion muistiosoite. Kummassakin tapauksessa metodikutsu funktiossa sidotaan dynaamisesti Sub-luokan metodiin. Kolmanneksi ohjelmassa luodaan Sub-luokan olio kekodynaamisesti ja asetetaan kantaluokan (eli Base-luokan) osoitin osoittamaan luotiin olioon. Kun funktiota kutsu() kutsutaan mainittu osoitin parametrina, tapahtuu dynaamisen sidonnan ansiosta Subluokan metodin kutsu samoin kuin aiemmissakin tapauksissa.