Ihvilläpä ihmettele
silmukalla suorittele
lopetukset laskeskele
virityksii vierastele.
Alusta kun ehto jääpi
siit silmukka iänikuinen
aina suru ei surkeen suuri
joutaapa avuksi tääkin.
Katkoo saapi keskeltäkin
jatkaa vaikka muualtakin
paluu kelpo keino myöskin
kunhan kaikki katseltuna.
Mitä tässä luvussa käsitellään?
· if-else –lause
· loogiset operaattorit: &&, || ja !
· bittitason operaattorit: &,|,^ ja ~
· silmukat while, do-while ja for
· silmukan "katkaisu" break, continue, goto
· sijoituslauseet: = += -= jne.
· valintalause switch
Syntaksi:
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 false tai true
esim a < 5
( 5 < a ) && ( a < 10 )
!(a == 0) // 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
switch 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
}
Luvun esimerkkikoodit:
Ohjelma jossa ei ole minkäänlaista valinnaisuutta tai silmukoita on varsin harvinainen. Kertaamme seuraavassa Java–kielen tarjoamat mahdollisuudet suoritusjärjestyksen ohjaamiseen. Samalla näemme kuinka suomenkielisen algoritmin kääntäminen ohjelmointikielelle on varsin mekaanista puuhaa.
Mikäli meillä on kaksi lukua, jotka pitäisi olla suuruusjärjestyksessä, voisimme hoitaa järjestämisen seuraavalla algoritmilla:
1. Jos luvut väärässä järjestyksessä,
niin vaihda ne keskenään
Tä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.
Olkoon meillä aliohjelma nimeltään tulosta, joka parametrina viedyn luvun:
if ( a > b ) tulosta(a);
Jos esimerkiksi luvut pitäisi vaihtaa keskenään, täytyisi meidän voida suorittaa useita lauseita muuttujien vaihtamiseksi. Java–kielessä voidaan lausesuluilla kasata joukko lauseita yhdeksi lauseeksi (lohko, koottu lause, block):
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 ) {
tulosta(a);
}
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?).
Tehtävä 10.1 vaihda
Esitä pöytätestin avulla miksei vaihtaminen onnistu pelkästään lauseilla:
a = b; b = a;
Tehtävä 10.2 abs
Kirjoita funktio
int itseisarvo(int i),
joka palauttaa i:n itseisarvon (negat. muutet. posit.).
Tehtävä 10.3 jarjesta2
Kirjoita aliohjelma
void tulosta2(int a, int b),
joka tulostaa luvut suuruusjärjestyksessä .
Tehtävä 10.4 maksimi ja minimi
Kirjoita funktio
int maksimi(int a, int b),
joka palauttaa suuremman kahdesta luvusta.
Kirjoita vastaava funktio minimi.
Java–kielessä vain boolean-arvoiset lausekeet käsitellään loogisina lausekkeina. Arvo false on epätosi ja true on tosi.
a = 4;
if ( a == 4 ) ...
boolean samat;
samat = ( a == 4 );
if ( samat ) ...
Vertailuoperaattorin käyttö muodostaa loogisen lausekkeen, jonka arvo on 0 tai 1. Vertailuoperaattoreita ovat:
== yhtäsuuruus
!= erisuuruus
< pienempi kuin
<= pienempi tai yhtä kuin
> suurempi kuin
>= suurempi tai yhtä kuin
Esimerkkejä vertailuoperaattoreiden käytöstä:
if ( a < 5 ) System.out.println("a alle viisi!");
if ( a > 5 ) System.out.println("a yli viisi!");
if ( a == 5 ) System.out.println("a tasan viisi!");
if ( a != 5 ) System.out.println("a ei ole viisi!");
Yhtäsuuruutta verrataan == operaattorilla, EI sijoituksella =. Tämä on eräs tavallisimpia aloittelevan (ja kokeneenkin) C–ohjelmoijan virheitä:
/* Seuraava tulostaa vain jos a == 5 */
if ( a == 5 ) tulosta("a on viisi!\n"); /* Kääntyy Javassa ja C:ssä */
/* Seuraava sijoittaa aina a = 5 ja tulostaa AINA! */ L
if ( a = 5 ) printf("a:ksi tulee AINA 5!\n"); /* Kääntyy vain C:ssä */
Sijoitus a=5 on myös lauseke, joka palauttaa arvon 5. Siis sijoitus kelpaa tästä syystä vallan hyvin loogiseksi lausekkeeksi C-kielessä. Onneksi Javassa tämä sijoituksen tuloksena synytynyt lausekkeen kokonaislukuarvo EI kelpaa boolean-arvoksi, joten kääntäjä ei hyväksy sijoitusta vahingossa yhtäsuuruuden vertailun tilalle..
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:
int a,b=5;
/*1*/ // a = b; if ( b ) tulosta("b ei ole nolla!");
/*2*/ a = b; if ( b != 0 ) tulosta("b ei ole nolla!");
/*3*/ // if ( a = b ) tulosta("b ei ole nolla!");
/*4*/ if ( (a=b) != 0 ) tulosta("b ei ole nolla!");
Edellisistä tapa 3 on C–mäisin, mutta Java-kääntäjä ei onneksi hyväksy sitä. Jotta C–mäinen tapa voitaisiin säilyttää, voidaan käyttää tapaa 4 jonka kääntäjä hyväksyy. Oleellista on, että sijoitus on suluissa (muuten tulisi sijoitus a =(b!=0) ). Mikäli asian toimimisesta on pieninkin epäilys kannattaa käyttää tapaa 2!
Tyypillinen esimerkki sijoituksesta ja testauksesta samalla on vaikkapa tiedoston lukeminen:
while ( ( rivi = f.readLine() ) != null ) { // jos sijoitus palauttaa null,
// on tiedosto loppu
... käsitellään tiedoston riviä
}
Jos edellisen esimerkin tiedoston lukemisessa ei käytettäisi sijoitusta ja testiä samalla, pitäisi tämä kirjoittaa muotoon:
while ( true ) {
rivi = f.readLine();
if ( rivi == null ) break;
... käsitellään tiedoston riviä
}
Loogisia lauseita voidaan yhdistää loogisten operaatioiden avulla. Tietysti lauseita voidaan yhdistää myös normaaleilla operaatioilla (+,–,*,/), mutta tämä ei ole oikein hyvien tapojen mukaista.
&& ja
|| tai
! muuttaa ehdon arvon päinvastaiseksi (eli false–>true, true->false)
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 ) ) tulosta("Mennään elokuviin!");
if ( ( rahaa < 50 ) || ( kello >3 ) ) tulosta("Ei kannata mennä kapakkaan!");
if ( ( 8 <= kello ) && ( kello <= 16 ) ) tulosta("Pitäisi olla töissä!");
if ( ( rahaa == 0 ) || ( sademaara < 10 ) ) tulosta("Kävele!");
Usein tulee vastaan tilanne, jossa pitäisi testata onko luku jollakin tietyllä välillä. Esimerkiksi onko
1900 <= vuosi <= 1999
palauttaisi C-kielisenä lauseena aina 1. Miksikö? Koska lause jäsentyy
( 1900 <= vuosi ) <= 1999
0 tai 1 <= 1999 eli aina 1
Javassa onneksi lauseke ei edes käänny, koska totuusarvoa ja kokonaislukua ei voi verrata keskenään. Oikea tapa kirjoittaa väli olisi:
if ( ( 1900 <= vuosi ) && ( vuosi <= 1999 ) ) ...
Huomattakoon edellä miten väliä korostettiin kirjoittamalla välin päätepisteet lauseen laidoille.
Java–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====================
Loogiset lausekkeet suoritetaan AINA vasemmalta oikealle, kunnes ehdon arvo on selvinnyt.
Siis: Loogisen lausekkeen evaluoiminen lopetetaan heti kun ehdon arvo selviää (boolean expression shortcut).
Esimerkiksi:
if ( a != 0 || ( (b=c)==0 ) ) System.out.println("Kukkuu");
Tai-operaattorin (||) oikealla puolella oleva sijoitus suoritetaan vain mikäli a==0:
b |
c |
|
sij.suor |
tulostetaan |
|
|
0 0 5 5 |
? ? ? ? |
0 3 0 3 |
|
kyllä kyllä ei ei |
kyllä ei kyllä kyllä |
|
Tätä ominaisuutta voidaan käyttää hyväksi esimerkiksi jos on vaara että olion arvo on null:
if ( (jono != null) && jono.equals("kissa") ) tulosta("On kissa");
Tällöin testissä ei turhaan tule null-viittausta koska ehtoa jono.equals ei suoriteta muuta kuin jonon ollessa viite todelliseen olioon.
Ja (&&) ja tai (||) -operaattoreista on myös versiot, joilla aina evaluoidaan (suoritetaan) kaikki lausekkeen osat, vaikka ehdon arvo selviäisi jo aikaisemminkin.
& ja - suorittaa aina lauskekkeen molemmat puolet
| tai - suorittaa aina lausekkeen molemmat puolet
Aikasempaa esimerkkiä mukaellen:
if ( a != 0 | ( (b=c)==0 ) ) System.out.println("Kukkuu");
Tai-operaattorin (|) oikealla puolella oleva sijoitus suoritetaan riippumatta a: n arvosta:
a |
b |
c |
|
sij.suor |
tulostetaan |
|
0 0 5 5 |
? ? ? ? |
0 3 0 3 |
|
kyllä kyllä kyllä kyllä |
kyllä ei kyllä kyllä |
|
Vastaavasti olisi paha virhe kirjoittaa:
if ( (jono != null) & jono.equals("kissa") ) tulosta("On kissa"); L
Yksi C–kielen vahvoista piirteistä erityisesti alemman tason ohjelmoinnissa on mahdollisuus käyttää bittitason operaattoreita.
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ä):
|
Binäärisenä |
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 |
Toimii vain C:ssä |
a || b |
0000 0001 |
1 |
Toimii vain C:ssä |
!a |
0000 0000 |
0 |
Toimii vain C:ssä |
Huomautus! Tyypillinen ohjelmointivirhe on sotkea keskenään loogiset ja bittitason operaattorit. Javassa onneksi kääntäjä tekee tämän vaikeammaksi.
Tehtävä 10.5 Loogiset/bittitason operaattorit
Mitä tulostaa seuraava ohjelman osa.
int a=5, b=2;
if ( a != 0 && b != 0 ) tulosta("On ne!");
if ( (a&b) != 0) tulosta("Ei ne ookkaan!");
if ( a != 0 ) tulosta("a on!");
if ( ~b != 0) tulosta("b ehkä on!");
if ( !(b == 0) ) tulosta("b ei ole!");
Tehtävä 10.6 Luku parilliseksi
Kirjoita funktio parilliseksi, joka palauttaa parametrinään olevan kokonaisluvun pienemmäksi parilliseksi luvuksi "katkaistuna". Eli esim. 3 –> 2. 5 –> 4. 4 –> 4.
if –lauseesta on myös versio, jossa jotakin voidaan tehdä ehdon ollessa epätosi:
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.
if ( a < 5 ) tulosta("a alle viisi!");
else tulosta("a vähintään viisi!");
// Eri riville:
if ( a < 5 )
tulosta("a alle viisi!");
else
tulosta("a vähintään viisi!");
// Lausesulkujen käyttö:
if ( a < 5 ) {
tulosta("a alle viisi!");
}
else {
tulosta("a vähintään viisi!");
}
// Seuraavaa tyyliä käytetään myös usein:
if ( a < 5 ) {
tulosta("a alle viisi!");
} else {
tulosta("a vähintään viisi!");
}
Meillä oli aikaisemmin tehtävänä kirjoittaa funktio, joka palauttaa toisen asteen yhtälön ax2+bx+c=0 toisen juuren. Tällöin oletuksena oli, että a<>0 ja D>=0. Mikäli ratkaisukaavaa sovelletaan sellaisenaan ja a=0 tai D<0, niin tällöin ohjelman suoritus päättyy ajonaikaiseen virheeseen.
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 imaginaarisia
Funktio ja sen testiohjelma voisi olla esimerkiksi seuraavanlainen:
/**
* Ohjelmalla testataan 2. asteen polynomin juurien etsimistä
* @author Vesa Lappalainen
* @version 1.0, 16.02.2003
*/
public class P2_2 {
public static void testi(double a, double b, double c) {
Polynomi2 p = new Polynomi2(a,b,c);
System.out.print("Polynomi: " + p);
if ( p.getReaalijuuria() <= 0 ) {
System.out.println(" Ei yhtään reaalijuuria! ");
return;
}
System.out.print(" juuret: ");
System.out.print("x1 = " + p.getX1() + " => P(x1) = " + p.f(p.getX1()) );
System.out.print(" ja ");
System.out.print("x2 = " + p.getX2() + " => P(x2) = " + p.f(p.getX2()) );
System.out.println();
}
public static void main(String[] args) {
testi(1,2,1);
testi(2,1,0);
testi(1,-2,1);
testi(2,-1,0);
testi(2,1,1);
testi(2,0,0);
testi(0,2,1);
testi(0,0,1);
}
}
/**
* Luokka toisen asteen polynomille ja sen nollakohdille
* @author Vesa Lappalainen
* @version 1.0, 16.02.2003
*/
class Polynomi2 {
private double a,b,c,x1,x2;
private int reaalijuuria;
public Polynomi2(double a, double b, double c) {
this.a = a; this.b = b; this.c = c;
reaalijuuria = ratkaise_2_asteen_yhtalo();
}
private int ratkaise_2_asteen_yhtalo() {
double D,SD;
x1 = x2 = 0;
if ( a==0 ) { /* bx + c = 0 */
if ( b==0 ) { /* c = 0 */
if ( c==0 ) { /* 0 = 0 */
return 1; /* id. tosi */
} /* c==0 */
else { /* c!=0 */ /* 0 != c = 0 */
return 0; /* Aina epät. */
} /* c!=0 */
} /* b==0 */
else { /* b!=0 */ /* bx + c = 0 */
x1 = x2 = -c/b;
return 1;
} /* b!=0 */
} /* a==0 */
else { /* a!=0 */ /* axx + bx + c = 0 */
D = b*b - 4*a*c;
if ( D>=0 ) { /* Reaaliset juuret */
SD = Math.sqrt(D);
x1 = (-b-SD)/(2*a);
x2 = (-b+SD)/(2*a);
return 2;
} /* D>=0 */
else { /* Imag. juuret */
return -1;
} /* D<0 */
} /* a!=0 */
}
public static double P2(double x, double a, double b, double c) {
return (a*x*x + b*x + c);
}
public double f(double x) { return P2(x,a,b,c); }
public double getX1() { return x1; }
public double getX2() { return x2; }
public int getReaalijuuria() { return reaalijuuria; }
public String toString() { return a + "x^2 + " + b + "x + " + c; }
}
Edellinen metodi ratkaise_2_asteen_yhtalo 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:
private int ratkaise_2_asteen_yhtalo() {
double D,SD;
x1 = x2 = 0;
if ( a == 0 )
if ( b == 0 ) {
if ( c == 0 ) return 1;
else return 0;
}
else {
x1 = x2 = -c/b;
return 1;
}
else {
D = b*b - 4*a*c;
if ( D >= 0 ) {
SD = Math.sqrt(D);
x1 = (-b-SD)/(2*a);
x2 = (-b+SD)/(2*a);
return 2;
}
else return 0;
}
}
Joskus kannattaa harkita olisiko luettavuuden kannalta paras esitystapa sellainen, että käsitellään "normaaleimmat" tapaukset ensin:
private int ratkaise_2_asteen_yhtalo() {
double D,SD;
x1 = x2 = 0;
if ( a != 0 ) {
D = b*b - 4*a*c;
if ( D >= 0 ) {
SD = Math.sqrt(D);
x1 = (-b-SD)/(2*a);
x2 = (-b+SD)/(2*a);
return 2;
}
else return -1;
}
else /* a==0 */
if ( b != 0 ) {
x1 = x2 = c/b;
return 1;
}
else { /* a==0, b==0 */
if ( c == 0 ) return 1;
else return 0;
}
}
Usein aliohjelman return–lauseen ansiosta else osat voidaan jättää poiskin:
private int ratkaise_2_asteen_yhtalo() {
double D,SD;
x1 = x2 = 0;
if ( a == 0 ) {
if ( b == 0 ) {
if ( c == 0 ) return 1;
return 0;
}
x1 = x2 = -c/b;
return 1;
}
D = b*b - 4*a*c;
if ( D < 0 ) return -1;
SD = Math.sqrt(D);
x1 = (-b-SD)/(2*a);
x2 = (-b+SD)/(2*a);
return 2;
}
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 true;
else return false;
voitaisiin korvata rakenteella
return ( c != 0 );
Tehtävä 10.7 else –osat pois
Kirjoita ratkaise_2_asteen_yhtalo P2_2n.java ilman else –osia.
Vaikka rakenne
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ä:
static double postimaksu(double paino)
{
if ( paino < 50 ) return 0.60;
else if ( paino < 100 ) return 0.90;
else if ( paino < 250 ) return 1.30;
else if ( paino < 500 ) return 2.10;
else if ( paino < 1000 ) return 3.50;
else if ( paino < 2000 ) return 5.50;
else return 0.00;
}
Sovimme siis, että rakenne onkin muotoa:
if ( ehto1 ) lause1
else if ( ehto2 ) lause2
else if ( ehto3 ) lause3
else lause4
Tehtävä 10.8 elset pois
Voiko aliohjelmasta postimaksu jättää if-lauseiden else-osat pois? Päteekö väittämä yleisesti?
Tehtävä 10.9 Lääni
Kirjoita aliohjelma
void laani(string rekisteri)
joka tulostaa missä läänissä auto on rekisteröity. (Ennen oli Suomessa monta lääniä ja rekisterinumeron 1. kirjain määräsi missä läänissä auto oli rekisteröity).
Kirjaimen yhtäsuuruutta testataan if ( c == 'a' ) ...
Merkkijonon 1. merkki saadaan c = rekisteri[0]; edellyttäen tietysti että rekisteri != ””.
Tehtävä 10.10 if–else
Mitä ovat muuttujien arvot seuraavien ohjelmanpätkien jälkeen (pöytätesti!)?
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; |
|
Sisennä ohjelmanpätkät "asianmukaisesti".
Aikaisemmin olemme tutustuneet erääseen algoritmiin selvittää onko luku alkuluku vai ei. Koska algoritmi on valmis, voimme kirjoittaa vastaavan ohjelman (% –operaattori antaa jakojäännöksen, 10 % 3 == 1 ):
**
* Ohjelmalla testataan onko_alkuluku-aliohjelmaa
* @author Vesa Lappalainen
* @version 1.0, 17.01.2002
*/
public class Alkuluku {
/**
* Aliohjelmalla tutkitaan onko parametrina tuotu
* luku alkuluku vai ei<br>
* Algoritmi: Jaetaan tutkittavaa lukua jakajilla 2,3,5,7...luku/2.
* Jos jokin jako menee tasan, niin ei alkuluku:
* @param luku tutkittava luku
* @return tieto siitä, onko luku alkuluku vai ei
*/
public static String onko_alkuluku(int luku)
{
int jakaja=2, kasvatus=1;
if ( luku == 2 ) return "alkuluku";
do {
int jakojaannos = luku % jakaja;
if ( jakojaannos == 0 )
return "jaollinen";
jakaja += kasvatus;
kasvatus = 2;
} while ( jakaja < luku/2 );
return "alkuluku";
}
public static void main(String[] args) {
String tulos;
tulos = onko_alkuluku(25);
System.out.println(tulos);
tulos = onko_alkuluku(123);
System.out.println(tulos);
tulos = onko_alkuluku(7);
System.out.println(tulos);
}
}
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:
import fi.jyu.mit.ohj2.Syotto;
/**
* Ohjelmalla luetaan luk, kunnes se on halutulla välillä
* @author Vesa Lappalainen
* @version 1.0, 07.02.2003
*/
public class Dowhile {
public static void main(String[] args) {
int luku;
do {
luku = Syotto.kysy("Anna luku väliltä [0-20]",0);
} while ( luku < 0 || 20 < luku );
System.out.println("Annoit luvun " + luku);
}
}
do–while –silmukka suoritetaan aina vähintään 1. kerran. Joskus on tarpeen silmukka, jonka runkoa ei suoriteta yhtään kertaa. Muutamme edellisen esimerkkimme käyttämään while –silmukkaa:
while ( ehto ) lause
Muutamme samalla algoritmia siten, että 2:lla jaolliset käsitellään erikoistapauksena. Näin pääsemme eroon "inhottavasta" kasvatus–muuttujasta.
public static int pienin_jakaja(int luku)
{
int jakaja=3;
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;
}
Eräs C–kielen hienoimmista rakenteista on for–silmukka. Usein C–hakkereiden tavoite on saada kirjoitettua koko ohjelma yhteen for–silmukkaan. Tätä ei tietenkään tarvitse tavoitella, mutta se osoittaa for–silmukan mahdollisuuksia.
Tyypillisesti for–silmukkaa käytetään silloin, kun silmukan kierrosten lukumäärä on ennalta tunnettu:
/**
* Lasketaan yhteen luvut 1..ylaraja
* @param ylaraja summan yläraja
* @return summa
*/
public static int valin_summa(int ylaraja)
{
int i,summa=0;
for (i=1; i<=ylaraja; i++)
summa += i;
return summa;
}
Tehtävä 10.11 valin_summa
Muuta valin_summa –aliohjelmaa siten, että myös alaraja viedään parametrinä. Kirjoita pääohjelma, jolla toiminta voidaan testata.
Käytännössä tällaisia silmukoita ei saa tehdä, koska ongelman ratkaisuun on valmis kaava. Millainen?
Olemme tutustuneet jo Java–kielen "normaaliin" sijoitusoperaattoriin =.
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;
valin_summa aliohjelmassa meillä esiintyi myös kaksi uutta sijoitusoperaattoria, jotka ovat lyhenteitä tavallisille sijoituksille:
lyhenne |
tavallinen sijoitus |
summa += i; i++ |
summa = summa + i; i = i + 1; |
+= sijoituksessa + voidaan korvata millä tahansa operaattoreista:
+ – * / % << >> ^ & |
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)
Tehtävä 10.12 +=
Mitä ovat muuttujien arvot seuraavien sijoitusten jälkeen:
int a=10,b=3,c=5;
a %= b;
b *= a+c;
b >>= 2;
Erittäin tyypillisiä C–operaattoreita ovat ++ ja––.
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; |
Vaikka C–hakkerit rakentavatkin mitä ihmeellisimpiä kokonaisuuksia ++ –operaattorin avulla, kannattaa operaattorin liikaa käyttöä välttää. Esimerkiksi lauseet joissa esiintyy samalla kertaa useampia lisäyksiä samalle muuttujalle, saattavat olla jopa määrittelemättömiä:
/**
* Esimerkki epäselvästä ++-operaattorin käytöstä
* @author Vesa Lappalainen
* @version 1.0, 16.02.2003
*/
public class Plusplus {
public static void main(String[] args) {
double i=1.0,a;
a = i++/i++;
System.out.println("(a = " + a + ", i = " + i);
}
}
Ohjelma saattaa Java–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.0
Aluksi ++ –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ää jos muuttuja johon lisäysoperaattori kohdistuu, esiintyy samassa lausekkeessa useammin kuin kerran.
Kiellettyjä on siis esimerkiksi:
a = ++i + i*i;
ali(i++,i);
Yleensä ohjelmointikielissä for–silmukka on varattu juuri siihen tarkoitukseen, kuin ensimmäinen esimerkkimmekin; tasan tietyn kierrosmäärän tekemiseen.
Java–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:
public static 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:
public static int valin_summa_3(int i) {
int s;
for (s=0; i >= 0; s += i--);
return s;
}
Tämä viimeinen esimerkki on juuri niitä C–hakkereiden suosikkeja, joita ehkä kannattaa osin vältellä.
Tehtävä 10.13 1+2+..+i
Miksi valin_summa_3 laskee yhteen luvut 1..i?
Joskus kesken silmukan tulee vastaan tilanne, jossa silmukan suoritus haluttaisiin keskeyttää. Tällöin voidaan käyttää C–kielen break–lausetta, joka katkaisee sisimmän silmukan suorituksen.
private static void break_testi1() {
int summa=0,luku;
System.out.println("Anna lukuja. Summaan niitä kunnes annat 0 tai summa>20");
do {
luku = Syotto.kysy("Summa on " + summa + ". Anna luku",0);
if ( luku == 0 ) break;
summa += luku;
} while ( summa <= 20 );
System.out.println("Lukujen summa on " + summa);
}
Koska 0:lla lisääminen ei muuta summaa, olisi tietenkin do-while –silmukan ehto voitu kirjoittaa muodossa
do {
luku = Syotto.kysy("Summa on " + summa + ". Anna luku",0);
summa += luku;
} while ( luku != 0 && summa <= 20 );
mutta aina ei voida break –lausetta korvata näin yksinkertaisesti. Perus 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 nimeämällä silmukat ja ilmoittamalla break-lauseessa mikä silmukka katkaistaan:
private static void break_testi3() {
int valisumma, loppusumma = 0,luku;
System.out.println("Anna lukuja.");
System.out.println("Summaan niitä kunnes annat 99.");
System.out.println("Antamalla 0, näet välisumman");
System.out.println("Välisumman näet myös jos välisumma > 20");
laskeloppusummaa: do {
valisumma = 0;
do {
luku = Syotto.kysy("Anna luku",0);
if ( luku == 0 ) break;
if ( luku == 99 ) break laskeloppusummaa;
valisumma += luku;
} while ( luku != 0 && valisumma <= 20 );
System.out.println("Lukujen välisumma on " + valisumma);
loppusumma += valisumma;
System.out.println("Kaikkien summa on " + loppusumma);
} while ( loppusumma < 100 );
System.out.println("Lukujen loppusumma on " + loppusumma);
}
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.
Hyvin usein aliohjelmassa break voidaan korvata return-lauseella.
Lisäksi näkyviä sisäkkäisiä silmukoita voidaan välttää tekemällä sisäsilmukasta oma aliohjelma:
while ( ulkoehto ) {
while ( sisaehto ) {
hommia();
}
}
Eli sisäkkäisten silmukoiden tilalle kirjoitetaan:
void sisahommat() {
while ( sisaehto ) {
hommia();
}
}
...
while ( ulkoehto ) {
sisahommat();
}
Tehtävä 10.14 Tarvitaanko sisäkkäisiä silmukoita?
Tarvitaanko aliohjelmassa break_testi3 todella sisäkkäisiä silmukoita? Esitä ratkaisu jossa on vain yksi silmukka.
Vastaavasti saattaa tulla tilanteita, jolloin itse silmukan suoritusta ei haluta katkaista, mutta menossa oleva kierros halutaan lopettaa. Tällöin continue –lauseella voidaan suoritus siirtää suoraan silmukan loppuun ja näin lopettaa tämän kierroksen suoritus:
/**
* Esitellään continue-lauseen käyttöä
* @author Vesa Lappalainen
* @version 1.0, 07.02.2003
*/
public class Continue {
public static void main(String[] args) {
int alku= -5, loppu=5,i;
double inv_i;
System.out.println("Tulostan lukujen " + alku + " - " + loppu +
"käänteisluvut");
for (i = alku; i<=loppu; i++ ) {
if ( i == 0 ) continue;
inv_i = 1.0/i;
System.out.println(i + ":n käänteisluku on " + inv_i);
}
}
}
Vastaavasti myös continue:n kanssa voi käyttää nimettyä silmukkaa, jos pitääkin siirtyä jatkamaan muuta kuin sisintä silmukkaa.
Tehtävä 10.15 continuen korvaaminen
Kirjoita käänteislukujen tulostusohjelma ilman continue–lausetta.
Tehtävä 10.16 Eri silmukoiden vertailu
Kirjoita lukujen alaraja-yläraja summausfunktio käyttäen
a) while -lausetta
b) do–while -lausetta
c) goto –lausetta
Muista, että alaraja saattaa olla suurempi kuin yläraja, eli summa väliltä [3,0] on 0!
Jäsenrekisteriohjelmamme päävalinta olisi näppärintä toteuttaa switch –lauseella:
/**
* Silmukka jossa odotetaan näppäint ja suoritetaan vastaava toiminto.
* 0:n painaminen lopettaa silmukan ja palaa kutsuvaan ohjelmaan.
* @return palauttaa 0 jos kaikki meni hyvin, 1 jos tuli virhe
*/
public int paavalinta() {
char nappain;
while ( true ) {
paamenu();
nappain = IO.odota_nappain("?012345",IO.EI_OLETUSTA,IO.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 : tulosta("Näin ei voi käydä!"); 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:
public static int switch_testi(int x,int operaatio) {
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 */
}
return x;
}
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.
Tehtävä 10.17 switch –> if
Kirjoita Switch.java ohjelmanpätkä käyttäen if–rakenteita muuttamatta itse suoritettavia lauseita.
Tehtävä 10.18 Päävalinta
Kirjoita paavalinta käyttäen vain if ja else rakenteita.
Tehtävä 10.19 lääni, versio 2
Kirjoita laani–aliohjelma käyttäen Switch–rakennetta.
On huomattava, että jos halutaan suorittaa jokin switch–lauseen osista kahdella eri arvolla, EI voida käyttää rakennetta:
switch (operaatio) { /* VÄÄRIN: */
case 4 | 5: x *= 2; break; /* 5 tai 4 laskee x=2*x */ L
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 bittilausekkeen OR eli 5. Siis
case 4 | 8:
on sama kuin
case 12:
Usein silmukat lipsahtavat tahottomasti sellaisiksi, ettei niistä koskaan päästä ulos. Ikuisen silmukan huomaa heti esimerkiksi siitä, ettei silmukan rungossa ole yhtään lausetta joka muuttaa silmukan ehdon totuusarvoa.
Joskus kuitenkin Java–kielessä tehdään tarkoituksella "ikuisia" –silmukoita:
for (;;) {
...
if (lopetus_ehto) break;
...
}
while ( true ) {
...
if (lopetus_ehto) break;
...
}
do {
...
if (lopetus_ehto) break;
...
} while ( true );
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.