* C++- kielisen ohjelman peruskäsitteet
* kääntämisen ja linkittämisen merkitys
* vakiotyyliset makrot (#define)
* C++:n vakiot (const)
Seuraavassa lauseke on mikä tahansa jonkin tyypin tuottava ohjelman osa, esim: 1+2 sin(x)+9 sormia henkilon_nimi kommentti, C /* vapaata tekstiä, vaikka monta riviäkin */ kommentti, C++ // loppurivi vapaata tesktiä sisällyttäminen: #include <tiedoston_nimi> tai #include "tiedoston_nimi" makro: #define tunnisteXkorvattava_teksti // X mikä tahansa // ei A-Z,a-z,_,0-9 // X tulee mukaan korv.tekst vakio, C++: const tyyppi nimi = arvo; tulostus, C: printf(format,lauseke, lauseke); // 0-n x ,lauseke tulostus, C++: cout << lauseke << lauseke; // 1-n x <<lausekeOhjelman toteuttamista varten täytyy valita jokin todellinen ohjelmointikieli. Lopullisesta ohjelmasta ei valintaa toivottavasti huomaa. Valitsemme käyttökielen tällä kurssilla puhtaasti "markkinaperustein": käytetyin ja työelämässä vielä tällä hetkellä kysytyin - C++.
Tässä luvussa käsittelemme rinnakkain C ja C++ - kielistä ohjelmaa. Seuraavissa luvuissa käsitellään pelkästään C++:aa, kuitenkin siten että jos ohjelman tarkeninen on .C, niin ohjelma toimii samalla myös C- kielisenä ohjelmana.
Kumpi sitten kannattaa opetella ensiksi? Puristit sanovat että jokin "leikkikieli", jotkut että C ja "oliogurut" sanovat että ilman muuta jokin oliokieli. Otamme siis tällä kurssilla kultaisen(?) keskitien ja opettelemme C++:sta eräänlaisen "lasten" version, jossa jatkossa käytämme hyväksi myös kielen olio- ominaisuuksia.
/* Ohjelma tulostaa tekstin Hello world! */ #include <stdio.h> int main(void) { printf("Hello world!\n"); return 0; }
// Ohjelma tulostaa tekstin Hello world! #include <iostream.h> int main(void) { cout << "Hello world!" << endl; return 0; }
Terve! Olen Matti Meikäläinen 25 vuotta. Asun Kortepohjassa. Puhelinnumeroni on 603333.
Esimerkiksi Borlandin ympäristöissä ohjelma kirjoitetaan tekstinä ja kun ohjelmakoodi on valmis, saadaan koodi käännettyä, linkitettyä ja ladattua ajoa varten vain painamalla [Ctrl- F9].
/* Ohjelma tulostaa tekstin Hello world! */Ohjelman alussa on kommentoitu mitä ohjelma tekee. Yleensä ohjelmakoodit on hyvä varustaa kuvauksella siitä, mitä ohjelma tekee, kuka ohjelman on tehnyt, milloin ja miksi. Milloin ohjelmaa on viimeksi muutettu, kuka ja miten.
Lisäksi jokainen vähänkin ei- triviaali lause tai lauseryhmä kommentoidaan. Kommenttien tarkoituksena on kuvata ohjelmakoodia lukevalle lukijalle se mistä on kyse.
Kommentti alkaa /* - merkkiyhdistelmällä ja päättyy */ - merkkiyhdistelmään. Kommentteja voidaan sijoittaa C- koodissa mihin tahansa mihin voitaisiin pistää myös välilyönti. Rivin loppuminen ei sinänsä lopeta kommenttia. Kommentin sisällä SAA esiintyä / ja * - merkkejä yhdessä tai erikseen, muttei lopettavaa yhdistelmää */.
Yleinen virhe on unohtaa kommentin loppusulku pois. Mikäli esimerkissämme puuttuisi kommentin loppusulku, olisi koko loppuohjelma kommenttia ja mitään ohjelmaa ei siis olisikaan. Mikäli kääntäjä antaa vyöryn ihmeellisiä virheilmoituksia, kannattaa aina ensin tarkistaa kommenttisulkujen täsmäävyys. Tosin tähän auttaa nykyisten ohjelmointiympäristöjen värikoodien käyttö eri ohjelman osille, eli esimerkiksi kommentit näkyvät eri värisinä ja puuttuva komenttisulku paljastuu välittömästi.
// Ohjelma tulostaa tekstin Hello world!C++:ssa kommentti voidaan ilmaista myös // - merkkiyhdistelmällä, jolloin rivinloppu lopettaa kommentin. Siirrettävyyden takia niissä ohjelman osissa, jotka ovat puhtaasti C- kieltä, voitaisiin käyttää /* */ - kommentointia ja muualla // - kommentointia.
#include <stdio.h>
#include <iostream.h>Tarvitsemme ohjelmassamme tulostusfunktiota printf. Tämä funktio löytyy C- kielen kirjastosta ja se on esitelty otsikkotiedostossa stdio.h. Kääntäjää varten meidän täytyy esitellä millaisia parametreja funktiolle voidaan välittää. Kutakin kirjastoa varten on esittelytiedostot ("header"- tiedostot, yleensä nimetään .h), joissa kirjastofunktioiden parametrilistat on esitelty.. Tässä ohjelmassa stdio.h - tiedostosta käytetään vain printf:n esittelevää riviä ja voitaisiin myös kirjoittaa #include - rivin tilalle printf:n esittely:
int printf(const char __format, ...);mutta oikean muodon muistaminen voisi olla vaikeampaa.
C++:an tulostusvirta cout löytyy kirjastosta iostream.h. cout - olion määrittely iostream.h - tiedostossa on niin monimutkainen ettei sitä käytännössä voisi itse edes kirjoittaa! #include on C- kielen esikääntäjän (pre- prosessor) käsky, joka ilmoittaa että perässä olevan niminen tiedosto on luettava ja käsiteltävä koodin sekaan tässä kohti käännöstä.
< > - merkit tiedoston nimen ympärillä ilmoittavat, että ko. tiedostoa etsitään C:n systeemin mukaisesta INCLUDE- hakemistosta. Mikäli nimi suljettaisiin "- merkeillä, etsittäisiin tiedostoa myös käyttäjän kotihakemistosta. Näin voidaan tehdä omia tehtäväkohtaisia kirjastoja.
int main(void)Seuraavaksi esitellään ohjelman pääohjelma ("oikea" ohjelma koostuu isosta kasasta aliohjelmia ja yhdestä pääohjelmasta, jonka nimi on main). int tarkoittaa, että pääohjelmamme palauttaa kokonaisluvun kutsuvalle ohjelmalle - käyttöjärjestelmälle. Palautettavan luvun arvo on tyypillisesti 0 mikäli kaikki menee ohjelman aikana niinkuin pitääkin ja muilla numeroilla ilmaistaan erilaisia virhetilanteita. MS- DOSissa tätä arvoa voidaan tutkia ERRORLEVEL- muuttujalla.
main tarkoittaa pääohjelman nimeä. Tämä TÄYTYY aina olla main.
(void) ilmoittaa, että funktio jota kirjoitamme ei tarvitse yhtään parametria (eng. void = mitätön). Myöhemmin huomaamme että myös pääohjelmalla voi olla parametrejä.
printf("Hello world!\n")printf("?") tulostaa ajonaikana sen tekstin, joka on lainausmerkkien välissä. Myöhemmin opimme, että funktion kutsussa voi olla myös useampia parametreja ja voidaan käyttää myös muuttujia.
\n on C:n erikoismerkki, joka kääntyy merkkijonon sisällä käyttöjärjestelmän rivinvaihtomerkiksi. Tällaisen esiintyminen merkkijonossa aiheuttaa tulostuksen siirtymisen uuden rivin alkuun. Muista käyttää!
cout << "Hello world!" << endl;cout on C++:n yksi tulostustietovirtaluokan (output stream class) ostream esiintymä, eli olio jolle (coutin tapauksessa) siirretyt merkit tulostuvat näyttöön (Console OUTput) .
<< on operaattori, jolla C++:ssa on useita merkityksiä. Tässä tapauksessa kun operaattorin vasempana operandina on tietovirtaolio, on kyseessä tietovirtaan siirtämisoperattoori (inserter). Käytämme tästä jatkossa nimitystä tulostusoperaattori. Koska operaattorikin on vain aliohjelma, voisi <<- operaattorin varsinainen kutsumuoto olla esimerkiksi
operator<<(cout, "Hello world!"); operator<<(cout, endl);Koska operaattori palauttaa cout- olion, voidaan kutsu kirjoittaa myös (samoin kuin 1+2 palauttaa kokonaisluvun) ketjumuotoon
operator<<( operator<<(cout, "Hello world!"), endl); // lyhennetty muoto (cout << "Hello world!") << endl;joka ilman sulkuja on ohjelmassa hello.cpp esitetty muoto. Operaattorin muoto voi olla myös:
cout.operator<<("Hello world!"); cout.operator<<(endl); // josta ketjutettuna: (cout.operator<<("Hello world!")).operator<<(endl); // lyhennetty muoto: (cout << "Hello world!") << endl;Vertaa vastaavaan ketjuttamiseen + - operaattorin kanssa:
int i,j; i = 1 + 2; j = i + 4; // voidaan kirjoittaa myös: j = ( 1 + 2 ) + 4;endl on tietovirran muotoilija (manipulator), joka vaihtaa tulostuksen uudelle riville ja tyhjentää tulostuspuskurin. Rivinvaihto voitaisiin tehdä myös jonolla "\n", mutta tulostuspuskuri on muistettava myös tyhjentää. endl olisi hyvä olla vähintään viimeisessä tulostettavassa lauseessa ennen pysähtymistä. Joissakin koneissa ohjelmat voivat toimia myös ilman endl:ää, mutta jos halutaan standardin mukaista koodia, joka toimii KAIKISSA koneissa, on sitä syytä käyttää. Kumpiko tulostuslause sitten on parempi? Jos kirjoitetaan C- koodia, on tietysti käytettävä printf:ää, mutta C++:n tapauksessa molemmat käyvät. cout on parempi sen vuoksi, että siihen liittyvään tulostusoperaattorin voidaan lisätä uusia tietotyyppejä tulostettavaksi (kuormittaa, operator overloading). Toisaalta tulostuksen muotoilu on helpompaa printf:n kanssa. Määrityksissä sanotaan että molempia voi käyttää, muttei samalla tulostusrivillä. Laajennettavuuden takia valitaan coutaina kuin se vain on mahdollista.
return 0return jokainen C- funktio tulisi lopettaa return- lauseeseen. Mikäli funktio on esitelty muun kuin void- tyyppiseksi, pitää kertoa myös arvo, joka palautetaan. Funktio voi tarvittaessa sisältää myös useita eri return- lauseita. Heti kun kohdataan ensimmäinen return- lause, poistutaan funktiosta. void- tyyppisessä funktiossa return- lause ei ole pakollinen.
Int MAIN(Void) /* VÄÄRIN! */
C- koodi voi sisältää tyhjiä merkkejä missä tahansa, kunhan niitä ei kirjoiteta keskelle sanaa tai tekstiä määrittelevän ""- parin ollessa auki. ""- parin sisällä tyhjätkin merkit ovat merkityksellisiä. Tyhjillä merkeillä ei saa myöskään sotkea esikääntäjälle tarkoitettuja #- direktiivi - rivejä, näiden pitää muodostaa täsmälleen yksi rivi.
Lainausmerkkeihin suljettu jono voidaan tarvittaessa katkaista tyhjillä merkillä sulkemalla ja avaamalla lainausmerkit. Esimerkiksi
"Kissa" "istuu" - > "Kissaistuu"Tarvittaessa C- ohjelman riviä voidaan jatkaa uudelle riville kirjoittamalla \- merkki edellisen rivin loppuun ja sen jälkeen välittömästi rivinvaihto.
Siis kääntäjän kannalta malliohjelmamme voitaisiin kirjoittaa myös seuraavillakin tavoilla:
#include\ <stdio.h> int main ( void ) { printf ( "Hel\ lo " "w" /* kommentti keskellä jonoa */ "or" "ld!" "\n" ) ; return 0 ; }
#include <stdio.h> int main(void){ printf("Hello " "world!\n" );return 0;}
#include <stdio.h> int main(void){printf("Hello world!\n");return 0;}Yleinen tyyli on kuitenkin jakaa koodia riveihin ja sisentää lohkoja muutamalla pykälällä. Kunnes lukija on varma omasta tyylistään, kannattaa matkia tässä monisteessa (ei kuitenkaan edellisiä esimerkkejä) esitettyä kirjoitustapaa ohjelmille.
Makro- direktiivi #define on tiedote siitä, että tällä määreellä myöhemmin tekstissä esiintyvät sanat täytyy korvata toisella sanalla/sanoilla/merkeillä.
Yksinkertaisten makrojen käytön hyöty on siinä, että usein ohjelmassa esiintyvät sanat/vakionumerot voidaan koota helposti hallittavaksi ohjelman alkuun tai jopa omaan määrittelytiedostoonsa.
Siis #define- direktiivi on puhdas tekstinkäsittelyotus, joka toimii suurinpiirtein seuraavasti:
#define TERVE "Hello "tarkoittaisi esikääntäjälle:
vaihda kaikki TERVE- sanat merkkijonoiksi "Hello " eli seuraavat vaihdettaisiin: p=TERVE+1 /* => p="Hello "+1 */ "Kissa"TERVE"istuu" /* => "Kissa""Terve ""istuu" */ TERVE MIEHEEN /* => "Hello" MIEHEEN */ muttei seuraavia TERVEYDEKSI /* Ei pelkkä TERVE-sana */ TERVE_MIEHEEN /* Ei pelkkä sana */ "OLEN OLLUT TERVE 2 PÄIVÄÄ" /* Lainausmerkeissä */ Terve MIEHEEN /* Eri tavalla kirjoitettu */Huomattakoon, ettei lainausmerkkien sisällä oleviin sanoihin kajota lainkaan!
Määriteltävä sana voidaan kirjoittaa isoja ja/tai pieniä kirjaimia käyttäen, mutta yleiseen C- tyyliin kuuluu kirjoittaa #define - määritellyt sanat isoilla kirjaimilla.
Vaihdettava sana loppuu ja korvaava jono alkaa ensimmäisestä ei- kirjaimesta tai numerosta, eli
#define X=2; /* VAARALLINEN! X => =2; */ int i=X; /* => int i==2;; */
/* Ohjelma tulostaa tekstin Hello world! */ #include <stdio.h> #define TERVE "Hello " #define MAAILMA "world" int main(void) { printf(TERVE MAAILMA"!\n"); return 0; }Miksikö? Koska esikääntäjä muuttaisi lauseen
printf(TERVE MAAILMA"!\n");muotoon
printf("Hello " "world""!\n");Kun tästä lisäksi poistetaan ylimääräiset "white space"- merkit saadaan:
printf("Hello world!\n");
/* Ohjelma tulostaa tietoja monitahokkaasta */ #include <stdio.h> #define TAHOKAS "Kuutiossa" #define KARKIA 8 #define SIVUTASOJA 6 #define SARMIA 12 int main(void) { printf("%20s on %2d kärkeä,\n" ,TAHOKAS,KARKIA); printf("%20s %2d sivutasoa ja\n"," " ,SIVUTASOJA); printf("%20s %2d särmää.\n" ," " ,SARMIA); return 0; }
0 1 2 3 4 1234567890123456789012345678901234567890 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Kuutiossa on 8 kärkeä, 6 sivutasoa ja 12 särmää.
#include <stdio.h> #define ALKU int main(void) { #define LOPPU return 0; } #define mk *100 #define km *1000 ALKU double hinta_penneina,matka_m; hinta_penneina = 5 mk; matka_m = 3.5 km; LOPPUmuotoon
... kaikki stdio.h:ssa oleva koodi makrot käsiteltynä ... int main(void) { double hinta_penneina,matka_m; hinta_penneina = 5 *100; matka_m = 3.5 *1000; return 0; }Tosin tällaisia makrotemppuiluja kannattaa välttää ellei siitä saa suunnatonta ohjelman ylläpidollista hyötyä. Tarvittaessa pelkkä esikäännös voidaan tehdä vaikka Borland C++:lla:
C:\OMAT\OHJELMOI\VESA>CPP makroja.c[RET] tulee tiedosto makroja.i
a;bd(kissa)on 3 tokenia: a, bd ja kissa.
Kun esikääntäjä käsittelee yhtä tokenia, etsii se sitä sisäisestä listastaan. Jos token löytyy listasta, korvataan se vastaavalla merkkijoukolla ja listan ko. token merkitään käytetyksi. Mahdollinen korvaus muodostaa 0 - n uutta tokenia. Nämä kaikki käsitellään rekursiivisesti esikääntäjän listan kanssa kunnes yhtään korvausta ei voida tehdä (token ei löydy listasta tai kaikki listan alkiot on merkitty käytetyiksi). Näin tämä yksi token on saatu muutetuksi. Tämän jälkeen listan kaikki alkiot vapautetaan ja siirrytään rivin seuraavaan mahdolliseen tokeniin.
Miksi listaan merkitään sanoja käytetyksi? Miten kävisi muuten (kävi vanhoilla C- esikääntäjillä) seuraavan ohjelmanpätkän kanssa:
#define K T T #define T K K K TLyhyesti: esikääntäjä ei korvaa sisäkkäisiä #definejä heti, vaan sitten kun ne esiintyvät. Näin useissa normaalitapauksissa (kuten ei edelläkään) ei ole väliä sillä, missä järjestyksessä #define- rivit kirjoitetaan.
// Ohjelma tulostaa tekstin Hello world! #include <iostream.h> const char *TERVE = "Hello "; const char *MAAILMA = "world"; int main(void) { cout << TERVE << MAAILMA << "!" << endl; return 0; }Valitettavasti on tilanteita, joissa makroja on edelleen lähes pakko käyttää. Esimerkiksi merkkijonojen yhdistäminen TERVE MAAILMA tyyliin ei onnistu. Myöhemmin opimme joitakin muitakin tilanteita, joissa makrot ovat "välttämättömiä". Erityisesti lukuvakioiden määrittelyssä const- vakiot ovat omimmillaan:
// Ohjelma tulostaa tietoja monitahokkaasta #include <iostream.h> #include <iomanip.h> const char *TAHOKAS = "Kuutiossa"; const int KARKIA = 8; const int SIVUTASOJA = 6; const int SARMIA = 12; int main(void) { cout<<setw(20)<<TAHOKAS<<" on "<<setw(2)<<KARKIA <<" kärkeä,\n"; cout<<setw(20)<<" " <<" "<<setw(2)<<SIVUTASOJA<<" sivutasoa,\n"; cout<<setw(20)<<" " <<" "<<setw(2)<<SARMIA <<" särmää" << endl; return 0; }
// Ohjelmalla tutkitaan mikä ero on const ja #define -vakioiden välillä #include <iostream.h> const int sormia = 1 + 4; #define VARPAITA 1 + 4 int main(void) { int syht = 2 * sormia; int vyht = 2 * VARPAITA; cout << "Sormia = " << syht << " ja varpaita = " << vyht << endl; return 0; }