-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsaoa-faq.sgml
767 lines (730 loc) · 36 KB
/
saoa-faq.sgml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
<!doctype debiandoc public "-//DebianDoc//DTD DebianDoc//EN">
<book>
<title>Ohjelmoinnin alkeet: Vastauksia usein esitettyihin kysymyksiin
<author>Juha Autero <email>[email protected]
<author>Antti-Juhani Kaijanaho <email>[email protected]
<author>Jori Mäntysalo <email>[email protected]
<version>$Revision: 1.49 $ $Date: 2004/09/29 06:20:02 $
<copyright>
Copyright © 1999, 2000 Antti-Juhani Kaijanaho ja Jori Mäntysalo.
Copyright © 2000 Jani Miettinen (<ref id="taulukot">)
Copyright © 2002 Juha Autero
<p>
Tätä kirjoitelmaa saadaan levittää ja muuttaa vapaasti sillä ehdolla,
että muutosten tekijä ja päiväys ilmoitetaan muutetuissa versioissa
selkeästi. Kirjoitelman tekijät (mukaanlukien kaikki mahdollisten
muutosten tekijät) on mainittava kirjoitelmasta ja sen
johdannaisteoksista tehdyissä teoskappaleissa, jollei kukin sitä
omalta osaltaan erikseen kiellä.
<p>
Toivomme, että kaikissa viittauksissa tähän artikkeliin mainitaan sen
ikiosoite
<url id="http://www.iki.fi/jautero/faq/saoa-faq.html"
name="http://www.iki.fi/jautero/faq/saoa-faq.html"> .
Viittauksissa ei tule käyttää muita osoitteita, sillä vain mainitun
osoitteen toimiminen taataan.
<p>
<strong/Kirjoitelman tekijät eivät vastaa kirjoitelmassa annetun
tiedon oikeellisuudesta./ Kirjoitelman on tarkoitus olla
mahdollisimman luotettava ja puolueeton tietolähde, totta kai, mutta
koska sitä kirjoitetaan lähinnä harrastuksen vuoksi, ei siitä voida
ottaa sellaista vastuuta, joka voisi joskus viedä kirjoittajat
käräjille.
</copyright>
<toc>
<chapt>Yleisiä asioita
<p>
<sect>Tästä kirjoitelmasta
<p>
Tämä kirjoitus on niin sanottu FAQ. Tämä kirjainlyhenne tulee
englanninkielen sanoista "Frequently Asked Questions", 'usein
esitettyjä kysymyksiä'. Niinpä tämä kirjoitelma pyrkiikin
<em/vastaamaan usein esitettyihin kysymyksiin/, sellaisiin, jotka
pidemmän päälle toistuessaan alkavat kyllästyttää keskusteluareenan
vakiosallistujia.
<p>
Tämä kirjoitelma on keskusteluryhmän <tt/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.
<p>
Tämä kirjoitelma postataan kerran kuukaudessa uutisryhmään
<tt/sfnet.atk.ohjelmointi.alkeet/. Uusin julkaistu versio on myös
saatavilla seitistä seuraavissa muodoissa: <url
id="http://www.iki.fi/jautero/faq/saoa-faq.html" name="HTML">, <url
id="http://www.iki.fi/jautero/faq/saoa-faq.txt" name="tavallinen
teksti">, <url id="http://www.iki.fi/jautero/faq/saoa-faq.tov"
name="teksti korostuksin"> ja <url
id="http://www.iki.fi/jautero/faq/saoa-faq.sgml" name="SGML/Debiandoc">.
Viimeksimainitun käsittelemiseen tarvitaan <url
id="http://www.debian.org/Packages/unstable/text/debiandoc-sgml.html"
name="Debiandoc-SGML-paketti">, joka toiminee ainakin kaikissa
Unixeissa.
<p>
Tätä kirjoitusta ylläpitää Juha Autero <email/[email protected]/.
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 <url
id="http://www.iki.fi/jautero/faq/ChangeLog" name="GNU ChangeLog"> sekä
<url id="http://www.iki.fi/jautero/faq/changelog.html"
name="HTML">-muodossa.
<sect>Sananen hyvästä käytöksestä uutisryhmissä
<p>
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 <em/peelon/ eli idiootin mainetta. Tämä netti-etiketti
eli <em/netiketti/ ei ole kovinkaan monimutkainen, ja terveellä
järjellä pärjää hyvin.
<p>
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.
<p>
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ä.
<p>
Lisää tietoa netiketistä saa Timo Kiravuon kirjoituksesta <url
id="http://www.nixu.fi/%7ekiravuo/etiketti/" name="News-etiketti"> ja
Jukka Korpelan kirjoituksesta <url
id="http://www.malibutelecom.com/yucca/nyysit/index.html" name="Nyysiopas">.
Kumpikin ovat suositeltavaa luettavaa kaikille uutisryhmiä
seuraaville.
<sect>Ohjelmoinnin alkeet -uutisryhmästä
<p>
Uutisryhmän <tt/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ä <url
id="http://www.iki.fi/gaia/saom/"
name="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.
<p>
<em/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ä
<tt/sfnet.viestinta.www/, kun olet ensin tarkistanut, ettei
kysymykseesi ole vastattu <url
id="http://www.heikniemi.net/svwww-vukk/"
name="ryhmän VUKK:ssa"> tai englanninkielisessä <url
id="http://www.stack.nl/htmlhelp/faq/wdgfaq.htm" name="FAQ:ssa">.
<p>
Ennen kysymyksen lähettämistä uutisryhmään tarkista, ettei
kysymykseesi ole vastattu tässä kirjoituksessa tai <url
id="http://www.iki.fi/jautero/faq/sao-faq.html" name="moderoidun
ohjelmointiryhmän FAQ:ssa">.
<chapt>Ohjelmoinnin opettelemisesta
<p>
<sect>Ensimmäinen ohjelmointikieli
<p>
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.
<p>
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.
<p>
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
<em/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.
<p>
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), <em/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.
<p>
Hyviä aloituskieliä ovat Scheme, Python, Prolog, Java, Pascal ja C++.
Muitakin kelpo kieliä on. Käsittelen nyt kutakin nimeltä
mainitsemaani kieltä erikseen.
<p>
<taglist>
<tag>Scheme<item>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ä.
<p>
Scheme-materiaalia on verkossa varsin paljon, lähinnä englanniksi.
Hyviä aloituspaikkoja ovat <url id="http://www.schemers.org/"
name="Schemers.org"> ja suomalainen vaan ei suomenkielinen <url
id="http://www.niksula.cs.hut.fi/%7Ecandolin/scheme/"
name="Schememonster's friends">. Schemeä käyttäviä, aloittelevalle
ohjelmoijalle sopivia kirjoja on myös, lähinnä englanniksi.
<tag>Python<item>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.
<p>
Kaikki olennainen Pythonista on luettavissa kielen <url
name="seittisivustolta" id="http://www.python.org/">.
<tag>Prolog<item>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.
<p>
Prolog-materiaalia on netissä, mm. <url
name="Prolog Programming A First Course"
id="http://www.cbl.leeds.ac.uk/%7Epaul/prologbook/">, <url
name="A Short Tutorial on Prolog"
id="http://www.cbl.leeds.ac.uk/%7Etamsin/prologtutorial/"> ja suomeksi
<url
name="Keskeneräisiä harjoituksia Prolog-ohjelmointikieleen tutustumiseksi"
id="http://www.ling.helsinki.fi/courses/ctl170/1998/">.
<p>
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''.
<tag>Java<item>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.
<p>
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 <url name="seittisivusto"
id="http://www.cs.Helsinki.FI/%7Ewikla/JohdOhj/JaOh/">, jota Wikla käyttää
Java-kurssinsa kurssimateriaalina ja joka siksi soveltuu hyvin
aloittelevan ohjelmoijan luettavaksi.
<tag>Pascal<item>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ä.
<p>
Jori Mäntysalo on kirjoittanut perusteluja sille miksi <url
name="pascal sopii ohjelmoinnin opiskeluun" id="http://www.uta.fi/%7Ejm58660/jutut/ohjelmointi/pascal.html">.
<p>
Seitistä löytyy <url
name="pascal-opas" id="http://www.tuug.utu.fi/%7ef/pascal/">.
<tag>C++<item>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.
<p>
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.
<p>
Netistä löytyy myös <url name="C++-opas"
id="http://www.nic.funet.fi/c++opas">, joka kevyestä kirjoitustyylistään
huolimatta sisältää painavaa asiaa.
</taglist>
<sect>Kumpi opetella ensin: C vai C++?
<p>
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 <url name="C++-FAQ Lite"
id="http://www.parashift.com/c++-faq-lite/">, kysymys 28.2.
<chapt>Peliohjelmoinnista
<p>
<sect>Haluan oppia tekemään pelin. Miten aloitan?
<p>
Opettele ohjelmoinnin alkeet käyttäen jotain edellä esiteltyä kieltä.
Sitten tutustu esimerkiksi kirjoitukseen <url
id="http://www.hut.fi/~jjpihlaj/data/laama211.zip" name="Laaman tie
peliohjelmointiin">, joka olettaa C-kielen hallinnan sekä siedettävän
laskutaidon.
<sect>Kuinka laitan peliini kuvan?
<p>
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.
<p>
Edellä mainittu Laaman tie peliohjelmointiin sisältää joitakin ohjeita.
<chapt>Satunnaislukuongelmia
<p>
<sect>Satunnaislukugeneraattori arpoo aina saman sarjan
<p>
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 <em/alustettava/. Alustustapa
riippuu kielestä; kohtuuhyvä metodi on uudehkoissa Pascal-murteissa
<tt/randomize/-käsky sekä C:ssä <tt/srand(time(0))/ (tässä tarvitaan
<tt/stdlib.h/- ja <tt/time.h/-headerfileitä).
<sect>Satunnaislukugeneraattori arpoo aina samaa lukua
<p>
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. <url id="http://www.iki.fi/jautero/faq/srand-virhe.c"
name="Esimerkki virheestä"> tuottaa ajettaessa <url
id="http://www.iki.fi/jautero/faq/srand-virhe-esim" name="mainitun
tuloksen">.
<p>
Pääsääntö on yksinkertainen: <strong/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ä.
<sect>Miten saan satunnaisen kokonaisluvun väliltä 42..512?
<p>
<strong>VAROITUS: Tässä annettu vastaus voi olla väärä. Keskustele
oman satunnaisuuden asiantuntijasi kanssa ennen kuin käytät tässä
annettuja ohjeita.</strong>
<p>
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ä.
<p>
Jos kirjaston satunnaislukugeneraattori palauttaa liukuluvun f, jonka
tiedetään olevan välillä <tt>0 <= f < 1</tt>, on suhteellisen
yksinkertainen tapa skaalata tämä halutulle välille <tt> MIN <= f'
< MAX</tt> kaavan <tt>f' := (MAX - MIN) * f + MIN</tt> käyttäminen.
Tässä toki pitää käytetyn liukulukutyypin olla riittävän tarkka. Jos
haluat tuloksen olevan kokonaisluku väliltä <tt/MIN...MAX/, käytä
kaavaa <tt/tulos := int( (MAX - MIN + 1) * f) + MIN/, missä funktio
<tt/int/ tekee liukuluvusta kokonaisluvun katkaisemalla (poimimalla
suurimman kokonaisluvun, joka ei ole suurempi kuin kyseinen
liukuluku).
<p>
Mikäli satunnaislukugeneraattori palauttaa kokonaisluvun väliltä
<tt/0...RAND_MAX/ ja haluat tulokseksi liukuluvun väliltä <tt/MIN
<= tulos < MAX/, voit käyttää kaavaa <tt>tulos := double(MAX -
MIN) / (RAND_MAX + 1) * f + MIN</tt>. Jos haluat tuloksen olevan
kokonaisluku väliltä <tt/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:
<example>
MIN + (int)((MAX - MIN + 1.0) * rand() / (RAND_MAX + 1.0))
</example>
missä <tt>MAX</tt> ja <tt>MIN</tt> ovat halutun luvun ylä- ja alaraja,
ja <tt/RAND_MAX/ satunnaislukugeneraattorin <tt/rand()/ tuottama
suurin kokonaisluku (pienin on nolla). Tämän metodin hankaluutena on
liukulukujen käytön aiheuttama laskentaepätarkkuus.
<p>
Toinen tapa on seuraava: Toinen tapa on seuraava: Olkoon ensin <tt/N
:= MAX - MIN + 1/ ja <tt/RM := RAND_MAX + 1/ (missä <tt/RAND_MAX/ on
suurin luku, jonka satunnaislukugeneraattori tuottaa). Jaetaan väli
<tt/0..RAND_MAX/ <tt/N+1/:een osaan, joista <tt/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 <tt/f/
osuu johonkin muuhun kuin viimeiseen osaan eli <tt/f < N * osan_koko/,
ja palautetaan <tt>f / osan_koko + MIN</tt>.
Valmis C-koodi löytyy Niemitalon artikkelista
<tt><[email protected]></tt> (<url
id="http://groups.google.com/groups?as_umsgid=iznpv693fk8.fsf%40stekt38.oulu.fi" name="Google: Re:
Satunnaislukujen generointi C++:ssa?">).
<p>
Edellämainituissa menetelmissä täytyy halutun välin pituuden olla
enintään <tt/RAND_MAX/.
<p>
Vielä yksi tapa on käyttää omaa satunnaislukugeneraattoria. Tässä
pitää olla <em/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 <url
id="http://www.cs.tut.fi/~ava/algoritmit/random.cc" name="verkosta">.
<sect>Lottorivin arvonta
<p>
Lottoriviä arvottaessa olennaista on, että samaa lukua ei arvota kahta
kertaa. Tätä <em/ei/ pidä toteuttaa arpomalla joka kierroksella luku
väliltä 1..39 ja hylkäämällä jo arvotut numerot. Tuo nimittäin voi
johtaa umpiluuppiin.
<p>
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
<tt><[email protected]></tt> (<url
id="http://groups.google.com/groups?as_umsgid=u1lbtaksryk.fsf%40mustahaikara.cs.tut.fi"
name="Google: Re: Lotto C++:lla">), jossa on pieni virhe: ohjelman
kolmanneksi viimeinen ja toiseksi viimeinen rivi pitää vaihtaa.
<chapt>Muita yleisiä ohjelmointiongelmia
<p>
<sect id="taulukot">Taulukot eli Kuinka tehdä muuttujia lennossa
<p>
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 <tt>kuva1</tt>, <tt>kuva2</tt>, <tt>kuva3</tt>, ja näitä
käsitellään yksitellen. Esim:
<example>
tyhjenna_kuva( kuva1 )
tyhjenna_kuva( kuva2 )
tyhjenna_kuva( kuva3 )
</example>
<p>
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.
<p>
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:
<example>
for i in 1 upto kuvien_lukumaara
loop
tyhjenna_kuva( kuva + i )
endloop
</example>
<p>
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 <tt>kuva1</tt> ja <tt>kuva2</tt> 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ä.
<p>
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.
<p>
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:
<example>
for i in 1 upto kuvien_lukumaara
loop
tyhjenna_kuva( kuvat[i] )
endloop
</example>
<p>
Tässä esimerkissä "kuvat" on taulukko jossa kaikki käsiteltävät kuvat
ovat jonossa. <tt>Kuvat[1]</tt> tarkoittaa ensimmäistä kuvaa tässä
taulukossa, <tt>kuvat[2]</tt> toista kuvaa; haluttu numeroarvo voidaan
korvata ohjelman suoritusvaiheessa laskettavalla arvolla ja taulukosta
voidaan hakea kyseinen kuva.
<p>
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.
<p>
<sect id="liukuluvut">Liukuluvuista
<p>
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
<url id="http://www.uta.fi/~jm58660/jutut/ohjelmointi/liukuluku/mika_on.html"
name="mikä on liukuluku">. Hänon myös tehnyt sivun "<url
id="http://www.uta.fi/~jm58660/jutut/ohjelmointi/liukuluku/laskee_vaarin.html"
name="Tietokone laskee väärin">", jossa esitetään muutama esimerkki asiasta.
<p>
<sect id="tiedostot">Miten poistan tai lisään rivejä tiedostosta/tiedostoon?
<p>
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.
<P>
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.
<P>
Niinpä yksinkertaisin algoritmi rivien poistamiseksi on seuraava:
<enumlist>
<item>Avaa haluttu tiedosto lukua varten.</item>
<item>Luo väliaikaistiedosto kirjoittamista varten.</item>
<item>Lue tiedostosta rivi</item>
<item>Jos se ei ole poistettava rivi, kirjoita se
väliaikaistiedostoon.</item>
<item>Toista kohtia 3. ja 4. kunnes alkuperäinen tiedosto on luettu
kokonaan.</item>
<item>Sulje molemmat tiedostot.</item>
<item>Poista alkuperäinen tiedosto ja nimeä väliaikaistiedosto
alkuperäiseksi.
</item>
</enumlist>
<P>
Vastaavasti tiedostoon lisätään rivejä keskelle tai alkuun
seuraavalla tavalla:
<enumlist>
<item>Avaa haluttu tiedosto lukua varten.</item>
<item>Luo väliaikaistiedosto kirjoittamista varten.</item>
<item>Lue tiedostosta rivi</item>
<item>kirjoita se väliaikaistiedostoon.</item>
<item>Toista kohtia 3. ja 4. kunnes ollaan kohdassa, johon haluat
uuden rivin lisätä.</item>
<item>Lisää uusi rivi (tai uudet rivit).</item>
<item>Kirjoita loput alkuperäisestä tiedostosta uuteen tiedostoon
samalla tavalla kuin kohdissa 3-5.</item>
<item>Sulje molemmat tiedostot.</item>
<item>Poista alkuperäinen tiedosto ja nimeä väliaikaistiedosto
alkuperäiseksi.</item>
</enumlist>
Tiedoston alkuun lisätään rivejä hyppäämällä kohtien 3-5 yli ja
jatkamalla kohdasta 6 eteenpäin.
<sect>Ongelmia C tai C++ -kielen kääntämisen kanssa
<p>
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ä.
<p>
Myös C++-ohjelmat käännetään samalla tavalla.
<sect1>Miten C-kääntäjä kääntää ohjelman?
<p>
C-lähdekoodin kääntäminen koostuu itseasiassa kolmesta
vaiheesta:
<enumlist>
<item>
<p><em>Esikääntäjä</em> käsittelee lähdekooditiedoston
ennen sen kääntämistä. Se sisällyttää lähdekoodiin
<tt>#include</tt>-direktiivillä määritellyt
tiedostot, korvaa <tt>#define</tt> -makrot niiden
arvoilla ja käsittelee ehdolliset <tt>#ifdef</tt>
-osiot. Se myös usein poistaa kommentit.
</p>
</item>
<item>
<p>
<em>Kääntäjä</em> lukee <em>esikääntäjän</em>
käsittelemän lähdekooditiedoston ja kääntää sen. Se luo joko
luo suoraan binäärikoodisen <em>objektitiedoston</em>
tai <em>symbolisen konekoodin</em>, josta tehdään
<em>objektitiedosto</em> <em>assemblerin</em>
avulla. Jokaisesta lähdekooditiedostosta tehdään oma
<em>objektitiedosto</em>.
</p>
</item>
<item>
<p><em>Linkkeri</em> kokoaa <em>kääntäjän</em>
luomista <em>objektitiedostoista</em> ja
<em>kirjastotiedostoista</em> ajokelpoisen ohjelman.
</p>
</item>
</enumlist>
<p>
Muutamaan asiaan kannattaa kiinnittää huomiota.
<em>Esikääntäjä</em> ei ymmärrä C:n syntaksia. Sille
ohjelmakoodi on pelkkää tekstiä. Rivi <tt>#include
"jotain.h"</tt> vastaa täysin sitä, että sijoittaisit tuon
rivin tilalle tiedoston "jotain.h" sisällön ja
<tt>#define</tt> -määrittelyt vain korvaavat merkkijonoja
toisilla.
<P>
<em>Kääntäjä</em> käsittelee kerrallaan vain yhtä
tiedostoa. Vasta linkkeri kokoaa eri ohjelmatiedostoissa
olevan koodin yhdeksi ohjelmaksi. <em>Kääntäjä</em> ei
myöskään käsittele <tt>#include</tt> -tiedostoista
sisällytettyä koodia mitenkään erikoisella tavalla.
<P>
Linkkeri käsittelee konekoodia sisältäviä
<tt>objektitiedostoja</tt>. Se ei tiedä millä
ohjelmointikielellä kyseinen ohjelmakoodi on kirjoitettu.
<sect1>Miksi kääntäjä valittaa "unresolved symbol", vaikka
ohjelmassani on kaikki vaaditut <tt>#includet</tt>?
<P>
Kuten edellisestä kävi selville <tt>#include</tt> ei ole
ohje <em>C-kääntäjälle</em> liittää ohjelmaan tietty
kirjasto, vaan ohje <em>esikääntäjälle</em> sisällyttää
kyseisen tiedoston sisältö kyseiseen kohtaan
ohjelmakoodia. Tämä tiedosto
(ns. <em>header</em>-tiedosto) yleensä sisältää kirjastoa
käyttävien ohjelmien <em>kääntämiseen</em> tarvittavat
määrittelyt. Sinun on vielä kerrottava
<tt>linkkerille</tt> että haluat kyseisen kirjaston
mukaan.
<sect1>Miksi GCC ei osaa sisällyttää C++-kirjastoja, kun käännän
C++-ohjelmia?
<P>
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 <em>kääntäjää</em>. Tämä ei kuitenkaan
vaikuta ohjelmien <em>linkittämiseen</em> mitenkään.
<P>
Toinen ominaisuus on se, että kun <em>linkkeriä</em>
kutsutaan <tt>g++</tt>-komennon avulla, GCC
automaattisesti linkittää mukaan C++-ohjelman tarvitsemat
ajonaikaiset kirjastot. Käännä siis C++-ohjelmat käyttäen
<tt>g++</tt>-komentoa.
<chapt>Kommentteja halutaan, kiitoksia annetaan
<p>
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ä!
<p>
Haluan kiittää kaikkia, jotka ovat auttaneet minua tämän kirjoitelman
kokoamisessa. Erityisesti haluan kiittää seuraavia ihmisiä, jotka
mainitsen aakkosjärjestyksessä:
<list>
<item>Heikki Kantola
<item>Paul Keinänen
<item>Jukka Korpela
<item>Jouko Koski
<item>Vesa Lappalainen
<item>Jani Miettinen
<item>Kalle Olavi Niemitalo
<item>Mika Rantanen
<item>Jukka Suomela
<item>Kimmo Surakka
<item>Antti Valmari
</list>
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.
<p>
FAQ:n ylläpitäjä tarvitsee avustajia. Jos kiinnostaa, ota yhteyttä
meilillä (<email/[email protected]/).
</book>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:2
sgml-indent-data:t
sgml-parent-document:nil
sgml-exposed-tags:nil
sgml-local-catalogs:("/usr/lib/sgml/catalog")
sgml-local-ecat-files:nil
End:
-->