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.
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.