{!!! {rd="194920" rp="4ze4dPPT6NHE" visible="%%'ohj2s19viesti1'|belongs%%"} !!!}
{!!!#- {.huomautus .hidden-print visible="%%'ohj1tenttivika'|belongs%%" nocache="true"} **HUOMIO!** [Olen laittanut sinulle sähköpostia otsikolla: Sulta puuttuu kuittaus että tenttiaika käy! Käytkö kuittaamassa äkkiä!]{.red}. Käythän kuittaamassa! !!!}
{!!!#- {.huomautus .hidden-print visible="%%'ohj1s19email'|belongs%%" nocache="true"} **HUOMIO!** [Olen laittanut sinulle sähköpostia otsikolla: \ "***Saatko kurssin sähköpostia***..."]{.red}, jos sait tuon, niin kaikki OK. Jos et saanut, niin tarkista/korjaa Oma-palvelussa <https://account.jyu.fi/> postiosoiteesi. Tarkista positosoitteesi myös Korpissa `Viestintä/Postilistat`-sivulla. Erityisesti Ohj1-listat! Ja muista lukea ne postit joita et ole saanut Korpin [arkistosta](https://korppi.jyu.fi/kotka/servlet/list-archive/ohj1s18k/ind.html). Jos haluat tämän ilmoituksen pois TIM-sivuiltasi, laita Vesalle postia, jossa on käyttäjätunnuksesi ja tieto siitä, että olet vaihtanut osoitteet toimiviksi sekä lukenut arkiston. Kiitoksia. Vesa !!!}
{!!!- [[klikkaa tätä ja kuittaa oletko saanut kurssin postia](https://tim.jyu.fi/view/kurssit/tie/ohj1/2020s/postitulee)]{.small} !!!}
#

Ohjelmointi 1

Esipuhe

Arvaa mikä olisi oikea järjestys, jotta alla oleva olisi toimiva ohjelma (vinkki: koita päätellä sulkujen parillisuudesta ja sisennyksistä):

#

Mitä on ajaminen? Ohjelma kirjoitetaan ensin tekstiksi (kuten vierellä). Sitten tämä teksti pitää tallentaa sekä kääntää koneen ymmärtämään muotoon ja tämä on se varsinainen ohjelma.
Kun on saatu ohjelma käännettyä, niin sen jälkeen ohjelma ajetaan ja se tekee sille määritellyn tehtävän. Vieressä Aja-painike tekee tallentamisen, kääntämisen ja sitten ajamisen mikäli käännös voidaan tehdä, eli teksti noudattaa valitun kielen kielioppia. -vl

17 Sep 15 (edited 07 Oct 16)

Tämä oppimateriaali on niin kutsuttu luentomoniste kurssille Ohjelmointi 1. Luentomoniste tarkoittaa sellaista kirjallista materiaalia, jossa esitetään asiat suurin piirtein samassa järjestyksessä ja samassa valossa kuin ne esitetään luennolla. Jotta moniste ei paisuisi kohtuuttomasti, ei asioita käsitellä missään nimessä kaikenkattavasti. Siksi opiskelun tueksi tarvitaan jokin hyvä aihetta käsittelevä kirja, sekä rutkasti ennakkoluulotonta asennetta ottaa asioista itse selvää. Tuorein tieto löytyy tietenkin internetistä - kunhan muistaa lähdekritiikin. On myös huomattava, että useimmat saatavilla olevat kirjat lähestyvät ohjelmointia tietyn ohjelmointikielen näkökulmasta — erityisesti aloittelijoille tarkoitetut. Osin tämä on luonnollista, koska ihmisetkin tarvitsevat jonkin yhteisen kielen kommunikoidakseen toisen kanssa. Siksi ohjelmoinnin aloittaminen ilman, että ensin opetellaan jonkun kielen perusteet, on aika haastavaa.

Jäsentämisen selkeyden takia kirjoissa käsitellään yleensä yksi aihe järjestelmällisesti alusta loppuun. Aloittaessaan puhumaan lapsi ei kuitenkaan ole kykeneväinen omaksumaan kaikkea tietyn lauserakenteen kieliopista yhdellä kertaa. Vastaavasti ohjelmoinnin alkeita kahlattaessa vastaanottokyky ei vielä riitä kaikkien rakenteiden ja mahdollisuuksien käsittämiseen. Tässä luentomonisteessa ja samoin luennolla asioiden käsittelyjärjestys on sellainen, että asioista annetaan ensin esimerkkejä tai johdatellaan näiden esimerkkien tarpeellisuuteen, ja sitten kerrotaan niin teoreettisesti kuin käytännöllisesti mistä oli kyse. Näin ollen tästä monisteesta saa yhden näkemyksen mukaisen pintaraapaisun ohjelmoinnin alkutaipaleelle. Kirjoista ja nettilähteistä asiaa on kuitenkin syvennettävä.

Tässä monisteessa käytetään esimerkkikielenä C#-kieltä. Kuitenkin nimenomaan esimerkkinä, koska monisteen rakenne ja esimerkit voisivat olla aivan samanlaisia mille tahansa muullekin ohjelmointikielelle. Tärkeintä ohjelmoinnin johdantokurssilla on ohjelmoinnin ajattelutavan oppiminen. Kielen vaihtaminen toiseen samansukuiseen kieleen on ennemmin verrattavissa Savon murteen vaihtamiseen Turun murteeseen, kuin suomen kielen vaihtamiseen ruotsin kieleen. Toisin sanoen, jos yhdellä kielellä on oppinut ohjelmoimaan, kykenee jo lukemaan toisella kielellä kirjoitettuja ohjelmia pienen harjoittelun jälkeen. Toisella kielellä kirjoittaminen on hieman haastavampaa, mutta samat rakenteet sielläkin toistuvat. Ohjelmointikielet tulevat ja menevät, eikä kannata tyytyä yhteen kieleen, vaan kannattaa opetella useita. Tätäkin vastaavaa kurssia on pidetty Jyväskylän yliopistossa seuraavilla kielillä: Fortran, Pascal, C, C++, Java ja nyt C#. Joissakin yliopistoissa aloituskielenä on Python, toisissa Scala. Nämä kaikki ovat tietyssä mielessä samansukuisia kieliä ja noudattavat monilta osin samanlaisia periaatteita, vaikka yksityiskohdat vaihtelevat joskus paljonkin.

Ohjelmointia on täysin mahdotonta oppia pelkästään kirjoja lukemalla. Siksi kurssi sisältää luentojen ohella myös viikoittaisten harjoitustehtävien (demojen) tekemistä, ohjattua pääteharjoittelua tietokoneluokassa sekä harjoitustyön tekemisen. Näistä lisätietoa, samoin kuin kurssilla käytettävien työkalujen hankkimisesta ja asentamisesta löytyy kurssin kotisivuilta:

Tämä moniste perustuu Martti Hyvösen ja Vesa Lappalaisen syksyllä 2009 kirjoittamaan Ohjelmointi 1 -monisteeseen, joka osaltaan sai muotonsa monen eri kirjoittajan työn tuloksena aina 80-luvulta alkaen. Suurimman panoksen monisteeseen ovat antaneet Timo Männikkö ja Vesa Lappalainen.

Jyväskylässä 2.1.2013

Martti Hyvönen, Vesa Lappalainen, Antti-Jussi Lakanen

Esipuheen jälkipuhe

Monisteen uusin versio on kirjoitettu TIM-järjestelmään (The Interactive Material). TIM-järjestelmän ideana on, että asioita, esimerkiksi ohjelmointia, pääsee kokeilemaan ilman mitään ohjelmien asentamista. Tämä toivottavasti helpottaa hieman ohjelmoinnin aloituskynnystä. Valitettavasti käyttämämme tekniikka (kurssille valittu kieli ja aliohjelmakirjastot) eivät anna mahdollisuutta interaktiivisten pelien tekemiseen, joten vakavampaa ohjelmointia varten joudumme kuitenkin asentamaan ohjelmointityökaluja, tässä tapauksessa Visual Studion ja Jypelin. Näistä myöhemmin tässä monisteessa ja muussa kurssin materiaalissa.

Materiaalissa olevista algoritmivisualisaatioista kiitos Aalto-yliopiston ACOS Content Server -projektille.

Jyväskylässä 29.8.2014 Vesa Lappalainen, Antti-Jussi Lakanen

#

0. Johdanto

Vaikka kurssi onkin tehty “peliohjelmointi”-kurssiksi, on 90% sen sisällöstä täysin samaa asiaa minkä ohjelmointikurssin kanssa tahansa. Jos joku ei halua tehdä kurssin harjoitustyönä peliä, voi toki tehdä myös minkä tahansa muun pienen ohjelman.

0.1 Kurssin sisällöstä ja tavoitteista

Pikaisen idean (englanniksi) tämän kurssin sisältöön saat katsomalla videon siitä, miten tehdään alle 5 minuutissa Galaksit räjähtää - peli. Jos katsot alla olevia videoita, älä pelkää ettet osaa (vielä), vaan katso mitä sinun pitää kurssin aikana oppia ja opitkin.

#

Video 1: GalaxyTrip less than 5 minutes, Demonstrated in SIGCSE11 symposium. Antti-Jussi Lakanen/Vesa Lappalainen

Jos haluat samasta aiheesta pidemmän version (suomeksi), niin katso video:

#

Video 2: Galaksit räjähtää: Pelin tekeminen 45 minuutissa, Antti-Jussi Lakanen, Levels-tapahtuma 9.4.2011

Seuraavista videoista näet millaisia pelejä kursseilla on tehty:

#

Video 3: Ohjelmointi 1, kevät 2014 -kurssin harjoitustöitä

#

Video 4: Nuorten peliohjelmointikurssi (1 viikko), kesä 2013

0.2 Kurssin osaamistavoitteet

Kurssin aluksi sinun oletetaan osaavan tietokoneen käyttöä. Tuttuja asioita pitäisi olla muun muassa erilaisten editorien käyttö, näppäinoikotiet sekä mielellään komentorivi. Toki nykypäivänä komentorivi ei valitettavasti ole kovin hyvin tunnettu asia ja voitkin tutustua komentoriviin esimerkiksi kurssin lisätietosivuilta tai Paavon selviytymisoppaasta.

#
Tarkista tietosi

Mitä seuraavista osaat tehdä komentoriviltä? Vastaamisen jälkeen näytetään hyvin yksinkertainen komentolista, jossa kauttaviivalla erotetaan Windows / Linux ja macOS -ohjeet. Paremmat ohjeet ylläolevissa linkeissä.

Aikaisempaa ohjelmointikokemusta sinulla ei tarvitse olla.

{"area": "ohjbloom"}

Kurssin aikana sinun on tarkoitus oppia seuraavia asioita (osaamisen taso sovelletulla Bloomin asteikolla: 1=muistaa, 2=ymmärtää, 3=osaa soveltaa, 4=osaa analysoida, 5=osaa arvioida, 6=osaa luoda)

Siirrä alla osaamisesi (punainen pallukka) aina sitä vastaavalle kohdalle. Keltainen ruutu on tavoite johon tulisi päästä kurssin lopuksi. Ruksaa ensin muokkaa.

#

Please to interact with this component.

Osattava asia123456
Rakenteisen ohjelmoinnin perusajatus o
Algoritminen ajattelu o
C#-kielen perusteet o
Peräkkäisyys o
Muuttujat o
Aliohjelmat ja funktiot o
Parametrin välitys o
Ehtolauseet o
Silmukat o
Taulukot o
Tiedostot ohjelmasta käytettynä o
Olioiden käyttö o
Yksikkötestit (TDD) o
Debuggerin käyttö o
Lukujärjestelmät, ASCII-koodi o
Rekursio o
Dokumentointi ja sen lukeminen o

{"area_end": "ohjbloom"}

Muista katsoa tarvittaessa myös kurssin videohakemisto.

0.3 TIM-käyttöohjeita

Alla olevat ohjeet koskevat tämän monisteen interaktiivista nettiversiota, joka on saatavilla osoitteessa https://tim.jyu.fi/view/1. Suosittelemme nettiversion käyttöä printatun monisteen rinnalla. Esimerkiksi malliohjelmien koko koodit saa näkyville vain nettiversiossa.

#
Yleiset TIM-ohjeet Luento 1 (2m46s)

Tämä TIM-pohjainen moniste koostuu erilaisista interaktiivista osista. Videoihin jo varmaan edellä tutustuitkin. Laitteesi kapasiteetin säästämiseksi videot kannattaa sulkea katsomisen jälkeen.

Monisteessa voi olla linkkejä muuhun materiaaliin. Nämä linkit on tarkoitettu lisälukemiseksi ja niitä ei kannata seurata kun monistetta käy ensimmäisen kerran lävitse. Linkkiviidakkoon vaan eksyy turhan helposti.

TIM-monisteessa kannattaa aina olla kirjautuneena (Login), niin voit seurata omaa edistymistäsi. Kirjautuneille monisteen oikeassa reunassa näkyy punaisia palkkeja niissä kohti, mitä et ole vielä lukenut. Kun olet lukenut (ja ymmärtänyt :-) ) jonkin tekstinpätkän, klikkaa punaista palkkia palkin poistamiseksi. Näin näet helposti mitä kohtia sinulla on vielä käymättä. Erityisesti tästä on hyötyä, jos hyppelet monistetta eri järjestyksessä kuin missä se on kirjoitettu. Palkki voi olla myös keltainen silloin, kun olet lukenut kappaleen, mutta sen sisältö on muuttunut viimeisen lukemisesi jälkeen. Klikkaa tämäkin pois jos sisäistät muutetun tekstin. Jos et tykkää toiminnosta, voit rattaan kuvan takaa klikata kaikki punaiset kerralla pois.

Vasemmassa yläkulmassa on kirjan kuva tai näytön koosta riippuen menun kuva jonka takaa löytyy kirjan kuva. Kirjan kuvasta aukeaa sisällysluettelo. Samasta paikasta voi sisällysluettelon sulkea.

Kun viet hiiren kappaleen vasempaan reunaan tulee sinivihreä palkki, jota painamalla aukeaa menu, josta saat mm. Comment/Note -painikkeen, mistä voit lisätä itsellesi muistiinpanoja kuhunkin kappaleeseen liittyen. Käytä tätä ominaisuutta ahkerasti. Voit laittaa huomioita itsellesi tai huomauttaa, jos jonkin kappaleen sisältö on epäselvä tai virheellinen. Anna mielellään tällaisessa tapauksessa myös korjausehdotus. Muuten käytä harkiten “Everyone” valintaa ja laita omat kommmentit “Just me”.

Jos haluat etsiä jotakin, käytä selaimen etsi toimintoa (Ctrl-F useimmissa selaimissa).

Jos haluat helposti löytää jonkin sivun uudelleen, niin tee siitä TIMin kirjanmerkki. Kirjanmerkin voit tehdä vasemmassa yläkulmassa “klemmarin” kohdalta. Toki voit tehdä kirjanmerkin selaimeesikin normaalisti, mutta TIMin kirjanmerkin hyvä puoli on siinä, että se toimii missä selaimessa tahansa. Aloita tekemällä tästä sivusta kirjanmerkki itsellesi. Eli paina “klemmarin” kuvaa ja lisää sivu vaikkapa otsikon Ohj1 alle nimellä Moniste.

Edellisissä videoissa ohjelmia kirjoitettiin Visual Studio -nimisessä ohjelmointi-ympäristössä (IDE = Integrated Development Environment). TIMissä on itsessään pieni sisäänrakennettu ympäristö, jolla voi tehdä yksinkertaisia tehtäviä, esimerkiksi:

#

Aja ensin alla oleva koodi painamalla Aja-painiketta. Muuta sitten koodia niin, että pallo on punainen. Aja uudelleen. Muuta vielä tausta siniseksi.

//
        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200,200,Shape.Circle);
        pallo.Color = Color.Yellow;
        Add(pallo);

 

Mä sain ton pallon ja taustan myös violetiksi, tästä alkaa koodarin ura :D

09 Sep 18

Itehä tein pallosta kolmion 8D

06 Sep 19

Oon ylpee teistä!

11 Sep 19
#
Katso tehtävään ohjeita videolta Luento 1 (2m21s)

Tehtävälaatikon alla on Näytä koko koodi-linkki, jota painamalla näet kaiken sen koodin, mitä ohjelman takia tarvitaan. Voit edelleen muutella ohjelmaa, mutta et voi kirjoittaa “väärään” paikkaan. Samasta linkistä voit piilottaa “ylimääräisen koodin”.

Highlight-linkistä voit vaihtaa editorin tyypin sellaiseksi, että se värittää koodia käytettävän kielen syntaksin mukaan sekä osaa täydentää editorille tuttuja sanoja.

Alusta-linkistä voit “nollata” oman vastauksesi ja aloittaa uudelleen mallista. Kokeile kumpaakin linkkiä.

Tehtävä voisi jatkua vielä niin, että: Lisää ennen Add(pallo)-riviä rivi

       pallo.Position = new Vector(150, 100);

Kokeile tätäkin, eli copy/paste yllä oleva koodi siihen isompaan koodiin Add(pallo) rivin yläpuolelle. Kokeile myös mitä tapahtuu, jos kirjoitat värissä Red pienillä kirjaimilla. Korjaa takaisin Red ja kokeile mitä vaikuttaa, kun vaihtaa Vectorissa olevia arvoja.

Voit kokeilla myös toisella kielellä (VPython) tehtyä esimerkkiä. Tätä voit myös pyöritellä hiiren oikealla painikkeella.

#
ball=sphere(pos=vector(4,7,3),radius=2,color=color.green)
redbox=box(pos=vector(4,2,3),size=vector(8,4,6),color=color.red)

 

For WebGL to work in Google Chrome (and Chromium), Here are the steps to enable WebGL in Google Chrome. Step 1: Open Google Chrome

Step 2: Type chrome://flags in the address bar

Step 3: Press Ctrl + f and type ” Rendering list “, “Override software rendering list” should come up, Now click on Enable and restart the browser.

Step 4: Completely kill Chrome: Type killall chrome into a console.

Step 5: Go to chrome://settings and enable Use hardware acceleration when available. By default it is off since version 43.

02 Mar 17
#

1. Mitä ohjelmointi on?

Sana ohjelmointi sisältää sanan ohje.

1.1 Algoritmit

Ohjelmointi on yksinkertaisimmillaan toimintaohjeiden antamista ennalta määrätyn toimenpiteen suorittamista varten. Ohjelmoinnin kaltaista toimintaa esiintyy jokaisen ihmisen arkielämässä lähes päivittäin. Algoritmista esimerkkinä voisi olla se, että annamme jollekulle puhelimessa ajo-ohjeet, joiden avulla hänen tulee päästä perille ennestään vieraaseen paikkaan. Tällöin luomme sarjan ohjeita ja komentoja, jotka ohjaavat toimenpiteen suoritusta. Nykyisin navigaattori lukee ohjeista aina seuraavan kun sitä tarvitaan. Vastaavalla tavalla ohjelmassakin tulee olemaan kohta missä suoritus on mennossa. Alkeellista ohjelmointia on tavallaan myös mikroaaltouunin käyttäminen, sillä tällöin uunille annetaan ohjeet siitä, kuinka kauan ja kuinka suurella teholla sen tulee toimia.

Ohjelmointi jakautuu hyvin monelle tasolle. Nykyisin on esimerkiksi traktoreita, joissa maanviljelijä ohjelmoi, miten peltoja kuljetaan. Varotoimenpiteenä ja tiukkoja käännöksiä varten tosin viljelijän pitää vielä itse olla mukana traktorissa varmistamassa, että kaikki sujuu hyvin. Eli tietyssä mielessä viljelijänkin pitää osata ohjelmoida. Mutta ennen kuin traktori on saatu tähän vaiheeseen, on tarvittu valtavasti insinöörityötä ja ohjelmointia. GPS-satelliitit, virheenkorjaus, traktorin varsinaisen tietokoneen ohjelmointi sille tasolle, että se tekee viljelijän ohjelmoinnin helpoksi jne.

Suonenjoella mansikoita keräävällä poimijalla on kaulassaan lähilukukortti (NFC-siru) ja aina kun hän saa tuokkosen täyteen ja vie sen keruupaikalle, rekisteröityy tieto siitä, kuka on kerännyt, mistä on kerännyt ja paljonko on tullut kiloja. Viljelijä on ohjelmoinut taustalle tiedot peltojen sijainneista ja toimenpiteistä ja voi seurata aikaisempaa tarkemmin, milloin joltakin saralta tuotto pienenee ja se kannattaa “alustaa” kokonaan.

Eli itse asiassa tietokoneet ja ohjelmointi tulevat joka paikkaan arkipäivän elämään. Tosin useinkaan käyttäjä ei välttämättä ymmärrä (ja toivottavasti ei tarvitsekaan ymmärtää), että hän käyttää tietokonetta ja ehkä jopa ohjelmoi sitä.

Näissä tapauksissa puhutaan sulautetuista järjestelmistä ja/tai IoT (Internet of Things) -laitteista, jos laite on yhteydessä verkkoon, kuten esimerkiksi traktorin ja maanviljelijän tapauksessa.

Edellisissä esimerkeissä oli siis kyse yksikäsitteisten ohjeiden antamisesta. Kuitenkin esimerkit käsittelivät hyvinkin erilaisia viestintätilanteita. Ihmisten välinen kommunikaatio, mikroaaltouunin kytkimien kiertäminen tai nappien painaminen, samoin kuin digiboxin ajastaminen kaukosäätimellä, ovat ohjelmoinnin kannalta toisiinsa rinnastettavissa, mutta ne tapahtuvat eri työvälineitä käyttäen. Ohjelmoinnissa työvälineiden valinta riippuu asetetun tehtävän ratkaisuun käytettävissä olevista välineistä. Ihmisten välinen kommunikaatio voi tapahtua puhumalla, kirjoittamalla tai näiden yhdistelmänä. Samoin ohjelmoinnissa voidaan usein valita erilaisia toteutustapoja tehtävän luonteesta riippuen.

Vaikka ohjelmointia käytännössä tehdään suurelta osin tietokoneella, on silti kynä ja paperia syytä aina olla esillä. Ohjelmoinnin suurin vaikeus aloittelijalle onkin siinä, että ei malteta istua kynän ja paperin kanssa ja miettiä mitä ollaan tekemässä. Jos esimerkiksi pitää tehdä laivanupotuspeli, pitää ensin pelata useita kertoja peliä, jotta hahmottuu, mitä kaikkia asioita tulee aikanaan vastaan.

Ohjelmoinnissa on olemassa eri tasoja riippuen siitä, minkälaista työvälinettä tehtävän ratkaisuun käytetään. Pitkälle kehitetyt korkean tason työvälineet mahdollistavat työskentelyn käsitteillä ja ilmaisuilla, jotka parhaimmillaan muistuttavat luonnollisen kielen käyttämiä käsitteitä ja ilmaisuja, kun taas matalan tason työvälineillä työskennellään hyvin yksinkertaisilla ja alkeellisilla käsitteillä ja ilmaisuilla.

Eräänä esimerkkinä ohjelmoinnista voidaan pitää sokerikakun valmistukseen kirjoitettua ohjetta:

Sokerikakku

6       munaa
1,5 dl  sokeria
1,5 dl  jauhoja
1,5 tl  leivinjauhetta

1.  Vatkaa sokeri ja munat vaahdoksi.
2.  Sekoita jauhot ja leivinjauhe.
3.  Sekoita muna-sokerivaahto ja jauhoseos.
4.  Paista 45 min 175°C lämpötilassa.

Valmistusohje on ilmiselvästi kirjoitettu ihmistä varten, vieläpä sellaista ihmistä, joka tietää leipomisesta melko paljon. Jos sama ohje kirjoitettaisiin ihmiselle, joka ei eläessään ole leiponut mitään, ei edellä esitetty ohje olisi alkuunkaan riittävä, vaan siinä täytyisi huomioida useita leipomiseen liittyviä niksejä: uunin ennakkoon lämmittäminen, vaahdon vatkauksen salat, yms.

Oleellista tässä ohjeessa on se, että sitä suoritetaan “käsky” (esimerkissä rivi) kerrallaan. Seuraavaa käskyä ei voida suorittaa ennen kuin edellinen on valmis. Tällöin puhutaan peräkkäisestä ohjelmoinnista. Jotta pysytään selvillä mitä käskyä ollaan tekemässä, pitää jossakin pitää mielessä käsky numero (jatkossa puhutaan käskyosoittimesta, IP tai ohjelmalaskurista, PC).

Rinnakkaisessa ohjelmoinnissa voisi olla kaksi kokkia, joista toinen tekisi käskyn 1 sillä aikaa kun toinen tekee käskyn 2. Käskyjä 3 ja 4 ei voi kuitenkaan rinnakkaistaa. Eli välttämättä kaksi kokkia ei saa kakkua valmiiksi puolta nopeammassa ajassa.

Koneelle kirjoitettavat ohjeet poikkeavat merkittävästi ihmisille kirjoitetuista ohjeista. Kone ei osaa automaattisesti kysyä neuvoa törmätessään uuteen ja ennalta arvaamattomaan tilanteeseen. Se toimii täsmälleen niiden ohjeiden mukaan, jotka sille on annettu, olivatpa ne vallitsevassa tilanteessa mielekkäitä tai eivät. Kone toistaa saamiaan toimintaohjeita uskollisesti sortumatta ihmisille tyypilliseen luovuuteen. Näin ollen tämän päivän ohjelmointikielillä koneelle tarkoitetut ohjeet on esitettävä hyvin tarkoin määritellyssä muodossa ja niissä on pyrittävä ottamaan huomioon kaikki mahdollisesti esille tulevat tilanteet. [MÄN]

1.2 Ohjelmointikielistä

Tässä aliluvussa kerrotaan mutkia oikoen hieman tietokoneen ideasta ja ohjelmointikielistä. Asiasta tulee tarkemmin ja lisää Tietokoneen rakenne ja arkkitehtuurikurssilla sekä Käyttöjärjestelmät. Asiaa sivutaan myös luvussa Lukujen esitys tietokoneessa.

1.2.1 Prosessori ja konekieli

Tietokoneen tärkeimmät osat ovat prosessori ja muisti. Prosessorin oleellinen ominaisuus on se, että sillä on tiedossa suoritettava käsky. Yleensä tämä tieto on IP-rekisterissä (Instruction Pointer, myös PC = Program Counter on yleisesti käytetty termi tälle). IP-rekisteri osoittaa koneessa muistipaikkaan, josta löytyy suoritettava käsky. Prosessorin toiminta on periaatteessa hyvin yksinkertaista:

  1. hae käsky IP-rekisterin osoittamasta paikasta
  2. kasvata IP-rekisterin sisältöä niin, että se osoittaa seuraavan käskyyn
  3. suorita haettu käsky (voi muuttaa IP:tä JUMP-käskyillä)
  4. jatka kohdasta 1.

Rekisterit ovat prosessorin sisäisiä nopeita muistipaikkoja. Käskyt ovat usein hyvin alkeellisia tyyliin:

  • hae luku muistipaikasta 7F34 rekisteriin AX
  • lisää rekisteriin AX rekisterin BX arvo

Jokaisella käskyllä on oma numeerinen arvo, joka tietokoneessa tietysti esitetään bitteinä. Esimerkiksi käsky

  • laita luku 62 (heksaluku) rekisteriin BL

olisi Intel x86 -sarjan prosessorissa

B3 62

ja muistissa siis binäärisenä

10110011 01100010

Eli periaatteessa ohjelmointi olisi saada koneen muistiin noita oikeita binäärilukuja. Koska binäärilukuja on aika vaikea ihmisen hahmottaa, käytetään niille usein edellä olevaa heksalukuesitystä. Tuokaan ei ole ihan helppoa muistaa, että B3 tarkoittaisi, että “laita BL rekisteriin”. Siksi käytetään yleensä assembly-kieltä, jossa on suurin piirtein 1:1 vastaavuus konekielisen binääriluvun ja ihmisen luettavan mnemonicin (muistikas) välillä. Eli eräällä (niitä on monia variantteja) assembly-kielellä edellinen komento olisi

mov bl,$62

Aluksi tietokoneita ohjelmoitiinkin syöttämällä suoraan käskyjen numeroarvoja. Sitten assembly-kielten myötä ihminen kirjoitti assembly-kieltä ja se käännettiin noiksi numeroarvoiksi ja näin saatiin syntymään koneen muistiin tarvittava ohjelma.

Koska prosessorin käskyt ovat varsin “alkeellisia”, tarvitaan niitä paljon yksinkertaisenkin ohjelman tekemiseksi. Erityisesti tiedon lukemiseksi ihmissyötteestä tai tiedostosta. Siksi tarvitaan käyttöjärjestelmä, joka tarjoaa usein tarvittavat ominaisuudet valmiina. Mutta siltikin assembly-kielillä joutuisi kirjoittamaan pieneenkin ohjelmaan paljon koodia.

1950-luvulta lähtien alettiin kehittämään ohjelmointikieliä, joilla ohjelmien kirjoittaminen olisi helpompaa ja selkeämpää kuin assemblerilla. Näin syntyi monia vieläkin käytössä olevia ohjelmointikieliä, kuten Fortran (1957), Lisp (1958), Cobol (1959) ja Pascal (1970). 70-luvulle tultaessa kieliä oli jo kymmeniä ellei jopa satoja, kun pienet kielet lasketaan mukaan.

1.2.2 C-kieli ja robotti

Kielen kääntäjä on ohjelma, joka lukee syötteenään ihmisen kirjoittaman selkokielisen (esim C#-kieli) ohjelmatiedoston (tekstitiedosto) ja tuottaa siitä binäärimuotoisen suoritettavan (executable) konekielisen tiedoston, joka voidaan sitten ajaa. Tämän takia esimerkiksi Windows-järjestelmässä ajettavan tiedoston nimen tarkentimena on usein .exe. Kun ohjelma käynnistetään, on käyttöjärjestelmän tehtävä laittaa ohjelmakoodi koneen muistiin ja siirtää ohjelmalaskuri ohjelman ensimmäiseen käskyyn.

Jälkeenpäin tunnetuin 70-lukulainen käännettävä korkeamman tason kieli on C-kieli (1972). Ideana (kuten sen edeltäjissäkin) on nostaa abstraktiota ylemmäksi, eli voidaan suoraan sanoa esimerkiksi:

int a = 15;
int b = 23;
int c = a + b;

Jos vastaava kirjoitettaisiin konekielellä, joutuisi ohjelmoija itse miettimään mitä kohtaa muistista käyttää muuttujille a, b ja c. C-ohjelmassa (ja kurssin käyttämässä C#) kääntäjä pitää kirjaa tarvittavista muistipaikoista ja aina kun puhutaan muuttujasta a, kääntäjä kääntää konekieliseen koodiin viittauksen a:lle varattuun muistipaikkaan.

Kurssin demotehtävissä on esimerkkinä pieni robotti, joka osaa vain muutamia käskyjä. Tämä robotti toimii hyvin vastaavalla tavalla kuin prosessori. Esimerkiksi edellinen C-ohjelman osa (joka itse asiassa tuolta osin on täsmälleen samanlainen C#-kielellä) olisi robotilla:

Voit kokeilla robotin toimintaa painamalla Step-painiketta. Harjoitustehtävänä voit muuttaa sen laskemaan yhteen kaikki Input-hihnalla olevat luvut (tosin tämä vaatii sopimuksen että esim hihnalla oleva 0 lopettaa laskemisen). Input hihnalle saat uusia lukuja laittamalla ne Preset input-kohtaan ja painamalla Reset. Run-painikkeesta robotti suorittaa kerralla koko ohjelman.

Robotissa Program-kohdassa oleva keltainen rivi vastaa prosessorin IP-rekisteriä, eli osoittaa suoritettavaa käskyä.

Käytetty kieli on nyt tavallaan robotin assembly-kieltä.

Jos käskyille annettaisiin numeeriset arvot (joita niillä sisäisesti onkin), esimerkiksi:

00 = INPUT
01 = OUTPUT
02 = ADD
03 = SUB
04 = COPYTO
...
09 = JUMPIFNEG

olisi tämä ohjelma robotin “konekielellä”:

00 04 00 00 02 00 01

jossa siis osa käskyistä vaatii kaksi tavua (tavu on 8 bittiä, esitetään kahden numeron pareina), kuten esim COPYTO jossa on käskyn vastaava lukuarvo ja sitten käskyn kohteen osoite (nyt muistipaikka 00).

Sitten meillä voisi olla C-kääntäjä, joka kääntäisi aikaisemmin kuvatun ohjelman osan tuoksi lukujonoksi. Paitsi että muistipaikat a ja b tuossa tapauksessa kääntyisivät Input-hihnalla oleviksi paikoiksi. Toki sama ohjelma voitaisiin tehdä myös muistipaikkoja käyttäen:

Tämä vastaisi jo melko tarkoin kirjoitettua C-ohjelmaa. Kääntäjän yksi tehtävä on silloin päättää, että vaikkapa muuttujasta a puhuttaessa tarkoitetaan muistipaikkaa 00 ja b:stä muistipaikkaa 01.

#

Tehtävä: Robotin konekieli

Millainen olisi tämä ohjelma robotin "konekielellä"? Erota tavut yhdellä välilyönnillä toisistaan.

 

1.2.3 Tavukielet

C-kieli oli valtakieli 70-luvun lopulta 80-luvun lopulle. 80-luvun alussa C-kielestä tehtiin alaspäin yhteensopiva oliolla laajennettu kieli C++ (1982). Myös tämä oli käännettävä kieli. 90-luvulla kehitettiin Java-kieli (1995) alun perin erilaisten sulautettujen järjestelmien kieleksi. Samalla Java paikkasi C++:n tunnettuja ongelmia. Javassa oli C++:aan nähden muutamia merkittäviä eroja:

  1. Javaa ei käännetä suoraan konekieleksi, vaan välikieleksi. Välikielistä tiedostoa ajetaan erikseen kullekin prosessorille tehdyllä Java-nimisellä ohjelmalla. Java-ohjelma (Java-virtuaalikone) lukee välikielen tavukoodia (vrt em robotin kielen lukuarvoinen esitys) ja suorittaa sitä askel kerrallaan. Java ei suinkaan ollut ensimmäinen tavukoodiin perustuva kieli, mutta se on tunnetuin tämän hetken virtuaalikoneeseen pohjautuvista kielistä.
  2. Javassa on automaattinen muistinhallinta, eli ohjelmoijan ei itse tarvitse muistaa vapauttaa varaamiaan muistialueita. Toki automaattinen muistinhallinta oli jo “tuttua” tekniikkaa vanhemmista kielistä.
  3. Javassa ei voi vahingossa osoittaa muistiin, jota ei ole varannut käyttötarkoitukseen (sanotaan ettei Javassa ole osoittimia)

Tavukoodin ideana on, että kääntäjää ei tarvitse tehdä erikseen joka prosessoriarkkitehtuurille ja käyttöjärjestelmälle. Riittää olla yksi kääntäjä, joka tuottaa välikooditiedoston (Javassa yleensä .class). Toisaalta ohjelman suorittaminen vaatii sitten välikielen tulkitsemista todellisen prosessorin konekielelle ja aluksi Java-ohjelmat olivatkin hitaampia kuin C-ohjelmat. Nykyisin Java-kääntäjien kehitykseen on panostettu paljon ja lisäksi tavukoodia suoritettaessa sitä käännetään samalla konekielelle (JIT = Just In Time compiling) ja näin jos samaan koodin kohtaan tullaan uudelleen, se onkin valmiiksi käännetty ja suoritusnopeus ei eroa oleellisesti C-koodin suoritusnopeudesta.

Javan suosio ponnahti raketin lailla 90-luvun puolivälin jälkeen. VL:n mielipide syistä:

Syynä oli automaattinen muistinhallinta ja sitä kautta helpommin vähemmän virheitä sisältävän ohjelmakoodin tuottaminen. Lisäksi Javassa oli toimivat merkkijonot, jotka puuttuivat esimerkiksi C++ standardista tuohon aikaan. Asiaa auttoi myös hyvin paljon C:tä muistuttava syntaksi, joka loivensi kielen vaihtoa.

Microsoft oli panostanut paljon C++ -kieleen, mutta huomasi Javan suosion nousun ja otti sen myös käyttöönsä, kuitenkin lisäten siihen omia ominaisuuksiaan. Tämä aiheutti lisenssiriitoja Javan kehittäneen Sun-yhtiön kanssa. Tästä syystä Microsoft lähti kehittämään omaa kieltä, jossa olisi kaikki Javan hyvät ominaisuudet. Tuloksena oli C#-kieli (C sharp, 2000). Monilta ominaisuuksiltaan kielet ovat hyvin samankaltaisia ja niiden välillä on aika helppoa ohjelmoijan siirtyä.

1.2.4 C# ja Jypeli

Jyväskylän yliopiston IT-tiedekunnassa ruvettiin miettimään nuorille sopivaa ohjelmointikurssia vuoden 2008-2009 tienoilla. Tällöin oli melko selkeää, että kurssilla pitäisi tehdä pelejä. Microsoftilla oli tällöin hyvät ympäristöt (Visual Studio) ja kirjastot (XNA) tehdä pelejä C#-kielellä ja saada ne toimimaan niin tietokoneissa kuin puhelimissakin (Windows Phone). Suoraan XNA:lla pelien ohjelmointi oli kuitenkin liian haastavaa ja siksi kehitettiin Jypeli-kirjasto, joka peittää alleen “turhia” yksityiskohtia, jotka jarruttaisivat aloittelevan ohjelmoijan ideointia. Tämä Nuorten pelikurssi osoittautui menestykseksi. Samaan aikaan takuttiin Java-pohjaisilla yliopiston ohjelmointikursseilla motivaation kanssa. Monia yliopistotason opiskelijoitakin pelit kiinnostavat ja siksi ensimmäiselle ohjelmointikurssille vaihdettiin teemaksi peliohjelmointi ja siinä samalla oli sujuvaa ottaa käyttöön Jypeli ja kieleksi C#. Tämä nostikin Ohjelmointi 1 -kurssin läpimenoa merkittävästi, kun voitiin tehdä “mielekkäämpiä” ohjelmia. Pelkkä Hello Worldin tulostaminen ei enää herättänyt intohimoa 2010-luvulla.

1.2.5 Muita kieliä

Edellä lueteltiin vain muutamia tunnettuja kieliä, C, C++, Java ja C#. Näillä on pitkälle samat sukujuuret. Puhuttiin myös välikielen tulkkaamisesta. Yksi hyvin tunnettu kokonaan alun perin tulkattavasi tehty kieli oli Basic (1964). Ideana on silloin että käännösvaihe puuttuu ja ihmisen kirjoittamaa ohjelmakoodia ruvetaan suorittamaan suoraan rivi riviltä. Nykyisin Python (1990) on noussut suosituksi tulkattavaksi kieleksi. Erilainen lähestymistapa ohjelmointiin on funktio-ohjelmointi, johon sopivia kieliä ovat esimerkiksi Haskell (1990), Scala (2004) ja F# (2005).

Vastaavasti Javascript on selainten käyttämä kieli, jonka avulla alunperin staattiset HTML-sivut saadaan “elämään”. Esimerkiksi tämä luentomoniste pyörii TIM-nimisessä sovelluksessa, jossa Pythonilla ja Haskelilla kirjoitettu palvelinohjelma lähettää selaimella Javascriptiä (1995) ja HTML:ää (1993), joiden avulla selain muodostaa interaktiivisen tekstin. Lisäksi TIMIä kirjoitettaessa käytetään nykyisin Javascriptin tilalla TypeScript-nimistä kieltä (2012), joka käännetään selainta varten Javascriptiksi. 3D-grafiikassa käytetään varjostinkieliä kuten GLSL ja HLSL riippumatta siitä, millä kielillä muut osiot grafiikkaa käyttävästä sovelluksesta kirjoitetaan. Näiden lisäksi tulevat erilaisiin sovelluskohteisiin kehitetyt kielet (DSL, domain specific language), joiden lukumäärää kukaan ei voi tietää. Eli käytännön elämässä yhden ohjelman kirjoittamisessa voidaan vaatia useiden eri ohjelmointikielten osaamista.

Eri kielten suosiosta ja historiasta voi katsoa lisää alla olevista linkeistä. Tosin kielten suosiota voidaan mitata hyvin eri tavoin, joten erilaisiin indekseihin kannattaa suhtautua kriittisesti.

Tällä kurssilla keskitytään kuitenkin käyttämään esimerkkinä C#-kieltä.

#

2. Ensimmäinen C#-ohjelma

2.1 Ohjelman kirjoittaminen

C#-ohjelmia (lausutaan c sharp) voi kirjoittaa millä tahansa tekstieditorilla. Tekstieditoreja on kymmeniä, ellei satoja, joten yhden nimeäminen on vaikeaa. Osa on kuitenkin suunniteltu varta vasten ohjelmointia ajatellen. Tällaiset tekstieditorit osaavat muotoilla ohjelmoijan kirjoittamaa lähdekoodia (tai lyhyesti koodia) automaattisesti siten, että lukeminen on helpompaa ja siten ymmärtäminen ja muokkaaminen nopeampaa. Ohjelmoijien suosimia ovat mm. Vim, Emacs, Visual Studio Code, Sublime Text ja NotePad++, mutta monet muutkin ovat varmasti hyviä. Monisteen alun esimerkkien kirjoittamiseen soveltuu hyvin mikä tahansa tekstieditori.

Koodi, lähdekoodi = Ohjelmoijan tuottama tiedosto, josta varsinainen ohjelma muutetaan kääntämällä tai tulkkaamalla tietokoneen ymmärtämäksi konekieleksi.

Kirjoitetaan tekstieditorilla alla olevan mukainen C#-ohjelma ja tallennetaan se vaikka nimellä HelloWorld.cs. Tiedoston tarkenteeksi (eli niin sanottu tiedostopääte) on sovittu juuri tuo .cs, joka tulee käytetyn ohjelmointikielen nimestä, joten tälläkin kurssilla käytämme tätä tarkenninta. Kannattaa olla tarkkana tiedostoa tallennettaessa, sillä jotkut tekstieditorit yrittävät oletuksena tallentaa kaikki tiedostot tarkenteella .txt, ja tällöin tiedoston nimi voi helposti tulla muotoon HelloWorld.cs.txt.

#
Selvennykseksi vielä video ohjelmakoodin kirjoittamisesta Notepad++:lla Luento 1 (3m18s)

Loppu ei tainnut mennä samallalailla,koska itsellä liian uusi versio c#???

10 Sep 18

Täällä sama ongelma..

  • VL: tässä videossahan oli tarkoitus vasta katsoa pelkkä kirjoittaminen :-)
  • VL: käytätte sitä kääntäjää mikä teillä on, ei vsaikuta tuohon koodiin. Pääteohjausohjeissa on ajantasaiset ohjeet.
11 Sep 18 (edited 14 Sep 19)
#
Sekä vastaava Sublime text -editorilla. Luento 1 (2m55s)


#
public class HelloWorld
{
    public static void Main()
    {
        System.Console.WriteLine("Hello World!");
    }
}

 

#

Animaatio: Tutki sanojen merkitystä ja ohjelman toimintaa

Tämän ohjelman pitäisi tulostaa näytölle teksti

Hello World!

Voidaksemme kokeilla ohjelmaa käytännössä, täytyy se ensiksi kääntää tietokoneen ymmärtämään muotoon.

Kääntäminen = Kirjoitetun lähdekoodin muuntaminen suoritettavaksi ohjelmaksi.

Kun painat tässä TIM-monisteessa Aja-painiketta, niin aluksi ohjelma käännetään konekieliseen muotoon ja sitten jos kääntäminen onnistuu virheittä, ohjelma ajetaan ja näytetään mitä se tulosti. Näistä vaiheista lisää seuraavassa alaluvuissa. Sitä ennen kuitenkin muutamia tehtäviä joissa voit kokeilla “taitojasi”.

Esimerkkejä muilla ohjelmointikielillä kirjoitetusta HelloWorld -ohjelmasta löydät vaikkapa:

#

Tehtävä 2.1

Muuta alla olevaa koodin osaa niin, että se tulostaa oman nimesi yhdelle riville ja kotipaikkakuntasi toiselle riville. Jos haluat tulostaa kaksi riviä, niin laita tulostuslause kaksi kertaa.

        System.Console.WriteLine("Hello World!");

 

#

Muuta tehtävä tulostamaan ISOLLA oma etunimesi. Saat käyttää vain asteriski (*)-merkkiä ja välilyöntiä.

       System.Console.WriteLine("*******      *       *     *");
       System.Console.WriteLine("   *         *       * * * *");
       System.Console.WriteLine("   *         *       *  *  *");
       System.Console.WriteLine("   *         *       *     *");

 

#

Edellisen esimerkin voisi tehdä myös seuraavasti

        System.Console.Write("*******      *       *     *\n" +
                             "   *         *       * * * *\n" +
                             "   *         *       *  *  *\n" +
                             "   *         *       *     *\n"  );

 

Kokeile mitä edellä tapahtuu (ja miksi?) jos jättää kirjaimet \n pois rivien lopuista.

#

2.2 Ohjelman kääntäminen ja ajaminen

Jotta ohjelman kääntäminen ja suorittaminen onnistuu, täytyy koneelle olla asennettuna joku C#-sovelluskehitin. Mikäli käytät Windowsia, niin aluksi riittää hyvin Microsoft .NET SDK (Software Development Kit, suom. kehitystyökalut). Muiden käyttöjärjestelmien tapauksessa sovelluskehittimeksi käy esimerkiksi Novell Mono. Hyvin monet tämän kurssin harjoituksista on tehtävissä Mono-sovelluskehittimellä, mutta tämän monisteen ohjeet ja esimerkit tullaan käsittelemään Windows-ympäristössä. Edelleen, Jypeli-kirjaston kaikkien ominaisuuksien käyttäminen on mahdollista vain Windows-ympäristössä.

Esimerkiksi tämä käyttämäsi TIM-ympäristö on toteuteutettu (Python, Haskell ja Javascript-kielillä) niin, että ruutuun kirjoittamasi teksti annetaan Linux-palvelimelle, joka tallettaa tiedoston tilapäistiedostoon ja kääntää sen edellä mainitulla Mono-kääntäjällä. Jos käännös menee virheittä, syntynyt konekielinen ohjelma ajetaan Linux-palvelimessa ja kaapataan ohjelman tuottama tulostus ja näytetään se selaimen ruudussa. Nämä vaiheet vievät yhteensä muutaman sekunnin.

Seuraavaksi opettelemme tekemään nämä vaiheet käsin, jotta ymmärtäisimme paremmin mitä taustalla tapahtuu.

Lisätietoa .NET-kehitystyökaluista ja asentamisesta löytyy kurssin kotisivuilta:

#
HelloWorld-ohjelman kääntäminen komentorivillä Luento 1 (2m53s)

Kun sovelluskehitin on asennettu, käynnistetään komentorivi (Command Prompt, lyhyemmin cmd) ja siirrytään siihen hakemistoon, johon HelloWorld.cs tiedosto on tallennettu. Ohjelma käännetään nyt komennolla:

csc HelloWorld.cs

ja MacOS:ssa komennolla

mcs HelloWorld.cs

Ohjelmaa kääntäessä csc komennolla tulee ‘csc’ is not recognized as an internal or external command, operable program or batch file.

VL: komento ei ole polussa. Tee pääteohjaus 1, jossa tuo tehdään.

07 Sep 20 (edited 07 Sep 20)

Mikä korvaa macilla tuon tabulaattorin, jolla saa selattua nopeasti tiedostojen läpi?

VL: Tab

09 Sep 20 (edited 09 Sep 20)

Komento csc tulee sanoista C Sharp Compiler (compiler = kääntäjä). Kääntämisen jälkeen hakemistoon ilmestyy HelloWorld.exe-niminen tiedosto, joka voidaan ajaa kuten mikä tahansa ohjelma syöttämällä ohjelman nimi:

HelloWorld

ja MacOS:ssa komennolla

mono HelloWorld.exe

Ohjelman tulisi nyt tulostaa näyttöön teksti Hello World!, kuten alla olevassa kuvassa.

Kuva 1: Ohjelman kääntäminen ja ajaminen Windowsin komentorivillä.
Kuva 1: Ohjelman kääntäminen ja ajaminen Windowsin komentorivillä.

Toimii oman koneen c: asemalla, mutta halusin tehdä saman tehtävän tuonne u: asemaan, kun yritän ajaa esim helloWorld.exe (eli .) niin valittaa että ‘Access denied […]’. Mitä teen?

  • VL: et mitään, koska ylläpito on estänyt ohjelmien ajamisen U:-levyltä. Vähintään siirrä se .exe-tiedosto C:-levylle ajamista varten.
12 Sep 18 (edited 27 Sep 20)

Huomaa, että käännettäessä kirjoitetaan koko tiedoston nimi .cs-tarkentimen kanssa.

Jos saat virheilmoituksen

'csc' is not recognized as an internal or external command, 
operable program or batch file.

niin kääntäjäohjelmaa csc.exe ei silloin löydy niin sanotusta hakupolusta, eli Windowsin PATH-ympäristömuuttujasta. Ohjelman lisääminen hakupolkuun onnistuu pääteohjaustehtävien ohjeilla.

toimi itellä vasta kun laitto muodossa “SET %WINDIR%.NET.0.30319;%PATH%” en tiiä mitä muuttu mutta alko toimimaan :D

10 Sep 19
#
Tehtävä 2.2

md:Avaa uuteen ikkunaan (ctrl+klikkaa linkkiä) oheinen materiaali ja tee siellä olevat tehtävät. Vastaa sitten alla olevaan testiin. >\ Mitkä komennot pitää antaa uudelleen kun lähdekoodia on muokattu?

Teen mikroluokassa tehtävää. Kun yrittää ajaa .exe:n, tulee rivi Access is denied.

  • VL: et kai ole tehnyt sitä U-levylle. Siellä olevia ohjelmia ei saa ajaa. Tee työt C:\MTemp hakemistossa.
12 Sep 18 (edited 27 Sep 20)

2.3 Ohjelman rakenne

Vaikka ensimmäisen ohjelmamme “ainoa oleellinen rivi” onkin

System.Console.WriteLine("Hello World!");

tarvitaan C#-kielessä tämän ympärillä tietoa siitä, mihin ohjelman osaan lause kuuluu sekä mistä kohti ohjelma pitää käynnistää. Tämä hieman lisää sinänsä yksinkertaisen ohjelma koodirivien määrää. Joissakin kielissä tulostavaan ohjelmaan riittää pelkkä tulostuslause. Rivimäärien ero pienenee ohjelman koon kasvaessa. Yleisesti ottaen rivien vähyys ei ole itseisarvo, joten sen perusteella ei pelkästään voi kieliä laittaa paremmuusjärjestykseen.

Kirjoittamamme ohjelma HelloWorld.cs (tai oikeastaan kirjoittamamme tekstitiedosto) on melkein yksinkertaisin mahdollinen C#-ohjelma. Alla yksinkertaisimman ohjelman kaksi ensimmäistä riviä.

public class HelloWorld
{

Ensimmäisellä rivillä määritellään luokka (class), jonka nimi on HelloWorld. Tässä vaiheessa riittää ajatella luokkaa “kotina” aliohjelmille. Aliohjelmista puhutaan lisää hieman myöhemmin. Toisaalta luokkaa voidaan verrata “piparkakkumuottiin” - se on rakennusohje olioiden (eli “piparkakkujen”) luomista varten. Ohjelman ajamisen aikana olioita syntyy tarvittaessa luokkaan kirjoitetun koodin avulla. Olioita voidaan myös tuhota. Yhdellä luokalla voidaan siis tehdä monta samanlaista oliota, aivan kuten yhdellä piparkakkumuotilla voidaan tehdä monta samanlaista (melkein samannäköistä) piparia.

Jokaisessa C#-ohjelmassa on vähintään yksi luokka, mutta luokkia voi olla enemmänkin. Luokan, jonka sisään ohjelma kirjoitetaan, on hyvä olla samanniminen kuin tiedoston nimi. Jos tiedoston nimi on HelloWorld.cs, on suositeltavaa, että luokan nimi on myös HelloWorld, kuten meidän esimerkissämme. Tässä vaiheessa ei kuitenkaan vielä kannata liikaa vaivata päätänsä sillä, mikä luokka oikeastaan on, se selviää tarkemmin myöhemmin.

Huomaa! C#:ssa ei samasteta isoja ja pieniä kirjaimia. Ole siis tarkkana kirjoittaessasi luokkien nimiä.

Huomaa! C#-kielessä luokka aloitetaan isolla alkukirjaimella. Skandeja (åäö yms) ei kannata käyttää luokan nimessä.

#

Tässä tulostuslauseen 'System' on kirjoitettuna pienellä. Jos koitat ajaa sitä, se ei käänny vaan antaa virheilmoituksen. Muuta ohjelma toimivaksi. Kokeile muuttaa muitakin merkkejä isoiksi tai pieniksi.

       system.Console.WriteLine("Tässä kohtaa tulostetaan kirjaimet sellaisenaan.");

 

Luokan edessä oleva public-sana on eräs saantimääre (eng. access modifier). Saantimääreen avulla luokka voidaan asettaa rajoituksetta tai osittain muiden (luokkien) saataville, tai piilottaa kokonaan. Sana public tarkoittaa, että luokka on muiden luokkien näkökulmasta julkinen, kuten luokat useimmiten ovat. Muita saantimääreitä ovat protected, internal ja private.

Määreen voi myös jättää kirjoittamatta luokan eteen, jolloin luokan määreeksi tulee automaattisesti internal. Puhumme aliohjelmista myöhemmin, mutta mainittakoon, että vastaavasti, jos aliohjelmasta jättää määreen kirjoittamatta, tulee siitä private. Tällä kurssilla kuitenkin harjoitellaan kirjoittamaan julkisia luokkia (ja aliohjelmia), jolloin public-sana kirjoitetaan lähes aina luokan ja aliohjelman eteen. Huomaa kuitenkin, että kun jatkossa tulee puhetta olion muuttujista (eli attribuuteista), niin niiden eteen kirjoitetaan lähes poikkeuksetta private.

Luokat ja aliohjelmat esitellään yleensä saantimääreellä public. Attribuutit esitellään vastaavasti private-määreellä.

Toisella rivillä on oikealle auki oleva aaltosulku {. Useissa ohjelmointikielissä yhteen liittyvät asiat ryhmitellään tai kootaan aaltosulkeiden sisälle. Oikealle auki olevaa aaltosulkua sanotaan aloittavaksi aaltosuluksi ja tässä tapauksessa se kertoo kääntäjälle, että tästä alkaa HelloWorld-luokkaan liittyvät asiat. Jokaista aloittavaa aaltosulkua kohti täytyy olla vasemmalle auki oleva lopettava aaltosulku }. HelloWorld-luokan lopettava aaltosulku on rivillä viisi, joka on samalla ohjelman viimeinen rivi. Aaltosulkeiden rajoittamaa aluetta kutsutaan lohkoksi (block).

public static void Main()
{

Rivillä kolme määritellään (tai oikeammin esitellään) uusi aliohjelma nimeltä Main. Nimensä ansiosta se on tämän luokan pääohjelma. Sanat static ja void kuuluvat aina Main-aliohjelman esittelyyn. static tarkoittaa, että aliohjelma on luokkakohtainen (vastakohtana oliokohtainen, jolloin static-sanaa ei kirjoiteta). Vastaavasti void merkitsee, ettei aliohjelma palauta mitään tietoa. Paneudumme näihin määreisiin tarkemmin myöhemmin. Main voisi myös palauttaa arvon ja silloin void tilalla olisi int, mutta tätä ominaisuutta emme käytä tällä kurssilla.

Samoin kuin luokan, niin myös pääohjelman sisältö kirjoitetaan aaltosulkeiden sisään. C#:ssa ohjelmoijan kirjoittaman koodin suorittaminen alkaa aina käynnistettävän luokan pääohjelmasta (Main). Toki sisäisesti ehtii tapahtua paljon asioita jo ennen tätä.

System.Console.WriteLine("Hello World!");

Rivillä neljä tulostetaan näytölle Hello World!. C#:ssa tämä tapahtuu pyytämällä .NET-ympäristön mukana tulevan System-luokkakirjaston Console-luokkaa tulostamaan WriteLine()-metodilla (method).

Huomaa! Viitattaessa aliohjelmiin on kirjallisuudessa usein tapana kirjoittaa aliohjelman nimen perään sulut. Kirjoitustyyli korostaa, että kyseessä on aliohjelma, mutta asiayhteydestä riippuen sulut voi myös jättää kirjoittamatta (mutta ei siis ohjelmakoodissa). Tässä monisteessa käytetään pääsääntöisesti jälkimmäistä tapaa, tilanteesta riippuen.

Kirjastoista, olioista ja metodeista puhutaan lisää kohdassa 4.1 ja luvussa 8. Tulostettava merkkijono kirjoitetaan sulkeiden sisälle lainausmerkkeihin (Shift + 2). Tämä rivi on myös tämän ohjelman ainoa lause (statement). Lauseiden voidaan ajatella olevan yksittäisiä toimenpiteitä, joista ohjelma koostuu. Jokainen lause päättyy C#:ssa puolipisteeseen. Koska lauseen loppuminen ilmoitetaan puolipisteellä, ei C#:n syntaksissa (syntax) “tyhjillä merkeillä” (white space), kuten rivinvaihdoilla ja välilyönneillä, ole merkitystä ohjelman toiminnan kannalta. Ohjelmakoodin luettavuuden kannalta niillä on kuitenkin suuri merkitys. Huomaa, että puolipisteen unohtaminen on yksi yleisimmistä ohjelmointivirheistä ja tarkemmin sanottuna syntaksivirheistä.

Syntaksi = Tietyn ohjelmointikielen (esimerkiksi C#:n) kielioppisäännöstö.

#

Tehtävä 2.3

Alla on vasta suunnitelma siitä, millainen ohjelma haluttaisiin tehdä. Kääntäjä ei kuitenkaan tunnista sanoja, joten korvaa sanat C#-kielellä. Kirjoita siis kokonainen ohjelma, joka tulostaa nimesi. Huomaa että ohjelma ei käänny, jos siinä on yksikin tunnistamaton sana.

//
julkinen luokka LuokanNimi{

  julkinen luokkakohtainen ei-palauta-mitään Pääohjelma(){

     Tulosta("Nimi");
  }

}

 

Huomaa että alla olevassa esimerkissä muuttujan a arvo saadaan tulostettua muodostamalla uusi merkkijono, joka yhdistää plus-operaattorilla toisen jonon ja a:n arvon. Näin WriteLine-aliohjelmalle saadaan vietyä parametrina vain yksi merkkijono kuten kuuluukin. WriteLine-aliohjelmalle ei perusmuodossa viedä pilkulla eroteltua listaa kuten joissakin kielissä.

#

Tehtävä 2.4

Kokeile mihin kaikkiin kohtiin voit koodissa laittaa ylimääräisen välilyönnin tai jopa rivinvaihdon niin, että ohjelma toimii vielä oikein.

public class Tyhjia
{
    public static void Main()
    {
       int a = 3;
       System.Console.WriteLine("a:n arvo on " + a);
       a++;  // Kasvattaa a:ta yhdellä
       System.Console.WriteLine("ja nyt se on yhtä isompi: " + a);
    }
}

 

#
Tarkista tietosi

Mihin kohti saa laittaa välilyönnin tai rivinvaihdon C\#-kielessä?

#
Tarkista tietosi

Mitkä väittämät pitävät paikkaansa koskien tehtävän 2.4 ohjelmaa.

2.3.1 Virhetyypit

Ohjelmointivirheet voidaan jakaa karkeasti syntaksivirheisiin ja loogisiin virheisiin.

Edellä tutkittiin mihin välilyönnin tai rivinvaihdon voi laittaa. Silloin kun ohjelma ei kääntynyt, oli kyseessä syntaksivirhe. Silloin kun ohjelma toimi, mutta tekstinä näytti erilaiselta, on kyseessä oikeastaan kirjoitustyylin virhe (tai mielipide-ero).

Syntaksivirhe estää ohjelman kääntymisen vaikka merkitys eli semantiikka olisikin periaatteessa oikein. Siksi ne huomataankin aina viimeistään ohjelmaa käännettäessä. Syntaksivirhe voi olla esimerkiksi joku kirjoitusvirhe tai puolipisteen unohtaminen lauseen lopusta.

Pitäisiköhän tässä olla ‘Syntaksivirheen’ tilalla ‘Staattinen virhe’ tai ‘käännösaikainen virhe’? Esimerkiksi tyyppivirhe on usein käännösaikainen virhe, mutta sitä ei yleisesti pidetä ‘syntaksivirheenä’ vaan semanttisena virheenä.

05 Mar 20

Loogisissa virheissä semantiikka, eli merkitys, on väärin. Ne ovat vaikeampia huomata, sillä ohjelma kääntyy semanttisista virheistä huolimatta. Ohjelma voi jopa näyttää toimivan täysin oikein. Jos looginen virhe ei löydy testauksessakaan (testing), voivat seuraukset ohjelmistosta riippuen olla tuhoisia. Tässä yksi tunnettu esimerkki loogisesta virheestä, jonka ajoissa havaitseminen ja korjaaminen kuitenkin esti isot tuhot:

#

Esimerkki ajonaikaisesta virheestä. Ohjelma tulostaa mitä 10 jaettuna 2:lla on. Kokeile ajaa ohjelma. Jos jakajaksi (2) laitetaankin 0, tulee ajonaikainen virhe, koska nollalla ei voi jakaa. Kokeile.

        int jakaja = 2;
        System.Console.WriteLine("10/" + jakaja + "=" + 10/jakaja );

 

2.3.2 Kääntäjän virheilmoitusten tulkinta

Alla on esimerkki syntaksivirheestä HelloWorld-ohjelmassa.

#
public class HelloWorld
{
    public static void Main()
    {
        System.Console.Writeline("Hello World!");
    }
}

 

Ohjelmassa on pieni kirjoitusvirhe, joka on (ilman apuvälineitä) melko hankala huomata. Tutkitaan csc-kääntäjän antamaa virheilmoitusta.

HelloWorld.cs(5,17): error CS0117: 'System.Console' does not
contain a definition for 'Writeline'

Kääntäjä kertoo, että tiedostossa HelloWorld.cs rivillä 5 ja sarakkeessa 17 on seuraava virhe: System.Console-luokka ei tunne Writeline-komentoa. Tämä onkin aivan totta, sillä WriteLine kirjoitetaan isolla L:llä. Korjattuamme tuon ohjelma toimii jälleen.

Valitettavasti virheilmoituksen sisältö ei aina kuvaa ongelmaa kovinkaan hyvin. Alla olevassa esimerkissä on erehdytty laittamaan puolipiste väärään paikkaan. Koeta ensin itse löytää mihin, ennen kuin jatkat tai kokeilet.

#
public class HelloWorld
{
    public static void Main();
    {
        System.Console.WriteLine("Hello World!");
    }
}

 

Virheilmoitus, tai oikeastaan virheilmoitukset, näyttävät kääntäjästä riippuen esimerkiksi seuraavalta.

HelloWorld.cs(4,3): error CS1519: Invalid token '{' in class,
struct, or interface member declaration
HelloWorld.cs(5,26): error CS1519: Invalid token '(' in class,       
struct, or interface member declaration
HelloWorld.cs(7,1): error CS1022: Type or namespace definition,
or end-of-file expected

Ensimmäinen virheilmoitus osoittaa riville 4, vaikka todellisuudessa ongelma on rivillä 3. Toisin sanoen, näistä virheilmoituksista ei ole meille tässä tilanteessa lainkaan apua, päinvastoin, ne kehottavat tekemään jotain, mitä emme halua.

Mikäli virhe ei löydy ilmoitetulta riviltä, kannattaa sitä usein lähteä etsimään edellisiltä riveiltä.

#

Tehtävä 2.5

Kokeile edellä olevia ja muita mahdollisia virhetyyppejä alla olevaan ohjelmaan. Muista että Alusta-linkistä saat ohjelman taas toimivaksi.

public class Virheita
{
    public static void Main()
    {
       int a = 5;  // Vaihda tähän kokeeksi iso A
       System.Console.WriteLine("a:n arvo on " + a);
    }
}

 

Lisää virheilmoitusten tulkintaesimerkkejä on kurssin lisämateriaalissa.

2.3.3 Tyhjät merkit (White spaces)

Kuten aikaisemmassa tehtävässä kokeilimme, esimerkkinämme ollut HelloWorld-ohjelma voitaisiin, ilman että sen toiminta muuttuisi, vaihtoehtoisesti kirjoittaa myös seuraavassa muodossa.

#
   public class HelloWorld
                             {


        public static void Main()
     {
System.Console.WriteLine("Hello World!");
       }


   }

 

Edelleen, koodi voitaisiin kirjoittaa myös seuraavasti.

#

Tehtävä 2.6

Korjaa rivitykset ja sisennykset.

public class HelloWorld { public static void Main() {
System.Console.WriteLine("Hello World!"); } }

 

Tai jopa niin, että koko koodi on yhdellä rivillä, kokeile.

Vaikka molemmat yllä olevista esimerkeistä ovat syntaksiltaan oikein, eli ne noudattavat C#:n kielioppisääntöjä, on niiden luettavuus huomattavasti heikompi kuin alkuperäisen ohjelmamme. C#:ssa on yhteisesti sovitut koodauskäytänteet (code conventions), jotka määrittelevät, miten ohjelmakoodia tulisi kirjoittaa. Kun kaikki kirjoittavat samalla tavalla, on muiden koodin lukeminen helpompaa. Tämän monisteen esimerkit on pyritty kirjoittamaan näiden käytänteiden mukaisesti. Linkkejä koodauskäytänteisiin löytyy kurssin lisätietosivulta osoitteesta

Merkkijono kirjoitetaan lainausmerkkien " väliin. Merkkijonoja käsiteltäessä välilyönneillä, tabulaattoreilla ja rivinvaihdoilla on kuitenkin merkitystä. Vertaa alla olevia tulostuksia.

#
        System.Console.WriteLine("Hello World!");

 

Yllä oleva rivi tulostaa

Hello World!

kun taas alla oleva rivi tulostaa:

H e l l o    W o r l d !
#
        System.Console.WriteLine("H e l l o    W o r l d !");

 

Lukemisen helpottamiseksi tyhjiä merkkejä käytetään rivien alussa sisentämään lohkoja. Tapana on, että jokaisen aloittavan aaltosulun jälkeen sisennetään koodia 4 yksikköä ja vastaavasti saman verran tullaan takaisin lopettavan aaltosulun jälkeen. Parina olevat aaltosulut pyritään (C#-tyylissä) laittamaan samaan sarakkeeseen. Yleensä IDEt osaavat muotoilla koodin ja tätä ominaisuutta kannattaa käyttää, jos ei itse osaa muotoilla koodia kauniisti.

2.4 Kommentointi

“Good programmers use their brains, but good guidelines save us having to think out every case.” -Francis Glassborow

C# -kielessä on kolme erilaista kommenttityyppiä ja sitä kautta neljä erilaista merkintää näiden käyttämiseen:

merkintä tarkoitus
// yhden rivin kommentti
/// dokumentaatiokomentti
/* monirivisen kommentin alku
*/ monirivisen kommentin loppu

Kommentointiin ja dokumentointiin kuuluu myös ohjelman kirjoittamisen käytänteiden noudattaminen (code conventions), mm. oikeanlainen sisentäminen ja muuttujien yms. hyvä nimeäminen. Pitää ajatella ohjelmakoodia sellaisena, että toinen kielen tunteva osaa sitä lukea.

Lähdekoodia on usein vaikea ymmärtää pelkkää ohjelmointikieltä lukemalla. Tämän takia koodin sekaan voi ja pitää lisätä selosteita eli kommentteja. Kommentit ovat sekä koodin kirjoittajaa itseään varten että tulevia ohjelman lukijoita ja ylläpitäjiä varten. Monet asiat voivat kirjoitettaessa tuntua ilmeisiltä, mutta jo viikon päästä saakin ähkäillä, että miksihän tuonkin tuohon kirjoitin.

Kääntäjä jättää kommentit huomioimatta, joten ne eivät vaikuta ohjelman toimintaan.

// Yhden rivin kommentti

Yhden rivin kommentti alkaa kahdella vinoviivalla (//). Sen vaikutus kestää koko rivin loppuun.

/* Tämä   kommentti
   on usean
   rivin
   pituinen */

Vinoviivalla ja asteriskilla alkava (/*) kommentti jatkuu kunnes vastaan tulee asteriski ja vinoviiva (*/). Huomaa, ettei asteriskin ja vinoviivan väliin tule välilyöntiä.

#
Luettavan koodin ohjeet Luento 1 (8m3s)
#

Tehtävä 2.7

Kokeile erilaisia kommentteja seuraavaan ohjelmaan eri paikkoihin.

public class HelloWorld
{
    public static void Main()
    {
        System.Console.WriteLine("Hello World!");
    }
}

 

Esimerkiksi kommenttijonon /* kissa */ voit kirjoittaa kaikkiin samoihin paikkoihin, mihin aikaisemmassa harjoituksessa pystyit laittamaan välilyönnin. Vastaavasti et voi kirjoittaa jonoa paikkoihin, joihin ei saa laittaa välilyöntiä.

2.4.1 Dokumentointi

Kolmas kommenttityyppi on dokumentaatiokommentti. Dokumentaatiokommenteissa on tietty syntaksi, ja tätä noudattamalla voidaan dokumentaatiokommentit muuttaa sellaiseen muotoon, että kommentteihin perustuvaa yhteenvetoa on mahdollista tarkastella esimerkiksi nettiselaimen avulla tai tuottaa siitä siisti paperituloste.

Dokumentaatiokommentti olisi syytä kirjoittaa ennen jokaista luokkaa, pääohjelmaa, aliohjelmaa ja metodia (aliohjelmista ja metodeista puhutaan myöhemmin). Lisäksi jokainen C#-tiedosto pitäisi alkaa aina dokumentaatiokommentilla, josta selviää tiedoston tarkoitus, tekijä ja versio.

Dokumentaatiokommentit kirjoitetaan siten, että rivin alussa on aina aina kolme vinoviivaa (Shift + 7). Jokainen seuraava dokumentaatiokommenttirivi aloitetaan siis myöskin kolmella vinoviivalla.

/// Tämä
/// on
/// dokumentaatiokommentti 

Dokumentoiminen tapahtuu tagien avulla. Jos olet joskus kirjoittanut HTML-sivuja, on merkintätapa sinulle tuttu. Dokumentaatiokommentit alkavat aloitustagilla, muotoa <esimerkki>, jonka perään tulee kommentin asiasisältö. Kommentti loppuu lopetustagiin, muotoa </esimerkki>, siis muuten sama kuin aloitustagi, mutta ensimmäisen kulmasulun jälkeen on yksi vinoviiva.

C#-tageja ovat esimerkiksi <summary>, jolla ilmoitetaan pieni yhteenveto kommenttia seuraavasta koodilohkosta (esimerkiksi pääohjelma tai metodi). Yhteenveto päättyy </summary> -lopetustagiin.

Ohjelman kääntämisen yhteydessä dokumentaatiotagit voidaan kirjoittaa erilliseen XML-tiedostoon, josta ne voidaan edelleen muuntaa helposti selattaviksi HTML-sivuiksi. Tageja voi keksiä itsekin lisää, mutta tämän kurssin tarpeisiin riittää hyvin suositeltujen tagien luettelo. Tiedot suositelluista tageista löytyvät C#:n dokumentaatiosta:

Mitä ovat Tagit?

11 Sep 18

Ainakin web-developmentissa tageja käytetään kun halutaan lajitella tietoa ja esittää niitä lukijalle, mikä ilmeisesti pätee myös tässä dokumentoimisessa Esim. Jos meillä on erilaisia kirjalajeja näytettäväksi. Käytetään taggeja tietojen esittämista varten: Erilaiset Kirjalajit Runo sci-fi Tietokirja

25 Nov 18 (edited 25 Nov 18)

Voisimme kirjoittaa nyt C#-kommentit HelloWorld-ohjelman alkuun seuraavasti:

#
/// @author  Antti-Jussi Lakanen
/// @version 28.8.2012
///
/// <summary>
/// Esimerkkiohjelma, joka tulostaa tekstin "Hello World!"
/// </summary>

public class HelloWorld
{
  /// <summary>
  /// Pääohjelma, joka hoitaa varsinaisen tulostamisen.
  /// </summary>
  public static void Main()
  { // Suoritus alkaa siis tästä, ohjelman "entry point"
    // seuraava lause tulostaa ruudulle
    System.Console.WriteLine("Hello World!");
  } // Ohjelman suoritus päättyy tähän
}

 

Ohjelman alussa kerrotaan kohteen tekijän nimi. Tämän jälkeen tulee ensimmäinen dokumentaatiokommentti (huomaa kolme vinoviivaa), joka on lyhyt ja ytimekäs kuvaus tästä luokasta. Huomaa, että jossain dokumentaation tiivistelmissä näytetään vain tuo ensimmäinen virke. Paina edellä Document-linkkiä ja tutki syntyvää dokumentaatiota painamalla siinä olevia linkkejä. Kaikki “muuttuva” teksti tuossa dokumentaatiossa kerätään ohjelmassa olevista /// alkavista dokumentaatiokommenteista.

Dokumentaatiokommenttien ansiosta ohjelmasta saadaan aikanaan vastaava dokumentaatio kuin Jypelistä.

Huomaa että dokumentaatiokomenttimerkkiä /// ei käytetä muuta kuin dokumenttikommenteissa (eli aliohjelman tai luokan edessä). Koodin sisällä käytetään tavallista yhden rivin komenttimerkkiä // tai monen rivin kommenttimerkiä /* ... */.

Dokumentointi on erittäin keskeinen osa ohjelmistotyötä. Luokkien ja koodirivien määrän kasvaessa dokumentointi helpottaa niin omaa työskentelyä kuin tulevien käyttäjien ja ylläpitäjien tehtävää. Dokumentoinnin tärkeys näkyy muun muassa siinä, että jopa 40-60% ylläpitäjien ajasta kuluu muokattavan ohjelman ymmärtämiseen. [KOSK][KOS]

#

Tehtävä 2.8

Lisää ohjelmaan dokumentaatiokommentit luokan ja pääohjelman edelle. Paina sitten Document-linkkiä ja tutki syntynyttä dokumentaatiota.

public class Tyhjia
{
    public static void Main()
    {
       int a = 3;
       System.Console.WriteLine("a:n arvo on " + a);
       a++; // a kasvaa yhdellä
       System.Console.WriteLine("ja nyt se on yhtä isompi: " + a);
    }
}

 

#
Tarkista tietosi

Mitkä seuraavista käsitteistä on hallussa? Kertaa tarvittaessa

#

3. Algoritmit

“First, solve the problem. Then, write the code.” - John Johnson

3.1 Mikä on algoritmi?

Pyrittäessä kirjoittamaan koneelle kelpaavia ohjeita joudutaan suoritettavana oleva toimenpide kirjaamaan sarjana yksinkertaisia toimenpiteitä. Toimenpidesarjan tulee olla yksikäsitteinen, eli sen tulee joka tilanteessa tarjota yksi ja vain yksi tapa toimia, eikä siinä saa esiintyä ristiriitaisuuksia. Yksikäsitteistä kuvausta tehtävän ratkaisuun tarvittavista toimenpiteistä kutsutaan algoritmiksi.

Ohjelman kirjoittaminen voidaan aloittaa hahmottelemalla tarvittavat algoritmit eli kirjaamalla lista niistä toimenpiteistä, joita tehtävän suoritukseen tarvitaan:

Kahvin keittäminen:

1.  Täytä pannu vedellä.
2.  Keitä vesi.
3.  Lisää kahvijauhot.
4.  Anna tasaantua.
5.  Tarjoile kahvi.

Algoritmi on yleisesti ottaen mahdollisimman pitkälle tarkennettu toimenpidesarja, jossa askel askeleelta esitetään yksikäsitteisessä muodossa ne toimenpiteet, joita asetetun ongelman ratkaisuun tarvitaan.

3.2 Tarkentaminen

Kun tarkastellaan lähes mitä tahansa tehtävänantoa, huomataan, että tehtävän suoritus koostuu selkeästi toisistaan eroavista osatehtävistä. Se, miten yksittäinen osatehtävä ratkaistaan, ei vaikuta muiden osatehtävien suorittamiseen. Vain sillä, että kukin osasuoritus tehdään, on merkitystä. Esimerkiksi pannukahvinkeitossa jokainen osatehtävä voidaan jakaa edelleen osasiin:

Kahvinkeitto:

1.  Täytä pannu vedellä:
  1.1.  Pistä pannu hanan alle.
  1.2.  Avaa hana.
  1.3.  Anna veden valua, kunnes vettä on riittävästi.
  1.4   Sulje hana.
2.  Keitä vesi:
  2.1.  Aseta pannu hellalle.
  2.2.  Kytke virta keittolevyyn.
  2.3.  Anna lämmetä, kunnes vesi kiehuu.
  2.4   Sammuta virta.
3.  Lisää kahvinporot:
  3.1.  Mittaa kahvinporot.
  3.2.  Sekoita kahvinporot kiehuvaan veteen.
4.  Anna tasaantua:
  4.1.  Odota, kunnes suurin osa valmiista kahvista on vajonnut
        pannun pohjalle.
5.  Tarjoile kahvi:
  5.1.  Tämä sitten onkin jo oma tarinansa...

Edellä esitetyn kahvinkeitto-ongelman ratkaisu esitettiin jakamalla ratkaisu viiteen osavaiheeseen. Ratkaisun algoritmi sisältää viisi toteutettavaa lausetta. Kun näitä viittä lausetta tarkastellaan lähemmin, osoittautuu, että niistä kukin on edelleen jaettavissa osavaiheisiin, eli ratkaisun pääalgoritmi voidaan jakaa edelleen alialgoritmeiksi, joissa askel askeleelta esitetään, kuinka kukin osatehtävä ratkaistaan.

Algoritmien kirjoittaminen osoittautuu hierarkkiseksi prosessiksi, jossa aluksi tehtävä jaetaan osatehtäviin, joita edelleen tarkennetaan, kunnes kukin osatehtävä on niin yksinkertainen, ettei sen suorittamisessa enää ole mitään moniselitteistä.

3.3 Yleistäminen

Eräs tärkeä algoritmien kirjoittamisen vaihe on yleistäminen. Tällöin valmiiksi tehdystä algoritmista pyritään paikantamaan kaikki alunperin annetusta tehtävästä riippuvat tekijät, ja pohditaan voitaisiinko ne kenties kokonaan poistaa tai korvata joillakin yleisemmillä tekijöillä.

3.4 Harjoitus

#

Tehtävä 3.1 Teen keittäminen

Tarkastele edellä esitettyä algoritmia kahvin keittämiseksi ja luo vastaava algoritmi teen keittämiseksi. Vertaile algoritmeja: mitä samaa ja mitä eroa niissä on? Onko mahdollista luoda algoritmi, joka yksiselitteisesti selviäisi sekä kahvin että teen keitosta? Onko mahdollista luoda algoritmi, joka saman tien selviytyisi maitokaakosta ja rommitotista?

 

3.5 Peräkkäisyys

Kuten luvussa 1 olevassa reseptissä ja muissakin ihmisille kirjoitetuissa ohjeissa, niin myös tietokoneelle esitetyt ohjeet luetaan ylhäältä alaspäin, ellei muuta ilmoiteta. Esimerkiksi ohjeen lumiukon piirtämisestä voisi esittää yksinkertaistettuna alla olevalla tavalla.

Piirrä säteeltään 20cm kokoinen ympyrä koordinaatiston pisteeseen (20, 80)
Piirrä säteeltään 15cm kokoinen ympyrä edellisen ympyrän päälle
Piirrä säteeltään 10cm kokoinen ympyrä edellisen ympyrän päälle

Yllä oleva koodi ei ole vielä mitään ohjelmointikieltä, mutta se sisältää jo ajatuksen siitä, kuinka lumiukko voitaisiin tietokoneella piirtää. Piirrämme lumiukon C#-ohjelmointikielellä seuraavassa luvussa.

#

Tässä yritetään lisätä palloa ennen kuin se on luotu. Se ei ole mahdollista ja siksi ohjelma ei käänny.

        Add(pallo);
        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200,200,Shape.Circle);
        pallo.Color = Color.Yellow;
        // Siirrä pallon lisäys tänne (eli eka rivi Add(pallo);)

 

#

Tässä määritellään taustaväri ja olion väri useaan kertaan. Viimeisin jää voimaan.

        Level.Background.Color = Color.Black;
        Level.Background.Color = Color.Blue;
        PhysicsObject pallo = new PhysicsObject(200,200,Shape.Circle);
        pallo.Color = Color.Yellow;
        pallo.Color = Color.Black;
        Add(pallo);

 

Otetaan seuraavaksi esimerkki eräästä algoritmista. Oletetaan, että sinulla on tilanne, jossa on taulukko lukuja ja kaikille taulukon luvuille pitäisi saada sama arvo kuin taulukon ensimmäiselle luvulle. Voit seuraavassa tehtävässä tehdä tälle “algoritmin” käyttämällä Tauno-ohjelmaa (=TAUlukot NOhevasti).

Taunossa raahaa taulukon alkioita niin, että sinulla on lopuksi haluamasi tulos. Katso samalla minkälaista koodia Tauno sinulle generoi. Tämä on C#-kielinen algoritmi tehtävän tekemiseksi. Jos haluat aloittaa Tauno-tehtävän alusta, piilota ja näytä Tauno uudelleen.

#
Taunon käytöstä löytyy myös video Luento 1 (3m10s)
#

Tehtävä 3.2 Kaikki alkiot samoiksi

Tee Taunolla ohjelma, jolla kaikki alkiot ovat samoja kuin taulukon vasemmanpuoleisin alkio.

 

Mieti onko edellä tekemäsi Tauno-vastaus sellainen, missä suoritettavien lauseiden järjestyksen saisi vaihtaa? Jos on, koodi on rinnakkaistuvaa, jos järjestyksen vaihtaminen taas rikkoisi “algortimin”, niin koodi on puhtaasti peräkkäistä.

#

Tee Taunolla ohjelma, jolla kolme ensimmäistä alkiota ovat samoja kuin ensimmäinen alkio ja kolme viimeistä samoja kuin viimeinen.

 

#

4. Yksinkertainen graafinen C#-ohjelma

Seuraavissa esimerkeissä käytetään Jyväskylän yliopistossa kehitettyä Jypeli-ohjelmointikirjastoa. Alunperin kirjasto suunniteltiin ja toteutettiin Nuorten Peliohjelmointi -kurssille, mutta sen todettiin hyvin sopivan myös Ohjelmointi 1 -tasoiselle kurssille. Kirjaston voit ladata koneelle osoitteesta

Vaikka tässä kohtaa emme vielä Visual Studio -kehitysympäristöä tarvitsekaan, on sen asentaminen tässä kohtaa viisasta, sillä Visual Studio on asennettava ennen MonoGamen ja Jypelin asentamista.

4.1 Mikä on kirjasto?

C#-ohjelmat koostuvat luokista. Luokat taas sisältävät metodeja (ja aliohjelmia/funktioita), jotka suorittavat tehtäviä ja mahdollisesti palauttavat arvoja suoritettuaan näitä tehtäviä. Metodi voisi esimerkiksi laskea kahden luvun summan ja palauttaa tuloksen tai piirtää ohjelmoijan haluaman kokoisen ympyrän. Samaan asiaan liittyviä metodeja kootaan luokkaan ja luokkia kootaan edelleen kirjastoiksi. Idea kirjastoissa on, ettei kannata tehdä uudelleen sitä minkä joku on jo tehnyt. Toisin sanoen, pyörää ei kannata keksiä uudelleen.

C#-ohjelmoijan kannalta oleellisin kirjasto on .NET Framework luokkakirjasto. Luokkakirjaston dokumentaatioon (documentation) kannattaa tutustua, sillä sieltä löytyy monia todella hyödyllisiä metodeja. Dokumentaatio löytyy Microsoftin sivuilta osoitteesta

https://docs.microsoft.com/fi-fi/dotnet/index

30 Jan 19

Luokkadokumentaatio = Sisältää tiedot kaikista kirjaston luokista ja niiden metodeista (ja aliohjelmista). Löytyy useimmiten ainakin WWW-muodossa.

#

Tehtävä 4.1 console-luokan metodit

Etsi System-nimiavaruuden Console-luokka. Mitä muita metodeja Console-luokalla on kuin WriteLine() ? Mitä tekee Write?

 

4.2 Jypeli-kirjasto

Jypeli-kirjaston kehittäminen aloitettiin Jyväskylän yliopistossa keväällä 2009. Tämän monisteen esimerkeissä käytetään versiota 4. Jypeli-kirjastoon on kirjoitettu valmiita luokkia ja metodeja siten, että esimerkiksi fysiikan ja matematiikan ilmiöiden, sekä pelihahmojen ja liikkeiden ohjelmointi lopulliseen ohjelmaan on helpompaa.

4.3 Esimerkki: Lumiukko

Käännösongelma: error CS0246: The type or namespace name ‘Jypeli’ could not be found. Komentoriviltä juu VL: missä yhteydessä? Jos komentoriviltä, niin tee pääteohjaus 1.

11 Sep 20 (edited 11 Sep 20)
#
Luentovideolta voi katsoa kuinka yksinkertaisen olion saa aikaiseksi: Video (8m34s)
#
Piirretään lumiukko käyttämällä Jypeli-kirjastoa. Katso sen tekeminen videolta Luento 2 (6m43s)
#
// Otetaan käyttöön Jyväskylän yliopiston Jypeli-kirjasto
using Jypeli;

/// @author  Vesa Lappalainen, Antti-Jussi Lakanen
/// @version 22.12.2011
///
///
/// <summary>
/// Luokka, jossa harjoitellaan piirtämistä lisäämällä ympyröitä ruudulle
/// </summary>
public class Lumiukko : PhysicsGame
{

  /// <summary>
  /// Pääohjelmassa laitetaan "peli" käyntiin Jypelille tyypilliseen tapaan
  /// </summary>
  public static void Main()
  {
    using (Lumiukko peli = new Lumiukko())
    {
      peli.Run();
    }
  }

  /// <summary>
  /// Piirretään oliot ja zoomataan kamera niin että kenttä näkyy kokonaan.
  /// </summary>
  public override void Begin()
  {
    Camera.ZoomToLevel();
    Level.Background.Color = Color.Black;

    PhysicsObject p1 = new PhysicsObject(2*100.0, 2*100.0, Shape.Circle);
    p1.Y = Level.Bottom + 200.0;
    Add(p1);

    PhysicsObject p2 = new PhysicsObject(2 * 50.0, 2 * 50.0, Shape.Circle);
    p2.Y = p1.Y + 100 + 50;
    Add(p2);

    PhysicsObject p3 = new PhysicsObject(2 * 30.0, 2 * 30.0, Shape.Circle);
    p3.Y = p2.Y + 50 + 30;
    Add(p3);
  }
}

 

Myöhemmässä selostuksessa viitataan tämän ohjelman rivinumeroihin. Ne saat näkyviin kun painat Highlight-linkkiä.

Ajettaessa ohjelman tulisi piirtää yksinkertainen lumiukko keskelle ruutua, kuten alla olevassa kuvassa.

#

Kuva 2: Lumiukko Jypeli-kirjaston avulla piirrettynä

Jatkoa varten hieman lyhennämme ohjelmaa ja aina samanlaisena toistuvan pääohjelman kirjoitamme omaan erilliseen tiedostoonsa. Näin voimme paremmin keskittyä pelkästään itse ongelmaan. Kokeile lisätä lumiukkoon neljäs pallo.

#

Tehtävä 4.2 neljäs pallo

Lisää lumiukkoon neljäs pallo

        Camera.ZoomToLevel(); // tai Camera.ZoomToAllObjects();
        Level.Background.Color = Color.Black;

        PhysicsObject p1 = new PhysicsObject( 2*100, 2*100, Shape.Circle);
        p1.X = 0; p1.Y = Level.Bottom + 200;
        Add(p1);

        PhysicsObject p2 = new PhysicsObject( 2*50, 2*50, Shape.Circle );
        p2.X = 0; p2.Y = p1.Y + 100 + 50;
        Add(p2);

        PhysicsObject p3 = new PhysicsObject( 2*30, 2*30, Shape.Circle );
        p3.X = 0; p3.Y = p2.Y + 50 + 30;
        Add(p3);

 

4.3.1 Ohjelman suoritus

Ohjelman suoritus aloitetaan aina pääohjelman avaavasta aaltosulusta, ja sitten edetään rivi riviltä ylhäältä alaspäin aina pääohjelman sulkevaan aaltosulkuun saakka. Pääohjelmassa (samoin kuin kaikissa muissakin aliohjelmissa) voi olla myös aliohjelmakutsuja, jolloin siirrytään pääohjelmasta suorittamaan aliohjelmaa ja palataan sitten takaisin pääohjelman (kutsuvan aliohjelman) suoritukseen. Aliohjelmista puhutaan enemmän luvussa 6. Itse asiassa edellisissä esimerkeissäkin kutsu Add(p1) oli aliohjelmakutsu.

Tarkastellaan ohjelman oleellisimpia kohtia.

02 using Jypeli;

Aluksi meidän täytyy kertoa kääntäjälle, että haluamme ottaa käyttöön koko Jypeli-kirjaston. Nyt Jypeli-kirjaston kaikki luokat (ja niiden metodit) ovat käytettävissämme. Itse asiassa meidän ei olisi pakko kirjoittaa tätä using-lausetta. Mutta jos jätämme sen pois, ei kääntäjä enää tunne mikä on esimerkiksi sana PhysicsGame. Ongelma voitaisiin kiertää sanomalla että se löytyy kirjastosta Jypeli:

11 public class Lumiukko : Jypeli.PhysicsGame

Ja samalla tavalla Jypeli. pitäisi lisätä kaikkien muidenkin Jypelissä olevien sanojen eteen. Eli helpotamme omaa kirjoittamistamme sanomalla, että käytetään Jypeliä. Itse asiassa, jos olisimme HelloWorld.cs -tiedostossa sanoneet alussa:

using System;

olisi riittänyt kirjoittaa tulostamista varten:

    Console.WriteLine("Hello World!");

Mutta jatketaan ohjelman tutkimista:

08 /// <summary>
09 /// Luokka, jossa harjoitellaan piirtämistä lisäämällä ympyröitä ruudulle
10 /// </summary>
11 public class Lumiukko : PhysicsGame
12 {

Rivit 8-10 ovat dokumentaatiokommentteja. Rivillä 11 luodaan Lumiukko-luokka, joka hieman poikkeaa HelloWorld-esimerkin tavasta luoda uusi luokka. Tässä kohtaa käytämme ensimmäisen kerran Jypeli-kirjastoa, ja koodissa kerrommekin, että Lumiukko-luokka, jota juuri olemme tekemässä, “perustuu” Jypeli-kirjastossa olevaan PhysicsGame-luokkaan. Täsmällisemmin sanottuna Lumiukko-luokka peritään PhysicsGame-luokasta. Näin Lumiukko-luokka saa käyttöönsä kaikki PhysicsGame-luokan ominaisuudet ja voi itse lisätä siihen uusia ominaisuuksia. Tässä lisäämme tuon Begin-metodin toiminnan, eli mitä “pelin” alussa piirretään. Begin onkin tavallaan Jypeli-ohjelman “pääohjelma”.

Tuon PhysicsGame-luokan avulla objektien piirtäminen, myöhemmin liikuttelu ruudulla ja fysiikan lakien hyödyntäminen on vaivatonta.

14   /// <summary>
15   /// Pääohjelmassa laitetaan "peli" käyntiin Jypelille tyypilliseen tapaan.    
16   /// </summary>
17   public static void Main()
18   {
19     using (Lumiukko peli = new Lumiukko())
20     {
21       peli.Run();
22     }
23   }

Myös Main-metodi, eli pääohjelma, on Jypeli-peleissä käytännössä aina tällainen vakiomuotoinen, joten jatkossa siihen ei tarvitse juurikaan koskea. Ohitamme tässä vaiheessa pääohjelman sisällön mainitsemalla vain, että pääohjelmassa Lumiukko-luokasta luodaan uusi olio (eli uusi “peli”), joka sitten laitetaan käyntiin peli.Run()-kohdassa. Jypeli-kirjaston rakenteesta johtuen kaikki varsinainen peliin liittyvä koodi kirjoitetaan omiin aliohjelmiinsa. Seuraavaksi käsiteltävään Begin-aliohjelmaankirjoitetaan se, mitä tapahtuu “pelin” alkaessa.

Tarkasti ottaen Begin alkaa riviltä 29. Ensimmäinen lause on kirjoitettu riville 30.

30     Camera.ZoomToLevel();
31     Level.Background.Color = Color.Black;

Näistä kahdesta rivistä ensimmäisellä kutsutaan Camera-olion ZoomToLevel-aliohjelmaa, joka pitää huolen siitä, että “kamera” on kohdistettuna ja zoomattuna oikeaan kohtaan. Aliohjelma ei ota vastaan parametreja, joten sulkujen sisältö jää tyhjäksi. Toisella rivillä muutetaan taustan väri.

Huomattakoon että Camera ja Level -oliot ovat Lumiukko-luokasta luodun pelin (pääohjelmassa peli) omia olioita. Oikeastaan pitäisikin kirjoittaa:

30     this.Camera.ZoomToLevel();
31     this.Level.Background.Color = Color.Black;

mutta viitattaessa olion omiin ominaisuuksiin, voidaan this. -itseviittaus jättää kirjoittamatta. Jotkut ohjelmoijat kirjoittavat silti selvyyden vuoksi myös tuon itseviittauksen näkyviin, vaikka sitä ei välttämättä tarvittaisi. Tämä on tyypillinen makuasia ohjelmoinnissa.

Kun olion konstruktorissa on samanniminen parametri kuin jokin luokan attribuutti, esim. “name”, viittaako konstruktorin sisällä this.name luokan attribuuttiin?

Esimerkiksi jos konstruktorissa sanottaisiin ‘this.name = name’, niin silloin luokan name-attribuuttiin sijoitettaisiin konstruktorin name-muuttuja?

22 Apr 20
33     PhysicsObject p1 = new PhysicsObject(2*100, 2*100, Shape.Circle);
34     p1.Y = Level.Bottom + 200;
35     Add(p1);

Näiden kolmen rivin aikana luomme uuden fysiikkaolio-ympyrän, annamme sille säteen, y-koordinaatin, sekä lisäämme sen “pelikentälle”, eli näkyvälle alueelle valmiissa ohjelmassa. Jos x-koordinaatin (tai y-koordinaatin) arvoa ei anneta, on se oletuksena 0.

Tarkemmin sanottuna luomme uuden PhysicsObject-olion eli PhysicsObject-luokan ilmentymän, johon viittaavan muuttujan nimeksi annamme p1. PhysicsObject-oliot ovat pelialueella liikkuvia olioita, jotka noudattavat fysiikan lakeja. Sulkujen sisään laitamme tiedon siitä, millaisen objektin haluamme luoda - tässä tapauksessa leveys ja korkeus (Jypeli-mitoissa, ei pikseleissä), sekä olion muoto. Teemme siis ympyrän (Circle), jonka säde on 100 (leveys = 2 * 100 ja korkeus = 2 * 100). Muita Shape-kokoelmasta löytyviä muotoja ovat muiden muassa kolmio (Triangle), ellipsi (Ellipse), suorakaide (Rectangle), sydän (Heart) jne. Olioista puhutaan lisää luvussa 8.

#

Tehtävä 4.3 olion muoto

Kokeile muuttaa olion muotoa:

        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.Color = Color.Yellow;
        Add(pallo);

 

#

Kokeile muuttaa olion kokoa:

        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.Color = Color.Yellow;
        Add(pallo);

 

Seuraavalla rivillä asetetaan olion paikka Y-arvon avulla:

34     p1.Y = Level.Bottom + 200;

Huomaa että Y kirjoitetaan isolla kirjaimella. Tämä on p1-olion ominaisuus eli attribuutti. X-koordinaattia meidän ei tarvitse tässä erikseen asettaa, se on oletusarvoisesti 0 ja se kelpaa meille. Saadaksemme ympyrät piirrettyä oikeille paikoilleen, täytyy meidän laskea koordinaattien paikat. Oletuksena ikkunan keskipiste on koordinaatiston origo eli piste (0, 0). x-koordinaatin arvot kasvavat oikealle ja y:n arvot ylöspäin, samoin kuin “normaalissa” koulusta tutussa koordinaatistossa.

#

Kokeile muuttaa olion X- ja Y-koordinaatteja. Kuinka saisit olion oikeaan yläkulmaan?

        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.Color = Color.Yellow;
        pallo.Y = 0;
        pallo.X = 0;
        Add(pallo);

 

Koordinaatti voidaan antaa myös vektori-muodossa, jolloin annetaan koordinaatin molemmat komponentit samalla kertaa. Esimerkiksi edellisessä tehtävässä pallo voitaiisiin sijoittaa paikkaan x=20, y=50 myös koodilla:

#
//
        pallo.Position = new Vector(20,50);

 

Peliolio täytyy aina lisätä kentälle, ennen kuin se saadaan näkyviin. Tämä tapahtuu Add-metodin avulla, joka ottaa parametrina kentälle lisättävän olion nimen (tässä p1).

35    Add(p1);

Tarkkaan ottaen tässäkin pitäisi kirjoittaa että lisäämme olion tähän peliin, eli:

35    this.Add(p1);

mutta kuten edellä sanottiin, itseviittaukset voidaan jättää myös kirjoittamatta.

#

Tästä esimerkistä puuttuu Add-metodin kutsu, eikä kentälle siksi lisätä mitään. Lisää metodin kutsu koodin loppuun ja aja ohjelma uudelleen. Kokeile laittaa kutsun eteen myös itseviittaus this.

        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.Color = Color.Yellow;

 

Metodeille annettavia tietoja sanotaan parametreiksi (parameter). ZoomToLevel-metodi ei ota vastaan yhtään parametria, mutta Add-metodi sen sijaan ottaa yhden parametrin: PhysicsObject-tyyppisen olion, joka halutaan kentälle lisätä. Add-metodille voidaan antaa toinenkin parametri: tasonnumero, jolle olio lisätään. Tasojen avulla voidaan hallita, mitkä oliot lisätään päällimmäiseksi. Tasoparametri voidaan kuitenkin jättää antamatta, jolloin ohjelma itse päättää tasojen parhaan järjestyksen.

#

Tässä on tehty kaksi oliota, mutta toinen peittää toisen. Olioiden tasonumerot ovat samat (0) ja siksi neliö peittää pallo-olion. Vaihda pallon tasonumeroksi 1 ja aja ohjelma uudelleen.

        Level.Background.Color = Color.Black;

        PhysicsObject nelio = new PhysicsObject(200, 200, Shape.Rectangle);
        Add(nelio, 0);

        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.Color = Color.Red;
        Add(pallo, 0);

 

Parametrit kirjoitetaan metodin nimen perään sulkeisiin ja ne erotetaan toisistaan pilkuilla.

MetodinNimi(parametri1, parametri2,..., parametriX);

Seuraavien rivien aikana luomme vielä kaksi ympyrää vastaavalla tavalla, mutta vaihtaen sädettä ja ympyrän koordinaatteja.

Lumiukko-esimerkissä koordinaattien laskemiseen on käytetty C#:n aritmeettisia operaatioita. Voisimme tietenkin laskea koordinaattien pisteet myös itse, mutta miksi tehdä niin, jos tietokone voi laskea pisteet puolestamme? C#:n aritmeettiset perusoperaatiot ovat summa (+), vähennys (-), kerto (*), jako (/) ja jakojäännös (%). Aritmeettisista operaatioista puhutaan lisää muuttujien yhteydessä kohdassa 7.7.1.

Keskimmäinen ympyrä tulee alimman ympyrän yläpuolelle niin, että ympyrät sivuavat toisiaan. Keskimmäisen ympyrän keskipiste sijoittuu siis siten, että sen x-koordinaatti on 0 ja y-koordinaatti on alimman ympyrän paikka +alimman ympyrän säde + keskimmäisen ympyrän säde. Kun haluamme, että keskimmäisen ympyrän säde on 50, niin silloin keskimmäisen ympyrän keskipiste tulee kohtaan (0, p1.Y + 100 + 50) ja se piirretään lauseella:

PhysicsObject p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
p2.Y = p1.Y + 100 + 50;
Add(p2);

Huomaa, että fysiikkaolion Y-ominaisuuden asettamisen (set) lisäksi voimme myös lukea tai pyytää (get) kyseisen ominaisuuden arvon. Yllä teemme sen kirjoittamalla yksinkertaisesti sijoitusoperaattorin oikealle puolelle p1.Y.

Seuraava kuva havainnollistaa ensimmäisen ja toisen pallon asettelua.

#

Kuva 3: Lumiukon kaksi ensimmäistä palloa asemoituina paikoilleen.

Ylin ympyrä sivuaa sitten taas keskimmäistä ympyrää. Harjoitustehtäväksi jätetään laskea ylimmän ympyrän koordinaatit, kun ympyrän säde on 30.

Kaikki tiedot luokista, luokkien metodeista sekä siitä mitä parametreja metodeille tulee antaa löydät käyttämäsi kirjaston dokumentaatiosta. Jypelin luokkadokumentaatio löytyy osoitteesta:

#

Tehtävä 4.4 olion paikka vektorilla

Kokeile olion paikan vaihtamista kutsulla
pallo.Position=new Vector(jokux,jokuy)

        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.Color = Color.Yellow;
        Add(pallo);

 

#

Tässä ohjelma piirtää nopan. Kokeile muuttaa nopalle muita silmälukuja.

       Level.Background.Color = Color.Black;

       double koko = 200;
       PhysicsObject nelio = new PhysicsObject(koko, koko, Shape.Rectangle);
       Add(nelio);

       PhysicsObject simmu1 = new PhysicsObject(koko/4, koko/4, Shape.Circle);
       simmu1.Color = Color.Black;
       simmu1.X = nelio.X - koko/4;
       Add(simmu1,1);

       PhysicsObject simmu2 = new PhysicsObject(koko/4, koko/4, Shape.Circle);
       simmu2.Color = Color.Black;
       simmu2.X = nelio.X + koko/4;
       Add(simmu2,1);

 

Minulla ei Visual Studiolla tule ruudulle mitään, kun ajan tämän koodin. Sen sijaan TIMissä homma pelittää kuten pitää. Sen verran sain paikannettua ongelmaa, että kun käytän simmun X akselissa tuota “nelio.X” arvoa mukana, niin se jotenkin estää sitten kaikkien layereiden piirtymisen siinä kohdassa.

17 Jan 19

Laita tuo koodi Begin-aliohjelman sisään (aaltosulkeiden väliin) niin pitäisi toimia. Antti-Jussi

17 Jan 19
#

4.4 Harjoitus

Etsi Jypeli-kirjaston dokumentaatiosta RandomGen-luokka. Mitä tietoa löydät
NextInt(int min, int max)-metodista?
Mitä muita metodeja luokassa on?

#

 

#

Tehtävä 4.5 paikan arvonta

Tutki miten pallo sijoittuu eri ajokerroilla. Kokeile osaatko laittaa pallolle satunnaisen värin.

        Level.Background.Color = Color.Black;
        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.X = RandomGen.NextInt(-200, 200);
        pallo.Y = RandomGen.NextInt(-200, 200);
        Add(pallo);
        System.Console.WriteLine(pallo.X +" " + pallo.Y);

 

Miten pallon värin saa satunnaisesti vaihtumaan?

  • VL: sitähän tässä kysytään ja vastaus löytyy Jypelin ohjeista.
19 Sep 18 (edited 20 Sep 18)

Seuraavassa esimerkissä on kerrottu, miten käytetään suoraan C#-kirjaston satunnaislukugeneraattoria. Esimerkki ajetaan Mono-järjestelmän alla ja se antaa satunnaisluvun osalta erilaisia tuloksia kuin .net -ajoympäristössä. Esimerkki näyttää että Mono-ympäristön satunnaisluku ei toimi hyvin. Jypelin satunnaisluvussa alla oleva ongelma on korjattu, samoin Windowsin .net-järjestelmässä ajettuna toimii oikein.

#

Tehtävä 4.6 jakauma

Alla olevalla koodilla tutkitaan minkälainen jakauma tulee, kun arvotaan lukuja välille [0,MAX[. Kokeile. Miten kävisi, jos tekisit rahanheittopelin?

       int MAX = 6;
       System.Random rnd = new System.Random();
       int[] t = new int[MAX];
       for (int i=0;i<1000; i++)
       {
           int n = rnd.Next(0,MAX);
           t[n]++;
       }
       System.Console.WriteLine(string.Join(" ",t));

 

#

Kun ajat edellisen ohjelman tulostuu taulukon t alkiot. Kukin niistä vastaa sitä, kuinka monta kertaa tämä luku arvottiin. Miltä noiden suhde näyttää?

 

4.5 Kääntäminen ja luokkakirjastoihin viittaaminen

Jotta Lumiukko-esimerkkiohjelma voitaisiin nyt kääntää C#-kääntäjällä, tulee Jypeli-kirjasto olla tallennettuna tietokoneelle. Jypeli käyttää MonoGame-kirjaston lisäksi vapaan lähdekoodin fysiikka- ja matematiikkakirjastoja. Fysiikka- ja matematiikkakirjastot ovat sisäänrakennettuina Jypeli-kirjastoon.

Ennen komentoriviltä kääntämistä kopioi seuraavat tiedostot kurssin kotisivuilta (ks: lumiukko) samaan kansioon Lumiukko.cs-tiedoston kanssa.

  • Jypeli.dll
  • Jypeli.Physics2d.dll
  • MonoGame.Framework.dll
  • SharpDX-kirjastot

Meidän täytyy vielä välittää kääntäjälle tieto siitä, että Jypeli-kirjastoa tarvitaan Lumiukko-koodin kääntämiseen. Tämä tehdään csc-ohjelman /reference-parametrin avulla. Lisäksi tarvitaan referenssi Jypelin käyttämään MonoGame-kirjastoon. Kirjoita nyt komentoriville (kaikki rivit samalle riville niin, että /-viivan edessä on yksi välilyönti)

Huomaa, että ;-merkin jälkeen EI TULE välilyöntiä. Sama pätee myös myöhemmin luotavaan csk.bat-tiedostoon.

04 Sep 15 (edited 04 Sep 15)
csc Lumiukko.cs /reference:Jypeli.dll;Jypeli.Physics2d.dll;MonoGame.Framework.dll

Jos tämän tekee PowerShellillä niin puolipisteet pitää eskapoida `-merkillä, ts. komento on

csc Lumiukko.cs /reference:Jypeli.dll`;Jypeli.Physics2d.dll`;MonoGame.Framework.dll
08 Sep 18

Jos käyttöjärjestelmäsi ei tunnista csc-komentoa, niin kertaa luvussa 2 olevat ohjeet komennon asettamisesta PATH-ympäristömuuttujan poluksi.

Vinkki! Yllä esitelty kääntämiskomento on varsin pitkä. Asioiden helpottamiseksi voit kirjoittaa tiedoston csk.bat (vaikkapa hakemistoon c:\bat\, joka sisältää seuraavat 2 riviä tekstiä (liimaa noista kaksi @-merkillä alkavaa riviä ilman että liimauksessa tulee yhtään uutta välilyöntiä):

@"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc" %* /nologo /reference:Jypeli.dll;
Jypeli.Physics2d.dll;
MonoGame.Framework.dll
@if NOT ERRORLEVEL 1 %~n1

eli:

@"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc" %* /nologo /reference:Jypeli.dll;Jypeli.Physics2d.dll;MonoGame.Framework.dll
@if NOT ERRORLEVEL 1 %~n1

Tämä asettaa puolestasi reference ja platform -parametrit. Varmista, että tekemäsi csk.bat-tiedosto on “polussa” tai edes samassa hakemistossa Lumiukko.cs:än kanssa. Tämän jälkeen kääntäminen onnistuu yksinkertaisemmin:

csk OhjelmanNimi.cs

#
Sama asia käsiteltynä luennolla: Luento 2 (7m21s)

Lisätietoa kurssin lisätietosivuilla.

5. Lähdekoodista prosessorille

5.1 Kääntäminen

Tarkastellaan nyt tarkemmin sitä kuinka C#-lähdekoodi muuttuu lopulta prosessorin ymmärtämään muotoon. Kun ohjelmoija luo ohjelman lähdekoodin, joka käyttää .NET Framework -ympäristöä, tapahtuu kääntäminen sisäisesti kahdessa vaiheessa. Ohjelma käännetään ensin välikielelle, MSIL:lle (Microsoft Intermediate Language), joka ei ole vielä suoritettavissa millään käyttöjärjestelmällä. Tästä välivaiheen koodista käännetään ajon aikana valmis ohjelma halutulle käyttöjärjestelmälle ja prosessorille. Käyttöjärjestelmän voi olla esimerkiksi Windows, macOS, iOS, Android tai Linuxille. Prosessori voi olla esimerkiksi joku Intel x86-arkkitehtuurin mukainen prosessori tai mobiileissa vaikka ARM. Tämä ajonaikainen kääntäminen suoritetaan niin sanotulla JIT-kääntäjällä (Just-In-Time). JIT-kääntäjä muuntaa välivaiheen koodin juuri halutulle käyttöjärjestelmälle sopivaksi koodiksi nimenomaan ohjelmaa ajettaessa - tästä tulee nimi “just-in-time”.

Ennen ensimmäistä kääntämistä kääntäjä tarkastaa, että koodi on syntaksiltaan oikein. [VES][KOS]

Kääntäminen tehtiin Windowsissa komentorivillä (Command Prompt) käyttämällä komentoa

csc Tiedostonnimi.cs

tai hyödyntämällä edellisessä luvussa esiteltyä komentojonoa

csk Tiedostonnimi.cs

5.2 Suorittaminen

C# tuottaa siis lähdekoodista suoritettavan (tai “ajettavan”) tiedoston. Tämä tiedosto on käyttöjärjestelmäriippuvainen, ja suoritettavissa vain sillä alustalla, johon käännös on tehty. Toisin sanoen, Windows-ympäristössä käännetyt ohjelmat eivät ole ajettavissa macOS:ssa, ja toisin päin.

Toisin kuin C#, eräät toiset ohjelmointikielet tuottavat käyttöjärjestelmäriippumatonta koodia. Esimerkiksi Java-kielessä kääntäjän tuottama tiedosto on niin sanottua tavukoodia, joka on käyttöjärjestelmäriippumatonta koodia. Tavukoodin suorittamiseen tarvitaan Java-virtuaalikone (Java Virtual Machine). Java-virtuaalikone on oikeaa tietokonetta matkiva ohjelma, joka tulkkaa tavukoodia ja suorittaa sitä sitten kohdekoneen prosessorilla. Tässä on merkittävä ero perinteisiin käännettäviin kieliin (esimerkiksi C ja C++), joissa käännös on tehtävä erikseen jokaiselle eri laitealustalle. [VES][KOS]

#

6. Aliohjelmat

“Copy and paste is a design error.” - David Parnas

Pääohjelman lisäksi ohjelma voi sisältää muitakin aliohjelmia. Aliohjelmaa kutsutaan pääohjelmasta, metodista tai toisesta aliohjelmasta suorittamaan tiettyä tehtävää. Aliohjelmat voivat saada parametreja ja palauttaa arvon, kuten metoditkin. Aliohjelma voi kutsua toista aliohjelmaa ja joskus jopa itseään (tällöin puhutaan rekursiosta). Oikea ohjelma koostuu useista aliohjelmista joista jokainen suorittaa oman pienen tehtävänsä. Näin iso tehtävä voidaan jakaa joukoksi pienempiä helpommin hallittavia alitehtäviä.

Aliohjelmia tehdään, koska

  • niiden avulla voidaan jakaa ohjelma pienempiin osiin
  • niiden avulla voidaan jäsentää ohjelmaa
  • ne auttavat uudelleenkäytössä
  • pienemmät osat helpottavat testaamista

Nykyisten oliokielten oliot ovat oikeastaan kokoelma olion sisäisiä muuttujia (attribuutteja) ja niitä käsitteleviä aliohjelmia (metodeja). Lisäksi nykyisten kielten API (Application Programming Interface) on usein huomattavasti itse kieltä suurempi. Kieleen kuuluvien aliohjelmakirjastojen lisäksi usein käytetään sovelluskohtaisia kirjastoja, jotka voivat olla hyvinkin laajoja. Tällä kurssilla esimerkkinä tällaisesta on Jypeli. Valmiin kirjaston käyttö helpottaa ohjelman kirjoittajaa ja hänen ei tarvitse kirjoittaa itse kaikkea.

Toisaalta myös itse kirjoitetaan aliohjelmia. Käytännössä usein käy niin, että ohjelmaan kirjoitetaan osa, joka kohta toistuu lähes samanlaisena. Tällöin ohjelmoija pyrkii löytämään koodin yhteisen osan ja siirtää sen aliohjelmaksi. Jos toiminnat eivät samankaltaisissa osissa olleet täysin samanlaiset, toimitetaan ero aliohjelmille parametreina. Näin sama aliohjelma voi eri kutsuilla tehdä hieman eri asioita. Otamme tästä kohta esimerkin.

Toisaalta monesti aliohjelmia tulee myös siitä, että ohjelmaa kirjoitettaessa ajatellaan tyyliin: “nyt pitäisi löytää taulukon suurin luku”. Useimmiten ei ole järkevää tällöin lähteä itse etsimistä kirjoittamaan, vaan esitetään toive: “olisipa meillä aliohjelma joka tekee tuon”. Ja kirjoitetaan:

iso = Suurin(taulukko);

Myöhemmin sitten toteutetaan tuo Suurin -aliohjelma (funktio tässä tapauksessa, koska se palauttaa arvon). Nyt jos sama tehtävä pitää tehdä uudelleen, ei tarvitse enää kirjoittaa muuta kuin kutsu tuohon aliohjelmaan (uudelleenkäyttö).

Usein samaa aliohjelmaa kutsutaan ohjelmasta useita kertoja, mutta koodin selkeyden vuoksi voi olla järkevää kirjoittaa aliohjelmaksi myös itsenäisiä kokonaisuuksia (jäsentäminen), vaikkei niitä kutsuttaisikaan kuin kerran koko ohjelmasta.

Seuraavana esimerkki jäsentämisestä, uudelleenkäytöstä ja selkeyttämisestä.

Jos tehtävänämme olisi piirtää useampi lumiukko, niin tämänhetkisellä tietämyksellämme tekisimme todennäköisesti jonkin alla olevan kaltaisen ratkaisun.

#
using Jypeli;

/// <summary>
/// Piirretään lumiukko.
/// </summary>
public class Lumiukko : PhysicsGame
{

  /// <summary>
  /// Pääohjelmassa peli käyntiin.
  /// </summary>
  public static void Main()
  {
    using (Lumiukko game = new Lumiukko())
    {
      game.Run();
    }
  }

  /// <summary>
  /// Aliohjelma, jossa
  /// piirretään ympyrät.
  /// </summary>
  public override void Begin()
  {
    Camera.ZoomToLevel();
    Level.Background.Color = Color.Black;

    PhysicsObject p1, p2, p3;

    // Eka ukko
    p1 = new PhysicsObject(2 * 100, 2 * 100, Shape.Circle);
    p1.Y = Level.Bottom + 200;
    Add(p1);

    p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
    p2.Y = p1.Y + 100 + 50;
    Add(p2);

    p3 = new PhysicsObject(2 * 30, 2 * 30, Shape.Circle);
    p3.Y = p2.Y + 50 + 30;
    Add(p3);

    // Toinen ukko
    p1 = new PhysicsObject(2 * 100, 2 * 100, Shape.Circle);
    p1.X = 200;
    p1.Y = Level.Bottom + 300;
    Add(p1);

    p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
    p2.X = 200;
    p2.Y = p1.Y + 100 + 50;
    Add(p2);

    p3 = new PhysicsObject(2 * 30, 2 * 30, Shape.Circle);
    p3.X = 200;
    p3.Y = p2.Y + 50 + 30;
    Add(p3);
  }
}

 

Huomataan, että ensimmäisen ja toisen lumiukon piirtäminen tapahtuu lähes samanlaisella koodilla. Itse asiassa ainoa ero on, että jälkimmäisen lumiukon pallot saavat ensimmäisestä lumiukosta eroavat koordinaatit. Ensimmäinen vaihe on yrittää saada molempien lumiukkojen piirtämisestä täysin samanlainen koodi.

Aluksi voisimme kirjoittaa koodin niin, että lumiukon alimman pallon keskipiste tallennetaan muuttujiin x ja y. Näiden pisteiden avulla voimme sitten laskea muiden pallojen paikat. Määritellään heti alussa myös p1, p2 ja p3 PhysicsObject-olioiksi. Rivinumerointi on tässä jätetty pois selvyyden vuoksi. Luvun lopussa korjattu ohjelma esitellään kokonaisuudessaan rivinumeroinnin kanssa. Muistetaan lisäksi, että voimme kirjoittaa olion omiin ominaisuuksiin viitattaessa this -viitteen.

double x, y;
PhysicsObject p1, p2, p3;

// Tehdään ensimmäinen lumiukko
x = 0; y = Level.Bottom + 200;
p1 = new PhysicsObject(2*100, 2*100, Shape.Circle);
p1.X = x;
p1.Y = y;
this.Add(p1);

p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
p2.X = x;
p2.Y = y + 100 + 50; // y + 1. pallon säde + 2. pallon säde
this.Add(p2);

p3 = new PhysicsObject(2 * 30, 2 * 30, Shape.Circle);
p3.X = x;
p3.Y = y + 100 + 2 * 50 + 30; // y + 1. pallon säde + 2. halk. + 3. säde
this.Add(p3);

Vastaavasti toiselle lumiukolle: asetetaan vain x:n ja y:n arvot oikeiksi.

// Tehdään toinen lumiukko
x = 200; y = Level.Bottom + 300;
p1 = new PhysicsObject(2 * 100, 2 * 100, Shape.Circle);
p1.X = x;
p1.Y = y;
this.Add(p1);

p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
p2.X = x;
p2.Y = y + 100 + 50;
this.Add(p2);

p3 = new PhysicsObject(2 * 30, 2 * 30, Shape.Circle);
p3.X = x;
p3.Y = y + 100 + 2*50 + 30;
this.Add(p3);

Tarkastellaan nyt muutoksia hieman tarkemmin.

double x, y;

Yllä olevalla rivillä esitellään kaksi liukulukutyyppistä muuttujaa. Liukuluku on eräs tapa esittää reaalilukuja tietokoneissa. C#:ssa jokaisella muuttujalla on oltava tyyppi, ja eräs liukulukutyyppi C#:ssa on double. Muuttujista ja niiden tyypeistä puhutaan lisää luvussa 7.

Liukuluku (floating point) = Tietokoneissa käytettävä esitysmuoto reaaliluvuille. Tarkempaa tietoa liukuluvuista löytyy luvusta 26.

x = 0; y = Level.Bottom + 200;

Yllä olevalla rivillä on kaksi lausetta. Ensimmäisellä asetetaan muuttujaan x arvo 0 ja toisella muuttujaan y arvo 50 (jos Level.Bottom sattuu olemaan vaikka -150). Nyt voimme käyttää lumiukon pallojen laskentaan näitä muuttujia.

x = 200; y = Level.Bottom + 300;

Vastaavasti yllä olevalla rivillä asetetaan nyt muuttujiin uudet arvot, joita käytetään seuraavan lumiukon pallojen paikkojen laskemiseen. Huomaa, että y-koordinaatti saa negatiivisen arvon, jolloin lumiukon alimman pallon keskipiste painuu kuvaruudun keskitason alapuolelle.

Nyt alimman pallon x-koordinaatiksi sijoitetaankin muuttuja x, ja vastaavasti y-koordinaatin arvoksi asetetaan muuttuja y, ja muiden pallojen sijainnit lasketaan ensimmäisen pallon koordinaattien perusteella.

#

Kuva 4: Kaksi lumiukkoa

Näiden muutosten jälkeen molempien lumiukkojen varsinainen piirtäminen tapahtuu nyt täysin samalla koodilla rivistä x= eteenpäin.

Uusien lumiukkojen piirtäminen olisi nyt jonkin verran helpompaa, sillä meidän ei tarvitse kuin ilmoittaa ennen piirtämistä uuden lumiukon paikka, ja varsinainen lumiukkojen piirtäminen onnistuisi kopioimilla ja liittämällä koodia (copy-paste). Kuitenkin, jos koodia kirjoittaessa joutuu tekemään suoraa kopiointia, pitäisi pysähtyä miettimään, onko tässä mitään järkeä.

Kahden lumiukon tapauksessa tämä vielä onnistuu ilman, että koodin määrä kasvaa kohtuuttomasti, mutta entä jos meidän pitäisi piirtää 10 tai 100 lumiukkoa? Kuinka monta riviä ohjelmaan tulisi silloin? Kun lähes samanlainen koodinpätkä tulee useampaan kuin yhteen paikkaan, on useimmiten syytä muodostaa siitä oma aliohjelma. Koodin monistaminen moneen paikkaan lisäisi vain koodirivien määrää, tekisi ohjelman ymmärtämisestä vaikeampaa ja vaikeuttaisi testaamista.

Lisäksi jos monistetussa koodissa olisi vikaa, jouduttaisiin korjaukset tekemään myös useampaan paikkaan. Hyvän ohjelman yksi mitta (kriteeri) onkin, että jos jotain pitää muuttaa, niin kohdistuvatko muutokset vain yhteen paikkaan (hyvä) vai joudutaanko muutoksia tekemään useaan paikkaan (huono).

6.1 Aliohjelman kutsuminen

#
Parametrittoman metodin tekeminen Luento 2 (4m56s)
#
TeeLumiukko-metodi parametreilla Luento 2 (8m39s)
Näytelmä siitä mitä aliohjelman katsominen tarkoittaa Luento 3, 2018s (33m38s)

Haluamme siis aliohjelman, joka piirtää meille lumiukon tiettyyn pisteeseen. Kuten metodeille, myös aliohjelmalle viedään parametrien avulla sen tarvitsemaa tietoa. Parametreina tulisi viedä vain minimaaliset tiedot, joilla aliohjelman tehtävä saadaan suoritettua.

Sovitaan, että aliohjelmamme piirtää aina samankokoisen lumiukon haluamaamme pisteeseen. Mitkä ovat ne välttämättömät tiedot, jotka aliohjelma tarvitsee piirtääkseen lumiukon?

Aliohjelma tarvitsee tiedon mihin pisteeseen lumiukko piirretään. Viedään siis parametrina lumiukon alimman pallon keskipiste. Muiden pallojen paikat voidaan laskea tämän pisteen avulla. Lisäksi tarvitaan yksi Game-tyyppinen parametri, jotta aliohjelmaamme voisi kutsua myös toisesta ohjelmasta. Nämä parametrit riittävät lumiukon piirtämiseen.

Kun aliohjelmaa käytetään ohjelmassa, sanotaan, että aliohjelmaa kutsutaan. Kutsu tapahtuu kirjoittamalla aliohjelman nimi ja antamalla sille parametrit. Aliohjelmakutsun erottaa metodikutsusta vain se, että metodi liittyy aina tiettyyn olioon. Esimerkiksi pallo-olio p1 voitaisiin poistaa pelikentältä kutsumalla metodia Destroy(), eli kirjoittaisimme:

p1.Destroy();
#

Aja ensin ohjelma. Pitäisi piirtyä neliö ja ympyrä (pallo). Lisää ohjelman loppuun rivi, jolla tuhoat ympyrän kutsumalla Destroy-metodia ja aja uudelleen. Tuhoa vielä myös neliö.

//
        Level.Background.Color = Color.Black;
        PhysicsObject nelio = new PhysicsObject(200, 100, Shape.Rectangle);
        nelio.X = -200;
        nelio.Color = Color.Blue;
        Add(nelio, 0);

        PhysicsObject pallo = new PhysicsObject(200, 200, Shape.Circle);
        pallo.X = nelio.X + 250;
        pallo.Color = Color.Yellow;
        Add(pallo, 0);

 

Toisin sanoen metodeja kutsuttaessa täytyy ensin kirjoittaa sen olion nimi, jonka metodia kutsutaan, ja sen jälkeen pisteellä erotettuna kirjoittaa haluttu metodin nimi. Sulkujen sisään tulee luonnollisesti tarvittavat parametrit. Yllä olevan esimerkin Destroy-metodi ei ota vastaan yhtään parametria.

6.1.1 Aliohjelmakutsun kirjoittaminen

Päätetään, että aliohjelman nimi on PiirraLumiukko. Sovitaan myös, että aliohjelman ensimmäinen parametri on tämä peli, johon lumiukko ilmestyy (kirjoitetaan this). Toinen parametri on lumiukon alimman pallon keskipisteen x-koordinaatti ja kolmas parametri lumiukon alimman pallon keskipisteen y-koordinaatti. Tällöin kentälle voitaisiin piirtää lumiukko, jonka alimman pallon keskipiste on (0, Level.Bottom + 200), seuraavalla kutsulla:

PiirraLumiukko(this, 0, Level.Bottom + 200);

Kutsussa voisi myös ensiksi mainita sen luokan nimen mistä aliohjelma löytyy. Tällä kutsulla aliohjelmaa voisi kutsua myös muista luokista, koska määrittelimme Lumiukot-luokan julkiseksi (public).

Lumiukot.PiirraLumiukko(this, 0, Level.Bottom + 200);

Vaikka tämä muoto muistuttaa jo melko paljon metodin kutsua, on ero kuitenkin selvä. Metodia kutsuttaessa toimenpide tehdään aina tietylle oliolle, kuten p1.Destroy() tuhoaa juuri sen pallon, johon p1-olio viittaa. Pallojahan voi tietenkin olla myös muita erinimisiä (kuten esimerkissämme onkin). Alla olevassa aliohjelmakutsussa kuitenkin käytetään vain luokasta Lumiukot löytyvää PiirraLumiukko-aliohjelmaa.

Jos olisimme toteuttaneet jo varsinaisen aliohjelman, piirtäisi Begin meille nyt kaksi lumiukkoa.

/// <summary>
/// Kutsutaan PiirraLumiukko-aliohjelmaa
/// sopivilla parametreilla.
/// </summary>
public override void Begin()
{
  Camera.ZoomToLevel();
  Level.Background.Color = Color.Black;

  PiirraLumiukko(this, 0, Level.Bottom + 200);
  PiirraLumiukko(this, 200, Level.Bottom + 300);
}

Koska PiirraLumiukko-aliohjelmaa ei luonnollisesti vielä ole olemassa, ei ohjelmamme vielä toimi. Seuraavaksi meidän täytyy toteuttaa itse aliohjelma, jotta kutsut alkavat toimimaan.

Usein ohjelman toteutuksessa on viisasta edetä juuri tässä järjestyksessä: suunnitellaan aliohjelmakutsu ensiksi, kirjoitetaan kutsu sille kuuluvalle paikalle, ja vasta sitten toteutetaan varsinainen aliohjelman kirjoittaminen.

Lisätietoja aliohjelmien kutsumisesta löydät dokumentista Aliohjelmien kutsuminen:

6.2 Aliohjelman kirjoittaminen

Ennen varsinaista PiirraLumiukko-aliohjelman toiminnallisuuden kirjoittamista täytyy aliohjelmalle tehdä määrittely (kutsutaan myös esittelyksi, declaration). Kirjoitetaan määrittely aliohjelmalle, jonka kutsun jo teimme edellisessä alaluvussa.

Lisätään ohjelmaamme aliohjelman runko. Dokumentoidaan aliohjelma myös saman tien.

/// <summary>
/// Kutsutaan PiirraLumiukko-aliohjelmaa
/// sopivilla parametreilla.
/// </summary>
public override void Begin()
{
  Camera.ZoomToLevel();
  Level.Background.Color = Color.Black;

  PiirraLumiukko(this, 0, Level.Bottom + 200);
  PiirraLumiukko(this, 200, Level.Bottom + 300);
}

/// <summary>
/// Aliohjelma piirtää lumiukon
/// annettuun paikkaan.
/// </summary>
/// <param name="peli">Peli, johon lumiukko tehdään.</param>
/// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
/// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
public static void PiirraLumiukko(Game peli, double x, double y)
{
}

Alla oleva kuva selvittää aliohjelmakutsun ja aliohjelman määrittelyn sekä vastinparametrien yhteyttä.

#

Kuva 5: Aliohjelmakutsu ja aliohjelman vastinparametrit.

Aliohjelman toteutuksen ensimmäistä riviä

public static void PiirraLumiukko(Game peli, double x, double y)

sanotaan aliohjelman otsikoksi (header) tai esittelyriviksi. Otsikon alussa määritellään aliohjelman näkyvyys julkiseksi (public). Kun näkyvyys on julkinen, aliohjelmaa voidaan kutsua eli käyttää myös muissa luokissa.

Aliohjelma määritellään myös staattiseksi (static). Staattisen aliohjelman toteutuksessa ei voi käytää this-viitettä, sillä se ei ole minkään olion oma. Hyötynä on kuitenkin se, että silloin aliohjelmaa voidaan kutsua mistä tahansa ohjelman osasta ja se ei ole riippuvainen esimerkiksi tässä tapauksessa meidän pelistämme, vaan jonkin muunkin pelin tekijä voisi kutsua aliohjelmaa. Jos emme määrittelisi aliohjelmaa staattiseksi, olisi se metodi eli olion toiminto (ks. luku 8.5).

Staattisen aliohjelman pitää pystyä tekemään kaikki toimensa pelkästään parametreina tuodun tiedon perusteella.

Tosin staattinen aliohjelma voi käyttää myös staattisia (globaaleja) muuttujia ja vakioita. Staattisten muuttujien käyttö ei ole suositeltavaa. Vakioita voi toki käyttää.

Aliohjelmalle on annettu palautusarvoksi void, mikä tarkoittaa sitä, että aliohjelma ei palauta mitään arvoa. Aliohjelma voisi nimittäin myös lopettaessaan palauttaa jonkun arvon, jota tarvitsisimme ohjelmassamme. Tällaisista aliohjelmista puhutaan luvussa 9. void-määrityksen jälkeen aliohjelmalle on annettu nimeksi PiirraLumiukko.

Huomaa! C#:ssa aliohjelman nimet kirjoitetaan tyypillisesti isolla alkukirjaimella.

Huomaa! Aliohjelmien (ja metodien) nimien tulisi olla verbejä tai tekemistä ilmaisevia lauseita, esimerkiksi LuoPallo, Siirry, TormattiinEsteeseen.

Aliohjelman nimen jälkeen ilmoitetaan sulkeiden sisässä aliohjelman parametrit. Jokaista parametria ennen on ilmoitettava myös parametrin tietotyyppi. Parametrinä annettiin lumiukon alimman pallon x- ja y-koordinaatit. Molempien tietotyyppi on double, joten myös vastinparametrien tyyppien tulee olla double. Annetaan myös nimet kuvaavasti x ja y.

Vielä kertauksena esittelyrivin sanat:

public static void PiirraLumiukko(Game peli, double x, double y)
Sana Selitys
public aliohjelma on julkinen ja sitä voi kutsua kuka tahansa
static aliohjelma tarvitsee vain parametrinä tuotuja tietoja
void aliohjelma ei palauta mitään arvoa
PiirraLumiukko aliohjelmalle itse keksitty nimi
Game tietotyyppi pelille
peli itse keksitty nimi 1. parametrille
double tietotyyppi x-koordinaatille
x itse keksitty nimi x-koordinaatille (2. parametri), voisi olla muukin
double tietotyyppi y-koordinaatille
y itse keksitty nimi y-koordinaatille (3. parametri)

Miksi tietotyyppi Game kirjoitetaan isolla, mutta muut tietotyypit (double) pienellä?

18 Sep 16

Nämä ovat "C# Coding Convetions", eli sovittuja tapoja. Valmiit tietotyypit kirjoitetaan pienellä ja oliotietotyyppien (eli luokkien) nimet isolla.

18 Sep 16 (edited 14 Sep 17)

Ja nuo vasemmalla mainitut säännöt pätevät C#:lle, eivät muille kielille. Jokaisella kielellä on omat konventionsa, joita on syytä noudattaa, jotta muutkin voivat lukea kirjoitettuja ohjelmia.

06 Apr 17

Koska päätimme kutsua aliohjelmaa 3:lla todellisella parametrilla tyyliin:

PiirraLumiukko(this, 200, Level.Bottom + 300);

on esittelyrivillä oltava kolme muodollista parametria samassa järjestyksessä ja esiteltynä vastaavan tyyppisinä muuttujina. Toki 200 on kokonaisluku, mutta kokonaisluku voidaan sijoittaa reaalilukuun ja siksi yleiskäyttöisyyden vuoksi tässä tapauksessa x ja y on esitelty reaalilukuina. Tämä ansiosta aliohjelmaa voitaisiin kutsua myös:

PiirraLumiukko(this, 10.3, 200.723);

Tietotyypeistä voit lukea lisää kohdasta 7.2 ja luvusta 8.

Parametrit erotellaan toisistaan pilkulla sekä kutsussa (todelliset parametrit) että esittelyrivillä (muodolliset parametrit).

Huomaa! Aliohjelman muodollisten parametrien nimien ei tarvitse olla samoja kuin kutsussa. Nimien kannattaa kuitenkin olla mahdollisimman kuvaavia.

Huomaa! Parametrien tyyppien ei tarvitse olla keskenään samoja, kunhan kukin parametri on sijoitusyhteensopiva kutsussa olevan vastinparametrin kanssa. Esimerkkejä funktioista löydät dokumentista Aliohjelminen kirjoittaminen:

https://tim.jyu.fi/view/kurssit/tie/ohj1/materiaali/aliohjelmienKirjoittaminen.

Itse asiassa edellä kutsussa oleva this on tyyppiä Lumiukot joka on peritty luokasta PhysicsGame, mutta koska PhysicsGame periytyy tavallisesta pelistä Game, voidaan sekä Lumiukot että PhysicsGame-tyyppinen muuttuja sijoittaa Game-tyyppiselle muuttujalle. Aliohjelman esittelyrivillä voitaisiin toki esitellä peli myös Lumiukot tai PhysicsGame-tyyppiseksi, mutta tällöin aliohjelmalla ei voitaisi piirtää lumiukkoa Game-tyyppiseen (Game-luokasta perittyyn) peliin. Eli tässä on kyseessä vähän samanlainen yleistys kuin se että 200 (kokonaisluku) voidaan sijoittaa reaaliluku- tyyppiseen muuttujaan (double).

Aliohjelmakutsulla ja aliohjelman määrittelyllä on siis hyvin vahva yhteys keskenään. Aliohjelmakutsussa annetut tiedot (todelliset parametrit) “sijoitetaan” kullakin kutsukerralla aliohjelman määrittelyrivillä esitellyille vastinparametreille (muodolliselle parametrille). Toisin sanoen aliohjelmakutsun yhteydessä tapahtuu väljästi sanottuna seuraavaa.

aliohjelman peli = this;
aliohjelman x = 200;
aliohjelman y = Level.Bottom + 300;

Voimme nyt kokeilla ajaa ohjelmaamme. Se toimii (lähtee käyntiin), mutta ei tietenkään vielä piirrä lumiukkoja, eikä pitäisikään, sillä luomamme aliohjelma on “tyhjä” (tynkä). Lisätään aaltosulkujen väliin varsinainen koodi, joka pallojen piirtämiseen tarvitaan.

Pieni muutos aikaisempaan versioon kuitenkin tarvitaan. Rivit, joilla pallot lisätään kentälle, muutetaan muotoon

peli.Add(...);

missä pisteiden paikalle tulee pallo-olion muuttujan nimi. Tämä siksi, että oikeastaan alkuperäisessä lumiukon piirtämisessä meidän olisi pitänyt kirjoittaa aina:

this.Add(p1);
this.Add(p2);
jne.

Alkuperäisessä lumiukossa kirjoitimme Lumiukko luokan omaa metodia Begin ja siinä halusimme sanoa, että pallot lisätään nimenomaan tähän (this) peliin (peliolioon, joka on Lumiukko luokan ilmentymä). Useissa oliokielissä viitattaessa olion omiin metodeihin (tässä Add) tai attribuutteihin, voidaan this jättää kirjoittamatta, tai sen saa kirjoittaa. Tässä jokainen voi valita oman tyylinsä, mutta tässä monisteessa this jätetään usein kirjoittamatta. Nyt vastaavasti PiirraLumiukko aliohjelma ei ole minkään olion oma aliohjelma (static aiheuttaa tämän), ja siksi sille täytyy viedä parametrina tieto siitä, mihin peliin haluamme lumiukon piirtää. Meidän esimerkissämme veimme parametrina nimenomaan tuon this -arvon. Siksi meidän esimerkissämme aliohjelmaa suoritettaessa

peli.Add(p1);

on juurikin

this.Add(p1);    

Lopuksi Begin-metodi ja PiirraLumiukko -aliohjelma kokonaisena:

#
//
    /// <summary>
    /// Kutsutaan PiirraLumiukko-aliohjelmaa
    /// sopivilla parametreilla.
    /// </summary>
    public override void Begin()
    {
        Camera.ZoomToLevel();
        Level.Background.Color = Color.Black;

        PiirraLumiukko(this, 0, Level.Bottom + 200);
        PiirraLumiukko(this, 200, Level.Bottom + 300);
    }

    /// <summary>
    /// Aliohjelma piirtää lumiukon
    /// annettuun paikkaan.
    /// </summary>
    /// <param name="peli">Peli, johon ukko lisätään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    public static void PiirraLumiukko(Game peli, double x, double y)
    {
        PhysicsObject p1, p2, p3;
        p1 = new PhysicsObject(2 * 100, 2 * 100, Shape.Circle);
        p1.X = x;
        p1.Y = y;
        peli.Add(p1);

        p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
        p2.X = x;
        p2.Y = p1.Y + 100 + 50;
        peli.Add(p2);

        p3 = new PhysicsObject(2 * 30, 2 * 30, Shape.Circle);
        p3.X = x;
        p3.Y = p2.Y + 50 + 30;
        peli.Add(p3);
    }

 

C#-kielessä, kuten ei monessa muussakaan kielessä, ole väliä sillä onko ensin kirjoitettu pääohjelma (tässä tapauksessaBegin) vaiko ensin aliohjelma (tässä tapauksessa PiirraLumiukko). Oleellista on että ne muodostavat kokonaisuuksia (eli aaltosulkeisiin {} suljetut lohkot).

Aliohjelmia ei suoriteta siinä järjestyksessä kuin ne esiintyvät koodissa vaan siinä järjestyksessä missä niitä kutsutaan. Ohjelman suoritus aloitetaan aina Main-aliohjelmasta ja Jypeli-tapauksessa sieltä kutsutaan Begin-metodia josta voidaan kutsua muita aliohjelmia, joista voidaan taas kutsua muita aliohjelmia. Kun aliohjelma on valmis, palataan siihen kohtaan, mistä aliohjelmaa kutsuttiin.

Varsinaista aliohjelman toiminnallisuutta kirjoittaessa käytämme nyt parametreille antamiamme nimiä. Alimman ympyrän keskipisteen koordinaatit saamme nyt suoraan parametreista x ja y, mutta muiden ympyröiden keskipisteet meidän täytyy laskea alimman ympyrän koordinaateista. Tämä tapahtuu täysin samalla tavalla kuin aikaisemmassa esimerkissä. Itse asiassa, jos vertaa aliohjelman sisältöä edellisen esimerkin koodiin, on se täysin sama.

C#:ssa on tapana aloittaa aliohjelmien ja metodien nimet isolla kirjaimella ja nimessä esiintyvä jokainen uusi sana alkamaan isolla kirjaimella. Kirjoitustavasta käytetään termiä PascalCasing. Muuttujat kirjoitetaan pienellä alkukirjaimella, ja jokainen seuraava sana isolla alkukirjaimella: esimerkiksi double autonNopeus. Tästä käytetään nimeä camelCasing. Lisää C#:n nimeämiskäytännöistä voit lukea sivulta

Huomaa, että muilla ohjelmointikielillä on omat tapansa, miten asiat nimetään, käytetäänkö isoja kirjaimia ja milloin, vai ei. On huono tapa kirjoittaa lähdekoodi kielen käytänteiden vastaisesti, joten kun vaihdat kieltä, vaihda myös kirjoitustapaa. Esimerkiksi JavaScriptissä ei koskaan aloiteta funktioiden tai metodien nimeä isolla kirjaimella, paitsi jos funktio alustaa kopion ("luo olion"). Näin ison kirjaimen käyttö funktionimen ensimmäisenä kirjaimena JavaScriptissä johtaa lukijaa harhaan!

13 Dec 16

Tarkastellaan seuraavaksi mitä aliohjelmakutsussa tapahtuu.

PiirraLumiukko(this, 0, Level.Bottom + 200);

Yllä olevalla kutsulla aliohjelman peli-nimiseen muuttujaan sijoitetaan this, eli kyseessä oleva peli, x-nimiseen muuttujaan sijoitetaan arvo 0 (liukulukuun voi sijoittaa kokonaislukuarvon) ja aliohjelman muuttujaan y arvo Level.Bottom + 200. Voisimme sijoittaa tietenkin minkä tahansa muunkin liukuluvun.

Aliohjelmakutsun suorituksessa lasketaan siis ensiksi jokaisen kutsussa olevan lausekkeen arvo, ja sitten lasketut arvot sijoitetaan kutsussa olevassa järjestyksessä aliohjelman vastinparametreille. Siksi vastinparametrien pitää olla sijoitusyhteensopivia kutsun lausekkeiden kanssa. Esimerkin kutsussa lausekkeet ovat yksinkertaisia: muuttujan nimi (this), kokonaislukuarvo (0) ja reaalilukuarvo ( Level.Bottom + 200. Jos näyttö olisi vaikkapa 800 pikseliä korkea, olisi origo, eli piste (0,0) näytön keskellä ja silloinLevel.Bottom olisi -400 ja lausekkeen arvo olisi siis -400 + 200, eli -200). Ne voisivat kuitenkin olla kuinka monimutkaisia lausekkeita tahansa, esimerkiksi näin:

PiirraLumiukko(this, 22.7+sin(2.4), 80.1-Math.PI);

Lause (statement) ja lauseke (expression) ovat eri asia. Lauseke on arvojen, aritmeettisten operaatioiden ja aliohjelmien (tai metodien yhdistelmä), joka evaluoituu tietyksi arvoksi. Lauseke on siis lauseen osa. Seuraava kuva selventää eroa.

#

Kuva 6: Lauseen ja lausekkeen ero.

Koska määrittelimme koordinaattien parametrien tyypiksi double, voisimme yhtä hyvin antaa parametreiksi mitä tahansa muitakin desimaalilukuja. Täytyy muistaa, että C#:ssa desimaalilukuvakioissa käytetään pistettä erottamaan kokonaisosa desimaaliosasta.

6.2.1 Valmis kokonaisuus

Kokonaisuudessaan ohjelma näyttää nyt seuraavalta:

#
using Jypeli;


/// @author  Antti-Jussi Lakanen, Vesa Lappalainen
/// @version 22.8.2012
///
/// <summary>
/// Piirretään lumiukkoja ja harjoitellaan aliohjelman käyttöä.
/// </summary>
public class Lumiukot : PhysicsGame
{
    /// <summary>
    /// Pääohjelmassa laitetaan "peli" käyntiin.
    /// </summary>
    public static void Main()
    {
        using (Lumiukot peli = new Lumiukot())
        {
            peli.Run();
        }
    }

    /// <summary>
    /// Kutsutaan PiirraLumiukko-aliohjelmaa
    /// sopivilla parametreilla.
    /// </summary>
    public override void Begin()
    {
        Camera.ZoomToLevel();
        Level.Background.Color = Color.Black;

        PiirraLumiukko(this, 0, Level.Bottom + 200);
        PiirraLumiukko(this, 200, Level.Bottom + 300);
    }

    /// <summary>
    /// Aliohjelma piirtää lumiukon
    /// annettuun paikkaan.
    /// </summary>
    /// <param name="peli">Peli, johon ukko lisätään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    public static void PiirraLumiukko(Game peli, double x, double y)
    {
        PhysicsObject p1, p2, p3;
        p1 = new PhysicsObject(2 * 100, 2 * 100, Shape.Circle);
        p1.X = x;
        p1.Y = y;
        peli.Add(p1);

        p2 = new PhysicsObject(2 * 50, 2 * 50, Shape.Circle);
        p2.X = x;
        p2.Y = p1.Y + 100 + 50;
        peli.Add(p2);

        p3 = new PhysicsObject(2 * 30, 2 * 30, Shape.Circle);
        p3.X = x;
        p3.Y = p2.Y + 50 + 30;
        peli.Add(p3);
    }
}

 

Kutsuttaessa aliohjelmaa hyppää ohjelman suoritus välittömästi parametrien sijoitusten jälkeen kutsuttavan aliohjelman ensimmäiselle riville ja alkaa suorittamaan aliohjelmaa kutsussa määritellyillä parametreilla. Kun päästään aliohjelman koodin loppuun, palataan jatkamaan kutsun jälkeisestä seuraavasta lauseesta. Esimerkissämme kun ensimmäinen lumiukko on piirretty, palataan tavallaan ensimmäisen kutsun puolipisteeseen, ja sitten pääohjelma jatkuu kutsumalla toista lumiukon piirtämistä.

Jos nyt haluaisimme piirtää lisää lumiukkoja, lisäisi jokainen uusi lumiukko koodia vain yhden rivin.

Huomaa! Aliohjelmien käyttö selkeyttää ohjelmaa ja aliohjelmia kannattaa kirjoittaa, vaikka niitä kutsuttaisiin vain yhden kerran. Hyvää aliohjelmaa voidaan kutsua muustakin käyttöyhteydestä.

#

Tehtävä 6.1 lisää lumiukkoja

Lisää ohjelmaan kaksi muuta lumiukkoa

        PiirraLumiukko(this, 0, Level.Bottom + 200);
        PiirraLumiukko(this, 200, Level.Bottom + 300);

 

#

Parametrit voidaan antaa myös nimettyinä, jolloin niiden järjestystä voidaan muuttaa kutsussa. Lisää ohjelmaan kaksi muuta lumiukkoa. Kokeile, miten voit nimettyjä parametreja käyttää eri tavoilla. Kokeile myös lisätä Peli. PiirraLumiukko-kutsun eteen. Miksi Peli.?

        PiirraLumiukko(peli:this, y:Level.Bottom + 200, x:0);
        PiirraLumiukko(this, x:200, y:Level.Bottom + 300);

 

C#:ssa aliohjelmia ja funktioita voidaan kuormittaa (eng. overload) parametrien suhteen. Tämä tarkoittaa, että ohjelmassa voi olla monta samannimistä aliohjelmaa, joilla on eri määrä (tai eri tyyppisiä) parametreja. Lisää luvussa 6.5.

#
Lisätietoa kuormittamisesta myös videolla Lumiukon kuormitus (12m54s)
#

Tehtävä 6.2 järjestele toimivaksi

Muokkaa ohjelma toimivaksi. Laita pääohjelma ennen muita aliohjelmia.

public class Tulostus
{
   public static void Main()
   {
       TulostaLuvut(0, -99);
   }
   public static void TulostaLuvut(double p1, double p2)
   {
       System.Console.WriteLine(p1 + " " +  p2);
   }
}

 

6.3 Aliohjelmien dokumentointi

Hyvän ohjelmointitavan mukaan jokaisen aliohjelman tulisi sisältää dokumentaatiokommentti. Aliohjelman dokumentaatiokommentin tulee sisältää ainakin seuraavat asiat: Lyhyt kuvaus aliohjelman toiminnasta, selitys kaikista parametreista sekä selitys mahdollisesta paluuarvosta. Nämä asiat kuvataan tagien avulla seuraavasti:

  • Dokumentaatiokommentin alkuun laitetaan summary-tagien väliin lyhyt ja selkeä kuvaus aliohjelman toiminnasta.
  • Jokainen parametri selitetään omien param-tagien väliin ja
  • paluuarvo returns-tagien väliin.

PiirraLumiukko-aliohjelman dokumentaatiokommentit ovat edellisessä esimerkissämme riveillä 36-42.

36 /// <summary>
37 /// Aliohjelma piirtää lumiukon
38 /// annettuun paikkaan.
39 /// </summary>
40 /// <param name="peli">Peli, johon ukko lisätään.</param>
41 /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
42 /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>

Voit kokeilla dokumentaatiota edellisessä täydellisessä Lumiukko-esimerkissä painamalla Document-linkkiä. Sitten kokeile syntyvässä dokumentaatiossa eri linkkejä, niin näet mitä niiden takaa löytyy. Alla sama vielä kuvana.

Doxygen-työkalun (ks. http://en.wikipedia.org/wiki/Doxygen) tuottama HTML-sivu tästä luokasta näyttäisi nyt seuraavalta:

#

Kuva 7: Osa Lumiukot-luokan dokumentaatiosta.

Dokumentaatiossa näkyvät kaikki luokan aliohjelmat ja metodit. Huomaa, että Doxygen nimittää sekä aliohjelmia että metodeja jäsenfunktioiksi (member functions). Kuten sanottu, nimitykset vaihtelevat kirjallisuudessa, ja tässä kohtaa käytössä on hieman C++:n nimeämistapaa muistuttava tapa. Kysymys on kuitenkin samasta asiasta, josta me tällä kurssilla käytämme nimeä aliohjelmat ja metodit.

Jokaisesta aliohjelmasta ja metodista löytyy lisäksi tarkemmat tiedot Detailed Description -kohdasta. Aliohjelman PiirraLumiukko dokumentaatio parametreineen näkyy kuvan alaosassa.

6.3.1 Huomautus

Kaikki PiirraLumiukko-aliohjelmassa tarvittava tieto välitettiin parametrien avulla, eikä aliohjelman suorituksen aikana tarvittu aliohjelman ulkopuolisia tietoja. Tämä on tyypillistä aliohjelmille ja usein lisäksi toivottava ominaisuus. Tällöin aliohjelma esitellään static -tyyppiseksi.

6.4 Aliohjelmat, metodit ja funktiot

Kuten ehkä huomasit, aliohjelmilla ja metodeilla on paljon yhteistä. Monissa kirjoissa nimitetään myös aliohjelmia metodeiksi. Tällöin aliohjelmat erotetaan olioiden metodeista nimittämällä niitä staattisiksi metodeiksi. Tässä monisteessa metodeista puhutaan kuitenkin vain silloin, kun tarkoitetaan olioiden toimintoja. Jypelin dokumentaatiosta tutkit RandomGen-luokan staattisia metodeja, joilla voidaan luoda esimerkiksi satunnaisia lukuja. Yksittäinen pallo poistettiin metodilla Destroy, joka on olion toiminto.

Aliohjelmista puhutaan tällä kurssilla, koska sitä termiä käytetään monissa muissa ohjelmointikielissä. Tämä kurssi onkin ensisijaisesti ohjelmoinnin kurssi, jossa käytetään C#-kieltä. Päätavoitteena on siis oppia ohjelmoimaan ja työkaluna meillä sen opettelussa on C#-kieli.

Aliohjelmamme PiirraLumiukko ei palauttanut mitään arvoa (void). Aliohjelmaa (tai metodia), joka palauttaa jonkun arvon, voidaan kutsua myös tarkemmin funktioksi (function).

Aliohjelmia ja metodeja nimitetään eri tavoin eri kielissä. Esimerkiksi C++-kielessä sekä aliohjelmia että metodeja sanotaan funktioiksi. Metodeita nimitetään C++-kielessä tarkemmin vielä jäsenfunktioiksi, kuten Doxygen teki myös C#:n tapauksessa.

Kerrataan vielä lyhyesti aliohjelman, funktion ja metodin erot.

Aliohjelma: Yleisnimenä mikä tahansa aliohjelma, funktio tai metodi. Aliohjelma ei ota nimenä kantaa parametrien määrään tai paluuarvon tyyppiin. void-aliohjelmassa, eli aliohjelmassa joka ei palauta arvoa, voi olla return-lause, mutta sen perässä ei silloin ole lauseketta (vrt. return-lause funktiossa). Tällöin return-lauseen rooliksi jää vain hypätä aliohjelmasta pois.

Joissakin kielissä, esimerkiksi C++:ssa, kaikista aliohjelmista käytetään yleisnimeä funktio. Java-kirjallisuudessa kaikista aliohjelmista käytetään usein yleisnimeä metodi.

Tällä kurssilla käytetään yleisnimeä aliohjelma silloin kun ei erikseen haluta korostaa että kyseessä on erityisesti funktio tai metodi. Tarkennetaan funktion ja metodin käsitteitä seuraavaksi.

Funktio: Aliohjelma, joka palauttaa arvon, esimerkiksi kahden luvun keskiarvon. Tämän määritelmän mukaan funktiossa on aina vähintään yksi return-lause, jonka perässä on lauseke, esimerkiksi return (a+b)/2.0;

Tässä määritelmässä ei oteta kantaa parametrien määrään.

Funktion on useimmiten syytä olla static. Ihannetilanteessa puhtaalla funktiolla ei ole sivuvaikutuksia, eli se ei esimerkiksi muuta parametrina vietyä taulukkoa.

Metodi: Aliohjelma, joka tarvitsee tehtävän suorittamiseksi kohteena olevan olion omia tietoja. Metodeja käytetään tällä kurssilla (esimerkiksi merkkijono.IndexOf), mutta ei tehdä itse muuten kuin peliluokan metodeja (esimerkiksi Begin). Joku voi myös mahdollisesti tehdä loppukurssilla uuden luokan, jolle sitten kirjoitetaan omia metodeja. Käytännössä metodissa tarvitaan this-viitettä ja se ei saa silloin olla static.

Metodi voi myös funktion tapaan palauttaa arvon tai void-aliohjelman tapaan olla palauttamatta.

#

6.4.1 Aliohjelminen kirjoittaminen

Aliohjelman kirjoittamiseksi kannattaa aina edetä seuraavasti (kunhan ensin opitaan testaaminen, TDD, Test Driven Development, huomaa että tämä on eri asia kuin debuggaaminen):

  1. Jaa ongelma osiin.
  2. Mieti millaisella aliohjelmakutsulla pistät tietyn osaongelman ratkaisun käyntiin.
  3. Kirjoita aliohjelman kutsurivi ja mieti sen tarvitsemat parametrit.
  4. Kirjoita (aluksi manuaalisesti, myöhemmin generoi automaattisesti) aliohjelman esittelyrivi (otsikkorivi, eng. header).
    • mieti tarve public, static - sanoille
    • aliohjelman paluutyyppi void vai jotakin muuta?
    • aliohjelman nimi
    • parametrin lukumäärä sama kuin kutsussa
    • parametrien tyyppi sijoitusyhteensopivaksi kutsun kanssa.
  5. Tee aliohjelmasta syntaktisesti oikea tynkä joka kääntyy, esimerkiksi funktioaliohjelmassa pitää olla return-lause joka palauttaa lausekkeen (vaikka yksi luku) joka on samaa tyyppiä (tai muuntuu automaattisesti samaksi) kuin funktion tyyppi.
  6. Dokumentoi aliohjelma (nyt unohda mistä sitä kutsuttiin, sitä et enää saa ajatella).
  7. Kirjoita testit (TDD).
  8. Aja testit (pitää “feilata” = NÄE PUNAISTA).
  9. Muuta aliohjelma toimivaksi
  10. Aja testit (toista kohdat 8-10 kunnes toimii, = NÄE VIHREÄÄ)
  11. Siirry seuraavaan aliohjelmaan.

Lue lisää dokumentista Aliohjelmien kirjoittaminen.

Edellä on kirjoitettu yleinen “resepti” aliohjelminen kirjoittamiseksi. Siinä puhutaan testeistä, mutta tämän kurssin tiedoilla voidaan testejä tehdä vain funktioille, joista puhutaan tässä dokumentissa myöhemmin luvussa Aliohjelman paluuarvo. Pelkästään tulostavia ohjelmia ei osata testata tämän kurssin tiedoilla. Eli em. “reseptiä” voidaan kunnolla tällä kurssilla soveltaa vasta funktioiden opiskelun jälkeen.

#

Ohjelmassa on pääohjelma valmiina ja aliohjelma alustettuna. Aliohjelma ei kuitenkaan vielä tee mitään. Laita se tulostamaan "Hello World"

public class HelloWorld
{
    public static void Main()
    {

        TulostaHelloWorld();

    }

    ///<summary>
    ///Tulostaa "Hello World!"
    ///</summary>
    public static void TulostaHelloWorld() {

      // tänne tekstiä
    }
}

 

Tyngän tekemiseen oli ohje, mutta miten testataan, kun tulostetaan tekstiä? En löytänyt mallia.

VL: Täytyy tunnustaa että kaikki tämän luvun tehtävät ovat tosi huonoja tähän kohtaan, sillä mikään niistä ei ole helposti testattavissa. Funktiot (arvoja sisään, arvo ulos) on helppo testata. Ja tällä kurssilla ei oikeastaan muuta opitakkaan testaamaan. Jos ohjelmassa on outputtia ja mahdollisesti inputtia, niin silloin tarvitaan lisäluokkia, joihin kaapataan esim kaikki tulostus menemään ja sitten katsotaan mitä sinne on lopuksi mennyt. Ohj2:lla vasta tulee näitä. Tosin TDD-hengessä ongelmia voidaan usein kiertää niin, että meillä on helposti testattavia funktoita, jotka muodostavat tulosteen esimerkiksi merkkijonoon ja niitä funktioita testataan ja sitten luotetaan että kun tulosmerkkijono käsketään tulostaa, se toimii. Mutta tämän alaluvun tehtäviin tuokaan ei sovi. Pitää jossakin vaiheessa siirtää nämä tehtävät kaueammaksi testaamisesta ja laittaa tähän kohti kunnollisia funktiota. Mutta kun funktiot tulevat vasta luvussa 9, niin tämä pitää kirjoittaa jotenkin uusiksi. Lisään tekstiin tästä jotakin.

19 Sep 17 (edited 23 Sep 18)
#

Tehtävä 6.2

Valitse 'Näytä koko koodi' nähdäksesi ohjelman valmiit aliohjelmat. Miten tulostaisit "Hello World!" käyttäen vain parametrittomia aliohjelmakutsuja? Ensimmäinen aliohjelmakutsu on valmiina.

//
        TulostaHe();

 

Toki edellisen tehtävän kaltaiset aliohjelmat eivät ole järkeviä, vaan järkevämpää olisi viedä aliohjelmille parametrina että mitä pitää tulostaa.

#

Tehtävä 6.3

Täydennä alla oleva ohjelma Noppa.cs toimimaan kuten kommenteissa on sanottu.

using System;
using Jypeli;

/// <summary>
/// Ohjelma piirtää kuusi palloa neliön sisälle siten että
/// niistä muodostuu nopan näköinen olio, jonka silmälukuna on 6.
/// </summary>
public class Peli : PhysicsGame
{
    /// <summary>
    /// Ruudulla näkyvä sisältö.
    /// </summary>
    public override void Begin()
    {
       Camera.ZoomToLevel();
       Level.Background.Color = Color.Black;
       double koko = 500;
       PhysicsObject nelio = new PhysicsObject(koko, koko, Shape.Rectangle);
       Add(nelio);
       PiirraPallo(this, 0, 100);
       PiirraPallo(this, 120, 100);
       // Täydennä ...
    }

   /// <summary>
   /// Piirtää pallon, jonka sivun pituus on 80.
   /// </summary>
   /// <param name="peli">Peli, johon neliö piirretään</param>
   /// <param name="x">Pallon keskipisteen x-koordinaatti.</param>
   /// <param name="y">Pallon keskipisteen y-koordinaatti.</param>
   public static void PiirraPallo(Game peli, double x, double y)
   {
       PhysicsObject p1 = new PhysicsObject(80, 80, Shape.Circle);
       //Täydennä
       peli.Add(p1,1);
   }
}

 

Hei, voisiko tätä avata vähän lisää vielä, nyt en saa mitenkään järkevästi näkymään kuin neljä palloa. Nollaa se ei hyväksy koordinaatiksi ja en oikein pääse kärryille, että miten ne koordinaatit voisi järkevästi päätellä.

VL: piirrä pallo saa piirtää VAIN yhden pallon, mutta sitä voit kutsua useita kertoa.

16 Feb 20 (edited 16 Feb 20)

Mitä en nyt tajua, kun en saa noita palloja piirrettyä tuon valkoisen neliön päälle, vaan menevät aina sen alle?

VL: ne tulevat piirtojärjestyksessä, eli kannattaa ensin piirtää se alimmainen “olio”.

Hmmm, olen kyllä mielestäni kokeillut tuota molemmin päin. Defaulttina tuossa olikin neliö piirretty ennen palloja, ja kun en sitä saanut toimimaan niin kokeilin piirtää pallot ensin, joka johti samaan lopputulokseen. Luultavasti tässä on kyse jostain perusjutusta, jota en nyt vain huomaa.

Tilannepäivitys: Kokeilin huvikseni muuttaa pallot suorakulmioiksi, ja nyt ohjelma piirtää ne tuon valkoisen neliön päälle, kuten oli tarkoitus! Kolmiot toimivat myös, mutta timantit taas eivät (muita en ole kokeillut). Sama Visual Studiossa. Eli onko noilla eri muodoilla joku tietty kiveen hakattu järjestys, jossa ohjelma piirtää ne toistensa päälle?

Kappaleiden piirtojärjestykseen voi luottaa ainoastaan jos niiden muoto on sama. Jos haluaa varmistaa, että joku on jonkun päällä, pitää se lisätä ylempään kerrokseen. Esim. peli.Add(p1, 2); lisäisi kappaleen kerrokseen kaksi. Toistaiseksi sallitut kerrokset ovat väliltä [-3,3] -MR

22 Sep 20 (edited 23 Sep 20)
Tuloksen pitäisi näyttää jokseenkin tältä
Tuloksen pitäisi näyttää jokseenkin tältä

6.4.2 Tehtävä: Termistöä

/// <summary>
/// Kutsutaan PiirraLumiukko-aliohjelmaa
/// sopivilla parametreilla.
/// </summary>
public override void Begin()
{
  Camera.ZoomToLevel();
  Level.Background.Color = Color.Black;

  PiirraLumiukko(this, 0, Level.Bottom + 200);
  PiirraLumiukko(this, 200, Level.Bottom + 300);
}
#
Tehtävä: Terminologiaa

Mitkä seuraavista väitteistä pitää paikkaansa koskien ylläolevaa ohjelmaa?

#

6.5 Aliohjelman kuormittaminen

C#:ssa aliohjelmia ja funktioita voidaan kuormittaa (engl. overload) parametrien suhteen. Tämä tarkoittaa, että ohjelmassa voi olla monta samannimistä aliohjelmaa, joilla on eri määrä parametreja tai parametrit ovat eri tyyppisiä. Tätä voidaan hyödyntää siten, että se funktio joka ottaa enemmän parametreja, osaa tehdä enemmän tai tarkemmin asioita kuin vähemmän parametreja ottava funktio.

Miksi tässä käännös on kuormittaa? YLIkuormittaa vaikuttaisi olevan sekä käännöksenä että ajatuksena ihan validin oloinen.

  • VL: koska ylikuormittaminrn kuulostaa liisn täyteen lastatulta rekka-autolta ja silloin siinä on negatiivinen sävy ja kuormittsminrn on hieno adia kielessä. Ennen kun tuota ei ollut, piti keinotekoisesti keksiä aina uusi nimi sliohjelmalle.
28 Sep 18 (edited 28 Sep 18)

6.5.1 Yksinkertaisin esimerkki

Otetaan aluksi mahdollisimman yksinkertainen esimerkki kuormittamisesta. Käytetään tapauksena funktioita, jotka osaavat lisätä lukuja toisiinsa.

Tehdään aluksi funktio, joka palauttaa kahden luvun summan.

public static double Summa(double a, double b)
{
  return a + b;
}

Tätä voitaisiin kutsua esimerkiksi Main-pääohjelmasta kirjoittamalla

double summa = Summa(5, 10.5);

Sitten keksimme, että hei, tarvitsemme myös funktion, joka osaa summata kolme lukua, ja haluaisimme kutsua sitä kirjoittamalla

double summa = Summa(5, 10.5, 30.9);

Kirjoitetaan samanniminen funktio, mutta annetaan sille funktion määrittelyrivillä (eng. function signature) kolme parametria kahden sijaan. Toteutetaan funktio myös saman tien.

public static double Summa(double a, double b, double c)
{
  return a + b + c;
}

Mutta nyt huomaamme, että meillä on melkein sama koodi näissä kahdessa funktiossa. Muutetaan ensimmäistä funktiota siten, että kutsutaan ensimmäisestä funktiosta (joka osaa vähemmän) toista funktiota (joka osaa enemmän). Annetaan kolmanneksi summattavaksi luvuksi (siis kolmanneksi parametriksi) 0.

public static double Summa(double a, double b)
{
  return Summa(a, b, 0);
}

Tämän esimerkin avulla opimme, mitä kuormittaminen tarkoittaa. Seuraava esimerkki valottaa kuormittamisen hyötyjä paremmin.

6.5.2 Vakiokokoinen lumiukko vs ukon koko parametrina

Voimme luoda vakiokokoisen lumiukon seuraavalla aliohjelmalla.

/// <summary>
/// Aliohjelma piirtää vakiokokoisen lumiukon
/// annettuun paikkaan.
/// </summary>
/// <param name="peli">Peli, johon lumiukko tehdään.</param>
/// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
/// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
public static void PiirraLumiukko(Game peli, double x, double y)
{
  PhysicsObject alapallo, keskipallo, ylapallo;
  alapallo = new PhysicsObject(2 * 100.0, 2 * 100.0, Shape.Circle);
  alapallo.X = x;
  alapallo.Y = y;
  peli.Add(alapallo);
  
  keskipallo = new PhysicsObject(2 * 50.0, 2 * 50.0, Shape.Circle);
  keskipallo.X = x;
  keskipallo.Y = alapallo.Y + 100 + 50;
  peli.Add(keskipallo);
  
  ylapallo = new PhysicsObject(2 * 30.0, 2 * 30.0, Shape.Circle);
  ylapallo.X = x;
  ylapallo.Y = keskipallo.Y + 50 + 30;
  peli.Add(ylapallo);
}

Voimme kutsua tätä aliohjelmaa Begin:stä vaikkapa seuraavasti.

PiirraLumiukko(this, 0, Level.Bottom + 200.0);

Mutta entäs jos haluaisimmekin piirtää tämän lisäksi joskus eri kokoisiakin ukkoja? Toisin sanoen, joskus meille riittää, että PiirraLumiukko tekisi meille “vakiokokoisen” ukkojen lisäksi myös halutessamme jonkun muun kokoisen ukelin. Kutsut Begin:ssä voisivat näyttää tältä.

// Vakiokokoisen ukon kutsuminen (alapallon koko 2 * 100)
PiirraLumiukko(this, -200, Level.Bottom + 300.0);

// Samannimisen aliohjelman käyttäminen 
// pienemmän ukon tekemiseen (alapallon koko 2 * 50)
PiirraLumiukko(this, 0, Level.Bottom + 200.0, 50.0);

Mutta nyt Visual studio antaa virheilmoituksen

No overload for method 'PiirraLumiukko' takes 4 arguments.

Joten kirjoitetaan uusi aliohjelma, jonka nimeksi tulee PiirraLumiukko (kyllä, samanniminen), mutta peli-parametrin ja paikan lisäksi parametrina annetaan myös alapallon säde.

public static void PiirraLumiukko(Game peli, double x, double y, double sade)
{
 // tähän kirjoitetaan kohta koodia...
}

Siirretään nyt koodi alkuperäisestä aliohjelmasta tähän uuteen, ja laitetaan pallojen säde riippumaan parametrina annetusta säteestä. Lisäksi laitetaan keski- ja yläpallon paikat riippumaan pallojen koosta! Uusi (neljäparametrinen) aliohjelma näyttäisi nyt seuraavalta.

public static void PiirraLumiukko(Game peli, double x, double y, double sade)
{
    PhysicsObject alapallo, keskipallo, ylapallo;
    alapallo = new PhysicsObject(2 * sade, 2 * sade, Shape.Circle);
    alapallo.X = x;
    alapallo.Y = y;
    peli.Add(alapallo);

    // keskipallon koko on 0.5 * sade
    keskipallo = new PhysicsObject(2 * 0.5 * sade, 2 * 0.5 * sade, Shape.Circle);
    keskipallo.X = x;
    keskipallo.Y = alapallo.Y + alapallo.Height / 2 + keskipallo.Height / 2;
    peli.Add(keskipallo);

    // ylapallon koko on 0.3 * sade
    ylapallo = new PhysicsObject(2 * 0.3 * sade, 2 * 0.3 * sade, Shape.Circle);
    ylapallo.X = x;
    ylapallo.Y = keskipallo.Y + keskipallo.Height / 2 + ylapallo.Height / 2;
    peli.Add(ylapallo);
}

Nyt voimme kutsua kolmeparametrisesta PiirraLumiukko-aliohjelmasta tuota “versiota”, joka osaa tehdä asioita enemmän ilman, että copy-pastetamme koodia.

public static void PiirraLumiukko(Game peli, double x, double y)
{
    PiirraLumiukko(peli, x, y, 100);
}
Ukkelit sulassa sovussa.
Ukkelit sulassa sovussa.
#

Koko esimerkki kuormiteutusta lumiukosta

using System;
using System.Collections.Generic;
using Jypeli;
using Jypeli.Assets;
using Jypeli.Controls;
using Jypeli.Effects;
using Jypeli.Widgets;

/// @author Antti-Jussi Lakanen
/// @version 30.1.2014
///
/// <summary>
/// Aliohjelmien kuormittaminen
/// </summary>
public class Kuormittaminen : PhysicsGame
{
    /// <summary>
    /// Kutsutaan PiirraLumiukko-aliohjelmaa kahdella eri tavalla.
    /// Ensimmäisessä ei anneta parametrina kokoa, jolloin tulee "vakiokokoinen" lumiukko.
    /// Toisessa tavassa annetaan parametrina haluttu koko.
    /// </summary>
    public override void Begin()
    {
        Level.Background.Color = Color.Black;
        PiirraLumiukko(this, -200, Level.Bottom + 300.0);
        PiirraLumiukko(this, 0, Level.Bottom + 200.0, 50.0);

        PhoneBackButton.Listen(ConfirmExit, "Lopeta peli");
        Keyboard.Listen(Key.Escape, ButtonState.Pressed, ConfirmExit, "Lopeta peli");
    }

    /// <summary>
    /// Aliohjelma piirtää vakiokokoisen lumiukon
    /// annettuun paikkaan.
    /// </summary>
    /// <param name="peli">Peli, johon lumiukko tehdään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    public static void PiirraLumiukko(Game peli, double x, double y)
    {
        PiirraLumiukko(peli, x, y, 100);
    }

    /// <summary>
    /// Aliohjelma piirtää annetun kokoisen lumiukon
    /// annettuun paikkaan
    /// </summary>
    /// <param name="peli">Peli, johon lumiukko tehdään.</param>
    /// <param name="x">Lumiukon alimman pallon x-koordinaatti.</param>
    /// <param name="y">Lumiukon alimman pallon y-koordinaatti.</param>
    /// <param name="sade"></param>
    public static void PiirraLumiukko(Game peli, double x, double y, double sade)
    {
        PhysicsObject alapallo, keskipallo, ylapallo;
        alapallo = new PhysicsObject(2 * sade, 2 * sade, Shape.Circle);
        alapallo.X = x;
        alapallo.Y = y;
        peli.Add(alapallo);

        keskipallo = new PhysicsObject(2 * 0.5 * sade, 2 * 0.5 * sade, Shape.Circle);
        keskipallo.X = x;
        keskipallo.Y = alapallo.Y + alapallo.Height / 2 + keskipallo.Height / 2;
        peli.Add(keskipallo);

        ylapallo = new PhysicsObject(2 * 0.3 * sade, 2 * 0.3 * sade, Shape.Circle);
        ylapallo.X = x;
        ylapallo.Y = keskipallo.Y + keskipallo.Height / 2 + ylapallo.Height / 2;
        peli.Add(ylapallo);
    }
}

 

.

#

7. Muuttujat

tyyppi nimi;

Muuttujat (variable) toimivat ohjelmassa tietovarastoina erilaisille asioille. Muuttuja on kuin pieni laatikko, johon voidaan varastoida asioita, esimerkiksi lukuja, sanoja, tietoa ohjelman käyttäjästä ja paljon muuta. Proseduaalisissa kielissä ilman muuttujia järkevä tiedon käsittely olisi oikeastaan mahdotonta. Funktio-ohjelmoinnissa tosin asiat ovat hieman toisin. Olemme jo ohimennen käyttäneetkin muuttujia, esimerkiksi Lumiukko-esimerkissä teimme PhysicsObject-tyyppisiä muuttujia p1, p2 ja p3. Vastaavasti PiirraLumiukko-aliohjelman parametrit (Game peli, double x, double y) ovat myös muuttujia: Game-tyyppinen oliomuuttuja peli, sekä double-alkeistietotyyppiset muuttujat x ja y.

Termi muuttuja on lainattu ohjelmointiin matematiikasta, mutta niitä ei tule kuitenkaan sekoittaa keskenään - muuttuja matematiikassa ja muuttuja ohjelmoinnissa tarkoittaa hieman eri asioita. Tulet huomaamaan tämän seuraavien kappaleiden aikana.

Muuttujien arvot tallennetaan keskusmuistiin tai rekistereihin, mutta ohjelmointikielissä voimme antaa kullekin muuttujalle nimen (identifier), jotta muuttujan arvon käsittely olisi helpompaa. Muuttujan nimi onkin ohjelmointikielten helpotus, sillä näin ohjelmoijan ei tarvitse tietää tarvitsemansa tiedon keskusmuisti- tai rekisteriosoitetta, vaan riittää muistaa itse nimeämänsä muuttujan nimi. [VES]

Koska kääntäjän pitää osata varata muuttujalle oikean kokoinen muistialue, pitää muuttujalle esitellä myös tyyppi. Muuttujan tyyppiä tarvitaan myös siksi, että tiedetään miten muistipaikkaan tallennettua tietoa pitää käsitellä. Jotta ymmärtäisimme erilaisien tietotyyppien erilaisia tallennustapoja, tutustumme myöhemmin muun muassa binäärilukuihin. Esimerkiksi kahdeksan bitin yhdistelmä, eli tavu 01000001 voidaan tulkita esimerkiksi kirjaimeksi A tai etumerkittömäksi kokonaisluvuksi 65.

Esimerkiksi lauseessa Console.WriteLine(a) ei voitaisi tietää mitä pitää tulostaa, mikäli ei tiedetä muuttujan a tyyppiä. Aivan vastaavasti kuin lauseesta kuusi palaa, ei voida tietää mitä sillä tarkoitetaan jos asiayhteys ei ole selvillä.

7.1 Muuttujan määrittely

Kun matemaatikko sanoo, että “n on yhtä suuri kuin 1”, tarkoittaa se, että tuo termi (eli muuttuja) n on jollain käsittämättömällä tavalla sama kuin luku 1. Matematiikassa muuttujia voidaan esitellä tällä tavalla “häthätää”.

Ohjelmoijan on kuitenkin tehtävä vastaava asia hieman tarkemmin. C#-kielessä tämä tapahtuisi kirjoittamalla seuraavasti:

#
   int n;
   n = 1;

 

Ensimmäinen rivi tarkoittaa väljästi sanottuna, että “lohkaise pieni pala - johon mahtuu int-kokoinen arvo - säilytystilaa tietokoneen muistista, ja käytä siitä jatkossa nimeä n. Toisella rivillä julistetaan, että “talleta arvo 1 muuttujaan, jonka nimi on n, siten korvaten sen, mitä kyseisessä säilytystilassa mahdollisesti jo on”.

Merkki = on sijoitusoperaattori ja siitä puhutaan enemmän myöhemmässä luvussa.

Mikä sitten on tuo edellisen esimerkin int?

#

Tehtävä: Sijoitus

Aukaise Tauno. Sijoita n-muuttujaan arvo 1 vetämällä sen päälle <--1 'laatikko'. Aja ohjelma. Tulostuksen tulisi olla n=1. Kokeile sitten kasvattaa (vedä +1 n:än päälle) ja vähentää (vedä -1) muuttujan arvoa ja seuraa minkälaista ohjelmakoodia Tauno kirjoittaa.

 

C#:ssa jokaisella muuttujalla täytyy olla tietotyyppi (usein myös lyhyesti tyyppi). Tietotyyppi on määriteltävä, jotta ohjelma tietäisi, millaista tietoa muuttujaan tullaan tallentamaan. Toisaalta tietotyyppi on määriteltävä siksi, että ohjelma osaa varata muistista sopivan kokoisen lohkareen muuttujan sisältämää tietoa varten. Esimerkiksi int-tyypin tapauksessa tilantarve olisi 32 bittiä (4 tavua), byte-tyypin tapauksessa 8 bittiä (1 tavu) ja double-tyypin 64 bittiä (8 tavua). Muuttuja määritellään (declare) kirjoittamalla ensiksi tietotyyppi ja sen perään muuttujan nimi. Muuttujan nimet aloitetaan C#:ssa pienellä kirjaimella, jonka jälkeen jokainen uusi sana alkaa aina isolla kirjaimella. Kuten aiemmin mainittiin, tämä nimeämistapa on nimeltään camelCasing.

muuttujanTietotyyppi muuttujanNimi;

Tuo mainitsemamme int on siis tietotyyppi, ja int-tyyppiseen muuttujaan voi tallentaa kokonaislukuja. Muuttujaan n voimme laittaa lukuja 1, 2, 3, samoin 0, -1, -2, ja niin edelleen, mutta emme lukua 0.1 tai sanaa “Moi”. Mutta mitä ikinä laitammekin, niin muuttujassa voi olla vain yksi arvo kerrallaan. Kun muuttujaan sijoitetaan uusi arvo, ei edelliseen arvoon pääse enää mitenkään käsiksi.

Henkilön iän voisimme tallentaa seuraavaan muuttujaan:

int henkilonIka;

Huomaa, että tässä emme aseta muuttujalle mitään arvoa, vain määrittelemme muuttujan int-tyyppiseksi ja annamme sille nimen.

Samantyyppisiä muuttujia voidaan määritellä kerralla useampia erottamalla muuttujien nimet pilkulla. Tietotyyppiä double käytetään, kun halutaan tallentaa desimaalilukuja.

double paino, pituus;

Määrittely onnistuu toki myös erikseen (joka on jopa suositeltavampi tapa):

double paino;
double pituus;

Miksi tietotyypin määrittely erikseen on jopa suositeltavampi tapa kuin samalla rivillä? Siksikö, että ne ovat paljaalla silmällä helpompi löytää koodista vai jokin muu syy?

22 Sep 16 (edited 22 Sep 16)

VL: kyllä tuossa selkeämmin näkyy mitä muuttujia on esitelty. Ja on helpompi vaihtaa yhden muuttujan typpiä vahingossa vaihtamatta toista. Lisäksi C:ssä

int* a, b;  // luo yhden osoittinmuuttujan ja yhden intin
int *c;     // kun alla oleva taas selkeämmin näyttää eron
int d;
23 Sep 16

Muuttujaan voi asettaa arvon myös jo määrittelyn yhteydessä. Huomattakoon että arvo voi tulla myös lausekkeen tuloksena.

muuttujanTietotyyppi muuttujanNimi = VAKIO;
muuttujanTietotyyppi muuttujanNimi = lausekeJokaTuottaaArvon;
#
//
        bool onkoKalastaja = true;
        char merkki = 't';
        int kalojenLkm = 0;
        double luku1 = 0, luku2 = 2.0, luku3 = 3+2.4;

 

Muuttujalle sijoitettavan arvon (tai lausekkeen arvon) tulee olla tyypiltään sellainen, että se voidaan sijoittaa muuttujaan. Esimerkiksi int-muuttujaan ei voi sijoittaa reaalilukua:

#
//
        int ika = 2.5; // TÄMÄ KOODI EI KÄÄNNY

 

mutta reaalilukuun voi sijoittaa kokonaisluvun

#
//
        double hinta = 20000;

 

Huomattakoon, että reaalilukujen (mm. double) desimaalierottimen ohjelmakoodissa on aina piste (.) eikä tuhaterottimia käytetä.

#
Mitkä sallittuja?

Mitkä seurvaavista muuttujien määrittelyistä ovat sallittuja:

Miksi muuttujalle ei voi antaa nimeksi numeroa? Esim. tässä tapauksessa tuo int 4;

VL: Mietis mitä tästä seuraisi:

    int 4 = 3;
    Console.WriteLine(4);   
    

Ja jotta tuota olisi helpompi estää, ei muuttujan nimi edes saa alkaa numerolla (vaikka se olisi periaattessa vielä tehtävissä).

23 Sep 19 (edited 27 Sep 19)

7.2 Alkeistietotyypit

C#:n tietotyypit voidaan jakaa alkeistietotyyppeihin (primitive types, perustyyppi, perustietotyyppi) ja oliotietotyyppeihin (reference types). Oliotietotyyppeihin kuuluu muun muassa käyttämämme PhysicsObject-tyyppi, jota pallot p1 jne. olivat, sekä merkkijonojen tallennukseen tarkoitettu string-olio. Oliotyyppejä käsitellään myöhemmin luvussa 8.

Eri tietotyypit vaativat eri määrän kapasiteettia tietokoneen muistista. Vaikka nykyajan koneissa on paljon muistia, on hyvin tärkeää valita oikean tyyppinen muuttuja kuhunkin tilanteeseen. Suurissa ohjelmissa ongelma korostuu hyvin nopeasti käytettäessä muuttujia, jotka kuluttavat tilanteeseen nähden kohtuuttoman paljon muistikapasiteettia. C#:n alkeistietotyypit on lueteltu alla.

Taulukko 1: C#:n alkeistietotyypit koon mukaan järjestettynä.

Eikös bool ole 1 tavun kokoinen? Vaikka tyyppinsä puolesta sopisi kyllä yhteen bittiin.
VL: arvon tarkkuus vie yhden bitin, tallennustilan tarve voi vaihdella tavusta 8 tavuun prosessorin arkkitehtuurin mukaan. Yleensä tavu on pienin osoitettava yksikkö, mutta koska 32 bittinen prosesori hakee 32 bittiä eli 4 tavua kerralla, voiolla että muistipaikkoja sijoitetaan vain näiden rajoilta alkamaan. 64-bittisessä se voi sitten olla jopa 8 tavua. Tästä taulukosta ei saa luottaa muuta kuin tarkkuuden suhteen bittimäärään. “Tarkkuus” siis määrää suurimman mahdollisen eri arvojen määrän.

26 Dec 18 (edited 13 Sep 20)

Tällä kurssilla tärkeimmät alkeistietotyypit ovat: bool, char, int ja double.

Tässä monisteessa suositellaan, että desimaalilukujen talletukseen käytetään aina double-tietotyyppiä (jossain tapauksissa jopa decimal-tyyppiä), vaikka monessa muussa lähteessä float-tietotyyppiä käytetäänkin. Tämä johtuu siitä, että liukuluvut, joina desimaaliluvut tietokoneessa käsitellään, ovat harvoin tarkkoja arvoja tietokoneessa. Itse asiassa ne ovat tarkkoja vain kun ne esittävät jotakin kahden potenssin yhdistelmiä, kuten esimerkiksi 2.0, 7.0, 0.5 tai 0.375.

Useimmiten liukuluvut ovat pelkkiä approksimaatioita oikeasta reaaliluvusta. Esimerkiksi lukua 0.1 ei pystytä tietokoneessa esittämään biteillä tarkasti perustietotyypeillä. Tällöin laskujen määrän kasvaessa lukujen epätarkkuus vain lisääntyy. Tämän takia onkin turvallisempaa käyttää aina double-tietotyyppiä, koska se suuremman bittimääränsä takia pystyy tallettamaan enemmän merkitseviä desimaaleja.

Tietyissä sovelluksissa, joissa mahdollisimman suuri tarkkuus on välttämätön (kuten pankki- tai nanotason fysiikkasovellukset), on suositeltavaa käyttää korkeimpaa mahdollista tarkkuutta tarjoavaa decimal-tyyppiä. Reaalilukujen esityksestä tietokoneessa puhutaan lisää kohdassa 26.6. [VES][KOS]

Seuraavassa esimerkki mitä tapahtuu, kun lasketaan yhteen kaksi liian isoa kokonaislukua tai muuten lisätään muuttujaa liikaa.

#

Tehtävä 7.1

Aja ensin ohjelma muuttamatta. Poista sitten toisesta int-muuttujasta yksi 0. Aja. Mitä tapahtui. Laita takaisin 0. Vaihda tyypit niin, että laskut menevät oikein.

        int luku1 = 1000000000;
        int luku2 = 2000000000;
        int summa = luku1 + luku2;
        byte b = 254;
        b++; b++;
        sbyte sb = 127;
        sb++;

 

Miksi int luku2 tuottaa virheellisen tuloksen?

15 Sep 15

Mitä tarkoittaa? luku2 ei tuota virheellistä tulosta, mutta luku1 + luku2 ylittää kokonaisluvulla käytettävissä olevan arvoalueen ja siksi summa on väärin. -vl

17 Sep 15

Mitä esim. b++ tarkoittaa? Yritin etsiä sitä monisteesta, mutta nämä tässä olevat maininnat b++:sta ovat ainoat.

13 Sep 16 (edited 13 Sep 16)

VL: Kokeileppa etsiä pelkkää ++. Muuttujilla voi olla eri nimiä ja jos jossakin on kerrottu mitä on a++, niin ei erikseen tarvitse sanoa mitä on b++.

13 Sep 16 (edited 13 Sep 16)

7.3 Arvon asettaminen muuttujaan

Muuttujaan asetetaan arvo sijoitusoperaattorilla (assignment operator) =. Lauseita, joilla asetetaan muuttujille arvoja, sanotaan sijoituslauseiksi (assignment statement). On tärkeää huomata, että sijoitus tapahtuu aina oikealta vasemmalle: sijoitettava on yhtäsuuruusmerkin oikealla puolella ja kohde merkin vasemmalla puolella.

#

Aukaise Tauno. Tee uusi muuttuja, sen nimeksi ika ja arvoksi ikäsi. Tee myös toinen muuttuja, jonka nimeksi tulee opiskeluvuodet ja haluamasi arvo. Katso taunon tekemää koodia. Huomaa kuinka se lisää automaattisesti muuttujan tietotyypin int.

 

Eikö tauno ymmärtä desimaalikukua?

15 Sep 15

Tauno osaa vain kokonaisluvut. -vl

17 Sep 15
#

Tehtävä 7.2

Esittele muuttujat alla olevia sijoituksia varten niin, että ohjelma kääntyy ja toimii. Esim. int b;




        x = 20.0;
        henkilonIka = 23;
        paino = 80.5;
        pituus = 183.5;
        // 80.5 = paino;  // kokeile, tämä ei toimi!

 

Miksi float ei toimi?

15 Sep 15

Jos on float-tyyppinen muuttuja vaikka x, niin sille pitäisi sijoittaa float vakio:

x = 20.0f 
  • vl
17 Sep 15 (edited 04 Nov 15)

Saako tässä asettaa sellaisen formaatin, että desimaalit näkyisivät myös x:lle?
System.Console.WriteLine("x = {0:0.00}", x);

14 Sep 17 (edited 14 Sep 17)

Huomaa että reaalilukuvakioissa käytetään desimaalipistettä, ei pilkkua.

#

Tehtävä 7.3

Laita muuttujien tyyppi sijoitusriville.

        x = 20.0;
        henkilonIka = 23;
        paino = 80.5;
        pituus = 183.5;
        valovuosiKm = 9460730472580;
        summa = 128;
        merkki = '7';

 

Muuttuja täytyy olla määritelty tietyn tyyppiseksi ennen kuin siihen voi asettaa arvoa. Muuttujaan voi asettaa vain määrittelyssä annetun tietotyypin mukaisia arvoja tai sen kanssa sijoitusyhteensopivia arvoja. Esimerkiksi liukulukutyyppeihin (float ja double) voi sijoittaa myös kokonaislukutyyppisiä arvoja, sillä kokonaisluvut ovat reaalilukujen osajoukko. Alla sijoitamme arvon 4 muuttujaan nimeltä luku2, ja kolmannella rivillä luku2-muuttujan sisältämän arvon (4) muuttujaan, jonka nimi on luku1.

#
        double luku1;
        int luku2 = 4;
        luku1 = luku2;

 

Toisinpäin tämä ei onnistu: double-tyyppistä arvoa ei voi sijoittaa int-tyyppiseen muuttujaan. Alla oleva koodi ei kääntyisi:

#
// TÄMÄ KOODI EI KÄÄNNY!
        int luku1;
        double luku2 = 4.0;
        luku1 = luku2;

 

Jos edellä oleva sijoitus int <- double halutaan välttämättä tehdä, niin silloin on käytettävä tyypin muunnosta eli typecastia (kokeile edelliseen, vaihda myös 4.0 tilalle 4.8). Tosin tyypinmuunnokseen turvautuminen on aina huono ratkaisu.

   luku1 = (int)luku2;  // pakotetaan luku 2 int-tyyppiseksi. Katkaisu.

Kun decimal-tyyppinen muuttuja alustetaan jollain luvulla, tulee luvun perään (ennen puolipistettä) laittaa m (tai M)-merkki. Samoin float-tyyppisten muuttujien alustuksessa perään laitetaan f (tai F)-merkki ja long-tyyppisten perään L.

#
        decimal tilinSaldo = 3498.98m;
        float lampotila = -4.8f;

 

Mitä tarkoitetaan alustuksella?

  • VL: muuttujan voi joko vaan esitellä tai sen voi esitellä ja alustaa sille samalla alkuarvon. Voisi sen sanoa niinkin, että annetaan muuttujalla alkuarvo samalla kun sen tila varataan.
18 Sep 18 (edited 20 Sep 18)

Huomaa, että char-tyyppiseen muuttujaan sijoitetaan arvo laittamalla merkki heittomerkkien väliin, esimerkiksi näin.

#
        char ekaKirjain = 'k';

 

Näin sen erottaa myöhemmin käsiteltävästä string-tyyppiseen muuttujaan sijoittamisesta, jossa sijoitettava merkkijono laitetaan lainausmerkkien väliin, esimerkiksi seuraavasti.

#
        string omaNimi = "Antti-Jussi";

 

Sijoituslause voi sisältää myös monimutkaisiakin lausekkeita, esimerkiksi aritmeettisia operaatioita:

#
        double numeroidenKeskiarvo = (2 + 4 + 1 + 5 + 3 + 2) / 6.0;

 

Sijoituslause voi sisältää myös muuttujia.

#
        double huoneenPituus = 5.40;
        double huoneenLeveys = huoneenPituus;
        double huoneenAla = huoneenPituus * huoneenLeveys;

 

Eli sijoitettava voi olla mikä tahansa lauseke, joka tuottaa muuttujalle kelpaavan arvon. Yhdistämällä muuttujia ja operaatoita voi lauseke olla edellisiäkin “monimutkaisempi”:

#
        double alku = 30;
        double nopeus = 80;
        double matka = alku + (nopeus-10)*5 + System.Math.Sin(0.5);

 

Huomaa edellä, että vaikka paperilla kaavoja kirjoitettaessa ei tarvita kertomerkkiä, niin ohjelmointikielissä käytetään * -merkkiä kertomerkkinä.

C#:ssa täytyy aina asettaa joku arvo muuttujaan ennen sen käyttämistä. Kääntäjä ei käännä koodia, jossa käytetään muuttujaa jolle ei ole asetettu arvoa. Alla oleva ohjelma ei siis kääntyisi.

#
// TÄMÄ OHJELMA EI KÄÄNNY!!!!!!!!
public class Esimerkki
{
    public static void Main()
    {
        int ika;
        System.Console.WriteLine(ika);
    }
}

 

Virheilmoitus näyttää tältä:

Esimerkki.cs(7,34): error CS0165: Use of unassigned local variable 'ika'

Kääntäjä kertoo, että ika-nimistä muuttujaa yritetään käyttää, vaikka sille ei ole annettu vielä mitään arvoa. Tämä ei ole sallittua, joten ohjelman kääntämisyritys päättyy tähän.

#

Koita ajaa ohjelma. Se antaa varoituksen: The variable 'ika' is assigned but its value is never used. Tämä ei estä ohjelmaa kääntymästä, kuten virheilmoitukset tekevät. Poistamalla tulostuslauseen edestä kommenttimerkit //, on muuttuja käytössä, eikä virheilmoitusta tule.

public class Esimerkki
{
    public static void Main()
    {
        int ika = 5;
        // System.Console.WriteLine(ika);
    }
}

 

7.3.1 Sijoituksen kohde on aina vasemmalla

Muuttujan jolle sijoitetaan on lauseessa aina vasemmalla puolella. Sijoitusmerkin = oikealla puolella on jokin lauseke, jonka arvo lasketaan ennen sijoitusta ja tämä arvo sijoitetaan muuttujalle.

7.3.2 Tehtävä 7.4 a:n arvon sijoitus b:lle

#

Vastaa aluksi alla olevaan monivalintakysymykseen ja sitten kirjoita tähän miten sijoitat a:n arvon b:hen kirjoitamalla uuden ohjelmarivin (älä siis muuta kahta olemassa olevaa). Tämän jälkeen aja ohjelma ja katso että se tulostaa 3

        int a = 3;
        int b;

 

#
Tarkista tietosi

Miten edellisten alkuperäisten kahden rivin jälkeen voidaan a:n arvo sijoittaa b:lle?

7.3.3 Muuttujan arvo muuttuu vain kun siihen sijoitetaan

Muuttujan arvo muuttuu vain kun siihen sijoitetaan. Alkeismuuttujaan sijoitetaan aina arvo. Jos muuttujaan sijoitetaan toisen muuttujan arvo, niin muuttuja saa sen arvon, mikä toisella muuttujalla on sijoitushetkellä. Kokeile seuraavalla esimerkillä miten sijoituksen jälkeen i:n arvon muuttaminen ei enää vaikuta summa-muuttujaan:

#

Aukaise Tauno. Sijoita i:n arvo summa-muuttujaan. Kasvata i:n arvoa vetämälle sen päälle +1 'laatikko'. Muuttuuko summa-muuttujan arvo kun kasvatat i:tä?

 

7.3.3.1 Tehtävä 7.5 i:n kasvatus, mitä ohjelma tulostaa

#

Älä vielä aja ohjelmaa, vastaa ensin alla olevaan kysymykseen.

        int i = 2;
        int summa = i;
        System.Console.Write(summa + " ");
        i += 1; // tai i++;
        System.Console.WriteLine(summa);

 

Tiedän, että esim. int a = 1; int b = a++; tuottaa eri tuloksen kuin int a = 1; int b = a+1; mutta en tiedä miksi. Enkä löydä materiaalista selitystä tälle.


VL: katsoppa lukua Arvonmuunto-operaattorit.

01 Nov 19 (edited 03 Nov 19)
#
Mikä muuttuu?

Mita ohjelma tulostaa?

7.4 Muuttujan nimeäminen

Muuttujan nimen täytyy olla siihen tallennettavaa tietoa kuvaava. Yleensä pelkkä yksi kirjain on huono nimi muuttujalle, sillä se harvoin kuvaa kovin hyvin muuttujaa. Kuvaava muuttujan nimi selkeyttää koodia ja vähentää kommentoimisen tarvetta. Lyhyt muuttujan nimi ei ole itseisarvo. Vielä parikymmentä vuotta sitten se saattoi olla sitä, koska se nopeutti koodin kirjoittamista. Nykyaikaisia kehitysympäristöjä käytettäessä tämä ei enää pidä paikkaansa, sillä editorit osaavat täydentää muuttujan nimen samalla kun koodia kirjoitetaan, joten niitä ei käytännössä koskaan tarvitse kirjoittaa kokonaan, paitsi tietysti ensimmäisen kerran.

Yksikirjaimisia muuttujien nimiäkin voi perustellusti käyttää, jos niillä on esimerkiksi jo matematiikasta tai fysiikasta ennestään tuttu merkitys. Nimet x ja y ovat hyviä kuvaamaan koordinaatteja. Nimi l (eng. length) viittaa pituuteen ja r (eng. radius) säteeseen. Fysikaalisessa ohjelmassa s voi hyvin kuvata matkaa.

Huomaa! Muuttujan nimi ei voi C#:ssa alkaa numerolla.

C#:n koodauskäytänteiden mukaan muuttujan nimi alkaa pienellä kirjaimella. Jos muuttujan nimi koostuu useammasta sanasta, aloitetaan uusi sana aina isolla kirjaimella kuten alla.

int polkupyoranRenkaanKoko;

C#:ssa muuttujan nimi voi sisältää ääkkösiä, mutta niiden käyttöä ei suositella, koska siirtyminen koodistosta toiseen aiheuttaa usein ylimääräisiä ongelmia.

Koodisto = Määrittelee jokaiselle merkistön merkille yksikäsitteisen koodinumeron. Merkin numeerinen esitys on usein välttämätön tietokoneissa. Merkistö määrittelee joukon merkkejä ja niille nimen, numeron ja jonkinnäköisen muodon kuvauksen. Merkistöllä ja koodistolla tarkoitetaan usein samaa asiaa, kuitenkin esimerkiksi Unicode-merkistö sisältää useita eri koodaustapoja (UTF-8, UTF-16, UTF-32). Koodisto on siis se merkistön osa, joka määrittelee merkille numeerisen koodiarvon. Koodistoissa syntyy ongelmia yleensä silloin, kun siirrytään jostain skandimerkkejä (ä,ö,å, …) sisältävästä koodistosta seitsemänbittiseen ASCII-koodistoon, joka ei tue skandeja. ASCII-koodistosta puhutaan lisää luvussa 27.

7.4.1 C#:n avainsanat

Muuttujan nimi ei saa olla mikään ohjelmointikielen varatuista sanoista, eli sanoista joilla on C#:ssa joku muu merkitys.

Taulukko 2: C#:n avainsanat eli “varatut sanat”.

abstract do in protected true
as double int public try
base else interface readonly typeof
bool enum internal ref uint
break event is return ulong
byte explicit lock sbyte unchecked
case extern long sealed unsafe
catch false namespace short ushort
char finally new sizeof using
checked fixed null stackalloc virtual
class float object static void
const for operator string volatile
continue foreach out struct while
decimal goto override switch
default if params this
delegate implicit private throw
#
// TÄMÄ OHJELMA EI KÄÄNNY!!!!!!!!
public class Esimerkki
{
    public static void Main()
    {
        int event;
        event = 52;
        System.Console.WriteLine(event);
    }
}

 

#
Mitkä määrittelyt oikein?

Mitkä seuraavista muuttujien määrittelyistä ovat sekä syntaktisesti että koodaustapojen mukaan oikein

Mielestäni tämä nimeäminen on virheellinen “int tassa,on, pisteet = 7;”, koska siitä voisi luulla, että ohjelmoija tarkoitti luoda vain yhden muuttujan johon sijoittaa pistemäärän 7. Syntaksi on ok, mutta muuttujien nimet hämmentäviä. Eikö?

28 Jan 19
#

7.5 Muuttujien näkyvyys

Muuttujien näkyvyydellä (eng. scope) tarkoitetaan sitä, missä tilanteessa muuttuja on käytettävissä. Jos muuttuja “on näkyvissä” (in scope), niin voimme koodissamme kyseisessä kohdassa käyttää muuttujaa.

Muuttujaa voi käyttää (lukea ja asettaa arvoja) vain siinä lohkossa, missä se on määritelty. Lohko alkaa aaltosululla { ja päättyy aaltosululla }.

 {
    int luku = 5; 
 }

Muuttujat ovat olemassa niin kauan kuin lohkosta ei olla poistuttu. Aliohjelmakutsun aikana lohkosta ei ole poistuttu, koska lohkoon palataan kun aliohjelma on suoritettu. Sisempi lohko ei myöskään aiheuta poistumista.

 {
    int luku = 5; 
    AliohjelmaKutsu(); 
    {
        luku++; 
    }
 }

Muuttujan määrittelyn täytyy aina olla ennen (koodissa ylempänä) kuin sitä ensimmäisen kerran käytetään. Saman lohkon sisälläkin muuttuja tulee esitellä ennen sen käyttöä, sillä muuttuja alkaa näkyä vasta esittelynsä jälkeen.

#
//
    {
        luku++;  // EI TOIMI, muuttujaa ei ole vielä
        {
          luku++; // EI TOIMI, muuttujaa ei vielä esitelty

          int luku = 5;

          luku++; // TOIMII
          System.Console.WriteLine(luku);
        }
        luku++; // EI TOIMI, ei ole vaikutusalueessa
    }

 

Seuraavassa muuttujat luku ja d ovat nähtävissä ja muutettavissa vain pääohjelmassa (paitsi jos viedään C#:issa out-parametrina). Kaikki pääohjelmassa (tai missä tahansa muussakin aliohjelmassa) esitellyt muuttujat elävät pääohjelman loppusulkuun } saakka. Tässä muuttujan luku arvo kopioidaan aliohjelman vastinmuuttujaan. Aliohjelma ei mitenkään “näe” pääohjelman muuttujaa, vaan aliohjelma saa tiedokseen sille välitetyn arvon.

Aliohjelman sisällä määritelty muuttuja ei näy muissa aliohjelmissa ja sitä kutsutaan lokaaliksi muuttujaksi. Muuttujat luku ja d ovat pääohjelman (Main) lokaaleja muuttujia.

#
//
    public static void Main()
    {
        int luku = 9;
        double d = 0.5;
        Muuta(2, luku);
        System.Console.WriteLine("luku = {0}, d = {1}",luku,d);
    }

 

Esimerkissä parametrimuuttujan nimi on sama luku kuin pääohjelmassakin, mutta nimi voisi olla mikä tahansa muukin. Oleellista on, että kutsussa aliohjelman vastaavassa paikassa olevaan muuttujaan sijoitetaan sama arvo kuin kutsuvassakin ohjelmassa. Vaikka muuttujaan luku sijoitettaisiin jotakin, se ei vaikuta kutsuvaan ohjelmaan, koska luku on oma lokaalimuuttuja aliohjelmassa ja on olemassa vain siihen saakka kun kunnes tullaan aliohjelman loppusulkuun }.

Edellä on käytetty tulostuksessa versiota, jossa annetaan ensin muotoilujono ja sitten muotoiltavat lausekkeet pilkuilla eroteltuna. Tästä myöhemmin lisää.

#
//
    public static void Muuta(int ika, int luku)
    {
        ika--;
        int uusiarvo;
        uusiarvo = luku +3;
        luku = 12;
    }

 

Käännettäessä ohjelma tulee varoitus siitä, että aliohjelman muuttujaa uusiarvo ei käytetä enää sen jälkeen kun sille on sijoitettu arvo. Mikäli aliohjelmaa kutsuttaisiin uudelleen, syntyisi uudelle kutsukerralla oma uusiarvo -muuttuja, eikä sillä olisi enää mitään tekemistä edellisen kutsukerran vastaavan arvon kanssa.

Edellä apumuuttuja uusiarvo on näkyvissä aliohjelmassa esittelyrivinsä jälkeen, mutta lakkaa olemasta kun tullaan aliohjelman loppusulkuun }. Tähän muuttujaan tehdyt muutokset (vaikka jossakin olisi samanniminenkin muuttuja) eivät millään tavalla vaikuta mihinkään muuhun paikkaan kuin tähän muuttujaan. Pääohjelma tai kukaan muukaan ei pääse käsiksi tähän muuttujaan millään tavalla (paitsi tässä tapauksessa kun tuo arvo riippuu parametrina tuodun luku-muuttujan arvosta).

Parametrimuuttujien muuttamista ei yleisesti pidetä hyvänä tyylinä. Jos parametrimuuttujia pitää muuttaa, parempi on tehdä niistä lokaali kopio ja muuttaa sitä, näin aliohjelman lopussa parametreilla on samat arvon kuin aliohjelmaan tultaessakin.

Tässä on edellä esitetyt aliohjelmat luokan sisällä. Kaikki muuttujat ovat lokaaleja muuttujia.

#
public class LokaalitMuuttujat
{
    public static void Main()
    {
        int luku = 9;
        double d = 0.5;
        Muuta(2, luku);
        System.Console.WriteLine("luku = {0}, d = {1}",luku,d);
    }

    public static void Muuta(int ika, int luku)
    {
        ika--;
        int uusiarvo;
        uusiarvo = luku +3;
        luku = 12;
    }
}

 

Luokan sisällä muuttuja voidaan määritellä myös niin, että se näkyy kaikkialla, siis kaikille aliohjelmille. Kun muuttuja on näkyvissä kaikille ohjelman osille, sanotaan sitä globaaliksi muuttujaksi (global variable). Globaaleja muuttujia tulee välttää aina kun mahdollista.

#
public class GlobaalitMuuttujat
{
    public static int pisteet;
    public static int tulos;

    public static void Main()
    {
        tulos = 10;
        System.Console.WriteLine("pisteet = {0}, tulos = {1}",pisteet,tulos);
        Muuta();
        System.Console.WriteLine("pisteet = {0}, tulos = {1}",pisteet,tulos);
    }

    public static void Muuta()
    {
        tulos += 10;
        pisteet = 15;
    }
}

 

Edellä olevat muuttujat pisteet ja tulos ovat globaaleja muuttujia, koska ne esitellään aliohjelmien ulkopuolella. Ne ovat käytössä myös luokan ulkopuolisista luokista, koska ne on valitettavasti esitelty myös avainsanalla public. Mikäli sana static puuttuisi muuttujien esittelystä, ei niitä voisi käyttää staattisista aliohjelmista. Silloin muuttujat olisivat attribuutteja ja niiden käyttämiseksi pitäisi luoda olio, jonka sisälle attribuutit syntyvät. Tämä menee ohi tämän kurssin varsinaisesta sisällöstä.

#
   /// <summary>
   /// Tutkitaan muuttujinen näkyvyyttä
   /// </summary>
   public class MuuttujienNakyvyys
   {
       /// <summary>
       /// Missä pääohjelman muuttujat näkyvät
       /// </summary>
       /// <param name="args">ei käytössä</param>
       public static void Main(string[] args)  // args näkyy pääohjelmassa
       {
           int luku = 9;   // Näkyy vain pääohjelmassa
           double d = 5.5;  // Näkyy vain pääohjelmassa
           System.Console.WriteLine("Ennen muutosta: {0}, {1}", luku, d);
           Muuta(2, luku);
           {                          // apulohko, jossa omia muuttujia
               int uusi = 3;          // muuttuja joka näkyy vain tässä lohkossa
               System.Console.WriteLine("uusi: " + uusi);
           }                          // nyt uusi-muuttuja lakkaa olemasta
           // Nyt muuttujaa uusi ei ole olemassakaan
           System.Console.WriteLine("Muutosten jälkeen: {0}, {1}", luku, d);

       }
       /// <summary>
       /// Yritetään muuttaa pääohjelman lokaaleja muuttujia aliohjelmassa
       /// </summary>
       /// <param name="uusiArvo">muuttujalle annettava uusi arvo,
       //      näkyy vain aliohjelmassa, muuttaminen ei vaikuta kutsuvaan ohjelmaan</param>
       /// <param name="luku">muuttuja, jonka arvoa muutetaan,
       //     näkyy vain aliohjelmassa, sama nimi ei haittaa,
       //     muuttaminen ei vaikuta kutsuvaan ohjelmaan</param>
       public static void Muuta(int uusiArvo, int luku)
       {
           uusiArvo--;             // ei vaikuta pääohjelmaan
           int uusiarvo;         // aliohjelman lokaali muuttuja
           uusiarvo = luku + 3;
           luku = 12;            // ei vaikuta pääohjelmaan
       }

   }

 

Kokeile edellä mitä tapahtuu jos kirjoitat aliohjelmaan Muuta sijoituksen d = 4.

#

Samaa muuttujan nimeä voidaan käyttää uudelleen eri näkyvyysalueessa. Kussakin näkyvyysalueessa se on kuitenkin eri muuttuja.

public class SamaNimi
{
    public static void Main()
    {
        int i = 4;
        System.Console.WriteLine("Pääohjelman i = {0}",i);
        Ali1(i);
        System.Console.WriteLine("Pääohjelman i = {0}",i);
        Ali2();
        System.Console.WriteLine("Pääohjelman i = {0}",i);
    }


    public static void Ali1(int i)
    {
        System.Console.WriteLine("Ali1:n i = {0}",i);
        i++;
        System.Console.WriteLine("Ali1:n i = {0}",i);
    }

    public static void Ali2()
    {
        int i = 8;
        System.Console.WriteLine("Ali2:n i = {0}",i);
        i++;
        System.Console.WriteLine("Ali2:n i = {0}",i);
    }
}

 

C# ei kuitenkaan salli sisäkkäisen lohkon käyttää samaa nimeä, mitä on käytetty ulommassa lohkossa. Kuitenkin jos globaalilla ja lokaalilla muuttujalla on sama nimi, niin lokaali muuttuja näkyy omassa lohkossaan.

Kokeile seuraavassa kommentoida pois rivi i=9 niin ohjelma kääntyy ja tulostaa pääohjelman lokaalin i:n. Jos myös rivin i=5 kommentoi pois, niin tulostuu globaali i.

#
public class SisalohkossaSama
{
    public static int i = 6;

    public static void Main()
    {
        int i = 5; // peittää globaalin
        System.Console.WriteLine("Ulkolohkon i = {0}",i);
        {
            int i = 9; // TÄMÄ EI KÄÄNNY
            System.Console.WriteLine("Sisälohkon i = {0}",i);
        }
    }
}

 

Lisätietoa muuttujien näkyvyydestä löydät kurssin lisätietosivulta.

7.6 Vakiot

One man’s constant is another man’s variable. -Alan Perlis

Muuttujien lisäksi ohjelmointikielissä voidaan määritellä vakioita (constant). Vakioiden arvoa ei voi muuttaa määrittelyn jälkeen. C#:ssa vakio määritellään muuten kuten muuttuja, mutta muuttujan tyypin eteen kirjoitetaan lisämääre const.

#
        const int KUUKAUSIEN_LKM = 12;
        // KUUKAUSIEN_LKM = 13; // Kokeile poistaa tämä kommenteista

 

Tällä kurssilla vakiot kirjoitetaan suuraakkosin siten, että sanat erotetaan alaviivalla (_). Näin ne erottaa helposti muuttujien nimistä, jotka alkavat pienellä kirjaimella. Muitakin kirjoitustapoja on, esimerkiksi Pascal Casing on toinen yleisesti käytetty vakioiden kirjoitusohje.

#

Tehtävä 7.6

Ohjelmassa esitellään yhteensä 10 muuttujaa ja yksi vakio. Lisää jokaisen muuttujan/vakion perään kommentti, jossa ilmoitetaan, onko se muuttuja vai vakio ja sen tyyppi (globaali, lokaali, parametri)

public class Esimerkki
{

    static int luku1 = 1;
    static int luku2 = 2;

    public static void Main()
    {
        {
             const int LUKU3  = 3;
             int luku4 = 4;
        }
        //TÄSTÄ

        int luku5 = 5;

        //TÄHÄN
        {
            int luku3 = 3;
            int luku4 = 4;
        }
    }

    public static void Aliohjelma(int luku5, int luku6)
    {
        int luku7 = luku5;
        int luku8 = luku6;
    }
}

 

#
Tarkista tietosi

Edellisessä tehtävässä on kommentit `//TÄSTÄ` ja `//TÄHÄN`. Mitkä muuttujat ovat näkyvissä näiden kommenttien sisällä?

#
Tarkista tietosi

Mitkä seuraavista väitteistä pitää paikkaansa?

7.7 Operaattorit

Usein meidän täytyy tallentaa muuttujiin erilaisten laskutoimitusten tuloksia. C#:ssa laskutoimituksia voidaan tehdä aritmeettisilla operaatioilla (arithmetic operation), joista mainittiin jo kun teimme lumiukkoesimerkkiä. Ohjelmassa olevia aritmeettisia laskutoimituksia sanotaan aritmeettisiksi lausekkeiksi (arithmetic expression).

C#:ssa on myös vertailuoperaattoreita (comparison operators), loogisia operaattoreita, bittikohtaisia operaattoreita (bitwise operators), arvonmuunto-operaattoreita (shortcut operators), sijoitusoperaattori =, is-operaattori sekä ehto-operaattori ?. Tässä luvussa käsitellään näistä tärkeimmät.

7.7.1 Aritmeettiset operaatiot

C#:ssa peruslaskutoimituksia suoritetaan aritmeettisilla operaatiolla, joista + ja - tulivatkin esille aikaisemmissa esimerkeissä. Aritmeettisia operaattoreita on viisi.

Taulukko 3: Aritmeettiset operaatiot.

Operaattori Toiminto Esimerkki
+ yhteenlasku Console.WriteLine(1+2); // 3
- vähennyslasku Console.WriteLine(1-2); // -1
* kertolasku Console.WriteLine(2*3); // 6
/ jakolasku Console.WriteLine(6 / 2); // 3
Console.WriteLine(7 / 2); //Huom! 3
Console.WriteLine(7 / 2.0); // 3.5
Console.WriteLine(7.0 / 2); // 3.5
% jakojäännös (modulo) Console.WriteLine(18 % 7); // 4

Huom: 18/7 = 2 4/7. Kokonaislukujakolasku / palauttaa tuon 2 ja jakojäännös palauttaa 4. Jakojäännöstä käytetään usein sen testaamiseen, onko luku jaollinen jollakin luvulla, esim:

#

Animaatio: Suorita aritmeettisia operaatioita

Askella silmukan suoritusta vihreällä nuolella Tutki operaatioiden toimintaa
#
        int vuosi = 2001;
        if ( vuosi % 4 != 0 )
           System.Console.WriteLine("Vuosi ei ole karkausvuosi");

 

#

Tehtävä 7.7

Kokeile + -merkin tilalle kaikkia em. operaattoreita. Mieti ennen ajoa mitä ohjelma tulostaa ja kirjoita 'arvauksesi' alempana olevaan tehtävään. Ajon jälkeen kirjoita viereen mitä oikeasti tuli.

        int luku1 = 17;
        int luku2 = 2;
        int tulos = luku1 + luku2;

 

#

Kirjoita mitä tulostaa milläkin operaattorilla

+   tulos = 19
-   tulos =
*   tulos =
/   tulos =
%   tulos =

 

7.7.2 Vertailuoperaattorit

Vertailuoperaattoreiden avulla verrataan muuttujien arvoja keskenään. Vertailuoperaattorit palauttavat totuusarvon (true tai false). Vertailuoperaattoreita on kuusi. Lisää vertailuoperaattoreista luvussa 13.

#

7.7.3 Arvonmuunto-operaattorit

Arvonmuunto-operaattoreiden avulla laskutoimitukset voidaan esittää tiiviimmässä muodossa: esimerkiksi ++x; (4 merkkiä) tarkoittaa samaa asiaa kuin x = x+1; (6 merkkiä). Niiden avulla voidaan myös alustaa muuttujia.

Taulukko 4: Arvonmuunto-operaattorit.

Operaattori Toiminto Esimerkki
++ Lisäysoperaattori.
Lisää muuttujan arvoa yhdellä.

int luku = 0;

Console.WriteLine(luku++); // 0
Console.WriteLine(luku++); // 1
Console.WriteLine(luku); // 2
Console.WriteLine(++luku); // 3

-- Vähennysoperaattori.
Vähentää muuttujan arvoa yhdellä.

int luku = 5;

Console.WriteLine(luku--); // 5
Console.WriteLine(luku--); // 4
Console.WriteLine(luku); // 3
Console.WriteLine(--luku); // 2
Console.WriteLine(luku); // 2

+= Lisäysoperaatio. int luku = 0;
luku += 2; // luku muuttujan arvo on 2
luku += 3; // luku muuttujan arvo on 5
luku += -1; // luku muuttujan arvo on 4
-= Vähennysoperaatio int luku = 0;
luku -= 2; // luku muuttujan arvo on -2
luku -= 1; // luku muuttujan arvo on -3
*= Kertolaskuoperaatio int luku = 1;
luku *= 3; // luku-muuttujan arvo on 3
luku *= 2; // luku-muuttujan arvo on 6
/= Jakolaskuoperaatio double luku = 27;
luku /= 3; // luku-muuttujan arvo on 9
luku /= 2.0; // luku-muuttujan arvo on 4.5
%= Jakojäännösoperaatio int luku = 9;
luku %= 5; // luku-muuttujan arvo on 4
luku = 9;
luku %= 2; // luku-muuttujan arvo on 1

Lisäysoperaattoria (++) ja vähennysoperaattoria (--) voidaan käyttää ennen tai jälkeen muuttujan. Käytettäessä ennen muuttujaa, arvoa muutetaan ensin ja mahdollinen toiminto esimerkiksi sijoitus tai tulostus, tehdään vasta sen jälkeen. Jos operaattori sen sijaan on muuttujan perässä, toiminto tehdään (eli arvoa käytetään) ensiksi ja arvoa muutetaan vasta sen jälkeen.

Huomaa! Arvonmuunto-operaattorit ovat ns. sivuvaikutuksellisia operaattoreita. Toisin sanoen, operaatio muuttaa muuttujan arvoa toisin kuin esimerkiksi aritmeettiset operaatiot. Seuraava esimerkki havainnollistaa asiaa.

#
        int luku1 = 5;
        int luku2 = 5;
        System.Console.WriteLine(++luku1); // tulostaa 6;
        System.Console.WriteLine(luku1++); // tulostaa 6;
        System.Console.WriteLine(luku2 + 1 ); // tulostaa 6;
        System.Console.WriteLine(luku1); // 7
        System.Console.WriteLine(luku2); // 5
        System.Console.WriteLine(9%2); // 1
        int luku = 9;
        luku %= 2;
        System.Console.WriteLine(luku); // 1

 

7.7.4 Aritmeettisten operaatioiden suoritusjärjestys

Aritmeettisten operaatioiden suoritusjärjestys on vastaava kuin matematiikan laskujärjestys. Kerto- ja jakolaskut suoritetaan ennen yhteen- ja vähennyslaskua. Lisäksi sulkeiden sisällä olevat lausekkeet suoritetaan ensin.

#
        System.Console.WriteLine(5 + 3 * 4 - 2);  //tulostaa 15
        System.Console.WriteLine((5 + 3) * (4 - 2));  //tulostaa 16

 

7.8 Huomautuksia

7.8.1 Kokonaisluvun tallentaminen liukulukumuuttujaan

Kun yritetään tallentaa kokonaislukujen jakolaskun tulosta liukulukutyyppiseen (float tai double) muuttujaan, voi tulos tallentua kokonaislukuna, jos jakaja ja jaettava ovat molemmat kokonaislukuja (esim vakioita, joissa ei ole desimaaliosaa).

#
        double laskunTulos = 5 / 2;
        System.Console.WriteLine(laskunTulos); // tulostaa 2

 

Jos kuitenkin vähintään yksi jakolaskun luvuista on desimaalimuodossa, niin laskun tulos tallentuu muuttujaan oikein.

#
        double laskunTulos = 5 / 2.0;
        System.Console.WriteLine(laskunTulos); // tulostaa 2.5

 

Liukuluvuilla laskettaessa kannattaa pitää desimaalimuodossa myös luvut, joilla ei ole desimaaliosaa, eli ilmoittaa esimerkiksi luku 5 muodossa 5.0.

Kokonaisluvuilla laskettaessa kannattaa huomioida seuraava:

#
        int laskunTulos = 5 / 4;
        System.Console.WriteLine(laskunTulos); // tulostaa 1

        laskunTulos = 5 / 6;
        System.Console.WriteLine(laskunTulos); // tulostaa 0

        laskunTulos = 7 / 3;
        System.Console.WriteLine(laskunTulos); // tulostaa 2

 

Kokonaisluvuilla laskettaessa lukuja ei siis pyöristetä lähimpään kokonaislukuun, vaan desimaaliosa menee C#:n jakolaskuissa ikään kuin “hukkaan”. Jos sekä jakaja että jaettava ovat kokonaislukumuuttujissa, niin jakolasku siis katkeaa kokonaisluvuksi. Ongelmaa voi kiertää niin, että aloittaa koko laskutoimituksen reaaliluvulla.

#
//
        int luku1 = 5;
        int luku2 = 2;
        double laskunTulos = luku1 / luku2;
        System.Console.WriteLine(laskunTulos); // tulostaa 2
        laskunTulos = 1.0 * luku1 / luku2;
        System.Console.WriteLine(laskunTulos); // tulostaa 2.5

 

#

Tehtävä 7.7.1 Mitä sulut vaikuttavat

Mitä tapahtuu jos edellä laitetaan luku1/luku2 sulkuihin?

 

7.8.1.1 Tehtävä 7.8

Alla on ensin esiteltynä kaikki vastausvaihtoehdot. Mieti ensin kysymyksien kohdalla mikä on tulos ja katso vasta sitten oikea vastaus sitten luentovideolta.

Numero 1 2 3 4 5 6 7 8 9
Vastaus 0 1 1.5 2 7 8 9 13 Ohjelma kaatuu
7.8.1.1.1 Mitä seuraavien lausekkeiden tulos on?
#

Onkohan tämä video oikein…? En kylläkään katsonut kovin pitkälle.

20 Feb 19
7.8.1.1.2 Mitä muuttujien arvot ovat?
#
int a = 5 + 10 % 6 / 3 + 1; a:n arvo tämän jälkeen? Vastaus – 1h5m50s (49s)
#
double d = 5 + 10 % 6 / 3 + 1; d:n arvo tämän jälkeen? Vastaus – 1h7m10s (10s)
#
double e = 5.0 + 10 % 6 / 3 + 1; e:n arvo tämän jälkeen? Vastaus – 1h7m56s (1m49s)
#
double e = 5.0 + 10.0 % 6 / 3 + 1; e:n arvo tämän jälkeen? Vastaus – 1h10m20s (39s)

7.8.2 Lisäys- ja vähennysoperaattoreista

On neljä tapaa kasvattaa luvun arvoa yhdellä.

#
        int a = 3;
        int b,c;
        b = ++a; // Huom!  Ei olisi pakko sijoittaa mihinkään!
        ++a;     // eli näin voi kasvattaa
        c = a++; // idiomi.  c saa a:n alkuperäisen arvon ja sitten a kasvaa.
        a++;  // tätäkin voi käyttää (ja paljon käytetään) ilman sijoittamista
        a += 1;
        a = a + 1; // huonoin muutettavuuden ja kirjoittamisen kannalta

 

Ohjelmoinnissa idiomilla tarkoitetaan tapaa, jolla asia yleensä kannattaa tehdä. Näistä a++ on ohjelmoinnissa vakiintunut tapa ja (yleensä) suositeltavin, siis idiomi. Kuitenkin, jos lukua a pitäisikin kasvattaa (tai vähentää) kahdella tai kolmella, ei tämä tapa enää toimisi. Seuraavassa esimerkissä tarkastellaan eri tapoja kahdella vähentämiseksi. Siihen on kolme vaihtoehtoista tapaa.

#
        int a = 10;
        a -= 2;
        a += -2;  // lisättävä voisi olla lausekekin. Luku voi olla myös negatiivinen
        a = a - 2;

 

Tässä tapauksessa += -operaattorin käyttö olisi suositeltavinta, sillä lisättävä luku voi olla positiivinen tai negatiivinen (tai nolla), joten += -operaattori ei tässä rajoita sitä, millaisia lukuja a-muuttujaan voidaan lisätä.

#

Animaatio: Suorita operaattoreita

Askella ohjelmaa vihreällä nuolella. Mutta tässä esimerkissä on huonoa tuo että sijoitetaan result = result++; koska niin ei oikeasti koskaan tehdä Tutki operaattoreita.

7.8.3 Varo nollalla jakamista

Yksi yleisiä ohjelmointivirheitä on nollalla jakaminen. Tämä ei ole syntaksivirhe, koska sitä ei useinkaan voida havaita käännösaikana. Eli nollalla jakaminen on looginen, vasta ohjelman ajon aikana ilmenevä virhe. Ohjelmoijan on aina itse pidettävä ennen jakolaskua huolta siitä, että jakaja ei voi olla nolla. Tässä tosin tarvitaan apuna myöhemmin esiteltävää ehtolausetta (if):

#

Kokeile mitä tapahtuu kun ohjelma ajetaan.

//
        double tulos;
        int jakaja = 3;
        int jaettava = 7;
        tulos = jaettava / (jakaja - 3);

 

7.9 Esimerkki: Painoindeksi

Tehdään ohjelma, joka laskee painoindeksin. Painoindeksi lasketaan jakamalla paino (kg) pituuden (m) neliöllä, eli kaavalla

paino / (pituus * pituus)

C#:lla painoindeksi saadaan siis laskettua seuraavasti.

#
/// @author  Antti-Jussi Lakanen
/// @version 22.8.2012
///
/// <summary>
/// Ohjelma, joka laskee painoindeksin
/// pituuden (m) ja painon (kg) perusteella.
/// </summary>
public class Painoindeksi
{
    /// <summary>
    /// Pääohjelma, jossa painoindeksi tulostetaan ruudulle.
    /// </summary>
    public static void Main()
    {
      double pituus = 1.83;
      double paino = 75.0;
      double painoindeksi = paino / (pituus*pituus);
      System.Console.WriteLine("Painoindeksisi on {0:0.00}",painoindeksi);
   }
}

 

#

Tehtävä 7.9

Muuta edellinen esimerkki siten, että painoindeksi lasketaan aliohjelmassa, jota kutsutaan pääohjelmasta. Aliohjelma voi myös tulostaa tuloksen, mutta tällöin nimessä tulisi lukea se, esimerkiksi TulostaPainoindeksi. Lisää dokumentaatiokommentit.

 

#

Tehtävä 7.10

Tutki jypelin luokkaluetteloa. Etsi Level-luokka ja listaa sen attribuutteja eli ominaisuuksia tähän. Kerro myös ominaisuuden paluuarvo.

 

#

Tehtävä 7.11

Aikaisemmin tehtiin aliohjelma, joka tulostaa automaattisesti tekstin "Hello World". Tee nyt aliohjelma, joka tulostaa parametrina viedyn tekstin. Lisää myös dokumentaatiokommentit.

using System;

public class Tulostus
{
    public static void Main()
    {
        String teksti = "Jeps Jeps";
        TulostaTeksti();

    }


    public static void TulostaTeksti() {


    }
}

 

#

8. Oliotietotyypit

C#:n alkeistietotyypit antavat melko rajoittuneet puitteet ohjelmointiin. Niillä pystytään tallentamaan ainoastaan lukuja (int, double, jne.), yksittäisiä merkkejä (char) ja totuusarvoja (bool). Vähänkään monimutkaisemmissa ohjelmissa kuitenkin tarvitaan kehittyneempiä rakenteita tiedon tallennukseen. C#:ssa, Javassa ja muissa oliokielissä tällaisen rakenteen tarjoavat oliot. C#:ssa jo merkkijonokin (string) toteutetaan oliona.

8.1 Mitä oliot ovat?

Olio (engl. object) on tietorakenne, jolla pyritään ohjelmoinnissa kuvaamaan reaalimaailman ilmiöitä. Luokkapohjaisissa kielissä (kuten C#, Java ja C++) olion rakenteen ja käyttäytymisen määrittelee luokka, joka kuvaa siitä luodun olion attribuutit ja metodit. Attribuutit ovat olion ominaisuuksia ja metodit olion toimintoja. Olion sanotaan olevan luokan ilmentymä. Yhdestä luokasta voi siis (yleensä) luoda useita olioita, joilla on samat ominaisuudet ja toiminnallisuudet. Attribuuttien arvot muodostavat olion tilan. Huomaa kuitenkin, että vaikka oliolla olisi sama tila, sen identiteetti on eri. Esimerkiksi, kaksi täsmälleen samannäköistä palloa voi olla samassa paikassa (näyttää yhdeltä pallolta), mutta todellisuudessa ne ovat kaksi eri palloa.

Olioita voi joko tehdä itse tai käyttää jostain kirjastosta löytyviä valmiita olioita. Omien olioluokkien tekeminen ei kuulu vielä Ohjelmointi 1 -kurssin asioihin, mutta käyttäminen kyllä. Tarkastellaan seuraavaksi luokan ja olion suhdetta, sekä kuinka oliota käytetään.

Luokan ja olion suhdetta voisi kuvata seuraavalla esimerkillä. Olkoon luentosalissa useita ihmisiä. Kaikki luentosalissa olijat ovat ihmisiä. Heillä on tietyt samat ominaisuudet, jotka ovat kaikilla ihmisillä, kuten pää, kaksi silmää ja muitakin ruumiinosia. Kuitenkin jokainen salissa olija on erilainen ihmisen ilmentymä, eli jokaisella oliolla on oma identiteetti - eiväthän he ole yksi ja sama vaan heitä on useita. Eri ihmisillä voi olla erilainen tukka ja eriväriset silmät ja oma puhetyyli. Lisäksi ihmiset voivat olla eri pituisia, painoisia jne. Luentosalissa olevat identtiset kaksosetkin olisivat eri ilmentymiä ihmisestä. Jos Ihminen olisi luokka, niin kaikki luentosalissa olijat olisivat Ihminen-luokan ilmentymiä eli Ihminen-olioita. Tukka, silmät, pituus ja paino olisivat sitten olion ominaisuuksia eli attribuutteja. Ihmisellä voisi olla lisäksi joitain toimintoja eli metodeja kuten Syo, MeneToihin, Opiskele jne. Tarkastellaan seuraavaksi hieman todellisempaa esimerkkiä olioista.

Oletetaan, että suunnittelisimme yritykselle palkanmaksujärjestelmää. Siihen tarvittaisiin muun muassa Tyontekija-luokka. Tyontekija-luokalla täytyisi olla ainakin seuraavat attribuutit: nimi, tehtava, osasto, palkka. Luokalla täytyisi olla myös ainakin seuraavat metodit: MaksaPalkka, MuutaTehtava, MuutaOsasto, MuutaPalkka. Jokainen työntekijä olisi nyt omanlaisensa Tyontekija-luokan ilmentymä eli olio.

8.2 Olion luominen

Tyontekija teppo = new Tyontekija("Teppo Tunari", "Projektipäällikkö",
                                  "Tutkimusosasto", 5000);

Olioviite määritellään kirjoittamalla ensiksi sen luokan nimi, josta olio luodaan. Seuraavaksi kirjoitetaan nimi, jonka haluamme oliolle antaa. Nimen jälkeen tulee yhtäsuuruusmerkki, jonka jälkeen oliota luotaessa kirjoitetaan sana new ilmoittamaan, että luodaan uusi olio. Tämä new-operaattori varaa tilan tietokoneen muistista oliota varten.

Seuraavaksi kirjoitetaan luokan nimi uudelleen, jonka perään kirjoitetaan sulkuihin mahdolliset olion luontiin liittyvät parametrit. Parametrit riippuvat siitä, kuinka luokan konstruktori (constructor, muodostaja) on toteutettu. Konstruktori on metodi, joka suoritetaan aina kun uusi olio luodaan. Valmiita luokkia käyttääkseen ei tarvitse kuitenkaan tietää konstruktorin toteutuksesta, vaan tarvittavat parametrit selviävät aina luokan dokumentaatiosta. Yleisessä muodossa uusi olio luodaan alla olevalla tavalla.

Luokka olionNimi = new Luokka(parametri1, parametri2,..., parametriN);

Jos olio ei vaadi luomisen yhteydessä parametreja, kirjoitetaan silloin tyhjä sulkupari.

Ennen kuin oliolle on varattu tila tietokoneen muistista new-operaattorilla, ei sitä voi käyttää. Ennen new-operaattorin käyttöä oliomuuttujan arvo (eli viitteen arvo) on null. Oliomuuttujan, joka sisältää null-viitteen, käyttäminen aiheuttaa ajonaikaisen virheen. Oliomuuttujan arvo voidaan myös joissain erikoistilanteissa tarkoituksellisesti asettaa null-arvoksi sanomalla olionNimi = null.

Uusi Tyontekija-olio voitaisiin luoda esimerkiksi seuraavasti. Parametrit riippuisivat nyt siitä, kuinka olemme toteuttaneet Tyontekija-luokan konstruktorin. Tässä tapauksessa annamme nyt parametreina oliolle kaikki attribuutit.

Tyontekija akuAnkka = new Tyontekija("Aku Ankka", "Johtaja", "Osasto3", 3000);

Monisteen alussa loimme lumiukkoja piirrettäessä PhysicsObject-luokan olion seuraavasti.

PhysicsObject p1 = new PhysicsObject(2 * 100.0, 2 * 100.0, Shape.Circle);

Itse asiassa oliomuuttuja on C#:ssa ainoastaan viite varsinaiseen olioon. Siksi niitä kutsutaankin usein myös viitemuuttujiksi tai olioviitteeksi. Viitemuuttujat eroavat oleellisesti alkeistietotyyppisistä muuttujista.

8.3 Oliotietotyyppien ja alkeistietotyyppien ero

C#:ssa on kahden mallisia rakenteita, joihin tietoja voidaan tallentaa. Tapauksesta riippuen tiedot tallennetaan joko alkeistietotyyppeihin tai oliotietotyyppeihin. Oliotietotyypit eroavat alkeistietotyypeistä siinä, että ne ovat viitteitä tiettyyn olioon, ja siksi niitä kutsutaan myös nimellä viitetyypit tai viitemuuttujat.

  • Alkeistietotyypit säilyttävät sisältämänsä tiedon yhdessä paikassa tietokoneen muistissa (nimeltään pino).

  • Viitetyypit sisältävät viitteen johonkin toiseen paikkaan muistissa (kutsutaan nimellä keko), missä varsinainen data sijaitsee. Viittaus olioon sijaitsee kuitenkin pinossa.

Yleensä meidän ei tarvitse olla kovin huolissamme siitä, käytämmekö alkeistietotyyppiä (kuten int, double tai char) vai oliotyyppiä (kuten string). Yleisesti ottaen tärkein ero on siinä, että alkeistietotyyppien tulee (tiettyjä poikkeuksia lukuun ottamatta) aina sisältää jokin arvo, mutta oliotietotyypit voivat olla null-arvoisia (eli “ei-minkään” arvoisia). Jäljempänä esimerkkejä alkeistietotyyppien ja viitetyyppien eroista.

Samaan olioon voi viitata useampi muuttuja. Vertaa alla olevia koodinpätkiä.

#
        int luku1 = 10;
        int luku2 = luku1;
        luku1 = 0;
        System.Console.WriteLine(luku2); //tulostaa 10

 

Yllä oleva tulostaa “10” niin kuin pitääkin. Muuttujan luku2 arvo ei siis muutu, vaikka asetamme kolmannella rivillä muuttujaan luku1 arvon 0. Tämä johtuu siitä, että toisella rivillä asetamme muuttujaan luku2 muuttujan luku1 arvon, emmekä viitettä muuttujaan luku1. Oliotietotyyppisten muuttujien kanssa asia on toinen. Vertaa yllä olevaa esimerkkiä seuraavaan:

#
        PhysicsObject p1 = new PhysicsObject(2*100.0, 2*100.0, Shape.Circle);
        Add(p1);
        p1.X = -200;

        PhysicsObject p2 = p1;
        p2.X = 100;

 

Yllä oleva koodi piirtää seuraavan kuvan:

#

Kuva 8: Molemmat muuttujat, p1 ja p2, liikuttelevat samaa ympyrää. Lopputuloksena ympyrä seisoo pisteessä x=100.

Nopeasti voisi olettaa, että ikkunassamme näkyisi nyt vain kaksi samanlaista ympyrää eri paikoissa. Näin ei kuitenkaan ole, vaan molemmat PhysicsObject-oliot viittaavat samaan ympyrään, jonka säde on 50. Tämä johtuu siitä, että muuttujat p1 ja p2 ovat olioviitteitä, jotka viittaavat (ts. osoittavat) samaan olioon.

PhysicsObject p2 = p1;

Toisin sanoen yllä olevalla rivillä ei luoda uutta PhysicsObject-oliota, vaan ainoastaan uusi olioviite, joka viittaa nyt samaan olioon kuin p1.

#

Kuva 9: Sekä p1 että p2 viittaavat samaan olioon.

Oliomuuttuja = Viite todelliseen olioon. Samaan olioon voi olla useitakin viitteitä.

Viitteitä käsitellään tarkemmin luvussa 14.

8.4 Metodin kutsuminen

Jokaisella tietystä luokasta luodulla oliolla on käytössä kaikki tämän luokan julkiset metodit. Metodikutsussa käsketään oliota tekemään jotain. Voisimme esimerkiksi käskeä PhysicsObject-oliota liikkumaan, tai Tyontekija-oliota muuttamaan palkkaansa.

Olion metodeita kutsutaan kirjoittamalla ensiksi olion nimi, piste ja kutsuttavan metodin nimi. Metodin mahdolliset parametrit laitetaan sulkeiden sisään ja erotetaan toisistaan pilkulla. Jos metodi ei vaadi parametreja, täytyy sulut silti kirjoittaa, niiden sisälle ei vaan tule mitään. Yleisessä muodossa metodikutsu on seuraava:

olionNimi.MetodinNimi(parametri1,parametri2,...parametriN);

Voisimme nyt esimerkiksi muuttaa akuAnkka-olion palkkaa alla olevalla tavalla.

akuAnkka.MuutaPalkka(3500);

Tai laittaa p1-olion (oletetaan, että p1 on PhysicsObject-olio) liikkeelle käyttäen Hit-metodia.

p1.Hit(new Vector(1000.0, 500.0));

String-luokasta löytyy esimerkiksi Contains-metodi, joka palauttaa arvon True tai False. Parametrina Contains-metodille annetaan merkkijono, ja metodi etsii oliosta antamaamme merkkijonoa vastaavia ilmentymiä. Jos olio sisältää merkkijonon (yhden tai useamman kerran), palautetaan True. Muutoin palautetaan False. Alla esimerkki.

#
        string lause = "Pekka meni kauppaan";
        Console.WriteLine(lause.Contains("eni")); // Tulostaa True

 

8.5 Metodin ja aliohjelman ero

Aliohjelma esitellään static-tyyppiseksi, mikäli aliohjelma ei käytä mitään muita tietoja kuin parametreina tuodut tiedot. Esimerkiksi luvussa 20.4.2 on seuraava aliohjelma.

private void KuunteleLiiketta(AnalogState hiirenTila)
{
   pallo.X = Mouse.PositionOnWorld.X;
   pallo.Y = Mouse.PositionOnWorld.Y;

   Vector hiirenLiike = hiirenTila.MouseMovement;
}

Tässä tarvitaan hiiren tilan lisäksi pelioliossa (this) esitellyn pallo -olion tietoja, joten enää ei ole kyse staattisesta aliohjelmasta, ja siksi static-sana jätetään pois. Metodi sen sijaan pystyy käyttämään olion omia “ominaisuuksia”, attribuutteja, metodeja ja ns. ominaisuus-kenttiä (property fields). Muista, että olion omiin “asioihin” voisi viitata myös:

   this.pallo.X = Mouse.PositionOnWorld.X;

eli jos aliohjelma tarvitsee this -viitettä, se on metodi (eli ei staattinen).

8.6 Olion tuhoaminen ja roskienkeruu

Kun olioon ei enää viittaa yhtään muuttujaa (olioviitettä), täytyy olion käyttämät muistipaikat vapauttaa muuhun käyttöön. Oliot poistetaan muistista puhdistusoperaation avulla. Tästä huolehtii C#:n automaattinen roskienkeruu (garbage collection). Kun olioon ei ole enää viitteitä, se merkitään poistettavaksi, ja aina tietyin väliajoin puhdistusoperaatio (kutsutaan usein myös nimellä roskienkerääjä, garbage collector) vapauttaa merkittyjen olioiden muistipaikat.

Kaikissa ohjelmointikielissä näin ei ole (esim. alkuperäinen C++), vaan muistin vapauttamisesta ja olioiden tuhoamisesta tulee useimmiten huolehtia itse. Näissä kielissä on yleensä destruktori (destructor = hajottaja), joka suoritetaan aina kun olio tuhotaan. Itse kirjoitettavasta destruktorista on tapana kutsua olion elinaikanaan luomien olioiden tuhoamista tai muiden resurssien vapauttamista. Vertaa konstruktoriin, joka suoritettiin kun olio luodaan. Haastavaksi näiden kielien yhteydessä tuleekin se, että joissakin tapauksissa olioiden elinkaari on automaattista ja joissakin ei. Tästä seuraa helposti muistivuoto, eli jokin muistialue unohtuu vapauttaa, mutta siihen ei ole enää yhtään osoitinta, jolla siihen päästäisiin käsiksi ja näin muistialue jää varatuksi koko ohjelman loppuajaksi. Siksi muistivuodot ovat erittäin yleisiä aloittelevilla C++ -ohjelmoijilla. Javan ja C#:in kaltaiset kielet ovat tuoneet valtavan helpotuksen muistivuotojen välttämiseen.

Yleensä C#-ohjelmoijan ei tarvitse huolehtia muistin vapauttamisesta, mutta on tiettyjä tilanteita, joissa voidaan itse joutua poistamaan oliot. Yksi esimerkki tällaisesta tilanteesta on tiedostojen käsittely: Jos olio on avannut tiedoston, olisi viimeistään ennen olion tuhoamista järkevää sulkea tiedosto. Tällöin samassa yhteydessä olion tuhottavaksi merkitsemisen kanssa suoritettaisiin myös tiedoston sulkeminen. Tämä tehdään esittelemällä hajotin (destructor), joka on luokan metodi, ja jonka tehtävänä on tyhjentää olio kaikesta sen sisältämästä tiedosta sekä vapauttaa sen sisältämät rakenteet, kuten kytkökset avoinna oleviin resursseihin (esim tiedostoon, tosin yleensä tiedostoa ei ole hyvä pitää avoinna niin kauan aikaa kuin jonkin olion elinkaari voi olla).

Olisiko periaatteessa parasta ottaa tavaksi vapauttaa muistialueet manuaalisesti riippumatta käytettävästä kielestä?

VL: ei ole hyvä idea. Ihminen ei tuohon käytännössä pysty tuota tekemään. Sen on C/C++ koodaus osoittanut. Suuri osa C/C++ koodista vuotaa kuin seula. Siksi on kehitetty noita kieliä jossa ohjelma hoitaa muistinhallinnan ja päästään paljon luotettavampaan tulokseen. Eli jos kielessä on automaattinen muistinhallinta, sitä kannattaa käyttää. Poikkeuksen tekee ehkä suurta suorituskykyä vaativat ohjelmat ja/tai sulautetut järjestelmät. Mutta usein niissäkin pärjää “paremmilla” kielillä. Esim fyysikot ovat ruvennet paljon käyttämään Pythonia.

11 Sep 20 (edited 11 Sep 20)

8.7 Olioluokkien dokumentaatio

Luokan dokumentaatio sisältää tiedot luokasta, luokan konstruktoreista ja metodeista. Luokkien dokumentaatioissa on yleensä linkkejä esimerkkeihin, kuten myös String-luokan tapauksessa. Tutustutaan nyt tarkemmin String-luokan dokumentaatioon. String-luokan dokumentaatio löytyy sivulta http://msdn.microsoft.com/en-us/library/system.string.aspx, jossa on muun muassa lista jäsenistä eli käytössä olevista konstruktoreista, attribuuteista (fields), ominaisuuksista (property) ja metodeista.

Olemme kiinnostuneita tässä vaiheessa kohdista String Constructor ja String Methods (sivun vasemmassa osassa hierarkiapuussa). Klikkaa kohdasta String Constructor saadaksesi lisätietoa luokan konstruktoreista tai String Methods saadaksesi tietoja käytössä olevista metodeista.

8.7.1 Konstruktorit

Avaa luokan String sivu String Constructor. Tämä kohta sisältää tiedot kaikista luokan konstruktoreista. Konstruktoreita voi olla useita, kunhan niiden parametrit eroavat toisistaan. Jokaisella konstruktorilla on oma sivu, ja sivulla kunkin ohjelmointikielen kohdalla oma versionsa, sillä .NET Framework käsittää useita ohjelmointikieliä. Me olemme luonnollisesti tässä vaiheessa kiinnostuneita vain C#-kielisistä versioista.

Kunkin konstruktorin kohdalla on lyhyesti kerrottu mitä se tekee, ja sen jälkeen minkä tyyppisiä ja montako parametria konstruktori ottaa vastaan. Kaikista konstruktoreista saa lisätietoa klikkaamalla konstruktorin esittelyriviä. Esimerkiksi linkki

vie sivulle (http://msdn.microsoft.com/en-us/library/ttyxaek9.aspx) jossa konstruktorista public String(char[]) kerrotaan lisätietoja ja annetaan käyttöesimerkkejä.

#

Kuva 10: Tiedot luokan konstruktoreista löytyvät MSDN-dokumentaatioissa Constructor-kohdasta.

Huomaa, että monet String-luokan konstruktoreista on merkitty unsafe-merkinnällä, jolloin niitä ei tulisi käyttää omassa koodissa. Tällaiset konstruktorit on tarkoitettu ainoastaan järjestelmien keskinäiseen viestintään.

Tässä vaiheessa voi olla vielä hankalaa ymmärtää kaikkien konstruktorien merkitystä, sillä ne sisältävät tietotyyppejä, joita emme ole vielä käsitelleet. Esimerkiksi tietotyypin perässä olevat hakasulkeet (esim. int[]) tarkoittavat, että kyseessä on taulukko. Taulukoita käsitellään lisää luvussa 15.

String-luokan olio on C#:n ehkä yleisin olio, ja on itse asiassa kokoelma (taulukko) perättäisiä yksittäisiä char-tyyppisiä merkkejä. Se voidaan luoda seuraavasti.

#
        string nimi = new String(new char [] {'J', 'a', 'n', 'n', 'e'});
        Console.WriteLine(nimi); // Tulostaa Janne

 

Näin kirjoittaminen on tietenkin usein melko vaivalloista. String-luokan olio voidaan kuitenkin poikkeuksellisesti luoda myös alkeistietotyyppisten muuttujien määrittelyä muistuttavalla tavalla. Alla oleva lause on vastaava kuin edellisessä kohdassa, mutta lyhyempi kirjoittaa.

#
        string nimi = "Janne";
        Console.WriteLine(nimi); // Tulostaa Janne

 

Huomaa, että merkkijonon ympärille tulee lainausmerkit. Näppäimistöltä lainausmerkit saadaan näppäinyhdistelmällä Shift+2. Vastaavasti merkkijono voitaisiin kuitenkin alustaa myös muilla String-luokan konstruktoreilla, joita on pitkä lista.

Jos taas tutkimme PhysicsObject-luokan dokumentaatiota (löytyy osoitteesta http://kurssit.it.jyu.fi/npo/material/latest/documentation/html/ -> Luokat -> Luokkalista -> Jypeli -> PhysicsObject), löydämme useita eri konstruktoreita (ks. kohta Staattiset julkiset jäsenfunktiot, jotka alkavat sanalla PhysicsObject). Konstruktoreista järjestyksessä toinen saa parametreina kaksi lukua ja muodon. Tätä konstruktoria käytimme jo lumiukkoesimerkissä.

#

Kuva 11: Jypeli-kirjaston luokan konstruktorit löytyvät Julkiset jäsenfunktiot -otsikon alta.

Voisimme kuitenkin olla antamatta muotoa (ensimmäinen konstruktori) ja määritellä muodon vasta myöhemmin fysiikkaolion Shape-ominaisuuden avulla.

#

8.7.2 Harjoitus

Tutki muita konstruktoreja. Mitä niistä selviää dokumentaation perusteella? Mikä on oletusmuoto?

#

 

8.7.3 Metodit

Kohta Methods (http://msdn.microsoft.com/en-us/library/system.string_methods.aspx) sisältää tiedot kaikista luokan metodeista. Jokaisella metodilla on taulukossa oma rivi, ja rivillä lyhyt kuvaus, mitä metodi tekee. Klikattuasi jotain metodia saat siitä tarkemmat tiedot. Tällä sivulla kerrotaan mm. minkä tyyppisen parametrin metodi ottaa, ja minkä tyyppisen arvon metodi palauttaa. Esimerkiksi String-luokassa käyttämämme ToUpper-metodi, joka siis palauttaa String-tyyppisen arvon.

8.7.4 Huomautus: Luokkien dokumentaatioiden googlettaminen

Huomaa, että kun haet luokkien dokumentaatioita hakukoneilla, saattavat tulokset viitata .NET Frameworkin vanhempiin versioihin (esimerkiksi 1.0 tai 2.0). Kirjoitushetkellä uusin .NET Framework-versio on 4, ja onkin syytä varmistua, että löytämäsi dokumentaatio koskee juuri oikeaa versiota. Voit esimerkiksi käyttää hakutermissä versionumeroa tähän tapaan: “c# string documentation .net 4”. Versionumeron näkee otsikon alapuolella. Voit halutessasi vaihtaa johonkin toiseen versioon klikkaamalla Other Versions -pudotusvalikkoa.

8.8 Tyyppimuunnokset

C#:ssa yhteen muuttujaan voi tallentaa vain yhtä tyyppiä. Tämän takia meidän täytyy joskus muuttaa esimerkiksi String-tyyppinen muuttuja int-tyyppiseksi tai double-tyyppinen muuttuja int-tyyppiseksi ja niin edelleen. Kun muuttujan tyyppi vaihdetaan toiseksi, sanotaan sitä tyyppimuunnokseksi (cast, tai type cast).

Kaikilla alkeistietotyypeillä sekä C#:n oliotyypeillä on ToString-metodi, jolla olio voidaan muuttaa merkkijonoksi. Alla esimerkki int-luvun muuttamisesta merkkijonoksi.

#
        // kokonaisluku merkkijonoksi
        int kokonaisluku = 24;
        string intMerkkijonona = kokonaisluku.ToString();

 

#
        // liukuluku merkkijonoksi
        double liukuluku = 0.562;
        string doubleMerkkijonona = liukuluku.ToString();

 

Merkkijonon muuttaminen alkeistietotyypiksi onnistuu sen sijaan jokaiselle alkeistietotyypille tehdystä luokasta löytyvällä metodilla. Alkeistietotyypithän eivät ole olioita, joten niillä ei ole metodeita. C#:sta löytyy kuitenkin jokaista alkeistietotyyppiä vastaava rakenne (struct), josta löytyy alkeistietotyyppien käsittelyyn hyödyllisiä metodeita. Rakenteet sijaitsevat System-nimiavaruudessa, ja tästä syystä ohjelman alussa tarvitaan lause

using System;

Alkeistietotyyppejä vastaavat rakenteet löytyvät seuraavasta taulukosta.

Taulukko 5: Alkeistietotyypit ja niitä vastaavat rakenteet.

Alkeistieto-tyyppi Rakenne
bool Boolean
byte Byte
char Char
short Int16
int Int32
long Int64
ulong UInt64
float Single
double Double

Huomaa, että rakenteen ja alkeistietotyypin nimet ovat C#:ssa synonyymejä. Seuraavat rivit tuottavat saman lopputuloksen (mikäli System-nimiavaruus on otettu käyttöön using-lauseella).

#
        int luku1 = 5;
        Int32 luku2 = 6;

 

Vastaavasti kaikki rakenteiden metodit ovat käytössä, kirjoittipa alkeistietotyypin tai rakenteen nimen. Tästä esimerkki seuraavaksi.

Merkkijonon (String) muuttaminen int-tyypiksi onnistuu C#:n int.Parse-funktiolla seuraavasti.

#

Kun olet kokeillut, kokeile vaihtaa jonoon jotakin mikä ei ole numero. Mitä tapahtuu?

        string jono = "24";
        int luku2 = int.Parse(jono);

 

Tarkasti sanottuna Parse-funktio luo parametrina saamansa merkkijonon perusteella uuden int-tyyppisen tiedon, joka talletetaan muuttujaan luku2.

Jos luvun parsiminen (jäsentäminen, muuttaminen) ei onnistu, aiheuttaa se niin sanotun poikkeuksen. double-luvun parsiminen onnistuu vastaavasti Double-rakenteesta (iso D-kirjain) löytyvällä Parse-funktiolla.

#
        string jono = "2.45";
        double luku = Double.Parse(jono);

 

Käytännössä jos tieto saadaan ihmisen syöttämänä, niin on erittäin todennäköistä, että se ei muodosta laillista numeroa. Siksi usein kannattaa käyttää funktiota TryParse:

#
        string jono = "2.45";
        double luku = 5;
        bool onnistui;
        onnistui = Double.TryParse(jono,out luku);

 

Asiaa vielä monimutkaistaa se, että käyttöjärjestelmän desimaalierotin saattaa olla pilkku (,) tai piste (.).

#

9. Aliohjelman paluuarvo

#

Muokkaa ohjelma toimivaksi. Laita pääohjelma ennen muita aliohjelmia.

public class Vahennys
{
       public static void Main()
       {
           int luku = 102;
           System.Console.WriteLine(Vahenna(luku, 3000));
       }
       public static double Vahenna(double luku, double montakoVahennetaan)
       {
           double tulos = luku - montakoVahennetaan;
           return tulos;
       }
}

 

Aliohjelmat-luvussa tekemämme Lumiukko-aliohjelma ei palauttanut mitään arvoa. Usein on kuitenkin hyödyllistä, että lopettaessaan aliohjelma palauttaa jotain tietoa aliohjelman suorituksesta. Mitä hyötyä olisi esimerkiksi aliohjelmasta, joka laskee kahden luvun keskiarvon, jos emme koskaan saisi tietää mikä niiden lukujen keskiarvo on? Voisimmehan me tietenkin tulostaa luvun keskiarvon suoraan aliohjelmassa, mutta lähes aina on järkevämpää palauttaa tulos “kysyjälle” paluuarvona. Tällöin aliohjelmaa voidaan käyttää myös tilanteessa, jossa keskiarvoa ei haluta tulostaa, vaan sitä tarvitaan johonkin muuhun laskentaan. Paluuarvon palauttaminen tapahtuu return-lauseella, ja return-lause lopettaa aina aliohjelman suorittamisen (eli palataan takaisin kutsuvaan ohjelman osaan).

Yleensä aliohjelmaa joka palauttaa arvon, sanotaan funktioksi.

9.1 Keskiarvon laskeva funktio

Luvun sisältö videona, jota voit katsoa samaan aikaan kun luet tätä lukua:

#
Keskiarvo-funktion kirjoittaminen ja kutsuminen Luento 5 – 35m0s (50m0s)

Ennen funktion toteuttamista suunnitellaan, että sitä kutsuttaisiin seuraavasti:

        double keskiarvo;
        keskiarvo = Keskiarvo(3, 4);

Eli kun funktiosta palataan, se palauttaa laskemansa tuloksen, ja kutsuva sijoittaa saamansa tuloksen apumuuttujaan.

Toteutetaan nyt kyseinen funktio.

#
    public static double Keskiarvo(int a, int b)
    {
       double keskiarvo;
       keskiarvo = (a + b) / 2.0; // Huom 2.0, jotta reaaliluku
       return keskiarvo;
    }

 

Ensimmäisellä rivillä määritellään jälleen julkinen ja staattinen aliohjelma. Lumiukko-esimerkissä static-sanan jälkeen luki void, joka tarkoitti, että aliohjelma ei palauttanut mitään arvoa. Koska nyt haluamme, että aliohjelma palauttaa parametreina saamiensa kokonaislukujen keskiarvon, niin meidän täytyy kirjoittaa paluuarvon tyyppi void-sanan tilalle static-sanan jälkeen. Koska kahden kokonaisluvun keskiarvo voi olla myös desimaaliluku, niin paluuarvon tyyppi on double. Sulkujen sisällä ilmoitetaan jälleen parametrit. Nyt parametreina on kaksi kokonaislukua a ja b. Toisella rivillä määritellään reaalilukumuuttuja keskiarvo. Kolmannella rivillä lasketaan parametrien a ja b summa ja jaetaan se kahdella muuttujaan keskiarvo. Neljännellä rivillä palautetaan keskiarvo-muuttujan arvo.

9.2 Funktion kutsuminen

Aliohjelmaa voitaisiin nyt käyttää pääohjelmassa esimerkiksi alla olevalla tavalla.

#

Kokeile laskea muidenkin lukujen keskiarvoja. Kokeile myös kutsua Keskiarvo(2+3, 4+7)

        double keskiarvo;
        keskiarvo = Keskiarvo(3, 4);
        Console.WriteLine("Keskiarvo = " + keskiarvo);

 

Kutsu voitaisiin kirjoittaa myös lyhyemmin:

#
        Console.WriteLine("Keskiarvo = " + Keskiarvo(3, 4));

 

Koska Keskiarvo-aliohjelma palauttaa aina double-tyyppisen liukuluvun, voidaan kutsua käyttää kuten mitä tahansa double-tyyppistä arvoa. Se voidaan esimerkiksi tulostaa tai tallentaa muuttujaan.

Alla olevassa animaatiossa on ensin kirjoitettu funktio ja sitten pääohjelma. Näiden järjestyksellähän ei ole väliä C#-kielessä. Ohjelman suoritus aloitetaan aina pääohjelmasta, ja aliohjelmia suoritetaan niiden kutsumisjärjestyksessä, olipa aliohjelmien lähdekoodi kirjoitettu mihin kohtaan tahansa luokan sisällä.

Alla olevassa animaatiossa on kaksi peräkkäistä kutsua, jotka havainnollistavat aliohjelman kutsuja eri arvoilla. Jälkimmäisessä kutsussa nähdään miten kutsun yhteydessä lasketaan lausekkeen arvo. Eli funktion (ja minkä tahansa aliohjelman) kutsussa voi olla mitä tahansa lausekkeita, jotka tuottavat tyypiltään sellaisen arvon, joka voidaan sijoittaa vastinparametrille. Tässä tapauksessa 2+6 on lauseke, jonka arvo on int ja aliohjelman vastinparametri, nimeltään b, on myös tyypiltään int. Jatkossa huomaamme että lauseke voi sisältää myös funktiokutsuja.

#

Animaatio: Tutki funktion kutsua

Askella silmukan suoritusta vihreällä nuolella Tutki funktion kutsua

9.3 Funktion kirjoittaminen toisella tavalla

Itse asiassa koko Keskiarvo-aliohjelman voisi kirjoittaa lyhyemmin muodossa:

#
    public static double Keskiarvo(int a, int b)
    {
        double keskiarvo = (a + b) / 2.0;
        return keskiarvo;
    }

 

Yksinkertaisimmillaan Keskiarvo-aliohjelman voisi kirjoittaa jopa alla olevalla tavalla.

#
//
    public static double Keskiarvo(int a, int b)
    {
     return (a + b) / 2.0;
    }

 

Kaikki yllä olevat tavat ovat oikein, eikä voi sanoa, mikä tapa on paras. Joskus “välivaiheiden” kirjoittaminen selkeyttää koodia, mutta Keskiarvo-aliohjelman tapauksessa viimeisin tapa on selkein ja lyhin.

Jos funktiota tarvitsee debugata, silloin se on helpointa mikäli osatuloksia on laskettu apumuuttujiin. Tällöin voi olla että yhdelle riville kirjoitettua funktiota voi joutua paloittelemana takaisin osiin.

Testien yksi tarkoitus on pitää huolta siitä, että vaikka toteutusta muuttaa, niin tuloksen oikeellisuus on helpompi tarkistaa. Pitää tosin silti muistaa, että testit eivät koskaan todista että joku toimii kaikissa tapauksissa! Katso edellisessä esimerkissä testit painamalla Näytä koko koodi ja ja myös testit painamalla Test. Katso myös syntyvät dokumentaatio painamalla Document.

9.4 Useita return-lauseita

Aliohjelmassa voi olla myös useita return-lauseita. Tästä esimerkki kohdassa: 13.5.1. Mikäli koodissa on useita return-lauseita, pitää niistä “ylimääräisten” olla ehdollisesti suoritettavia.

Usein pidetään kuitenkin riskinä koodia, jossa on useita return-lauseita. Hyvä esimerkki on sellainen, missä esimerkiksi ensin on tehty koodi, joka jossakin tilanteessa laskee jotakin ja palauttaa sen:

   ...
   if ( a < 0 ) return summa / lkm;
   ...
   return summa/lkm;

Kun koodia on testattua useilla arvoilla huomataankin, että lkm voi olla nolla ja muutetaan koodia:

   ...
   if ( a < 0 ) return summa / lkm;
   ...
   if ( lkm == 0 ) return 0;
   return summa/lkm;

Mikä nyt menee pieleen? Se, että ensimmäisessäkin return-lauseessa voi olla tilanne missä lkm on nolla.

On makuasia välttääkö useita poistuiskohtia vaiko ei. Usein return-lauseiden kanssa saa myös koodista selkeämpää kun ei tule paljoa sisäkkäisiä lohkoja.

9.5 Funktio palauttaa yhden arvon

Aliohjelma ei kuitenkaan voi palauttaa kerralla suoranaisesti useita arvoja. Toki voidaan palauttaa esimerkiksi taulukko, jossa sitten on monia arvoja. Toinen keino olisi luoda olio, joka sisältäisi useita arvoja ja palautettaisiin viite tähän olioon (tosin taulukkokin on olio, joten sen palauttaminen on vain tämän tapauksen erikoistapaus). C#:ssa on olemassa kolmaskin keino, jota ei käsitellä tällä kurssilla: ref- ja out-parametrit (tosin TryParse-funktion tapauksessa käytimme out-parametriä), katso:

Onkohan vikaa linkissä vai enkö vain ymmärrä mitä avautuvalla sivulla kuuluisi tehdä?

VL: tyypillistä M$… Vaihdoin toisen linkin.

30 Sep 19 (edited 30 Sep 19)

Metodeita ja aliohjelmia, jotka ottavat vastaan parametreja ja palauttavat arvon, sanotaan funktioiksi. Nimitys ei ole hullumpi, jos vertaa Keskiarvo-aliohjelmaa vaikkapa matematiikan funktioon \(f(x, y) = (x + y) / 2\).

Funktioiden tulisi olla sellaisia, että ne toimivat parametreina saatujen tietojen avulla, eivätkä tarvitse toimiakseen muuta tietoa ohjelmasta. Vastaavasti parametrina saatujen arvojen muuttamista pitäisi välttää. Puhtaasti funktionaalisessa ohjelmoinnin ajattelutavassa funktiolla ei ole sivuvaikutuksia. Sivuvaikutuksia ovat esimerkiksi ruudulle tulostaminen tai ohjelman tilan muuttaminen. Olio-ohjelmointiin perustuvassa ajattelussa (ja myös tällä kurssilla) tästä vaatimuksesta joudutaan joissain kohdissa hieman tinkimään. Esimerkiksi Jypeli-peleissä funktiot usein muuttavat pelin tilaa esimerkiksi lisäämällä pelikentälle uuden olion, ja siten ne eivät ole täysin vapaita sivuvaikutuksista.

9.6 Funktion kutsu maksaa

Mitä eroa on tämän

#
        double tulos = Keskiarvo(5, 2); // lasketaan Keskiarvo
        Console.WriteLine(tulos); //tulostaa 3.5
        Console.WriteLine(tulos); //tulostaa 3.5

 

ja tämän

#
        Console.WriteLine(Keskiarvo(5, 2)); //tämäkin tulostaa 3.5
        Console.WriteLine(Keskiarvo(5, 2)); //tämäkin tulostaa 3.5

 

koodin suorituksessa?

Ensimmäisessä lukujen 5 ja 2 keskiarvo lasketaan vain kertaalleen, jonka jälkeen tulos tallennetaan muuttujaan. Tulostuksessa käytetään sitten tallessa olevaa laskun tulosta.

Jälkimmäisessä versiossa lukujen 5 ja 2 keskiarvo lasketaan tulostuksen yhteydessä. Keskiarvo lasketaan siis kahteen kertaan. Vaikka alemmassa tavassa säästetään yksi koodirivi, kulutetaan siinä turhaan tietokoneen resursseja laskemalla sama lasku kahteen kertaan. Tässä tapauksessa tällä ei ole juurikaan merkitystä, sillä Keskarvo-aliohjelman suoritus ei juurikaan rasita nykyaikaisia tietokoneita. Kannattaa kuitenkin opetella tapa, ettei ohjelmassa tehtäisi mitään turhia suorituksia.

Nykyään meitä opettaa säästävään ajatteluun ainakin IOT-laitteet, joissa voidaan saavuttaa pidempi huoltoväli (pariston vaihto) kun ei tuhlata resursseja.

03 Oct 18
#

9.7 YmpyranAla, esimerkki yhden parametrin funktiosta

Edellisessä esimerkissä on funktiolle viety kaksi parametria. Paramaterien määrä riippuu ihan tarpeesta ja voi olla mitä tahansa 0:sta n:ään. Tosin parametrittomat funktiot ovat aika harvinaisia.

Seuraavaksi vielä esimerkki yhden parametrin funktiosta:

#
//
    /// <summary>
    /// Kutsutaan malliksi funktioita
    /// </summary>
    public static void Main()
    {
        double ala;
        ala = YmpyranAla(2);
        Console.WriteLine("Ympyrän ala on {0:0.00}", ala);
    }

    /// <summary>
    /// Lasketaan ympyrän pinta-ala
    /// </summary>
    /// <param name="r">ympyrän säde</param>
    /// <returns>ympyrän pinta-ala</returns>
    /// <example>
    /// <pre name="test">
    ///   YmpyranAla(1) ~~~ 3.1415926;
    ///   YmpyranAla(2) ~~~ 12.5663706;
    /// </pre>
    /// </example>
    public static double YmpyranAla(double r)
    {
        return Math.PI * r * r;
    }

 


Muista että edellistä funktiota voisit kutsua myös millä tahansa seuraavista tavoista (kokeile esimerkkiin):

  ...
  double sade = 2.1;
  ala = YmpyranAla(sade);  // luonnollisesti muuttujalla
  ...
  ala = YmpyranAla(sade + 7.0); // ja millä tahansa lausekkeella joka tuottaa double
  ...
  YmpyranAla(12);  // näinkin voi kutsua, mutta tässä ei sinällään ole järkeä
                   // tässä tapauksessa kun tulosta ei oteta vastaan.    

Ei toimi, tai sitten en vain ymmärrä.???
VL: Toki se ala-muuttuja pitää esitellä, se että sädettä käyttää toisella tavalla ei vapauta sen esittelystö

22 Sep 17 (edited 29 Sep 17)

Kiitos, nyt toimi!

29 Sep 17

9.8 Tehtäviä funktioista

#
Kysymyksiä paluuarvosta

Mitkä seuraavista kommenteista pitää paikkaansa:

#

9.8.1 Harjoitus

Muuttujat-luvun lopussa tehtiin ohjelma, joka laski painoindeksin. Tee ohjelmasta uusi versio, jossa painoindeksin laskeminen tehdään funktiossa. Funktio saa parametreina pituuden ja painon ja palauttaa painoindeksin. Tuloksen tulostaminen tapahtuu pääohjelmassa.

#

Tehtävä 9.8.1

public class Painoindeksi
{
    public static void Main()
    {
      double pituus = 1.83;
      double paino = 75.0;
      double painoindeksi = paino / (pituus*pituus);
      System.Console.WriteLine(painoindeksi);
   }
}

 

#

Tehtävä 9.8.2

Kirjoita kutsuja vastaavat funktiot niin että ohjelma toimii. Älä muuta aliohjelmakutsuja, kirjoita pelkät aliohjelman toteutukset. Uusia aliohjelmia/funktioita tarvitaan yhteensä neljä.

public class Funktioita
{
    public static void Main()
    {
      double summa = LaskeSumma(5, 6.6, 7);
      double erotus = LaskeErotus(7, 9);
      int luku = MiinustaYksi(9);
      Tulosta(summa, erotus, luku);
   }
}

 

Kysyin tätä koodarikaverilta ja jumpattiin tätä Tulosta-kutsua aika kauan. Täytyykö nuo luvut muuttaa ensin stringeiksi ja sitten luoda vielä oma neljäs string-muuttuja koko mömmölle?

-vl: ei ole välttämätöntä muuttaa itse, WriteLine("a="+a) muuttaa meidän puolesta.

28 Sep 18 (edited 29 Sep 18)

Olisitko niin kiltti että antaisit jonkun vinkin kuinka tuo Tulostus saadaan tehtyä? Olen nyt määrittänyt luvut ja yrittänyt tulostaa mutta herjaa että ei voi muuttaa double chariksi tai stringiksi tai sitten saan vaan tulostettua muuttujan symbolin. Edit: Yritin siis tulostaa aliohjelmassa. Jos tekee esimerkin tavalla niin herää kysymys mihin neljättä aliohjelmaa tarvitsee?

  • VL: tuossa alla seuraavassa esimerkissähän on miten lukuja voi tulostaa. Se tehdään esimerkin tavalla sinne “neljänteen” aliohjelmaan. ELi ongelma jaetaan osiin ja pääohjelmassa ei puututa yksityiskohtiin vaan kerrotaan mitä pitäisi tehdä. Aliohjelmat hoitelevat yksityiskohdat. Toki tässä vaiheessa aliohjelmat eivät vielä ole vaikeusasteeltaan kummoisia ja helposti heräääkin kysymys että miksi niitä tehdään. Mutta pitää ajatella, että oikeassa elämässä niiden tehtävä on merkittävästi suurempi kuin joku yhden rivin lasku.
02 Oct 18 (edited 03 Oct 18)

Onko ok tehdä tuo tulostava aliohjelma ja aliohjelmat ylipäänsä ilman return- komentoa? Sain aliohjelman toimimaan käyttämällä voidia, kun en keksinyt miten tuo toimisi return-komennon kanssa.
VL: Tulosta on void jonka näkee kutsusta, koska sen palauttamaa tulosta ei sijoiteta mihinkään. Tosin voi olla myös sellainen tilanne, että funktio palauttaa jotakin, mutta sen palauttamasta tuloskesta ei sillä kertaa olla kiinnostuneita. Eli joskus tuo kutsusta tehtävä päätelmä voi johtaa harhaan, mutta pääsääntöisesti niin, että jos palautettua arvoa ei sijoiteta, niin silloin on void-aliohjelma.

26 Sep 19 (edited 26 Sep 19)
#

Tehtävä 9.8.3

Kirjoita aliohjelmat nyt niin että lukujen summan voi laskea kahdesta tai kolmesta luvusta. Älä muuta aliohjelmakutsuja, kirjoita pelkät aliohjelman toteutukset. Kertaa tarvittaessa asiaa aliohjelmien kuormittamisesta.

public class Funktioita
{
    public static void Main()
    {
      double summa = LaskeSumma(5, 6.6, 7);
      double summa2 = LaskeSumma(7, 9);
      System.Console.WriteLine("{0} {1}",summa, summa2);
   }
}

 

#

9.8.2 Harjoitus 9.8.4

Olkoon meillä seuraavanlainen ohjelma, jonka aliohjelma on vielä kesken.

XXX YYY ZZZ KolmionAla (??? luku1, IIII luku2) {}. 

Mieti allaoleviin kysymyksiin vastaukset ja katso sitten videolta oikea vastaus. Täydennä lopullinen ohjelma toimimaan samalla.

#

Tehtava 9.4

Täydennä lopullinen ohjelma tähän:

using System;

/// <summary>
/// Esimerkki aliohjelmista
/// </summary>
public class FunktioitaNC
{
    /// <summary>
    /// Lasketaan keskiarvoja
    /// </summary>
    public static void Main()
    {
        double kanta = 15.0;
        double korkeus = 10.0;
        double ala;

        ala = KolmionAla(kanta, korkeus);
        Console.WriteLine(ala);
    }


    /// <summary>
    /// Lasketaan kolmion pinta-ala
    /// </summary>
    /// <param name="luku1">kanta</param>
    /// <param name="luku2">korkeus</param>
    /// <returns>pinta-ala</returns>
    XXX YYY ZZZ KolmionAla (??? luku1, IIII luku2)
    {
    }
}

 

9.8.2.1 Vastausvaihtoehdot

0 1 2 3 4 5 6
void static public int double char string

9.8.2.2 Kysymykset ja vastaukset

#
Kysymys 1: Mikä tulee XXX:n tilalle? Katso vastaus Luento 6 – 46m5s (1m7s)
#
Kysymys 2: Mitä tulee YYY:n tilalle? Katso vastaus Luento 6 – 47m25s (1m28s)
#
Kysymys 3: Mitä tulee ZZZ:n tilalle? Katso vastaus Luento 6 – 48m55s (3m1s)
#
Kysymys 4: Kirjoitetaanko return-lause? Katso vastaus Luento 6 – 52m5s (35s)
#
Kysymys 5: Mitä tulee ???:n tilalle? Katso vastaus Luento 6 – 55m25s (39s)
#
Kysymys 6: Mitä tulee III:n tilalle? Katso vastaus Luento 6 – 56m7s (1m23s)
#

Tehtävä 9.8.5 Henkilön ikä

Kirjoita kokonainen luokka, jossa on pääohjelma ja aliohjelma. Aliohjelma palauttaa henkilön iän, kun sille viedään parametreina tämä vuosi sekä syntymävuosi. Kirjoita myös dokumentaatiokommentit.

 

Mitä teen väärin, kun saan aliohjelman aloitusriville aina "Identifier expected" ja "Syntax error, ',' expected", vaikka minulta löytyy esim. "int" ja arvot ovat erotettu pilkuilla?

VL: Funktion esittelyrivillä parametrilistassa ei ikinä ole arvoja, vain muuttujien tyyppejä ja niiden nimiä. Katso mallia edellisestä KolmioAla funktiosta ja sen käytöstä. Kaikki (tyyppejä ja nimiä) lukuunottamatta ihan vastaavalla tavalla.

01 Oct 17 (edited 01 Oct 17)
#

10. Integroitu ohjelmointiympäristö (IDE)

Vaikka ohjelmakoodia voi kirjoittaa pelkällä tekstieditorillakin, ohjelmien koon kasvaessa alkaa kaipaamaan työvälineiltä hieman enemmän ominaisuuksia. Peruseditoreja enemmän ominaisuuksia tarjoavat sovelluskehittimet eli IDE:t (Integrated Development Environment).

IDE kasaa yhteen monia muutoin irrallisina esiintyviä työkaluja kuten:

  • tekstieditori (joka yleensä ymmärtää kohdekieltä tavallista editoria paremmin)
  • kielen kääntäjä
  • linkitystyökalut
  • versionhallintatyökalut
  • debuggeri

C#:lle tehtyjä ilmaisia sovelluskehittimiä ovat muun muassa

  • Visual Studio 2017 Community (Windows),
  • MonoDevelop (Linux) ja
  • Visual Studio for Mac (macOS).

Ohjeet on testattu toimiviksi Visual Studio 2017 Community -versiolla Windows-ympäristössä.

10.1 Käyttö

10.1.1 Ensimmäinen käyttökerta

Kun käynnistät VS:n ensimmäisen kerran, kysyy VS millä oletusasetuksilla haluat valikoita ja toimintoja käyttää. Valitse Visual C# Development Settings.

Aluksi rivinumerot eivät ole käytössä. Koodin seuraamisen helpottamiseksi otetaan ne käyttöön kohdasta Tools \(\rightarrow\) Options \(\rightarrow\) Text Editor \(\rightarrow\) C# \(\rightarrow\) Line Numbers. Jos Error list-ikkuna ei ole jo näkyvissä ruudun alareunassa, kannattaa se ottaa käyttöön: View \(\rightarrow\) Error List.

10.1.2 Projektit ja solutionit

Visual Studiossa on solutioneja ja projekteja. Yhdessä solutionissa voi olla monta projektia, ja jokaisen projektin täytyy kuulua johonkin solutioniin. Uuden ohjelman kirjoittaminen alkaa aina projektin perustamisella johonkin solutioniin, jolloin myös solution luodaan samalla, ellei sitä ole jo tehty.

Hierarkia ei ole kiveen hakattu, mutta solutioneja ja projekteja voi hahmottaa esimerkiksi seuraavasti:

  • Jokainen demokerta (tai luento, ohjauskerta tms) on uusi solution, eli kun aloitat tekemään ensimmäistä demotehtävää (ohjaustehtävää) niin toimitaan seuraavasti

    • Klikkaa File \(\rightarrow\) New project (Ctrl+Shift+N)

    • Valitse projektin tyyppi: Jos teet konsoliohjelmaa niin valitse Jypeli -kohdasta ConsoleMain. Jos teet Jypeli-peliä, valitse Visual C# \(\rightarrow\) Jypeli-kohdasta haluamasi projektimalli (esimerkiksi FysiikkaPeli).

    • Anna projektille (eli tehtävälle) nimi kohtaan Name, esimerkiksi Lumiukko tai HelloWorld. Tarkista demotehtävien nimeämiskäytäntö opettajalta.

    • Laita Location-kohtaan demotehtäviesi juuripolku, eli vaikkapa

      C:\MyTemp\<omatunnus>\ohj1\demot

      Laita omatunnus kohtaan luonnollisesti yliopiston mikroverkon käyttäjätunnuksesi. Älä käytä ääkkösiä tai välilyöntejä kansioiden tai tiedostojen nimissä, tulemme yleensä toimeen myös ilman niitä, mutta ongelmia ääkköset ja erikoismerkit aiheuttavat usein.

Huomaa! Tarkista luennoitsijalta demotehtävien tallennuspaikka yliopiston mikroverkossa.

  • Valitse Solution-valikosta Create new Solution ja paina Create directory for solution (tärkeä)

    • Laita Solution Name kohtaan demoN (N = demokerran numero), esimerkiksi demo3
    • Nyt sinulle on luotu uusi solution ja yksi projekti solutionin sisään.
  • Kun haluat lisätä demotehtäviä (projekteja) tiettyyn demokertaan (solutioniin) toimi näin

    • Avaa sen demokerran solution (.sln-tiedosto), johon haluat tehtävän lisätä (ellei se ole jo auki Visual Studiossa).
    • File \(\rightarrow\) Add \(\rightarrow\) New project tai Solution Explorerissa klikkaa hiiren oikealla solutionin päälle, ja klikkaa Add \(\rightarrow\) New project
    • Anna projektille nimi, jätä Location ennalleen, ja valitse Solution-valikosta Add to Solution. Solution Name -kohta himmenee ja Location-kohtaan pitäisi tulla automaattisesti lisäteksti demoN.

Näin tehtynä kaikki yhden demokerran tehtävät löytyvät “saman katon alta” eli yhden solutionin kaikki projektit menevät samaan kansioon. Resurssienhallinnassa hakemistopuusi voisi näyttää esimerkiksi tältä.

ohj1
 |
 +--demot 
 |  +--demo1
 |  |  +--HelloWorld
 |  |  +--Lumiukko
 |  |  '-demo1.sln
 |  '--demo2
 |     +--Lumiukko2
 |     '--LukujenLaskemista
 '--ohjaukset
    +--ohjaus1
    |  +--HelloWorld
    |  '--Lumiukko
    '--ohjaus2

Yksittäisten projektien kansioihin (tässä HelloWorld, Lumiukko, Lumiukko2, ja niin edelleen) syntyy myös läjä erilaisia tiedostoja. Tutkitaan vielä, millainen rakenne yksittäisen projektin kansioon on syntynyt. Esimerkkinä vaikkapa HelloWorld-kansio.

HelloWorld
 |
 +--bin
 |  +--Debug
 |     +-HelloWorld.vshost.exe
 |     +-HelloWorld.vshost.exe.manifest
 +--obj
 |  +--x86
 |     +-(...)
 +--Properties
 |  +-AssemblyInfo.cs
 +-HelloWorld.csproj
 '-HelloWorld.cs

Lähdekoodi sijaitsee HelloWorld.cs-tiedostossa. Kuten huomaat, projektiin kuuluu kuitenkin monia muitakin tiedostoja ja kansioita. bin- ja obj-kansiot sisältävät ohjelman käännökseen liittyviä väliaikaistiedostoja. Properties-kansio taas sisältää julkaisuun liittyvää tietoa esimerkiksi tekijänoikeuksista ja merkistöstä. Huomaa, että varsinainen projektitiedosto on .csproj-päätteinen.

Solution toimii tavallaan liimana näiden eri projektitiedostojen välillä yhdistäen ne Visual Studiossa. Tämän johdosta monen eri projektin käsittely yhtä aikaa on kätevää.

Viimeisenä tutkitaan Jypeli-projektimallista luotua projektia, sillä sen kansiorakenne eroaa hieman konsoliohjelmaan syntyvästä kansiorakenteesta, esimerkkinä Lumiukko-projekti.

Lumiukko
 |
 +--bin
 |  +-(...)
 +--obj
 |  +-(...)
 +--Properties
 |  +-AssemblyInfo.cs
 +-Game.ico
 +-GameThumbnail.png
 +-Lumiukko.csproj
 +-Ohjelma.cs
 '-Lumiukko.cs
LumiukkoContent
 |
 +--bin
 |  +-(...)
 +--obj
 |  +-(...)
 '-LumiukkoContent.contentproj

Projekti sisältää yhden kansion sijaan kaksi kansiota (Lumiukko ja LumiukkoContent), joista jälkimmäinen on varattu sisällön, kuten äänien ja kuvien, tuomiselle peliin. Lumiukko-kansiosta löytyy edelleen kaksi kooditiedostoa, Ohjelma.cs ja Lumiukko.cs, joista ensimmäinen on varattu Main-metodille, ja jälkimmäinen sisältää varsinaisen pelin lähdekoodin. Ohjelma.cs-tiedostoa ei käytännössä juurikaan tarvitse muokata.

Lumiukko.cs sisältää lähdekoodin, ja se on yleensä se tiedosto, joka pyydetään palautettavaksi demotehtävissä. Muita tiedostoja ei tarvitse palauttaa, ellei niitä erikseen pyydetä. Huomaa, että tiedoston nimen voi ja tulee muuttaa vastaamaan luokan nimeä.

10.1.3 Visual Studion perusnäkymä

Kun olet luonut ensimmäisen projektisi, pitäisi edessäsi olla VS:n perusnäkymä. VS voi vaikuttaa aluksi melko monimutkaiselta, mutta sen peruskäytön oppii nopeasti. Jos jotakin työkalua ei tarvita ja se vie mielestäsi tilaa ruudulla, sen voi yleensä piilottaa. Kannattaakin opetella käyttämään ruututilaa tehokkaasti ja poistaa turhat visuaaliset elementit käytöstä kun niitä ei tarvita.

Oikealla on Solution Explorer, jossa näkyvät kaikki projektit sekä projektien sisältämät tiedostot ja viitteet muihin kooditiedostoihin.

Alhaalla on Error List (mikäli aktivoit sen päälle aikaisemmin), joka näyttää koodin kirjoittamisen aikana havaittuja virheitä sekä käännöksessä tapahtuvia virheitä. Error List on yksi ohjelmoijan tärkeimmistä työkaluista ja Visual Studion ehdoton vahvuus - syntaksivirheet saadaan kiinni ilman, että ohjelmoijan tarvitsee erikseen edes kääntää tekemäänsä ohjelmaa. Voit tuplaklikata Error List-paneelissa virhettä, ja VS vie kursorin suoraan siihen pisteeseen, jossa olettaa virheen olevan.

Keskellä näkyy koodin kirjoitusikkuna. Eri kooditiedostot aukeavat välilehtiin, joita voi selata näppäinyhdistelmällä Ctrl+Tab (ja Shift+Ctrl+Tab).

#

10.1.4 Ohjelman kirjoittaminen

Jokainen C#-ohjelma kirjoitetaan luokan sisään. Luotaessa uusi konsoliohjelma, tekee Visual Studio meille valmiin kooditiedoston, jossa on pieni pätkä valmista ohjelmakoodia. Oletusarvoisesti konsoliohjelmaan luodaan yksi kooditiedosto nimeltä Program.cs, jossa on valmiina muutama using-lause, nimiavaruus eli namespace (projektia vastaavalla nimellä), luokka sekä pääohjelma eli Main-metodi. Kurssin ConsoleMain mallilla luotaessa nimiavaruus jää “tarpeettomana” pois, ja tiedoston nimi vastaa projektin nimeä.

Aivan aluksi kannattaa muuttaa kooditiedoston nimi johonkin hieman kuvaavampaan klikkaamalla Solution Explorerissa tiedoston päällä hiiren oikealla napilla ja valitsemalla Rename. Kun tiedoston nimi on muutettu, kysyy Visual Studio, haluatko vyöryttää tekemäsi muutokset myös muualle ohjelmaan. Vastaa tähän kyllä.

Nyt meillä on edessä uusi luokan raakile Main-metodeineen ja editori, jolla ohjelma voidaan kirjoittaa.

#

10.1.5 Ohjelman kääntäminen ja ajaminen

Kun ohjelma on kirjoitettu, sen kääntäminen ja ajaminen onnistuu ylhäältä Debug-napista (vihreä kolmio) tai klikkaamalla F5 (debug). Nappia painamalla VS kääntää ohjelman automaattisesti ja suorittaa heti sen jälkeen ns. debuggaustilassa. Ohjelma voidaan ajaa myös ilman debuggausta painamalla Ctrl+F5 (start without debugging). Ohjelman kehityksen aikana on kuitenkin usein hyödyllistä ajaa ohjelma nimenomaan debug-tilassa, jolloin mahdolliset ohjelman ajonaikaiset virhetilanteet saadaan näkymään Visual Studiossa. Toisaalta komentoriviohjelmat “pysähtyvät” (“Press any key to continue…”) ohjelman päättymisen jälkeen mikäli ne ajetaan ilman debuggausta, mikä joskus puoltaa tämän vaihtoehdon käyttöä erityisesti aivan pienten ohjelmien kohdalla.

Mitä konsoliohjelmaan pitikään kirjoittaa, jos halusi estää ikkunan automaattisen sulkeutumisen ohjelman ajamisen jälkeen?

  • VL: ajaa Ctrl-F5, ks vasstava pääteohjaus, jossa tehtiin tuo ekan kerran.
03 Oct 18 (edited 03 Oct 18)

Jos haluamme lopettaa ohjelman suorituksen jostain syystä kesken kaiken, onnistuu se painamalla Shift+F5.