Tekstivirta (string stream) on käytännöllinen olio, joka toimii aivan kuin tiedostovirta, mutta tiedoston paikalla onkin tekstiä (string). Tästäkin virrasta on sekä sisään- että ulospäin toimivat versiot (istringstream, ostringstream) ja sekä sisään- että ulospäin toimiva versio (stringstream).
Käyttöesimerkkinä on funktio, jolla voi muuttaa C++:n perustyyppejä (int, float) tekstiksi ja päin vastoin, tai halutessaan muutenkin perustyyppejä toisikseen. Esitteillä on stringstream-olio. Muunnettaviksi tyypeiksi kelpaavat mitkä tahansa, jotka voi syöttää stream-olioon.
Kannattaa muistaa, että template-funktiota käytettäessä ei voi kääntää erikseen funktiota ja sitä käyttävää koodia, koska funktio käännetään tarpeen mukaan eri tyyppisillä syötearvoilla.
#include <sstream> /* Template-funktio, joka ottaa kaksi parametriä. Template-rivillä määritellään, että TTyyppiA ja TTyyppiB ovat joitakin tyyppejä. Parametreiksi määritellään viittaus ensimmäisen tyyppiseen ja osoitin jälkimmäisen tyyppiseen muuttujaan. */ template <class TTyyppiA, class TTyyppiB> bool Muunna(const TTyyppiA & Lahde, TTyyppiB * Kohde) { std::stringstream Stream; // Stream-olio Stream << Lahde; // Syötetään arvo stream-olioon Stream >> *Kohde; // Luetaan arvo kohdemuuttujaan return Stream.good(); // Palautetaan tieto, onko kaikki kunnossa. }
#include <string> #include <iostream> // Ja tavalla tai toisella tuo funktio tähän väliin. int main(void) { // Määritellään string, jossa on desimaaliluku std::string Str = "04.50"; // Määritellään double (desimaaliluku) double Dbl; // Muunnetaan string doubleksi Muunna(Str, &Dbl); // Tulostetaan, mikä muutettiin ja minne std::cout << Str << " = " << Dbl << std::endl; return 0; }
Miksi toinen parametri (siis kohde) välitetään osoittimena? (Olettaisin syyn olevan selvyys siitä, että ko. parametria muokataan funktiossa, mutta sitä vartenhan on olemassa const-avainsana.) Itse määrittelisin funktion seuraavalla tavalla:
template <class TTyyppiA, class TTyyppiB> bool Muunna(const TTyyppiA & Lahde, TTyyppiB & Kohde) ...
Juuri sen takia. Määrittele vain miten huvittaa minun puolestani. Minä tykkään laittaa sinne osoittimen.
Tässä koodivinkissä on erinomaisen hyvä idea ja se on se, että tulostukseen (eli string
in rakenteluun) käytetään stream
eja.
Muunnosidea ei kyllä ole ihan hyvä. Kun tehdään tyyppimuunnoksia, ne kyllä hoituvat joko ihan sijoittamalla tai sitten static_cast
illa kiljoona kertaa tehokkaammin. Jos ei onnistu, niin silloin ollaan melko varmasti pahanteossa muutoinkin.
Kun ollaan muuntamassa jotakin string
iksi, ei oikeasti tehdä tyypinmuunnosta, vaan tulostuksen muotoilua (tai päinvastaisessa tapauksessa syötteen tulkintaa). Tulostusjutut (tai syöte-) eivät olekaan niin yksinkertaisia, vaan pitää aina varautua virhetilanteisiin ja maariippuvaisiin tapoihin. Homma voi olla myös hyvin tapauskohtaista: jos minulla on int i = 42;
ja siitä pitää saada string s;
, niin pitääkö olla s == "42"
, s == "0x2a"
vai peräti s == "XLII"
?
Liukuluvuilla pulma on ihan todellinen. stream
ien käyttö on ihan jees, mutta yleisellä tasolla Muunna
-templaatti ei pelaa kovin luotettavasti.
tn:n kommentti parametreista on ihan aiheellinen. C++-koodareille on muodostunut vakiintuneita tapoja "sanoa" ja "lukea" juttuja. Kun koodissa näkyy pointteri, C++-koodari tuumailee, että ahaa, a) meillä on tässä jotakin tai se voi olla myös olematta (siis NULL
), b) tässä on kyse dynaamisesta muistinhallinnasta (jota ei sellaisenaan paljon enää näekään) tai c) tämä touhu liittyy varmaankin jonkin C-rajapinnan käyttöön. Nyt ei ole kyse mistään noista, joten C++-tyylisesti funktio määritellään ihan niin kuin tn ehdottaa (paitsi että esimerkiksi minä tykkään panna const
-määreen tyyppinimen jälkeen).
Mutta ihan kelpo vinkki Metabolixilta, kun siitä saa näinkin paljon tuumailtavaa :-).
koo: Lähinnä tässä onkin tarkoitus muuttaa juuri stringiksi ja takaisin ja näyttää, kuinka se hoituu stringstreamilla. Totta kai lukumuunnokset menevät sijoittamalla, mutta tämä vinkki juontaa juurensa taannoisesta keskustelusta, jossa koo oli näköjään itsekin osallisena.
Ajatuksenani oli tuoda ihmisten nähtäville stringstream. Minusta koodivinkkipalstan tärkein tarkoitus on juuri kertoa, mitä kaikkea on mahdollista tehdä, tai esitellä jokin poikkeuksellisen hyvä oivallus. Vinkin ei olekaan tarkoitus olla täydellisesti toimiva, virheenkäsittelyt sisältävä funktio; suoraan kopiointikelpoinen koodi on sivuseikka. Tietenkin, jos kyseessä on jokin laskennallinen asia, tilanne on aivan toinen, mutta jos käytännön asioita selitetään, niin silloin yksinkertaisettu esimerkki on minusta varsin hyvä ajatus.
Oma käyttöni tuolle funktiolle on lähinnä siinä, että saan muutettua tekstinpätkän luvuksi, kun olen ensin parsinut sen esiin tiedostosta, ja tällöin ei tule mitään äsken mainittuja ongelmia formaatin kanssa; parseri luonnollisesti tunnistaa vain tietyn muotoiset luvut, eikä yritä syöttää funktiolle muuta.
Edit: Niin ja mikäs estää käyttämästä tuota C-rajapinnassa?
int StrToInt(char * Str, int * Integer) { return (Muunna(Str, Integer) ? 1 : 0); }
Tokihan tämä toimii myös muilla tyypeillä, kunhan vain sopivat operaattorit on määritelty.
Niin; kuten kirjoitin: "Muutettaviksi kelpaavat mitkä tahansa, jotka voi syöttää stream-olioon." Se on sitten aivan eri juttu, onko niissä muunnoksissa enää mitään järkeä. Ennemmin tekee suoraan muunnosoperaattorit sopiviin tyyppeihin.
Niin, taisin jo sanoakin, että stream
ien esittely i/o-tyyppisessä asiayhteydessä on hyvä idea. Jonkun asian muotoileminen string
iksi on siis tulostamista, mikä on ihan simppeli, muttei ehkä ihan kaikille tuttu oivallus.
Arvelin, että voisi kumminkin olla ihan paikallaan kommentoida, että vaikka tässä on ihan hyvä juttu, se ei ehkä pure ihan kaikkeen ja että miksei. Pointti on siis, että (tyyppi-) muunnos on eri asia kuin tulostuksen muotoilu tai syötteen tulkinta, muutoin tuo hyvä oivallus saattaa jäädä vähän vaarallisestikin puolitiehen.
Tuota esimerkkiä voi käyttää myös C-mäisesti, sehän tarjoaa silloin ihan samat puskuriylivuoto- ja virheansat joihin C:ssä on saanut tottua. :-)
No, ei tuo StrToInt
oikeastaan se ansa ole. Itse asiassa, siinä on oikein hyvä ajatus: Yleiskäyttöisen Muunna
-templaattifunktion sijasta tosielämässä kannattaa tehdä rajatumpia funktioita, jolloin virhekäsittelykin on paremmin toteutettavissa.
Ceez, homma tosiaan toimii, kun operaattorit >>
ja <<
on määritelty. Mutta jos tyypinmuunnoksia haluaa, suorempi tie on määritellä luokkiin tyypinmuunnosoperaattorit.
Tämmöinen löytyi vanhojen koodien seasta.
template <class T> T to(std::string s) { T t; std::istringstream ss(s); ss >> t; if(ss.bad()) throw std::ios_base::failure("Terppa."); return t; }
koo: Juu, hyvä, että joku jaksaa selittää tuollaiset asiat, kun minulle ei tullut edes mieleen, että noista tarvitsisi erikseen mainita :)
Miten tämä tehäisiin C kielellä
Funktiot sprintf ja sscanf pääsevät lähimmäksi, niistä löytyy vaikkapa Googlella tietoa.
Aihe on jo aika vanha, joten et voi enää vastata siihen.