Esipuhe
Arvaa mikä olisi oikea järjestys, jotta alla oleva olisi toimiva ohjelma (vinkki: koita päätellä sulkujen parillisuudesta ja sisennyksistä):
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.
Jos haluat samasta aiheesta pidemmän version (suomeksi), niin katso video:
Seuraavista videoista näet millaisia pelejä kursseilla on tehty:
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.
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.
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)
Osattava asia | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
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 |
Muista katsoa myös kurssin videohakemisto.
0.3 TIM-käyttöohjeita
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.
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.
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:
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 Vector
issa olevia arvoja.
Voit kokeilla myös toisella kielellä (VPython) tehtyä esimerkkiä. Tätä voit myös pyöritellä hiiren oikealla painikkeella.
1. Mitä ohjelmointi on?
Sana ohjelmointia sisältää sanan ohje.
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 ennenkuin tarktori on saatu tähän vaiheeseen, on tarvittu valtavasti insinööritöät ja ohjelmointia. GPS-satelliitit, niin virheenkorjaus, tarktorin varsinaisen tietokoneen ohjelmointi sille tasolle, että se tekee viljelijän ohjelmoinnin helpoksi jne.
Suonenjoella mansikoita keräävällä poimijalla on kaulassaan lähilukkortti (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 sijanneista ja toimenpiteistä ja voi seurrata aikasempaa tarkemmin milloin joltakin saralta tuotta 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 tarvitsekkaan) 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 marjanviljelijä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.
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]
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, 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ä tarenninta. 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.
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:
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:
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
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.

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 komennolla:
SETX PATH "%WINDIR%\Microsoft.NET\Framework\v4.0.30319;%PATH%"
Sulje komentorivi ja käynnistä se uudestaan.
Tämä asettaa PATH-ympäristömuuttujan pysyvästi oikeaksi kääntämistä varten.
md:Avaa uuteen ikkunaan (ctrl+klikkaa linkkiä) oheinen materiaali ja tee siellä olevat tehtävät.
Vastaa sitten alla olevaan testiin.
>
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 ei käytetä luokan nimessä.
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 luokkakirjaston 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ö.
md:Mihin kohti saa laittaa välilyönnin tai rivinvaihdon C#-kielessä?
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.
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:
2.3.2 Kääntäjän virheilmoitusten tulkinta
Alla on esimerkki syntaksivirheestä HelloWorld
-ohjelmassa.
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.
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.
Lisää virheilmoitusten tulkintaesimerkkejä 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.
Edelleen, koodi voitaisiin kirjoittaa myös seuraavasti.
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.
Yllä oleva rivi tulostaa
Hello World!
kun taas alla oleva rivi tulostaa:
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ä.
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:
Voisimme kirjoittaa nyt C#-kommentit HelloWorld-ohjelman alkuun seuraavasti:
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 luoken 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]
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
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.
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.
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ä.
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
josta löytyy myös ohjeet kirjaston asennukseen ja käyttöön. Huomaa, että tietokoneellasi tulee olla asennettuna .NET Framework 4 sekä XNA Game Studio 4, jotta graafinen ohjelma voidaan kääntää. .NET-frameworkin asennusohjeet löytyvät osoitteesta
Vaikka tässä kohtaa emme vielä Visual Studio -kehitysympäristöä tarvitsekaan, on sen asentaminen tässä kohtaa viisasta, sillä Visual Studio tulisi olla asennettuna ennen XNA:n 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
Luokkadokumentaatio = Sisältää tiedot kaikista kirjaston luokista ja niiden metodeista (ja aliohjelmista). Löytyy useimmiten ainakin WWW-muodossa.
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
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.

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.
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 Jypeli
ssä 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.
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, jonka 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.
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.
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:
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.
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.
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.

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:
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?
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.
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ää XNA-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
Meidän täytyy vielä välittää kääntäjälle tieto siitä, että Jypeli-kirjastoa tarvitaan Lumiukko-koodin kääntämiseen. Lisäksi annetaan kääntäjälle tieto siitä, että ohjelma tehdään 32-bittisille järjestelmille (x86). Tämä tehdään csc-ohjelman /reference
-parametrin avulla. Lisäksi tarvitaan referenssi Jypelin käyttämään XNA-kirjastoon. Kirjoita nyt komentoriville (kaikki rivit samalle riville niin, että /-viivan edessä on yksi välilyönti)
csc Lumiukko.cs /reference:Jypeli.dll;
"%XNAGSv4%\References\Windows\x86\Microsoft.Xna.Framework.Game.dll"
/platform:x86
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;
"%XNAGSv4%References\Windows\x86\Microsoft.Xna.Framework.Game.dll";
"%XNAGSv4%References\Windows\x86\Microsoft.Xna.Framework.dll"
/platform:x86 /define:WINDOWS
@if NOT ERRORLEVEL 1 %~n1
eli:
@"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc" %* /nologo /reference:Jypeli.dll;"%XNAGSv4%References\Windows\x86\Microsoft.Xna.Framework.Game.dll";"%XNAGSv4%References\Windows\x86\Microsoft.Xna.Framework.dll" /platform:x86 /define:WINDOWS
@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
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.
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.

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
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();
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ä.

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.
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) |
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ä". 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 viedään 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:
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
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
). 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.

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:
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ä.
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.
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:

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. 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äyteään yleisnimeä aliohjelma silloin kun ei eriksen haluta korostaa että kyseessä on erityisesti funktio tai metodi. Tarkennetaan näitä käsitteitä seuraavaksi.
Funktio Aliohjelma, joka palauttaa jonkin tuloksen, 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;
Myös void
-aliohjelmassa, eli aliohjelmassa joka ei palauta arvoa, voi olla return
-lause, mutta sen perässä ei silloin ole lauseketta. Tällöin return
-lauseen rooliksi jää vain hypätä aliohjelmasta pois. Funktion on useimmiten syytä olla static
.
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 aliohjelman tapaan olla palauttamatta. Emme erottele tätä enää eri nimillä.
6.4.1 Aliohjelminen kirjoittaminen
Aliohjelman kirjoittamiseksi kannattaa aina edetä seuraavasti (kunhan ensin opitaan testaaminen, TDD, Test Driven Development):
- Jaa ongelma osiin.
- Mieti millaisella aliohjelmakutsulla pistät tietyn osaongelman ratkaisun käyntiin.
- Kirjoita aliohjelman kutsurivi ja mieti sen tarvitsemat parametrit.
- 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.
- mieti tarve
- 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.
- Dokumentoi aliohjelma (nyt unohda mistä sitä kutsuttiin, sitä et enää saa ajatella).
- Kirjoita testit (TDD).
- Aja testit (pitää "feilata" = NÄE PUNAISTA).
- Muuta aliohjelma toimivaksi
- Aja testit (toista kohdat 8-10 kunnes toimii, = NÄE VIHREÄÄ)
- Siirry seuraavaan aliohjelmaan.
Lue lisää dokumentista Aliohjelmien kirjoittaminen.
Toki edellisen tehtävän kaltaiset aliohjelmat eivät ole järkeviä, vaan järkevämpää olisi viedä aliohjelmille parametrina että mitä pitää tulostaa.

6.4.2 Tehtävä: Terminologiaa
/// <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);
}
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.
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);
}

.
These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.