previous next Title Contents Index

20. Päätesyöttö


Mitä tässä luvussa käsitellään?

* päätesyöttö yksinkertaisimmalla tavalla

* näyttö tietämättömäksi jäsenen ominaisuuksista

* "indeksoidut" kentät

Oikeassa ohjelmassa päätesyöttö hoidetaan usein erilaisilta lomakepohjilta. Tällaisen ohjelman tekeminen ensimmäisenä ohjelmana on kuitenkin varsin työlästä. Siispä teemme aluksi suunnitelman mukaisen yksinkertaisen syötön, jossa ainoa korjailumahdollisuus on oletusarvon käyttö. Näppäinten painallusten lukumäärää laskettaessa nyt toteutettava syöttö on aivan yhtä hyvä kuin lomakepohjainen syöttö. Jos pystymme pitämään kaiken laiteriippuvuuden cNaytto- luokassa, on ohjelma kohtuullisella työllä muutettavissa myös lomakepohjaisesti toimivaksi esimerkiksi Windows- käyttöliittymään.

20.1 Lukeminen ilman tarkistuksia

Aluksi kannattaa oikeellisuustarkistukset unohtaa ja yrittää saada edes jotakin luettua.

20.1.1 lisaa_uusi_jasen (naytto.cpp)

Ohjelman edellinen versio osasi lisätä vain aina vakiojäsenen "Ankka Aku". Muutamme lisaa_uusi_jasen aliohjelmaa siten, että vakiojäsenen tilalla kysytäänkin jäsenen tiedot:

lukemine.3\naytto.cpp - päätesyöttö

	//----------------------------------------------------------------------------
	void cNaytto::lisaa_uusi_jasen(char valinta) 
	/*
	** Kysyllään uusien jäsenien tietoja ja lisätään jäsenet.
	----------------------------------------------------------------------------*/
	{
	  cJasen jasen;
	
	  otsikko(valinta,"Uuden jäsenen lisäys");
	
	  while ( 1 ) {
	    jasen.tyhjenna_omat();
	    jasen.rekisteroi();
	    cout << endl;
	    cout << "Jäseniä on nyt " << kerho->Jasenia() << "." << endl;
	    cout << "Anna uusi nimi muodossa sukunimi etunimi"; // ei endl
	
	    int ei_lisata = 1;
	    do {
	      cout << endl;                        // Jotta uudella kier. rivi vaiht
	      if ( kysy_tiedot(jasen) ) return;
	
	      cout << "Lisätäänkö" << endl;
	      tulosta(cout,jasen);
	      cout << ":";
	      if ( kylla_vastaus() )
	        ei_lisata = ilmoitus(kerho->Jasenet().lisaa(jasen));
	      if ( ei_lisata ) kerho->poista(jasen.Tunnus_nro());
	    } while ( ei_lisata );
	  }
	}

20.1.2 kysy_tiedot (naytto.cpp)

Varsinainen tietojen kysyminen on jätetty aliohjelmalle kysy_tiedot. Yksinkertaisimmillaan tämä olisi:
	int cNaytto::kysy_tiedot(cJasen &jasen)
	{
	  cJasen apujasen(jasen);
	  char jono[50];
	
	  cout << "Jäsenen nimi  >"; getline(cin,apujasen.nimi,'\n');
	  if ( apujasen.nimi == "" ) return 1;
	  cout << "Hetu          >"; getline(cin,apujasen.hetu,'\n');
	...
	  cout << "Jäsenmaksu mk >"; cin.getline(N_S(jono),'\n');
	  apujasen.jmaksu = jono_doubleksi(jono,"%lf");
	...
	  jasen = apujasen;
	  return 0;
	}
Tässä on vielä vähän vikoja. Ensinnäkin syötöstä ei voi poistua minkä tahansa kentän antamisen jälkeen. Tämä voidaan korjata esimerkiksi lisäämällä merkin "q" käsittely jokaisen lukemisen jälkeen:
	  cout << "Jäsenen nimi  >"; getline(cin,apujasen.nimi,'\n');
	  if ( apujasen.nimi == "" ) return 1;
	  if ( apujasen.nimi == "q" ) return 1;
	  cout << "Hetu          >"; getline(cin,apujasen.hetu,'\n');
	  if ( apujasen.hetu == "q" ) return 1;
	...
	  cout << "Jäsenmaksu mk >"; cin.getline(N_S(jono),'\n');
	  if ( strcmp(jono,"q") == 0 ) return 1;
	  apujasen.jmaksu = jono_doubleksi(jono,"%lf");
	...
Vieläkään ei ole ehdotettu oletusarvoa kentälle syöttöä varten. Lisäyksessähän tämä ei niin haittaa, mutta jos samaa aliohjelmaa haluttaisiin käyttää korjailussa, olisi oletusarvot toivottavia.

Toimiakseen se, että näyttö voi viitata suoraan jäseneen vaatii näytön jäsenen ystäväksi. No tämähän ei ollut kovin toivottava ominaisuus. Lisäksi olisi toivottavaa muutenkin, ettei näyttö tietäisi näin paljoa jäsenestä. Nyt nimittäin jos jäseneen lisätään yksikin kenttä, pitää muutoksia tehdä monessa paikassa (jo turhan monessa ilman näytönkin muuttamista).

20.2 Poistetaan riippuvuus näytön ja jäsenen väliltä

20.2.1 Algoritmi

Lisäämällä byrokratiaa näytön ja jäsenen välille, voidaan näyttö pitää tietämättömänä siitä, mitä kenttiä jäsenessä todella on. Minkälaista byrokratiaa? Esimerkiksi "keskustelu" näytön ja jäsenen välillä voisi olla seuraavanlainen:
	1.	Näyttö:  montako kenttää sinulla on jäsen?
	2.	Jäsen:   13 kenttää
	3.	Näyttö:  no annappa minulle 1. kenttä merkkijonona!
	4.	Jäsen:   Ankka Aku
	5.	Näyttö:  Milläs kysymyksellä tämä kenttä kysytään?
	6.	Jäsen:   Jäsenen nimi
	7.	Näyttö kysyy käytäjältä Jäsenen nimi (Ankka Aku) >  => jono
	8.	Näyttö tutkii vastattiinko q tms. erikoismerkki, jos niin pois
	9.	Näyttö:  Sijoitapa jäsen tämä jono 1. kentäksi.
	10.	Näyttö jatkaa kohdasta 3 mutta kentälle 2 kunnes kaikki 13 kenttää käsitelty 

20.2.2 kysy_tiedot

Kirjoitetaanpa edellinen algoritmi C++:lla:

lukemine.3\naytto.cpp - kysy_tiedot

	int cNaytto::kysy_tiedot(cJasen &jasen)
	{
	  cJasen apujasen(jasen);
	  string jono;
	  int k,kenttia = apujasen.kenttia(), eka = apujasen.eka_kysymys();
	
	  for (k=eka; k<kenttia; k++) {
	    jono = apujasen.kentta_jonoksi(k);
	    if ( kysy_kentta(apujasen.kysymys(k),jono) ) return 1;
	    if ( k == eka && jono == "" ) return 1; /* 1. kys pääsee pois pelk. ret *
	    apujasen.sijoita(k,jono);
	  }
	  jasen = apujasen;
	  return 0;
	}

20.2.3 kysy_kentta

"Likainen työ" jätettiinkin aliohjelmalle kysy_kentta, joka ei juurikaan ole sidoksissa kerho- ohjelmaan:

lukemine.3\naytto.cpp - kysy_kentta

	/****************************************************************************
	static int                /*                                                *
	kysy_kentta(              /*                                                *
	  const char *viesti     ,/* s   Viesti joka tulee näytölle                 *
	  string &jono            /* t   Jono johon kentän vastaus luetaan.         *
	) 
	/*
	** Funktiolla luetaan vastaus kenttään.
	**
	** Globaalit: POIS (jono, jolla syöttö katkaistaan)
	** Syöttö:    päätteeltä
	** Tulostus:  näyttöön
	** Kutsuu:    lue_jono_oletus
	----------------------------------------------------------------------------*
	{
	  char apu[80]; int paluu;
	
	  kopioi_jono(N_S(apu),jono.c_str());
	
	  paluu = lue_jono_oletus(viesti,OLET_ALKU,VAKANEN,apu,N_S(apu));
	
	  if ( paluu < OLETUS ) return 1;
	  if ( strcmp(apu,POIS) == 0 ) return 1;
	
	  poista_tyhjat(apu);
	  jono = apu;
	
	  return 0;
	}
Tulosta ei lueta suoraan muuttujaan jono, jotta mahdollisessa q - vastauksessa ei pilattaisi kentän alkuperäistä arvoa.

Tehtävä 20.170 lisaa_uusi_jasen - kutsut

Piirrä puumainen kuva siitä, mitä aliohjelmia/metodeja metodista lisaa_uusi_jasen alkaen kutsutaan.

20.3 Muutokset jäsen- luokaan

Edellä näytti siltä, että onnistuimme muuttamaan näyttö- luokkaa siten, että uuden kentän lisääminen tai kentän poistaminen ei vaadi muutoksia luokaan cNaytto. Tämä on tietenkin jo askel kohti oikeata ohjelmointia.

20.3.1 kenttia ja eka_kysymys

Koska näyttö keskustelee jäsenen kanssa, pitää tietysti toteuttaa myös ne metodit, joilla näyttö (tai kuka tahansa) voi kysellä kriittisiä tietoja jäseneltä:

lukemine.3\jasen.cpp - kenttia, eka_kysymys

	class cJasen {
	...
	  int kenttia() const                          { return 13;                  }
	  int eka_kysymys() const                      { return  1;                  }
	...
	};

20.3.2 kentta_jonoksi

Jäsenen tietyn kentän pääsemme muuttamaan merkkijonoksi käyttäen talletuksen yhteydessä tehtyä polymorfista jonoksi- funktiota:

lukemine.3\jasen.cpp - kentta_jonoksi

	const string cJasen::kentta_jonoksi(int k) const
	{
	  switch ( k ) {
	    case  0: return jonoksi(tunnus_nro);
	    case  1: return jonoksi(nimi);
	    case  2: return jonoksi(hetu);
	    case  3: return jonoksi(katuosoite);
	...
	    case 10: return jonoksi(jmaksu);
	    case 11: return jonoksi(maksu);
	    case 12: return jonoksi(lisatietoja);
	    default: return string("VIRHE");
	  }
	}

20.3.3 kysymys()

Näytön pitää päästä myös selvittämään mikä teksti näyttöön pitää laittaa tietyn kentän kysymiseksi käyttäjältä:

lukemine.3\jasen.cpp - kysymys

	const char *cJasen::kysymys(int k) const
	{
	  switch ( k ) {
	    case  0: return "Tunnus nro";
	    case  1: return "Jäsenen nimi";
	    case  2: return "Hetu";
	    case  3: return "Katuosoite";
	...
	    case 10: return "Jäsenmaksu mk";
	    case 11: return "Maksettu maksu mk";
	    case 12: return "Lisätietoja";
	    default: return "VIRHE!";
	  }
	}

20.3.4 sijoita

Kun näyttö on merkkijonon kysynyt, pitäisi merkkijonon muuttunut arvo saattaa jäsenen tietoon:

lukemine.3\jasen.cpp - sijoita

	int cJasen::sijoita(int k,const string &st)
	{
	  switch ( k ) {
	    case  0: muunna_jono(st,&tunnus_nro);      return 0;
	    case  1: muunna_jono(st,&nimi);            return 0;
	    case  2: muunna_jono(st,&hetu);            return 0;
	    case  3: muunna_jono(st,&katuosoite);      return 0;
	...
	    case  9: muunna_jono(st,&liittymisvuosi);  return 0;
	    case 10: muunna_jono(st,&jmaksu);          return 0;
	    case 11: muunna_jono(st,&maksu);           return 0;
	    case 12: muunna_jono(st,&lisatietoja);     return 0;
	    default:                                   return 1;
	  }

Tehtävä 20.171 Montako muutosta

Jos jäseneen lisätään vaikkapa kenttä Jasenmaksu-97 - kenttä, niin kuinka monta muutosta ja mihin tarvitsee tehdä (yksi muutos on aina yksi muutettu/lisätty/poistettu rivi).

20.3.5 Arvostelu valinnoista

Edellä tehdyillä valinnoilla ohjelmasta saadaan toimiva tiedon pääteeltä lukua myöten. Kuitenkin kuten tehtävässäkin todettiin, tulee muutoksi paljon, jos jäsenen attribuuttien määrä muuttuu.

Itse asiassa sijoita ja kentta_jonoksi sekä kysymys() ovat riittävä määrä jäsenen kysymysten toteuttamiseksi. Näitä metodeja käyttäen voimme samalla muuttaa ison osan aikaisemmin tehtyjä "itseään toistavia" jäsenen metodeja ja muita aliohjelmia silmukoiksi, mm:

	ostream &operator<<(ostream &os,const cJasen &jasen)
	{
	  int k,kenttia=jasen.kenttia();
	
	  for (k=0; k<kenttia; k++)
	    os << jasen.kentta_jonoksi(k) << jasen.erotin;
	
	  return os;
	}
	
	istream &operator>>(istream &is,cJasen &jasen)
	{
	  int k,kenttia=jasen.kenttia();
	
	  for (k=0; k<kenttia; k++) {
	    jasen[k].ota_seuraava(is,jasen.erottimet);
	  }
	  if ( jasen.tunnus_nro >= jasen.seuraava_nro )
	    jasen.seuraava_nro = jasen.tunnus_nro + 1;
	
	  tyhjenna(is);  // ohittaa loppurivin
	  return is;
	}

20.4 Testaus

Jälleen testataan ohjelman toimivuutta. Mikäli virheitä havaitaan, korjataan ne. Muuten voidaan jatkaa seuraavaan vaiheeseen joka voisi olla joko oikeellisuustarkistusten lisäys tai tietojen etsimisen lisäys.


previous next Title Contents Index