sao-faq

sfnet.atk.ohjelmointi ja sfnet.atk.ohjelmointi.alkeet uutisryhmien usein kysyttyjä kysymyksiä

View the Project on GitHub

Yleisiä asioita

Tästä kirjoitelmasta

Tämä kirjoitus on niin sanottu FAQ. Tämä kirjainlyhenne tulee englanninkielen sanoista “Frequently Asked Questions”, ‘usein esitettyjä kysymyksiä’. Niinpä tämä kirjoitelma pyrkiikin vastaamaan usein esitettyihin kysymyksiin, sellaisiin, jotka pidemmän päälle toistuessaan alkavat kyllästyttää keskusteluareenan vakiosallistujia.

Tämä kirjoitelma on keskusteluryhmän sfnet.atk.ohjelmointi.alkeet FAQ. Vaikka tuo ryhmä onkin varsin nuori, perustettu loppukeväästä 1998, on siellä ehditty jo kaluta muutama aihe kyllästymiseen asti. Siksi tämä kirjoitelma on olemassa: jotta ryhmän uudet lukijat saisivat vastauksen polttaviin kysymyksiinsä, joista ryhmän vanhat parrat eivät jaksa enää keskustella. Lienee hyvä mainita, että se, että jotakin asiaa käsitellään tässä kirjoituksessa, ei tarkoita, että se olisi jotenkin erityisen tärkeää tai toisaalta jotenkin tarpeetonta asiaa.

Tämä kirjoitelma postataan kerran kuukaudessa uutisryhmään sfnet.atk.ohjelmointi.alkeet. Uusin julkaistu versio on myös saatavilla seitistä seuraavissa muodoissa: HTML, tavallinen teksti, teksti korostuksin ja SGML/Debiandoc. Viimeksimainitun käsittelemiseen tarvitaan Debiandoc-SGML-paketti, joka toiminee ainakin kaikissa Unixeissa.

Tätä kirjoitusta ylläpitää Juha Autero jautero@iki.fi. Suurimman osan kirjoituksesta on kuitenkin tehnyt Antti-Juhani Kaijanaho, joten kirjoituksen “minä” on poikkeuksetta Antti-Juhani, mutta mukana on myös muidenkin kirjoittajien tekstiä. Juha ottaa vastaan tähän kirjoitelmaan liittyviä kommentteja, korjauksia ja muita sellaisia. Kirjoitelmaan tehdyt muutokset löytyvät GNU ChangeLog sekä HTML-muodossa.

Sananen hyvästä käytöksestä uutisryhmissä

Uutisryhmät ovat kokonaisuutena vanha keskusteluareena, itse asiassa paljon vanhempi kuin useimmat verkon käyttäjät tulevat ajatelleeksi. Tiedossani ei ole tarkkaa tietoa siitä, koska uutisryhmiä levittävien palvelimien verkko Usenet syntyi, mutta se on ollut olemassa nyt jo ainakin 15 vuotta. Osa nykyisistäkin keskustelijoista olivat paikalla jo silloin, maailmankaikkeuden hämärinä alkuhetkinä. Näin vanhalle areenalle on aikojen saatossa syntynyt oma hyvän käytöksen normisto, jota aloittelevankin kirjoittajan on syytä noudattaa jos ei halua saada verkossa peelon eli idiootin mainetta. Tämä netti-etiketti eli netiketti ei ole kovinkaan monimutkainen, ja terveellä järjellä pärjää hyvin.

Tärkeintä on se, että kirjoittaessasi uutisryhmään ilmaiset itseäsi selkeästi. Tässä muutamia nyrkkisääntöjä: Kirjoita yleiskieltä ja vältä sellaista erityissanastoa, jota ryhmän lukijakunta ei todennäköisesti tunne. Jos kirjoituksessasi esitetään kysymys, johon halutaan vastaus, esitä kysymys selkeästi ja mielellään kirjoituksesi alkupuolella. Kirjoita lyhyesti mutta älä liian lyhyesti. Pyri aina siihen, että kirjoituksesi sanoo jotain olennaista keskustelun kohteena olevasta asiasta. Käytä paljon puhuvia otsikoita; mitäänsanomattomasta subject-rivistä ei ole kenellekään mitään hyötyä. Esimerkiksi ‘‘Scheme: Merkkijonon pituus’’ on paljon hyödyllisempi otsikko kuin ‘‘Auttakaa!’’. On epäkohteliasta kysyä ryhmässä ja vaatia vastaukset sähköpostitse.

Muista aina kertoa mitä ohjelmointikieltä käytät. (Esimerkiksi C, C++ ja Java eivät välttämättä eroa toisistaan lainkaan pienessä esimerkkikoodissa.) Kerro myös ympäristö ja kääntäjä. Esimerkiksi C-kielessä ei ole mitään standardin mukaista tapaa tyhjentää ruutu, vaan vastaus on erilainen Borland C++ Builderilla konsoliohjelmia tehtäessä ja Linuxin gcc-kääntäjällä.

Lisää tietoa netiketistä saa Timo Kiravuon kirjoituksesta News-etiketti ja Jukka Korpelan kirjoituksesta Nyysiopas. Kumpikin ovat suositeltavaa luettavaa kaikille uutisryhmiä seuraaville.

Ohjelmoinnin alkeet -uutisryhmästä

Uutisryhmän sfnet.atk.ohjelmointi.alkeet virallisen kuvauksen mukaan se “on tarkoitettu ohjelmointia aloitteleville tai sellaisille, jotka tutustuvat itselleen uuteen ohjelmoinnin osa-alueeseen”. Ryhmässä käsitellään siis erilaisia alkuunpääsemisen ongelmia. Muunlaisia ohjelmointiongelmia varten on olemassa ryhmä sfnet.atk.ohjelmointi.moderoitu. Hyvä nyrkkisääntö on, että mikäli kysyjä on ohjelmoinut alle vuoden verran, on alkeet-ryhmä todennäköisesti oikea valinta. Toiseen suuntaan ohje ei päde, sillä kokeneempikin ohjelmoija saattaa joskus törmätä alkeisongelmiin, varsinkin tutustuessaan uuteen ohjelmointikieleen tai -metodiin. Toinen hyvä nyrkkisääntö on se, että mikäli kysyjä ei itse tiedä, mistä päin nettiä tai kirjallisuutta lähtisi etsimään vastausta, kysymys on mahdollisesti alkeista.

HTML-kieleen liittyvät kysymykset eivät kuulu mihinkään ohjelmointiryhmään, sillä HTML ei ole ohjelmointikieli. Esitä ne mielummin WWW-kysymyksille varatussa ryhmässä sfnet.viestinta.www, kun olet ensin tarkistanut, ettei kysymykseesi ole vastattu ryhmän VUKK:ssa tai englanninkielisessä FAQ:ssa.

Ennen kysymyksen lähettämistä uutisryhmään tarkista, ettei kysymykseesi ole vastattu tässä kirjoituksessa tai moderoidun ohjelmointiryhmän FAQ:ssa.

Ohjelmoinnin opettelemisesta

Ensimmäinen ohjelmointikieli

Kysymys siitä, millä ohjelmointikielellä kannattaisi aloittaa ohjemoinnin opettelu, on oikeastaan mielipideasia; jokaisella on oma mielipiteensä siitä. Esitän (AJK) tässä oman näkemykseni, ja pyydän muita kokeneita ohjelmoijia - ja erityisesti ohjelmoinnin opettajia - kirjoittamaan oman mielipiteensä ja lähettämään sen minulle, jotta voisin esittää tässä kirjoitelmassa mahdollisimman monipuolisen vastauksen kysymykseen.

Ensimmäisen ohjelmointikielen valinnalla ei yleensä ole maata järisyttävää merkitystä, mikäli opettelija aikoo käydä opintien loppuun asti eli ruveta ammattimaiseksi tai vakavasti harrastavaksi ohjelmoijaksi. Tällainen ihminen nimittäin kyllä ennemmin tai myöhemmin joutuu opettelemaan suurimman osan yleisessä käytössä olevista ohjelmointikielistä. Tärkeintä ohjelmoinnin opettelemisessa on tällaisen ihmisen kannalta ajattelutapojen ja ongelmanratkaisun oppiminen, ja suurin osa nykyisin käytössä olevista ohjelmointikielistä kyllä mahdollistavat tämän. Mikäli opettelijalla ei kuitenkaan ole näin voimakasta kunnianhimoa, kielen valinnalla on suurempi merkitys.

Sanoin äsken, että tosissaan ohjelmointia harrastava ihminen joutuu opettelemaan ennemmin tai myöhemmin kaikki yleisessä käytössä olevat kielet. Tätä ei pidä pelästyä: yleensä ensimmäinen kieli on kaikista vaikein, sillä sitä opetellessa joutuu samalla oppimaan myös ohjelmoimaan - ja ohjelmointihan on kaikkea muuta kuin kielen yksityiskohtien osaamista. Annanpa esimerkin itsestäni. Ensimmäisen kieleni opettelin joskus 1990-luvun alkussa. Minulla kesti kauan, ennen kuin opin tekemään sillä jotain hyödyllistä. Syksyllä 1998 opettelin Python-kielen muutamassa päivässä samalle tasolle, jonne pääseminen ensimmäisellä kielelläni kesti vuosia; python taisi olla minulle viides kieli, jonka opettelin kunnolla. Voin vain toistaa itseäni ja sanoa: kielet helpponevat sitä mukaa kun ohjelmointikokemus karttuu.

Väitän, että BASIC-kieli sellaisena kuin se esiintyy 1980-luvulla yleisesti käytetyissä kotitietokoneissa (esim. Commodore 64, Sinclair Spectrum ja varhaiset IBM PC -yhteensopivat tietokoneet), on täysin sopimaton aloittelevalle ohjelmoijalle. Kielestä puuttuvat nimittäin kaikki hyvää ohjelmointityyliä tukevat ominaisuudet, ja tämän mikrobasictaustan omaava ohjelmoija joutuu ennemmin tai myöhemmin opettelemaan pois tämän kielen tuomista pahoista tavoista, mikä ei ole ollenkaan helppo homma. Vastaava ongelma on kaikilla konekielillä. Nykyisin käytössä olevat BASIC-murteet ovat jo käyttökelpoisempia.

Hyviä aloituskieliä ovat Scheme, Python, Prolog, Java, Pascal ja C++. Muitakin kelpo kieliä on. Käsittelen nyt kutakin nimeltä mainitsemaani kieltä erikseen.

Scheme
Scheme on 1970-luvulta peräisin oleva kieli, jota käytetään nykyisin varsin paljon korkeakouluissa ja yliopistoissa ensimmäisenä opetuskielenä - esimerkiksi Teknillisen korkeakoulun pääaineopiskelijoilla Scheme on pakollinen ensimmäinen ohjelmointikieli. Kielessä on hyvin vähän opeteltavia yksityiskohtia, ja siksipä Schemeä käytettäessä päästään nopeasti käsiksi tärkeämpiin asioihin. Scheme kuuluu epäpuhtaiden funktionaalisten kielten luokkaan, minkä vuoksi joillakin on sitä kohtaan turhia ennakkoluuloja. Mikäli minua pyydettäisiin suosittelemaan ohjelmoinnista tosissaan kiinnostuneelle aloittelijalle jotain kieltä ensimmäiseksi ohjelmointikieleksi, suosittelisin Schemeä.

Scheme-materiaalia on verkossa varsin paljon, lähinnä englanniksi. Hyviä aloituspaikkoja ovat Schemers.org ja suomalainen vaan ei suomenkielinen Schememonster’s friends. Schemeä käyttäviä, aloittelevalle ohjelmoijalle sopivia kirjoja on myös, lähinnä englanniksi.

Python
Python on Schemeä huomattavasti nuorempi ohjelmointikieli. Pythonin kielioppi on johdonmukainen ja sisältää vähän opeteltavia yksityiskohtia. Kieli tarjoaa runsaasti valmiita työkaluja monien käytännön ohjelmointiongelmien ratkaisemiseen nopeasti ja vähällä kirjoittamisella. Tämän vuoksi se sopii hyvin sellaiselle aloittelevalle ohjelmoijalle, joka haluaa saada nopeasti hyvin toimivia ohjelmia aikaiseksi. Pythonin pahin ongelma on se, ettei siitä ole olemassa kunnollista kirjallista materiaalia aloittelevan ohjelmoijan käyttöön.

Kaikki olennainen Pythonista on luettavissa kielen seittisivustolta.

Prolog
Prolog on varsin omintakeinen ohjelmointikieli. Se perustuu muodolliseen logiikkaan ja päättelysääntöihin: kun Prologille kerrotaan, että Matti on Maijan poika, Mikko on Matin poika ja pojan poika on pojanpoika, niin Prolog osaa vastata kysymykseen “Onko Mikko Maijan pojanpoika?” oikein. Prolog poikkeaa muista kielistä siinä, että vain Prologissa tämä ongelma voidaan esittää suurin piirtein yhtä yksinkertaisesti kuin suomen kielessä. Prolog on hyvä kieli sellaiselle, jota kiinnostavat älylliset pelit ja kielelliset ongelmat.

Prolog-materiaalia on netissä, mm. Prolog Programming A First Course, A Short Tutorial on Prolog ja suomeksi Keskeneräisiä harjoituksia Prolog-ohjelmointikieleen tutustumiseksi.

Heikki Kantola on suositellut seuraavia Prolog-kirjoja: W.F. Clocksin, C.S. Mellish: ‘‘Programming in Prolog’’, L. Sterling, E. Shapiro: ‘‘The Art of Prolog’’ ja F. Pereira, S. Shieber: ‘‘Prolog and Natural-Language Analysis’’.

Java
Java on eri asia kuin seittisivuilla varsin usein nähtävä Javascript. Java on hyvin uusi mutta lupaavalta näyttävä ohjelmointikieli, joka on sukulaistaan C++:aa huomattavasti ystävällisempi käyttäjäänsä kohtaan. Kielen soveltuvuudesta aloittelijalle kertoo jotain se, että Helsingin yliopiston Tietojenkäsittelytieteen laitos käyttää Javaa ensimmäisenä opetuskielenään.

Helsingin yliopistossa aloitteleville ohjelmoijille pidettävää Java-kurssia luennoiva Arto Wikla on kirjoittanut kurssin luentomateriaalin pohjalta kelpo kirjan (Arto Wikla: Ohjelmoinnin perusteet Java-kielellä, OtaDATA 1998), joka sopii aloittavalle ohjelmoijalle. Yksityiseen käyttöön on saatavilla kirjan kanssa samaan materiaaliin perustuva seittisivusto, jota Wikla käyttää Java-kurssinsa kurssimateriaalina ja joka siksi soveltuu hyvin aloittelevan ohjelmoijan luettavaksi.

Pascal
Pascal on kehitettiin 1970-luvulla opetuskieleksi. Sitä käytettiinkin paljon opetuksessa pitkälle 1990-luvulle asti. Alkuperäisessä ja standardoidussa muodossaan kieli on kuitenkin aivan liian rajoittunut eikä sillä voi tehdä mitään todellisia ohjelmia. Sittemmin erityisesti entinen Borland, nykyinen Inprise on kunnostautunut Turbo Pascal ja Delphi-murteillaan niin, että nämä Pascaliin perustuvat ohjelmointikielet ovat DOS- ja Windows-ohjelmoijien laajassa suosiossa. Ihmiselle, joka aikoo opetella vain yhden kielen ja joka käyttää DOSia tai Windowsia, Turbo Pascal ja Delphi ovat oikein hyviä ohjelmointikieliä.

Jori Mäntysalo on kirjoittanut perusteluja sille miksi pascal sopii ohjelmoinnin opiskeluun.

Seitistä löytyy pascal-opas.

C++
C++ on kulttikieli, siitä ei pääse yli eikä ympäri. Se perustuu toiseen kulttikieleen nimeltä C, joka kehitettiin 1970-luvulla konekielen korvaajaksi käyttöjärjestelmäohjelmointikäyttöön. C++ on järkyttävän iso kieli, ja sen kaikkien yksityiskohtien muistaminen on käytännössä mahdotonta. Kaikesta huolimatta sekä C:tä että C++:aa opetetaan aloitteleville ohjelmoijille monissa yliopistoissa ja korkeakouluissa (Jyväskylän yliopisto yhtenä esimerkkinä). Itseopiskelijalle C++ on luultavasti liian hankala ensimmäiseksi kieleksi.

C++-kirjallisuutta on myynnissä uskomattomia määriä. Monetkaan kirjat eivät ole kovin kaksisia, ja asiavirheet eivät ole tavattomia. Aloittelijalle hyvä kirja lienee Jesse Libertyn ‘‘Opeta itsellesi C++’’ (alkuteos ‘‘Teach Yourself C++ Programming in 21 Days’’), vaikka sekään ei ole virheetön.

Netistä löytyy myös C++-opas, joka kevyestä kirjoitustyylistään huolimatta sisältää painavaa asiaa.

Kumpi opetella ensin: C vai C++?

Mikäli et tunne kumpaakaan kieltä ennestään ja sinulla on mahdollisuus valita, opettele ensin C++. Näin et joudu oppimaan eräitä pahoja tapoja, jotka C:ssä tarvitaan kielen suppeuden vuoksi; jos joskus joudut tekemään C-ohjelmia, nämä kiertoreitit on opittavissa varsin helposti C++-taustalla. Asiaa käsittelee myös C++-FAQ Lite, kysymys 28.2.

Peliohjelmoinnista

Haluan oppia tekemään pelin. Miten aloitan?

Opettele ohjelmoinnin alkeet käyttäen jotain edellä esiteltyä kieltä. Sitten tutustu esimerkiksi kirjoitukseen Laaman tie peliohjelmointiin, joka olettaa C-kielen hallinnan sekä siedettävän laskutaidon.

Kuinka laitan peliini kuvan?

Tämä riippuu monesta asiasta. Käyttämässäsi ohjelmointikielessä voi olla valmiita keinoja kuvan laittamiseen. Mikäli näin ei ole, etkä voi käyttää valmiita ohjelmakirjastoja, sinun täytyy piirtää kuva itse ruutuun käyttäen ohjelmointikielesi grafiikkakirjastoa.

Edellä mainittu Laaman tie peliohjelmointiin sisältää joitakin ohjeita.

Satunnaislukuongelmia

Satunnaislukugeneraattori arpoo aina saman sarjan

On melkeinpä vale puhua satunnaisuudesta samassa yhteydessä ohjelmointikielten erilaisten rand-käskyjen tai -kirjastoaliohjelmien kanssa. Näiden tuottama satunnaisuus on näennäistä: takana on täysin toistettavissa oleva matemaattinen metodi, joka pyrkii tuottamaan suhteellisen satunnaisilta näyttäviä lukuja. Yksi tämän aiheuttama ongelma on se, että generaattori on alustettava. Alustustapa riippuu kielestä; kohtuuhyvä metodi on uudehkoissa Pascal-murteissa randomize-käsky sekä C:ssä srand(time(0)) (tässä tarvitaan stdlib.h- ja time.h-headerfileitä).

Satunnaislukugeneraattori arpoo aina samaa lukua

On ehkä liiankin hyvin opetettu ihmisiä sanomaan ``randomize’’ tai ``srand(time(0))’’ (tai mikä se nyt milläkin kielellä onkaan), jotta satunnaislukugeneraattori antaisi hyvän tuloksen. Nimittäin on helppo ymmärtää tämä väärin, loitsu lausutaan joka kerta, kun halutaan satunnaisluku. Esimerkki virheestä tuottaa ajettaessa mainitun tuloksen.

Pääsääntö on yksinkertainen: Käytä satunnaisluvun alustajaa (randomize, srand tms) vain kerran ohjelmassa. Yleensä hyvä paikka tälle on juuri ohjelman alussa. Käytä alustajaa useita kertoja vain, jos ymmärrät sekä ohjelmointitekniikan että tilastomatematiikan kannalta, mitä olet tekemässä.

Miten saan satunnaisen kokonaisluvun väliltä 42..512?

VAROITUS: Tässä annettu vastaus voi olla väärä. Keskustele oman satunnaisuuden asiantuntijasi kanssa ennen kuin käytät tässä annettuja ohjeita.

Tyypillinen standardikirjastossa oleva satunnaislukugeneraattori palauttaa joko kokonaisluvun joltakin ennalta määrätyltä väliltä taikka sitten liukuluvun nollan ja ykkösen välistä. Yleensä kuitenkin kaivataan satunnaista kokonaislukua joltakin tietyltä väliltä.

Jos kirjaston satunnaislukugeneraattori palauttaa liukuluvun f, jonka tiedetään olevan välillä 0 <= f < 1, on suhteellisen yksinkertainen tapa skaalata tämä halutulle välille MIN <= f' < MAX kaavan `f’ := (MAX - MIN) * f

Mikäli satunnaislukugeneraattori palauttaa kokonaisluvun väliltä 0...RAND_MAX ja haluat tulokseksi liukuluvun väliltä MIN <= tulos < MAX, voit käyttää kaavaa tulos := double(MAX - MIN) / (RAND_MAX + 1) * f + MIN. Jos haluat tuloksen olevan kokonaisluku väliltä MIN...MAX, ongelma on hankalampi: mikä tahansa skaalaustapa saattaa tehdä huonoa satunnaislukujen satunnaisuudelle. Käytetyin tapa, jakojäännöksen ottaminen, suosii satunnaisluvun alimpia bittejä ja (mikäli generaattori on huono, kuten valitettavan usein on) saattaa jopa tuhota saatavien lukujen satunnaisuuden täysin. Hieman parempi tapa on käyttää “Numerical Recipes in C” -kirjan (Press, Flannery, Teukolsky ja Vetterling; Cambridge University Press, 1992) ohjetta ja käydä liukulukujen kautta. Tämä metodi toimii C:llä kirjoitettuna seuraavasti:

   MIN + (int)((MAX - MIN + 1.0) * rand() / (RAND_MAX + 1.0))

missä MAX ja MIN ovat halutun luvun ylä- ja alaraja, ja RAND_MAX satunnaislukugeneraattorin rand() tuottama suurin kokonaisluku (pienin on nolla). Tämän metodin hankaluutena on liukulukujen käytön aiheuttama laskentaepätarkkuus.

Toinen tapa on seuraava: Toinen tapa on seuraava: Olkoon ensin N := MAX - MIN + 1 ja RM := RAND_MAX + 1 (missä RAND_MAX on suurin luku, jonka satunnaislukugeneraattori tuottaa). Jaetaan väli 0..RAND_MAX N+1:een osaan, joista N ensimmäistä ovat yhtä pitkiä. Viimeinen osa saa olla tyhjä. Viimeisestä osasta kannattaa tehdä lyhyt, mutta sen ei välttämättä tarvitse olla lyhyin mahdollinen. Sitten tuotetaan valesatunnaislukuja kunnes tulos f osuu johonkin muuhun kuin viimeiseen osaan eli f literal>, ja palautetaan . Valmis C-koodi löytyy Niemitalon artikkelista ().

Edellämainituissa menetelmissä täytyy halutun välin pituuden olla enintään RAND_MAX.

Vielä yksi tapa on käyttää omaa satunnaislukugeneraattoria. Tässä pitää olla todella tarkkana: on helppoa keksiä surkea satunnaislukugeneraattori ja vaikeata keksiä hyvä sellainen. Satunnaiseen (!) käyttöön sopivan C-kielisen generaattorin on kirjoittanut Antti Valmari, ja se on saatavissa verkosta.

Lottorivin arvonta

Lottoriviä arvottaessa olennaista on, että samaa lukua ei arvota kahta kertaa. Tätä ei pidä toteuttaa arpomalla joka kierroksella luku väliltä 1..39 ja hylkäämällä jo arvotut numerot. Tuo nimittäin voi johtaa umpiluuppiin.

Hyvä ja yksinkertainen lottorivin arvonta-algoritmi on tässä (kiitokset Kimmo Surakalle): Kootaan taulukkoon kaikki 39 lukua, joista arvonta suoritetaan. Arvotaan ensimmäinen luku hakemalla satunnaisluku väliltä 1..39 ja katsotaan, mikä luku tämän numeron osoittamassa kohdassa on: se on ensimmäinen lottonumero. Poistetaan tämä numero taulukosta, ja arvotaan luku väliltä 1..38, jonka taulukosta osoittama luku on seuraava lottonumero, joka poistetaan. Toistetaan, kunnes kaikki seitsemän numeroa on arvottu. Algoritmin C-kielinen toteutus löytyy Surakan alkuperäisestä nyysiartikkelista <u1lbtaksryk.fsf@mustahaikara.cs.tut.fi> (Google: Re: Lotto C++:lla), jossa on pieni virhe: ohjelman kolmanneksi viimeinen ja toiseksi viimeinen rivi pitää vaihtaa.

Muita yleisiä ohjelmointiongelmia

Taulukot eli Kuinka tehdä muuttujia lennossa

Ennen pitkää törmätään tilanteeseen, jossa halutaan käsitellä useita samantyyppisiä tietoalkioita - kuvia näytöllä, lukumääriä listassa, sarakkeita tiedostosta luettavasta tietueesta yms. - yhtenä kokonaisuutena. Usein päädytään ratkaisuun jossa tietoalkiot nimetään tyyliin kuva1, kuva2, kuva3, ja näitä käsitellään yksitellen. Esim:

    tyhjenna_kuva( kuva1 )
    tyhjenna_kuva( kuva2 )
    tyhjenna_kuva( kuva3 )

Ratkaisu on sinällään täysin toimiva. Kun esim. näytölle lisätään uusi kuva, niin ohjelmaan lisätään uuden kuvan käsittely kaikkiin kohtiin joissa sitä tarvitaan. Se onkin tämän ratkaisun suurin ongelma. Jos kuvia lisätään, ennen pitkää ohjelmakoodi on täynnä identtisiä ohjelmarivejä, joissa vain käsiteltävä tietoalkio vaihtuu. Vielä ikävämpää on se, että joskus (useammin kuin haluaisikaan) tämä lisäys unohtuu ja ohjelma ei enää toimi niin kuin pitäisi. Toinen ongelma on se, että käsiteltävien tietoalkioiden määrä on tiedettävä ohjelman kirjoitusvaiheessa.

Monen mielestä luonolliselta tuntuva ratkaisu usean samanlaisen ja lähes samannimisen tietoalkion käsittelyyn olisi käsitellä tietoalkion nimeä kuin merkkijonoa ja korvata nimen lopussa oleva numeroarvo, tietoalkion järjestysnumero, arvolla joka lasketaan ohjelman suorituksen yhteydessä. Näin saadun nimen perusteella haettaisiin sitten kukin tietoalkio ohjelman suoritusvaiheessa erikseen, ja sitä käsiteltäisiin kuten aikaisemminkin. Esim:

    for i in 1 upto kuvien_lukumaara
      loop
        tyhjenna_kuva( kuva + i )
      endloop

Tässä kuitenkin sekoitetaan monta asiaa keskenään. Tietoalkioiden nimien perusteella toki löydetään haluttu tietoalkio, mutta tämän toimenpiteen tekee kääntäjä tai tulkki _ennen_ tuollaisen suorittamista joten senkin on tiedettävä etukäteen minkä niminen tietoalkio tällöin on kyseessä. Yleensä ohjelman suoritusvaiheessa tätä nimeä ei ole enää oikeastaan olemassa, ja siinä missä ohjelman lähdekoodissa on nimi, suoritettavassa ohjelmassa se on korvattu tiedolla siitä, mistä vastaava tietoalkio löytyy. Kääntäjän tai tulkin kannalta nimet kuva1 ja kuva2 ovat täysin erillisiä nimiä, eikä niillä ole yhtenevästä alkuosasta huolimatta mitään tekemistä toistensa kanssa. Aluksi tämä voin tuntua keinotekoiselta rajoitukselta, ja joissakin ohjelmointikielissä nimien luominen ja niitä vastaavien tietoalkioiden hakeminen lennossa onnistuukin. Tämä rajoitus muissa kielissä on kuitenkin perustelua mm. suoritusnopeuden takia, ja koska sen kiertäminen onnistuu turvallisestikin, sen poistaminen ei ole mielekästä.

Ratkaisun avaimet ovat taulukot, listat, assosiatiiviset taulukot yms.. Eri ohjelmointikielissä ja kirjastototeutuksissa näillä on omat nimensä, esim. perlissä assosiatiivinen taulukon nimi on hash ja C++:n STL:ssä map. Nämä ovat tietorakenteita joiden avulla useiden (yleensä) samantyyppisten alkoiden käsittely yleensä tehdään. Yksinkertaisuuden vuoksi käsittelen tässä vain yksinkertaisia taulukoita ja listoja.

Taulukot ja listat yleensä tekevät saman kuin edellä esitetty (tosin toimimaton) esimerkki järjestysnumeron laskemisesta, toimintatapa on vain hiukan erilainen. Taulukoissa tietoalkiot ovat jonossa ja kukin niistä saadaan käsiteltäväksi kertomalla mistä taulukosta ja kuinka mones alkio on kyseessä. Esim:

    for i in 1 upto kuvien_lukumaara
    loop
      tyhjenna_kuva( kuvat[i] )
    endloop

Tässä esimerkissä “kuvat” on taulukko jossa kaikki käsiteltävät kuvat ovat jonossa. Kuvat[1] tarkoittaa ensimmäistä kuvaa tässä taulukossa, kuvat[2] toista kuvaa; haluttu numeroarvo voidaan korvata ohjelman suoritusvaiheessa laskettavalla arvolla ja taulukosta voidaan hakea kyseinen kuva.

Taulukot tai listat mahdollistavat myös käsiteltävien alkioiden määrän kertomisen ohjelman suoritusaikana. Eri ohjelmointikielissä nämä on toteutettu eri tavoin, esim. C:ssä lähinnä taulukkoa vastaavan tietorakenteen koko on tiedettävä kun se luodaan kun taas C++:n STL:ssä olevaan list:iin voi lisätä alkioita missä vaiheessa tahansa. Hyvin tehdyt jo olemassaolevat toteutukset, jotka käsittelevät taulukoissa olevia tietoalkioita, eivät muutu jos taulukossa olevien alkoiden määrä muuttuu. Tämä säästää paljon aikaa ohjelmaa muutettaessa, on havainnollisempaa kuin useita lähes samanlaisia rivejä peräkäkin ja toimiikin yleensä nopeammin.

Liukuluvuista

Ohjelmointikielet käyttävät reaalilukujen esittämiseen yleensä liukulukuja. Liukuluvilla ei voida esittää reaalilukuja tarkasti, mistä usein seuraa yllättäviä ongelmia. Esimerkiksi liukuluvilla laskettaessa monet normaalit laskusäännöt eivät välttämättä päde, koska esitettävän luvun tarkkuus riippuu suuruusluokasta. Jori Mäntysalo on tehnyt sivun, jolla kerrotaan tarkemmin mikä on liukuluku. Hänon myös tehnyt sivun “Tietokone laskee väärin”, jossa esitetään muutama esimerkki asiasta.

Miten poistan tai lisään rivejä tiedostosta/tiedostoon?

Tiedostojen käsittely on periaatteessa käyttöjärjestelmäriippuvaista. Koska kaikki nykyiset yleiset käyttöjärjestelmät käsittelevät tiedostoja suunnilleen samalla tavalla, eräät perusasiat pätevät kaikkialla.

Yleensä tiedostoa käsitellään yhtenäisenä jonona tavuja., johon ei voi lisätä keskelle mitään tai poistaa mitään keskeltä. Kirjoitusoperaatiot kirjoittavat tiedostossa olevan datan päälle. Tiedoston kokoa voi yleensä muuttaa kirjoittamalla tiedoston loppuun tai katkaisemalla tiedosto tietyn kokoiseksi.

Niinpä yksinkertaisin algoritmi rivien poistamiseksi on seuraava:

  1. Avaa haluttu tiedosto lukua varten.

  2. Luo väliaikaistiedosto kirjoittamista varten.

  3. Lue tiedostosta rivi

  4. Jos se ei ole poistettava rivi, kirjoita se väliaikaistiedostoon.

  5. Toista kohtia 3. ja 4. kunnes alkuperäinen tiedosto on luettu kokonaan.

  6. Sulje molemmat tiedostot.

  7. Poista alkuperäinen tiedosto ja nimeä väliaikaistiedosto alkuperäiseksi.

Vastaavasti tiedostoon lisätään rivejä keskelle tai alkuun seuraavalla tavalla:

  1. Avaa haluttu tiedosto lukua varten.

  2. Luo väliaikaistiedosto kirjoittamista varten.

  3. Lue tiedostosta rivi

  4. kirjoita se väliaikaistiedostoon.

  5. Toista kohtia 3. ja 4. kunnes ollaan kohdassa, johon haluat uuden rivin lisätä.

  6. Lisää uusi rivi (tai uudet rivit).

  7. Kirjoita loput alkuperäisestä tiedostosta uuteen tiedostoon samalla tavalla kuin kohdissa 3-5.

  8. Sulje molemmat tiedostot.

  9. Poista alkuperäinen tiedosto ja nimeä väliaikaistiedosto alkuperäiseksi.

Tiedoston alkuun lisätään rivejä hyppäämällä kohtien 3-5 yli ja jatkamalla kohdasta 6 eteenpäin.

Ongelmia C tai C++ -kielen kääntämisen kanssa

Seuraavassa kuvataan, miten C-kääntäjä tuottaa lähdekoodista ohjelmatiedoston. Vaikka nykyiaikaiset kehitysympäristöt usein piilottavat käännöksen välivaiheet, toimintaperiaate on useimmissa ympäristöissä sama. Tämän toiminnan tunteminen saattaa usein auttaa ongelmien selvittämisessä.

Myös C++-ohjelmat käännetään samalla tavalla.

Miten C-kääntäjä kääntää ohjelman?

C-lähdekoodin kääntäminen koostuu itseasiassa kolmesta vaiheesta:

  1. Esikääntäjä käsittelee lähdekooditiedoston ennen sen kääntämistä. Se sisällyttää lähdekoodiin #include-direktiivillä määritellyt tiedostot, korvaa #define -makrot niiden arvoilla ja käsittelee ehdolliset #ifdef -osiot. Se myös usein poistaa kommentit.

  2. Kääntäjä lukee esikääntäjän käsittelemän lähdekooditiedoston ja kääntää sen. Se luo joko luo suoraan binäärikoodisen objektitiedoston tai symbolisen konekoodin, josta tehdään objektitiedosto assemblerin avulla. Jokaisesta lähdekooditiedostosta tehdään oma objektitiedosto.

  3. Linkkeri kokoaa kääntäjän luomista objektitiedostoista ja kirjastotiedostoista ajokelpoisen ohjelman.

Muutamaan asiaan kannattaa kiinnittää huomiota. Esikääntäjä ei ymmärrä C:n syntaksia. Sille ohjelmakoodi on pelkkää tekstiä. Rivi #include "jotain.h" vastaa täysin sitä, että sijoittaisit tuon rivin tilalle tiedoston “jotain.h” sisällön ja #define -määrittelyt vain korvaavat merkkijonoja toisilla.

Kääntäjä käsittelee kerrallaan vain yhtä tiedostoa. Vasta linkkeri kokoaa eri ohjelmatiedostoissa olevan koodin yhdeksi ohjelmaksi. Kääntäjä ei myöskään käsittele #include -tiedostoista sisällytettyä koodia mitenkään erikoisella tavalla.

Linkkeri käsittelee konekoodia sisältäviä objektitiedostoja. Se ei tiedä millä ohjelmointikielellä kyseinen ohjelmakoodi on kirjoitettu.

Miksi kääntäjä valittaa “unresolved symbol”, vaikka ohjelmassani on kaikki vaaditut #includet?

Kuten edellisestä kävi selville #include ei ole ohje C-kääntäjälle liittää ohjelmaan tietty kirjasto, vaan ohje esikääntäjälle sisällyttää kyseisen tiedoston sisältö kyseiseen kohtaan ohjelmakoodia. Tämä tiedosto (ns. header-tiedosto) yleensä sisältää kirjastoa käyttävien ohjelmien kääntämiseen tarvittavat määrittelyt. Sinun on vielä kerrottava linkkerille että haluat kyseisen kirjaston mukaan.

Miksi GCC ei osaa sisällyttää C++-kirjastoja, kun käännän C++-ohjelmia?

Itseasiassa GCC:ssä on kaksi eri ominaisuutta helpottamassa ohjelmointia. Ensimmäinen on se, että se tunnistaa lähdekooditiedoston päätteestä sen tyypin ja käyttää oikeaa kääntäjää. Tämä ei kuitenkaan vaikuta ohjelmien linkittämiseen mitenkään.

Toinen ominaisuus on se, että kun linkkeriä kutsutaan g++-komennon avulla, GCC automaattisesti linkittää mukaan C++-ohjelman tarvitsemat ajonaikaiset kirjastot. Käännä siis C++-ohjelmat käyttäen g++-komentoa.

Kommentteja halutaan, kiitoksia annetaan

Haluan kommentteja, risuja, kritiikkiä sekä ehdotuksia. Avuliaat palkitaan ikuisella kunnialla - sillä, että saa nimensä alla olevaan luetteloon. Niin, ja avulias saa itselleen hyvän mielen, mikä tärkeintä!

Haluan kiittää kaikkia, jotka ovat auttaneet minua tämän kirjoitelman kokoamisessa. Erityisesti haluan kiittää seuraavia ihmisiä, jotka mainitsen aakkosjärjestyksessä:

Mikäli mielestäsi sinun pitäisi olla mainittu tuossa listassa, olet luultavasti oikeassa; pistä minulle meiliä, olen todennäköisesti vain ollut hieman hajamielinen.

FAQ:n ylläpitäjä tarvitsee avustajia. Jos kiinnostaa, ota yhteyttä meilillä (jautero@iki.fi).