2.6 Valintalause

Ketjutettu if-lause ei välttämättä ole kovin hyvä tapauksissa, joissa valittavana on rajallinen määrä toisensa poissulkevia vaihtoehtoja. Tätä varten C++:ssa on parempi rakenne, switch-lause. Lauseen yleinen muoto on:

switch (lauseke) {
  case vakiolauseke1:
    lauseita;
    break;
  case vakiolauseke2:
    lauseita;
    break;
  ...
  default:
    lauseita;
}

Tässä kukin vakiolauseke on lauseke, jonka arvo on kokonaislukuvakio tai sellaiseksi tulkittavissa (esim. merkkivakio). Vakiolausekkeiden arvojen tulee olla keskenään erilaisia.

Lauseen suoritus alkaa siinä olevan lausekkeen arvon laskemisella. Sen jälkeen siirrytään siihen tapaukseen (case), josta löytyy arvoa vastaava vakiolauseke, ja suoritetaan siellä olevat lauseet. Jos arvo ei vastaa mitään vakiolauseketta, suoritetaan default-osan lauseet (se siis tavallaan vastaa if-lauseen else-osaa). Tämän jälkeen jatketaan ohjelman suoritusta switch-lausetta seuraavasta lauseesta.

Oletustapaus default ei ole pakollinen; jos se puuttuu eikä lausekkeen arvo vastaa mitään vakiolauseketta, niin silloin switch-lauseessa ei tehdä mitään. Oletustapauksen ei tarvitse olla viimeisenä, mutta se on luonnollisinta.

Tarkastellaan esimerkkinä yksinkertaisen valikon toteuttamista. Käyttäjää pyydetään valitsemaan seuraava toimenpide useammasta eri vaihtoehdosta. Ketjutetulla if-lauseella tämä voitaisiin tehdä seuraavasti:

cout << "Valitse 1=lisäys, 2=poisto tai 3=muutos > ";
cin >> toimenpide;
if (toimenpide == 1)
  lisaa();
else if (toimenpide == 2)
  poista();
else if (toimenpide == 3)
  muuta();
else
  virhe();

Ehkä selvemmin tämä voidaan kuitenkin toteuttaa switch-lauseella:

cout << "Valitse 1=lisäys, 2=poisto tai 3=muutos > ";cin >> toimenpide;
switch (toimenpide) {
  case 1:
    lisaa();
    break;
  case 2:
    poista();
    break;
  case 3:
    muuta();
    break;
  default:
    virhe();
}

Normaalisti kukin tapaus (paitsi viimeinen) täytyy erikseen lopettaa break-lauseella. Ilman break:ia ei siirrytä automaattisesti switch-lausetta seuraavaan lauseeseen, vaan "pudotaan" listassa seuraavaan tapaukseen ja suoritetaan myös siellä olevat lauseet. Joissain tilanteissa tätä ominaisuutta käytetään hyväksi ja "pudottaudutaan" tahallisesti seuraavaan tapaukseen:

cout << "Virhekoodi " << virhekoodi << " : ";
switch (virhekoodi) {
  case 0:
    cout << "kaikki OK!" << endl;
    break;
  case 1:
    cout << "varoitus!" << endl;
    break;
  case 3:
    cout << "vakava ";
    /* pudotaan seuraavaan tapaukseen */
  case 2:
    cout << "virhe!" << endl;
}

Mitä edellinen ohjelmanpätkä tulostaa muuttujan virhekoodi eri arvoilla? Mitä siihen kannattaisi vielä lisätä?

Sen lisäksi, että break-lause voi puuttua jostain tapauksesta, niin myös suoritettavat lauseet voivat puuttua, ts. jokaisessa kohdassa ei ole pakko tehdä mitään. Esimerkiksi:

char kirjain;
cout << "Anna kirjain > ";
cin >> kirjain;
switch (kirjain) {
  case 'a':
  case 'e':
  case 'i':
  case 'o':
  case 'u':
  case 'y':
  case 'ä':
  case 'ö':
    cout << "Annoit vokaalin." << endl;
    break;
  default:
    cout << "Annoit konsonantin." << endl;
}

Jos tässä merkkimuuttujalla merkki on vaikkapa arvo a, niin silloin mennään ensin tapaukseen 'a', pudotaan sieltä tapaukseen 'e', jne. kunnes tullaan tapaukseen 'ö', jossa tulostetaan rivi tekstiä ja siirrytään ulos lauseesta. Tätä tapaa täytyy käyttää, jos jokin toimenpide halutaan suorittaa useammalla eri vakioarvolla (siis samassa tapauksessa ei voi luetella useampia vakiolausekkeita).

Lopuksi vielä yksi esimerkki switch-lauseen käytöstä:

/* *********************************************************
VKONPV.CPP
  Lukee näppäimistöltä kokonaisluvun väliltä 1...7
  ja tulostaa näytölle vastaavan viikonpäivän.
********************************************************* */

#include <iostream.h>

void lue_luku(int &luku)
{
  cout << "Anna luku väliltä 1...7 > ";
  cin >> luku;
}

void tulosta_viikonpaiva(int viikonpv)
{
  cout << "Tänään on ";
  switch (viikonpv) {
    case 1:
      cout << "maanantai." << endl;
      break;
    case 2:
      cout << "tiistai." << endl;
      break;
    case 3:
      cout << "keskiviikko." << endl;
      break;
    case 4:
      cout << "torstai." << endl;
      break;
    case 5:
      cout << "perjantai." << endl;
      break;
    case 6:
      cout << "lauantai, ja huomenna on ";
    case 7:
      cout << "sunnuntai!" << endl;
      break;
    default:
      cout << "... ei tällaista viikonpäivää olekaan!" << endl;
  }
}

int main(void)
{
  int luku;

  lue_luku(luku);
  tulosta_viikonpaiva(luku);

  return 0;
}