Luento 20

C++ -merkkijonoista

Uudemmassa C++ -standardissa on määritelty merkkijonoluokka string. Luokka tarkoittaa tyyppimääritystä, joka sisältää tyyppiin liittyvät tiedot (merkkijonon esitys, yleensä char-taulukko) ja operaatiot (esim. merkkijonon pituuden laskeminen, merkkijonojen liittäminen, yksittäisen merkin saanti). Muuttujat, jotka ovat luokan määrittämää tyyppiä sanotaan olioiksi (ts. olio on luokan ilmentymä).

String-luokan etuja C-merkkijonoihin verrattuna ovat mm. turvallisuus ja käytön helppous. string-luokkaa käytettäessä ei tarvitse kiinnittää huomiota taulukon maksimikokoon (string-merkkijonono osaa 'kasvaa' tarvittaessa). Lisäksi esim. funktioita kutsuttaessa, merkkijonoja verrattaessa ja liitettäessä string-olioita voidaan käyttää kuten perustyyppejä. Esim.

void f(string s); 
/* Funktio, jolle annetaan merkkijonoargumentti. 
    Merkkijonosta otetaan kopio funktiota kutsuttaessa. */

void f(string &s);
/* Merkkijonoreferenssi, joka viittaa kutsuvassa funktiossa olevaan merkkijonoon.
    Merkkijonoa ei kopioida. */

void f(const string &s);
/* Referenssi vakiomerkkijonolle, viittaa kutsuvassa funktiossa olevaan merkkijonoon.
    Merkkijonoa ei kopioida eikä sen sisältöä saa muuttaa funktiossa. */

Erityisesti string-luokkaa käytettäessä ei tarvitse välttämättä tuntea osoittimia tai taulukoita.

C++ -merkkijonojen syöttö ja tulostus

string-luokkaa olevan olion voi lukea tai tulostaa perustyyppien tapaan cin:ia ja cout:ia käyttäen:

string s; // esitellään string-olio ja alustetaan tyhjäksi.

cin >> s; // luetaan merkkijono välilyöntiin asti (vrt. luvun lukeminen).
cout << s; // tulostetaan merkkijono.

Yleensä halutaan kuitenkin lukea koko rivi yhteen merkkijonoon. Otetaan käyttöön funktio getline:

getline(cin,s); // lukee rivin syöttövirrasta ja sijoittaa sen merkkijonoon s;

Huom! getline-funktio ei ole sama kuin cin-olion getline-metodi:

cin.getline(s); // EI TOIMI string-luokan kanssa! :(

getline-metodi toimii vain C-tyylisille char-taulukoille.

Käytännön ohjelmissa suurimman osan tietojen syötöstä voi hoitaa käyttämällä getline-funktiota ja lukemalla kaikki syöttötiedot merkkijonoihin (vaikka kysytty tieto olisi oikeastaan esim. int-tyyppiä). Tällöin cin-olion kannalta syöte on aina 'oikein' (fail-bitistä ei tarvitse välittää) ja syöttöpuskuri on aina 'ajan tasalla' (getline-funktio lukee koko syötteen, jolloin syöttöpuskuriin ei jää ylimääräistä tietoa).

Tietojen lukemisesta merkkijonoon seuraa uusi ongelma: kuinka muuntaa merkkijonossa olevia tietoja muihin tyyppeihin? Ongelman ratkaisu on C-kielen stdio.h-kirjastossa oleva funktio sscanf. sscanf:n yleinen muoto on

sscanf(puskuri, formaatti, muuttujat);

puskuri tarkoittaa c-tyylistä merkkijonoa, josta tietoja luetaan (tässä tapauksessa käyttäjän antama syöte), formaatti on printf:n tyylinen muotoilumerkkijono, jossa kuvataan, minkä tyyppisiä muuttujia puskurista etsitään. muuttujat ovat muistiosoitteita, jonne tiedot luetaan puskurista. sscanf palauttaa kokonaislukuna löydettyjen muuttujien määrän. Esimerkki:

string s="testi 10";
int i;

sscanf(s.c_str(),"testi %d",&i);
/* c_str-metodi muuttaa stringin c-merkkijonoksi.
   %d: kokonaisluvun muotoilumerkintä.
   &i: i-muuttujan osoite, jonne luku luetaan. 
   funktio palauttaa arvon 1: 1 muuttujaan sijoitettu arvo. */
   
cout << i; // tulostaa 10   

Huomaa, että viimeisessä argumentissa pitää olla mukana muistipaikan palauttava &-operaattori. Jos se jää pois, sscanf-metodi ei pysty päättelemään, mihin tietoja tallennetaan, tuloksena on todennäköisesti ajonaikainen virhe.

sscanf:lle pätevät samat edut ja ongelmat kuin printf:llä: Kääntäjä ei pysty päättelemään luettavien muuttujien tyypin oikeellisuutta (tai edes muuttujien määrää), ne ovat täysin ohjelmoijan vastuulla. Toisaalta sscanf:llä voi lukea hyvin monimuotoisia syötteitä yhdellä lauseella. sscanf:ää voidaan pitää siis 'vaarallisena', mutta samalla hyvin tehokkaana funktiona.

Myös virheellisen syötteen tarkistus on helppoa tehdä sscanf:n paluuarvon avulla:

string s;
double d;

do {
  cout << "Anna desimaaliluku>";
  getline(cin,s);
} while ( !sscanf(s.c_str(),"%lf",&d) );
/*  Muita kirjoitustapoja:
           sscanf(s.c_str(),"%lf",&d) == 0
         !(sscanf(s.c_str(),"%lf",&d != 0)
*/

cout << "Annoit luvun" << d << endl;

Esimerkkejä:

Huomaa, että getlineä ja sscanf-funktiota voi käyttää myös muualla kuin päätesyötössä. Funktioita voi hyödyntää esim. tiedostoja luettaessa.

Linkkejä

http://appro.mit.jyu.fi/2002/kesa/johdatusohjelmointiin/luennot/luento20/index.html
© Miika Nurminen ()
26.06.2002 12:31:04