* loogiset operaattorit: &&, || ja !
* bittitason operaattorit: &,|,^ ja ~
* silmukat while, do-while ja for
* silmukan "katkaisu" break, continue, goto
* sijoituslauseet: = += -= jne.
* valintalause switch
lause joko ylause; // HUOM! Puolipiste tai lohko // eli koottu lause ylause yksinkertainen lause esim a = b + 4 vaihda(a,b) lohko { lause1 lause2 lause3 } // lauseita 0-n esim { a = 5; b = 7; } ehto lauseke joka tuottaa 0 tai != 0 esim a < 5 ( 5 < a ) && ( a < 10 ) !a // jos a=0 => 1, muuten 0 HUOM! Vertailu a == 5 if-else if ( ehto ) lause1 else lause2 // ei pakollinen while while ( ehto ) lause; do-while do lause while ( ehto ); for for ( ylause1a,ylause2a; ehto ; ylause1k,ylause2k ) lause esim for ( i=0,s=0; i<10; i++ ) s += i; // ylause1a swicth switch ( lauseke ) { case arvo1: lause1 break; // valintoja 0-n case arvo2: // arvolla 2 ja 3 sama case arvo3: lause2 break; default: laused break; // ei pakollinen }Ohjelma jossa ei ole minkäänlaista valinnaisuutta tai silmukoita on varsin harvinainen. Kertaamme seuraavassa C/C++- kielen tarjoamat mahdollisuudet suoritusjärjestyksen ohjaamiseen. Samalla näemme kuinka suomenkielisen algoritmin kääntäminen ohjelmointikielelle on varsin mekaanista puuhaa.
1. Jos luvut väärässä järjestyksessä, niin vaihda ne keskenäänTämän kirjoittamiseksi ohjelmaksi tarvitsemme ehto- lausetta:
if ( ehto ) ylause1; lause2;Huomattakoon, että tässä sulut ehdon ympärillä ovat pakolliset. lause1 suoritetaan vain kun ehto on voimassa. lause2 suoritetaan aina. Lause voitaisiin kirjoittaa myös muodossa
if(ehto) ylause1; lause2;muttei näin tehdä, jotta erottaisimme paremmin funktion ja if- lauseen toisistaan. Saa tulee koskemaan myös for, while ja muita vastaavia rakenteita.
if ( a > b ) vaihda(&a,&b);
Vinkki
Sisennä kauniisti
if ( a > b ) { t = a; a = b; b = t; }Huomautus! Lauseiden kirjoittaminen samalle riville ei auttaisi mitään, sillä
if ( a > b ) t = a; a = b; b = t; /* vastaisi loogisesti rakennetta: */ if ( a > b ) t = a; a = b; b = t;Koodia voidaan kuitenkin usein lyhentää kirjoittamalla asioita samalle riville:
if ( a > b ) { t = a; a = b; b = t; } /* tai joskus jopa */ if ( a > b ) { t = a; a = b; b = t; }Niin kauan kuin todella hallitsee asian, voi olla helpointa laittaa aina if- lauseen ainoakin suoritettava lause lausesulkuihin
if ( a > b ) { vaihda(&a,&b); }Tästä on se etu, että myöhemmin monimutkaisten makrojen kanssa ei tule ongelmia, sekä se, että nyt if- lauseen suoritettaviksi lauseiksi on helppo lisätä uusia lauseita. Mikäli sulkuja ei olisi, täytyisi toisen lauseen lisäyksen yhteydessä muistaa lisätä myös sulut (tosin eihän hyvin suunniteltua ohjelmaa tarvinnut enää jälkeenpäin paikata?).
a = 4; if ( a ) ...
== yhtäsuuruus != erisuuruus < pienempi kuin <= pienempi tai yhtä kuin > suurempi kuin >= suurempi tai yhtä kuinEsimerkkejä vertailuoperaattoreiden käytöstä:
if ( a < 5 ) printf("a alle viisi!\n); if ( a > 5 ) printf("a yli viisi!\n); if ( a == 5 ) printf("a tasan viisi!\n); if ( a != 5 ) printf("a ei ole viisi!\n);
/* Seuraava tulostaa vain jos a == 5 */ if ( a == 5 ) printf("a on viisi!\n"); /* Seuraava sijoittaa aina a = 5 ja tulostaa AINA! */ if ( a = 5 ) printf("a:ksi tulee AINA 5!\n");
Sijoitus a=5 on myös lauseke, joka palauttaa arvon 5. Siis sijoitus kelpaa tästä syystä vallan hyvin loogiseksi lausekkeeksi. Onneksi useat C- kääntäjät osaavat varoittaa tästä katalasta virhemahdollisuudesta.
Joskus ominaisuutta voidaan tarkoituksella käyttää hyväksikin. Esimerkiksi halutaan sijoittaa AINA a=b ja sitten suorittaa jokin lause, mikäli b!=0. Tämä voitaisiin kirjoittaa useilla eri tavoilla:
/*1*/ a = b; if ( b ) printf("b ei ole nolla!\n"); /*2*/ a = b; if ( b != 0 ) printf("b ei ole nolla!\n"); /*3*/ if ( a = b ) printf("b ei ole nolla!\n"); /*4*/ if ( (a=b) != 0 ) printf("b ei ole nolla!\n");Edellisistä tapa 3 on C- mäisin, mutta kääntäjä saattaa varoittaa siitä (ja tämä varoitus kannattaa ottaa todesta). Jotta C- mäinen tapa voitaisiin säilyttää, voidaan käyttää tapaa 4 jolloin varoitusta ei tule, mutta generoidaan aivan vastaava koodi. Oleellista on, että sijoitus on suluissa (muuten tulisi sijoitus a =(b!=0) ). Mikäli asian toimimisesta on pieninkin epäilys kannattaa käyttää tapaa 1 tai 2!
&& ja || tai ! muuttaa ehdon arvon päinvastaiseksi (eli 0- >1, <>0 - >0)Mikäli yhdistettävät ehdot koostuvat esimerkiksi vertailuoperaattoreiden käytöstä, kannattaa ehtoja sulkea sulkuihin, jottei seuraa turhia epäselvyyksiä.
if ( ( rahaa > 50 ) && ( kello < 19 ) ) printf("Mennään elokuviin!\n); if ( ( rahaa < 50 ) || ( kello >3 ) ) printf("Ei kannata mennä kapakkaan!\n"); if ( ( 8 <= kello ) && ( kello <= 16 ) ) printf("Pitäisi olla töissä!\n"); if ( ( !rahaa ) || ( sademaara < 10 ) ) printf("Kävele!\n");Usein tulee vastaan tilanne, jossa pitäisi testata on luku jollakin tietyllä välillä. Esimerkiksi onko
1900 <= vuosi <= 1999palauttaisi C- kielisenä lauseena aina 1. Miksikö? Koska lause jäsentyy
( 1900 <= vuosi ) <= 1999 0 tai 1 <= 1999 eli aina 1Oikea tapa kirjoittaa väli olisi:
if ( ( 1900 <= vuosi ) && ( vuosi <= 1999 ) ) ...Huomattakoon edellä miten väliä korostettiin kirjoittamalla välin päätepisteet lauseen laidoille.
C- kielen sidontajärjestyksen ansiosta lause toimisi myös ilman sisimpiä sulkuja, mutta ne kannattaa pitää mukana varmuuden vuoksi. Vertailtavat kannattaa kirjoittaa nimenomaan tähän järjestykseen, koska tällöin vertailu muistuttaa eniten alkuperäistä väliämme!
Vastaavasti jos arvon halutaan olevan välin ulkopuolella, kannattaa kirjoittaa:
if ( ( vuosi < 1900 ) || ( 1999 < vuosi ) ) ...Tällöin epäyhtälöiden suuntaa ei joudu koskaan miettimään, vaan arvot ovat aina siinä järjestyksessä kuin lukusuorallakin:
1900 vuosi 1999 1900<=vuosi && vuosi <=1999 -----------o==============o-------------------- vuosi 1900 1999 vuosi vuosi<1900 || 1999 <vuosi ===========o--------------o====================
Siis: Loogisen lausekkeen evaluoiminen lopetetaan heti kun ehdon arvo selviää (boolean expression shortcut).
Esimerkiksi:
if ( a || ( (b=c)==0 ) ) printf("Kukkuu\n");Tai- operaattorin (||) oikealla puolella oleva sijoitus suoritetaan vain mikäli a==0:
a
|
b
|
c
|
sij.suor
|
tulostetaan
|
||
0 0 5
5
|
? ? ?
?
|
0 3 0
3
|
kyllä kyllä ei
ei
|
kyllä ei kyllä
kyllä
|
while ( f && f >> luku ) summa += luku;Tällöin lopussa olevaa tiedostoa ei enää lueta, koska AND- operaation (&&) arvo voidaan päättää epätodeksi heti ensimmäisestä osalauseesta. Tosin edellinen silmukka toimii myös muodossa
while ( f >> luku ) summa += luku;
Loogisia operaattoreita &&,|| ja! ei pidä sotkea vastaaviin bittitason operaattoreihin:
& bittitason AND | bittitason OR ^ bittitason XOR ~ bittitason NOT << rullaus vasemmalle, 0 sisään oikealta >> rullaus oikealle, 0 sisään vasemmalta (unsigned int ja int >=0) , voi tulla 0 tai 1 sisään vasemmalta (int joka <0) (laiteriippuva, esim. Turbo C:ssä tulee 1).Bittitason operaattoreita voidaan käyttää vain kokonaisluvuiksi muuttuviin operandeihin.
Operaattoreiden toimintaa voidaan kuvata seuraavasti. Olkoon meillä sijoitukset a=5; b=14;. Kuvitellaan kokonaisluvut tilapäisesti 8 bitin mittaisiksi (oikeasti yleensä 16 tai 32 bittiä):
|
Binaarisena
|
desim.
|
|
a
|
0000 0101
|
5
|
|
b
|
0000 1110
|
14
|
|
a
& b
|
0000 0100
|
4
|
|
a
| b
|
0000 1111
|
15
|
|
a
^ b
|
0000 1011
|
11
|
|
~a
|
1111 1010
|
-6
|
|
a<<2
|
0001 0100
|
20
|
|
b>>3
|
0000 0001
|
1
|
|
a
&& b
|
0000 0001
|
1
|
|
a
|| b
|
0000 0001
|
1
|
|
!a
|
0000 0000
|
0
|
{ /* binoper.c */ int a=5; b=2; if ( a&&b ) printf("On ne!\n"); if ( a&b ) printf("Ei ne ookkaan!\n"); if ( a ) printf("a on!\n"); if ( ~b ) printf("b ehkä on!\n"); if ( !b ) printf("b ei ole!\n"); }
if ( ehto ) ylause1; else ylause2;Jälleen, mikäli jommassa kummassa osassa tarvitaan useampia lauseita, suljetaan lausejoukko lausesuluilla. Tosin kannattaa taas harkita lausesulkujen käyttöä aina myös yhdenkin lauseen tapauksessa.
/* Samalle riville: */ if ( a < 5 ) printf("a alle viisi!\n"); else printf("a vähintään viisi!\n"); /* Eri riville: */ if ( a < 5 ) printf("a alle viisi!\n"); else printf("a vähintään viisi!\n"); /* Lausesulkujen käyttö: */ if ( a < 5 ) { printf("a alle viisi!\n"); } else { printf("a vähintään viisi!\n"); } /* Seuraavaa tyyliä käytetään myös usein: */ if ( a < 5 ) { printf("a alle viisi!\n"); } else { printf("a vähintään viisi!\n"); }
Voisimme muuttaa tehtävän määrittelyä siten, että kumpikin juuri pitää palauttaa ja funktion nimessä palautetaan tieto siitä, tuliko ratkaisussa virhe, eli jollei juuret olekaan reaalisia.
if ( a != 0 ) { D = b*b - 4*a*c; if ( D > 0 ) { ... } else { ... } } else { ... }Tosin yhtälö pystytään mahdollisesti ratkaisemaan myös kun a==0. Tällöin tehtävä jakautuu useisiin eri tilanteisiin kertoimien a,b ja c eri kombinaatioiden mukaan:
|
|
|
|
|
juuret
|
|
|
||
a
|
b
|
c
|
D
|
yhtälön
muoto
|
reaalisia
|
x1
|
x2
|
||
0
|
0
|
0
|
?
|
0 = 0
|
juu
|
0
|
0
|
||
0
|
0
|
c
|
?
|
c = 0
|
ei
|
0
|
0
|
||
0
|
b
|
?
|
?
|
bx - c = 0
|
juu
|
-c/b
|
-c/b
|
||
a
|
?
|
?
|
>=0
|
ax2 + bx + c = 0
|
juu
|
(-b-SD)/2a
|
(-b+SD)/2a
|
||
a
|
?
|
?
|
<0
|
- " -
|
ei
|
|
|
Algoritmiksi kirjoitettuna tästä seuraisi:
1. Jos a=0, niin Jos b=0 Jos c=0 yhtälö on muotoa 0=0 joka on aina tosi palautetaan vaikkapa x1=x2 =0 muuten (eli c<>0) yhtälö on muotoa c=0 joka on aina epätosi, palautetaan virhe muuten (eli b<>0) yhtälö on muotoa bx=c joten voidaan palauttaa vaikkapa x1=x1=- c/b 2. Jos a<>0, niin Jos D>=0 kyseessä aito 2. asteen yhtälö ja käytetään ratkaisukaavaa muuten (eli D<0) ovat juuret imaginaarisiaFunktio ja sen testiohjelma voisi olla esimerkiksi seuraavanlainen:
#include <iostream.h> #include <math.h> int ratkaise_2_asteen_yhtalo(double &x1, double &x2, double a, double b, double c) { double D,SD; x1 = x2 = 0; if ( a == 0 ) { /* bx + c = 0 */ if ( b == 0 ) { /* c = 0 */ if ( c == 0 ) { /* 0 = 0 */ return 0; /* id. tosi */ } /* c==0 */ else { /* c!=0 */ /* 0 != c = 0 */ return 1; /* Aina epät. */ } /* c!=0 */ } /* b==0 */ else { /* b!=0 */ /* bx + c = 0 */ x1 = x2 = - c/b; return 0; } /* b!=0 */ } /* a==0 */ else { /* a!= 0 */ /* axx + bx + c = 0 */ D = b*b - 4*a*c; if ( D >= 0 ) { /* Reaaliset juuret */ SD = sqrt(D); x1 = (- b- SD)/(2*a); x2 = (- b+SD)/(2*a); return 0; } /* D>=0 */ else { /* Imag. juuret */ return 1; } /* D<0 */ } /* a!=0 */ } double P2(double x, double a, double b, double c) { return (a*x*x + b*x + c); } int main(void) { double a,b,c,x1,x2; do { cout << "Anna 2. asteen yhtälön a b c >"; cin >> a >> b >> c; if ( ratkaise_2_asteen_yhtalo(x1,x2,a,b,c) ) { cout << "Yhtälöllä ei ole reaalisia juuria!" << endl; } else { cout << "1. ratkaisu on " << x1 << ". Arvoksi tulee tällöin " << P2(x1,a,b,c) << endl; cout << "2. ratkaisu on " << x2 << ". Arvoksi tulee tällöin " << P2(x2,a,b,c) << endl; } } while (a>0); return 0; }Edellinen funktio on äärimmäinen esimerkki sisäkkäisistä if- lauseista. Jälkeenpäin sen luettavuus on erittäin heikko ja myös kirjoittaminen hieman epävarmaa. Parempi kokonaisuus saataisiin lohkomalla tehtävää pienempiin osasiin aliohjelmien tai makrojen avulla.
Sisäkkäisten if- lauseiden kirjoittamista voidaan helpottaa kirjoittamalla niitä sisenevästi, eli aloittamalla ensin tekstistä:
if ( a == 0 ) { /* bx + c = 0 */ } /* a==0 */ else { /* axx + bx + c = 0 */ D = b*b - 4*a*c; } /* a!=0 */Sitten täydennetään vastaavalla ajatuksella sekä if- osan että else- osan toiminta.
Jos funktiosta karsitaan kaikki ylimääräinen (kommentit ja ylimääräiset lausesulut) pois, saamme seuraavan näköisen kokonaisuuden:
int ratkaise_2_asteen_yhtalo(double &x1, double &x2, double a, double b, double c) { double D,SD; x1 = x2 = 0; if ( a == 0 ) if ( b == 0 ) { if ( c == 0 ) return 0; else return 1; } else { x1 = x2 = - c/b; return 0; } else { D = b*b - 4*a*c; if ( D >= 0 ) { SD = sqrt(D); x1 = (- b- SD)/(2*a); x2 = (- b+SD)/(2*a); return 0; } else return 1; } }Joskus kannattaa harkita olisiko luettavuuden kannalta paras esitystapa sellainen, että käsitellään "normaaleimmat" tapaukset ensin:
int ratkaise_2_asteen_yhtalo(double &x1, double &x2, double a, double b, double c) { double D,SD; x1 = x2 = 0; if ( a != 0 ) { D = b*b - 4*a*c; if ( D >= 0 ) { SD = sqrt(D); x1 = (- b- SD)/(2*a); x2 = (- b+SD)/(2*a); return 0; } else return 1; } else /* a==0 */ if ( b != 0 ) { x1 = x2 = - c/b; return 0; } else { /* a==0, b==0 */ if ( c == 0 ) return 0; else return 1; } }Usein aliohjelman return- lauseen ansiosta else osat voidaan jättää poiskin:
int ratkaise_2_asteen_yhtalo(double &x1, double &x2, double a, double b, double c) { double D,SD; x1 = x2 = 0; if ( a == 0 ) { if ( b == 0 ) { if ( c == 0 ) return 0; return 1; } x1 = x2 = - c/b; return 0; } D = b*b - 4*a*c; if ( D < 0 ) return 1; SD = sqrt(D); x1 = (- b- SD)/(2*a); x2 = (- b+SD)/(2*a); return 0; }Edellä oli useita eri ratkaisuja saman ongelman käsittelemiseksi. Liika kommenttien määrä saattaa myös sekoittaa luettavuutta kuten 1. esimerkissä. Toisaalta liian vähillä kommenteilla ei ehkä kirjoittaja itsekään muista jälkeenpäin mitä tehtiin ja miten. Jokainen valitkoon edelläolevista itselleen sopivimman kultaisen keskitien.
Huomattakoon vielä lopuksi, että rakenne
if ( c == 0 ) return 0; else return 1;voitaisiin korvata rakenteella
return ( c != 0 );
if (ehto1) lause1; else if (ehto2) lause2; else if (ehto3) lause3; else lause4;jossain mallissa sisennetäänkin ylläkuvatulla tavalla, on ajatus useimmiten lähempänä seuraavaa sisennystä:
double postimaksu(double paino) /* Palautetaan kirjemaksun suuruus. 0 tarkoittaa pakettia */ { if ( paino < 50 ) return 2.10; else if ( paino < 100 ) return 3.50; else if ( paino < 250 ) return 5.50; else if ( paino < 500 ) return 10.00; else if ( paino < 1000 ) return 15.00; else return 0.00; }Sovimme siis, että rakenne onkin muotoa:
if ( ehto1 ) lause1 else if ( ehto2 ) lause2 else if ( ehto3 ) lause3 else lause4
if (a<5) /*1*/ a=1; b=2; c=3; b=3; a=6;
c=7;
|
if
(a<0) a=3; else /*5*/ a=1; b=2; c=3; if (a>2) b=3; a=6;
c=7;
|
||
/*2*/ a=1; b=2; c=3;
if (a<5) b=3; a=6; c=7;
|
/*6*/ a=1; b=2; c=3; if (a<- 5) if (a<0) a=6;
else a=2; c=7;
|
||
/*3*/
a=1; b=2; c=3; if (a<5) {b=3; a=6;}
c=7;
|
/*7*/ a=1; b=2; c=3; if (a<- 5) b=3; if (a<5) a=6;
else a=2; c=7;
|
||
/*4*/
a=1; b=2; c=3; if (a<5)
b=3; else { a=6; c=7; }
|
/*8*/ a=1; b=2; c=3; if (a<0) a=3; else; if (a>2) b=3; a=6;
c=7;
|
#include <stdio.h> /****************************************************************************/ int pienin_jakaja(int luku) /* Palautetaan luvun pienin jakaja (alkuluvulle 1). Algoritmi: Negatiivisesta luvusta otetaan itseisarvo. Kokeillaan jokaista pienempää jakajaa 2,3,5,7 jne, kunnes jako menee tasan tai jakaja on liian iso ollakseen luvun jakaja. */ { int jakojaannos,jakaja=2,kasvatus=1; if ( luku < 0 ) luku = - luku; if ( luku == 2 ) return 1; do { jakojaannos = luku % jakaja; if ( jakojaannos == 0 ) return jakaja; jakaja += kasvatus; kasvatus = 2; } while ( jakaja < luku/2 ); return 1; } /****************************************************************************/ int main(void) { int luku,jakaja; printf("Tutkin onko luku alkuluku. Anna luku >"); scanf("%d",&luku); jakaja = pienin_jakaja(luku); if ( jakaja == 1 ) printf("Luku on alkuluku.\n"); else printf("Luku on jaollinen esimerkiksi luvulla %d.\n",jakaja); return 0; }Käytimme tässä silmukkaa:
do lause while (ehto);Koska esimerkin silmukassa oli useita suoritettavia lauseita, oli lauseet suljettu lausesuluilla. Jälleen voi olla hyvä tapa käyttää AINA lausesulkuja.
Huomautus! Silmukoiden kanssa on syytä olla tarkkana sekä 1. kierroksen että viimeisen kierroksen kanssa. Myös silmukan lopetusehdon on syytä muuttua silmukan suorituksen aikana.
Eräs tyypillinen esimerkki do- while silmukan käytöstä olisi seuraava:
#include <stdio.h> int main(void) { int luku; do { printf("Anna luku väliltä [0- 20]>"); fflush(stdin); /* Standardin mukaan ei määritelty! */ } while ( ( scanf("%d",&luku) < 1 ) || ( luku < 0 ) || ( 20 < luku ) ); printf("Annoit luvun %d\n",luku); return 0; }
while ( ehto ) lauseMuutamme samalla algoritmia siten, että 2:lla jaolliset käsitellään erikoistapauksena. Näin pääsemme eroon "inhottavasta" kasvatus- muuttujasta.
int pienin_jakaja(int luku) { int jakaja=3; if ( luku < 0 ) luku = - luku; if ( luku == 2 ) return 1; if ( (luku % 2) == 0 ) return 2; while ( jakaja < luku /2 ) { if ( (luku % jakaja) == 0 ) return jakaja; jakaja += 2; } return 1; }
Tyypillisesti for- silmukkaa käytetään silloin, kun silmukan kierrosten lukumäärä on ennalta tunnettu:
/* Lasketaan yhteen luvut 1..ylaraja */ int valin_summa(int ylaraja) { int i,summa=0; for (i=1; i<=ylaraja; i++) summa += i; return summa; }
Sen ansiosta, että myös sijoitus palauttaa arvon, pystyimme tekemään mm seuraavia temppuja:
if ( (b=a) != 0 ) ... /* Suoritetaan jos a!=0 */ a = b = c = 0;Sijoitus monelle muuttujalle yhtäaikaa onnistuu, koska sijoitus jäsentyy seuraavasti:
1. a = ( b = (c = 0) ); - sijoitus c=0 palauttaa arvon 0 2. a = ( b = 0 ); - sijoitus b=0 palauttaa arvon 0 3. a = 0;
lyhenne
|
tavallinen
sijoitus
|
summa
+= i;
i++
|
summa
= summa + i;
i = i + 1;
|
+ - * / % << >> ^ & |Esimerkiksi luvun kertominen ja jakaminen 10:llä voitaisiin suorittaa:
luku *= 10; luku /= 10;Siis muuttuja O= operandi voidaan ajatella korvattavaksi seuraavasti:
0. laita sulut operandin ympärille muuttuja O= (operandi) 1. kirjoita muuttujan nimi kahteen kertaan muuttuja muuttuja O= (operandi) 2. siirrä = - merkki muuttujien nimien väliin muuttuja = muuttuja O (operandi)
int a=10,b=3,c=5; a %= b; b *= a+c; b >>= 2;
Nämä operaattorit lisäävät tai vähentävät operandin arvoa yhdellä. Operandin tyypin tulee olla numeerinen tai osoitin.
Operandeista on kaksi eri versiota: esilisäys ja jälkilisäys.
lyhenne
|
vastaa
lauseita
|
a
= i++; a = i-- a = ++i;
a = --i;
|
a
= i; i = i+1; a = i; i = i-1; i = i+1; a = i;
i = i-1; a = i;
|
#include <stdio.h> int main(void) { double i=1.0,a; a = i++/i++; printf("a = %5.2lf, i = %5.2lf\n",a,i); return 0; }Ohjelma saattaa C- kääntäjän toteutuksesta riippuen tulostaa mitä tahansa seuraavista a:n ja i:in kombinaatioista:
a: 0.5 1.0 2.0 i: 2.0 3.0Aluksi ++ - operaattoria kannattaa ehkä käyttää vain yksinäisenä lauseena lisäämään (tai vähentämään) muuttujan arvoa.
i++;Lisäysoperaattoria EI PIDÄ käyttää seuraavissa tapauksissa:
a = ++i + i*i; b = MACRO(i- - ); c = fun(++i,a,2*i);
C- kielen for- silmukka on kuitenkin yleisempi:
/* 1. 2. 5. 4. 7. 3. 6. */ for (alustus_lauseet; suoritus_ehto; kasvatus_lauseet) lause;for- silmukka vastaa melkein while- silmukkaa (ero tulee continue- lauseen käyttäytymisessä):
alustus_lauseet; /* 1. */ while ( suoritus_ehto ) { /* 2. 5. */ lause; /* 3. 6. */ kasvatus_lauseet; /* 4. 7. */ }Mikäli esimerkiksi alustuslauseita on useita, erotetaan ne toisistaan pilkulla:
/* Lasketaan yhteen luvut 1..ylaraja */ int valin_summa_2(int ylaraja) { int i,summa; for (summa=0, i=1; i<=ylaraja; i++) summa += i; return summa; }Erittäin C:mäinen tapa tehdä yhteenlasku olisi:
int valin_summa_3(int i) { int s; for (s=0; i; s += i- - ); return s; }Tämä viimeinen esimerkki on juuri niitä C- hakkereiden suosikkeja, joita ehkä kannattaa osin vältellä.
#include <stdio.h> int main(void) { int summa=0,luku; printf("Anna lukuja. Summaan niitä kunnes annat 0 tai summa > 20\n"); while ( summa <= 20 ) { printf("Anna luku>"); scanf("%d",&luku); if ( luku == 0 ) break; summa += luku; } printf("Lukujen summa on %d.\n",summa); return 0; }Jos edellä olisi ollut alustus luku=1, olisi tietenkin while - silmukan ehto voitu kirjoittaa muodossa
while ( ( luku != 0 ) && ( summa <= 20 ) ) {mutta aina ei voida break - lausetta korvata näin yksinkertaisesti. Esimerkiksi seuraava olisi jo hankalampi muuttaa:
while ( summa <= 20 ) { printf("Anna luku>"); if ( !scanf("%d",&luku ) break; if ( luku == 0 ) break; summa += luku; }break - lauseen vika on lähinnä siinä, ettei siitä suoraan nähdä sisäkkäisten silmukoiden tapauksessa sitä, mihin saakka suoritus katkeaa. Epäselvissä tapauksissa silmukan katkaisu voidaan hoitaa goto - lauseella.
Silmukka voidaan katkaista tietenkin myös muuttamalla silmukan lopetusehtoon vaikuttavia muuttujia. Varsinkin for- lauseen tapauksessa silmukan indeksin arvon muuttaminen muualla kuin kasvatus- lauseessa on todella väkivaltaista ja rumaa, eikä tällaista pidä mennä tekemään.
#include <stdio.h> int main(void) { int alku= - 5, loppu=5,i; double inv_i; printf("Tulostan lukujen %d - %d käänteisluvut\n",alku,loppu); for (i = alku; i<=loppu; i++ ) { if ( i == 0 ) continue; inv_i = 1.0/i; printf("%3d:n käänteisluku on %5.2lf.\n",i,inv_i); } return 0; }
Seuraavissa tapauksissa goto- lause voidaan hyväksyä:
1. silmukan suorituksen katkaisu 2. aliohjelman loppuun hyppääminen (silloin kun returnia ei lopetustoimenpiteiden vuoksi voida käyttää) 3. jos strukturoitu rakenne on selvästi monimutkaisempiMikäli yhteen aliohjelmaan tulee useita goto - lauseita, on rakenne selvästikin suunniteltu väärin, ja se on mietittävä uudelleen.
goto - lauseen syntaksiin kuuluu paikan nimi, johon hypätään. Tämä nimiö täytyy esitellä ohjelmassa laittamalla se tarvittavaan kohtaan:
#include <stdio.h> int main(void) { int summa=0,luku; printf("Anna lukuja. Summaan niitä kunnes annat 0 tai summa > 20\n"); while (summa <= 20) { printf("Anna luku>"); if ( !scanf("%d",&luku) ) goto luvut_loppu; if ( luku == 0 ) goto luvut_loppu; summa += luku; } luvut_loppu: printf("Lukujen summa on %d.\n",summa); return 0; }Siis break ja continue ovat "piilotettuja" goto- lauseita. Mikäli break tai continue pitäisi saada toimimaan 2 tasoa ulospäin, on pakko käyttää goto- lausetta.
Huom! Ensisijaisesti pyri välttämään goto- lauseita!
int paavalinta(cKerho &kerho) { char nappain; while (1) { paamenu(kerho); // Huom päämenun muuttunut parametrilista nappain = odota_nappain("?012345",0,MERKKI_ISOKSI); switch (nappain) { case '?': avustus(nappain); break; case '0': return 0; case '1': lisaa_uusi_jasen(nappain); break; case '2': etsi_jasenen_tiedot(nappain); break; case '3': tulosteet(nappain); break; case '4': tietojen_korjailu(nappain); break; case '5': paivita_jasenmaksuja(nappain); break; default : cout << "Näin ei voi käydä!" << endl; return 1; } } }switch - lauseessa case osien lopuksi break on yleensä välttämätön. break estää suorittamasta seuraavia rivejä.
Joskus harvoin breakin puuttumista voidaan käyttää hyväksi, mutta tällöin pitää olla todella tarkkana:
switch (operaatio) { case 5: /* Operaatio 5 tekee saman kuin 4 */ case 4: x *= 2; break; /* 4 laskee x=2*x */ case 3: x += 2; /* 3 laskee x=x+4 */ case 2: x++; /* 2 laskee x=x+2 */ case 1: x++; break; /* 1 laskee x=x+1 */ default: x=0; break; /* Muut nollaavat x:än */ }
Lause default suoritetaan jos mikään case- osista ei ole täsmännyt (tai tietysti jos jokin break puuttuu). default- lauseen ei tarvitse olla viimeisenä, mutta tällöin vaaditaan taitavaa breakin käyttöä, siis paras pitää default viimeisenä!
Yleistä switch- lausetta ei voi korvata joukolla if- lauseita käyttämättä goto- lausetta. Mikäli kuitenkin jokaisen case rakenteen perässä on break, voidaan switch- korvata sisäkkäisillä if- else - rakenteilla.
switch (operaatio) { /* VÄÄRIN: */ case 4 || 5: x *= 2; break; /* 5 tai 4 laskee x=2*x */ case 3: x += 2; /* 3 laskee x=x+4 */ case 2: x++; /* 2 laskee x=x+2 */ default: x=0; break; /* Muut nollaavat x:än */ }Kääntäjä ei tästä varoita, koska kaikki on aivan kieliopin mukaista. 4 || 5 on kahden loogisen lausekkeen OR eli 1 || 1 eli 1. Siis
case 4 || 5: on sama kuin case 1:Jos esimerkistämme ei olisi poistettu lausetta case 1:, olisi kääntäjä varoittanut koska 1 olisi esiintynyt kahdesti.
Joskus kuitenkin C- kielessä tehdään tarkoituksella "ikuisia" - silmukoita:
for (;;) { ... if (lopetus_ehto) break; ... } while (1) { ... if (lopetus_ehto) break; ... } do { ... if (lopetus_ehto) break; ... } while (1);Näissä kahdessa ensimmäisessä korostuu silmukan ikuisuus. Viimeinen ei ole hyvä vaihtoehto.
Tällaiset ikuiset silmukat ovat hyväksyttävissä silloin, kun silmukan lopetusehto on luonnollisesti keskellä silmukkaa. Usein kuitenkin lauseiden uudelleen järjestelyllä lopetusehto voidaan sijoittaa silmukan alkuun tai loppuun, jolloin tavallinen while- , do- while - tai for - silmukka kelpaa.