Monet varmasti tietävät Pythonin syötteenlukufunktiot kuten raw_input. Kirjoitin C++:lla pienen luokan, jonka metodi prompt ottaa tyyppiparametrina luettavan syötteen tietotyypin, sekä merkkijonoparametrina tulostettavan viestin. Metodi tulostaa viestin (jos se on määritelty), lukee seuraavan rivin syötevirrasta, muuntaa sen parametrilla määritellyn tyyppiseksi ja palauttaa sen.
Metodille on myös kuormitus, joka palauttaa syötteen std::string-tyyppisenä, jolloin se ei siis tee muunnoksia.
Lisäksi kirjoitin tyyppimuunnoksia varten staattisen luokan Convert, joka käyttää muunnoksissa std::stringstream-luokkaa.
Kirjoitin luokat englanniksi, mutta kommentoin ne suomeksi, kun päätin lähettää ne Ohjelmointiputkaan.
En periyttänyt Input-luokkaa std::istream:sta tai mistään muustakaan syötevirtaluokasta, sillä Input:sta tehtyjen olioiden ei kuulu itse olla virtoja tai viittauksia niihin, vaan niiden yksinkertaistettuja käyttöliittymiä. Eli jos Input-oliolla käytetään vaikka std::cin-virtaa, se ei itse tekeydy virraksi, vaan helpoksi työkaluksi virran lukemiseen.
Koodi toimii C++03:n rajoissa.
Convert - tyyppimuunnokset
// Convert.hpp #ifndef CONVERT_HPP #define CONVERT_HPP #include <sstream> template < class Src, class Dst > class Convert { public: /* Arvon muuntaminen toiseen tietotyyppiin. * Toimii ainoastaan tyypeillä, joille löytyy kuormitus std::stringstream:n * >> -operaattorilta * */ static Dst execute ( const Src& value ) { std::stringstream conv; Dst *ret = new Dst; conv << value; conv >> *ret; return *ret; } }; // Erikoistunut toteutus muunnoksille, joissa lähdeluokka on std::string template < class Dst > class Convert< std::string, Dst >{ public: /* Merkkijonon muuntaminen toiseen tietotyyppiin. * Toimii ainoastaan tyypeillä, joille löytyy kuormitus std::stringstream:n * >> -operaattorilta * */ static Dst execute ( const std::string& value ) { std::stringstream conv; Dst *ret = new Dst; conv.str (value); /* Jos conv:n sisältö merkkijonona ei ole tyhjä, * se puretaan ret:iin */ if ( !conv.str().empty() ) { conv >> *ret; } return *ret; } }; #endif // CONVERT_HPP
Input - Käyttöliittymä syötevirtaoliolle
// Input.hpp #ifndef INPUT_HPP #define INPUT_HPP #include <iostream> #include <string> #include "Convert.hpp" class Input { public: /* Muodostin ottaa parametrina viittauksen syötevirtaan * ja alustaa jäsenen m_stream viittaamaan siihen.*/ Input ( std::istream& stream ) : m_stream (stream) {} // Rivin lukeminen syötevirrasta, mikäli rivi on olemassa std::string nextLine () { std::string ret = ""; std::getline ( this->m_stream, ret ); return ret; } /* prompt kysyy käyttäjältä syötettä ja palauttaa luetun rivin muunnettuna DataType:ksi * Toimii ainoastaan tyypeillä, joille löytyy kuormitus std::stringstream:n * >> -operaattorilta * * Parametrit: * message: Viesti, joka tulostetaan ennen syötteen lukemista * */ template < class DataType > DataType prompt ( const std::string& msg = "" ) { std::string input; // Syöte luetaan ensin tänne... DataType ret; // ...ja muunnoksen jälkeen se sijoitetaan tänne. // Jos viesti ei ole tyhjä, se tulostetaan if ( !msg.empty() ) { std::cout << msg << std::flush; } // Luetaan seuraava rivi syötevirrasta input = this->nextLine (); // Muunnetaan luettu syöte T:ksi ret = Convert< std::string, DataType >::execute ( input ); return ret; } // std::string:lle oma metodi std::string prompt ( const std::string& msg = "" ) { // Tulostaa viestin, jos määritelty if ( !msg.empty() ) { std::cout << msg << std::flush; } return this->nextLine (); } protected: std::istream& m_stream; // Viittaus käytettävään syötevirtaan }; #endif // INPUT_HPP
// InputTesti.cpp #include "Input.hpp" // Luodaan globaali olio in Input in ( std::cin ); class InputTesti { public: static int main () { float ika = 0; std::string nimi = ""; // Luetaan merkkijono nimi = in.prompt ( "Kuka siellä? " ); std::cout << "Moikka, " << nimi << "!" << std::endl; // Luetaan liukuluku ika = in.prompt < float > ( "Montako vuotta olet vanha? " ); std::cout << "Elä elämäsi ajallisesti vielä uudelleen,"; std::cout << " niin olet " << ika * 2 << " vuotta vanha!"; std::cout << std::endl; return in.prompt<int>(); // Palautetaan kokonaisluku syötteestä } }; int main () { return InputTesti::main(); }
Mahdollisia tulosteita
Kuka siellä? Vesikuusi Moikka, Vesikuusi! Montako vuotta olet vanha? 17.858 Elä elämäsi ajallisesti vielä uudelleen, niin olet 35.716 vuotta vanha! 0
Kuka siellä? asd asd Moikka, asd asd! Montako vuotta olet vanha? asd Elä elämäsi ajallisesti vielä uudelleen, niin olet 0 vuotta vanha! asd
Kuka siellä? Moikka, ! Montako vuotta olet vanha? Elä elämäsi ajallisesti vielä uudelleen, niin olet 0 vuotta vanha!
Tiedosto
Nonesense verse: One fine day in the middle of the night, Two dead boys got up to fight, Back to back they faced each other, Drew their swords and shot each other, One was blind and the other couldn't see, So they chose a dummy for a referee. A blind man went to see fair play, A dumb man went to shout "hooray!" A paralysed donkey passing by, Kicked the blind man in the eye, Knocked him through a nine inch wall, Into a dry ditch and drowned them all, A deaf policeman heard the noise, And came to arrest the two dead boys, If you don't believe this story’s true, Ask the blind man; he saw it too!
// InputTestiTiedostolla.cpp #include <fstream> #include "Input.hpp" class InputTestiTiedostolla { public: static int main () { std::fstream tiedosto ("tiedosto"); if (tiedosto) { // Luodaan paikallinen olio käyttämään tiedostovirtaa Input fIn ( tiedosto ); std::string rivi = ""; std::cout << "Ensimmäinen rivi: " << fIn.nextLine(); std::cout << std::endl; // Luetaan tiedosto while ( !tiedosto.eof() ) { rivi = fIn.prompt ( "Seuraava rivi... " ); // TAI ilman viestiä: rivi = fIn.nextLine(); std::cout << "Rivillä lukee: " << rivi << std::endl; } } return 0; } }; int main () { return InputTestiTiedostolla::main(); }
Tuloste
Ensimmäinen rivi: Nonesense verse: Seuraava rivi... Rivillä lukee: Seuraava rivi... Rivillä lukee: One fine day in the middle of the night, Seuraava rivi... Rivillä lukee: Two dead boys got up to fight, Seuraava rivi... Rivillä lukee: Back to back they faced each other, Seuraava rivi... Rivillä lukee: Drew their swords and shot each other, Seuraava rivi... Rivillä lukee: Seuraava rivi... Rivillä lukee: One was blind and the other couldn't see, Seuraava rivi... Rivillä lukee: So they chose a dummy for a referee. Seuraava rivi... Rivillä lukee: A blind man went to see fair play, Seuraava rivi... Rivillä lukee: A dumb man went to shout "hooray!" Seuraava rivi... Rivillä lukee: Seuraava rivi... Rivillä lukee: A paralysed donkey passing by, Seuraava rivi... Rivillä lukee: Kicked the blind man in the eye, Seuraava rivi... Rivillä lukee: Knocked him through a nine inch wall, Seuraava rivi... Rivillä lukee: Into a dry ditch and drowned them all, Seuraava rivi... Rivillä lukee: Seuraava rivi... Rivillä lukee: A deaf policeman heard the noise, Seuraava rivi... Rivillä lukee: And came to arrest the two dead boys, Seuraava rivi... Rivillä lukee: If you don't believe this story’s true, Seuraava rivi... Rivillä lukee: Ask the blind man; he saw it too!
Tiivistelmä: Ihan kiva. Teknisesti jonkin verran parannettavaa mutta ei mitään ihmeitä. Input-luokan toiminta on melko rajoittunut eikä sovi kovinkaan moneen tilanteeseen, joten epäilen, ettei suuri yleisö hyödy tästä vinkistä. Toisaalta en usko, että edes hienompi Input-luokka olisi kovin hyödyllinen vinkki.
Teknisiä asioita; lista voi olla puutteellinen:
Koodin ulkoasu ei ole yhtenäinen, käytät sisennyksessä sekaisin tabulaattoreita ja välejä ja myös kirjoitat rakenteita välillä eri tavoin (esim. in.prompt<int> vs. in.prompt < float >).
Koodissa on muistivuodot molemmissa new-kohdissa.
Pari kertaa alustat turhaan string-olion tyhjäksi; se on tyhjä automaattisesti.
Joitain asioita voisi tehdä kätevämmin, esimerkiksi ensimmäisen prompt-funktion voisi toteuttaa yksinkertaisesti näin:
return Convert< std::string, DataType >::execute(prompt(msg));
Ei ole kovin siistiä käyttää Input-luokassa cout-oliota; sen voisi vaatia parametrina (ehkäpä prompt-funktiolle), kun kerran cin-oliokin vaaditaan.
Muunnoksen erillinen toteutus string-tyypille noin päin on melko turha. Toiseen suuntaan siinä olisi järkeä, koska saataisiin koko rivi eikä vain yhtä sanaa.
Miksi edes on Convert-luokka ja sillä execute-funktio? Voisi olla joko Convert-luokalla operator() tai suoraan convert-funktio, ja tuossakin minusta template kuuluisi paremminkin funktioon kuin luokkaan.
Esimerkin teksti on tarpeettoman pitkä.
Vinkin alkuun ei tarvita väliotsikkoa.
Sitten hieman koodin ideasta. Muunnoksen tekeminen operaattoreilla << ja >> on oikeastaan melko huono keksintö, kuten varsin perusteellisesti selitetään erään vanhan vinkkini kommenteissa. (Pitääkin ehkä joskus korvata tuo vinkki paremmalla.) Yleisesti ottaen ei ole kovin hyvä tehdä funktioita, jotka tuottavat mistä tahansa viallisesta syötteestä arvon, jossa ei ehkä ole mitään järkeä. Erityisesti tyhjän tekstin tai tekstin "x" sujuva muuttaminen luvuksi 0 on aika outoa. Jos tuollainen funktio kuitenkin pitää tehdä, sen nimeksi sopii paremmin vaikka makeSomeWildGuess. ;)
Input-touhujen osalta jää kuva, että yksinkertaisesta asiasta on tehty tarpeettoman mutkikas luokka, jota voi kuitenkin käyttää vain hyvin rajoittuneesti. Pyöräytin tuossa kokeeksi oman version, joka on mielestäni paljon joustavampi ja kuitenkin lyhyempi. En tosin aio luultavasti julkaista sitäkään: minusta idea ei ole kovin kekseliäs tai hyödyllinen, aloittelijat eivät osaa käyttää, gurut eivät tarvitse, ja kaikki pätijät kyselevät, miksi luokka tekee asian X ja miksei se tee asiaa Y.
Voit toki vielä perustella, miksi tällaisen vinkin julkaiseminen olisi hyödyllistä. :)
Kiitokset asiallisesta palautteesta.
Metabolix kirjoitti:
käytät sisennyksessä sekaisin tabulaattoreita ja välejä
Tämä johtuu siitä, että vaihdoin editoria jossain välissä. Toinen korvaa tabulaattorit neljällä välilyönnillä.
Metabolix kirjoitti:
Koodissa on muistivuodot molemmissa new-kohdissa.
Mietin ja googlasin tätä, kun nämä kohdat tein. Käsittääkseni osoittimen varaama muisti vapautetaan lohkon päätyttyä (jolloin ei tarvita delete-operaattoria). Vai puhunko eri asiasta?
Metabolix kirjoitti:
Joitain asioita voisi tehdä kätevämmin, esimerkiksi ensimmäisen prompt-funktion voisi toteuttaa yksinkertaisesti näin:
return Convert< std::string, DataType >::execute(prompt(msg));Ei ole kovin siistiä käyttää Input-luokassa cout-oliota; sen voisi vaatia parametrina (ehkäpä prompt-funktiolle), kun kerran cin-oliokin vaaditaan.
Totta, pistetään korvan taakse!
Metabolix kirjoitti:
Sitten hieman koodin ideasta. Muunnoksen tekeminen operaattoreilla << ja >> on oikeastaan melko huono keksintö--
Mielenkiintoista, ja vähän odotettavissakin :D
Metabolix kirjoitti:
Pyöräytin tuossa kokeeksi oman version--
Näyttää hyvin mielenkiitoiselta tuokin. Uskon, että osaat arvioida vinkin julkaisemisen kannattavuuden, tosin itse ainakin luin ja pistin kirjanmerkkeihin :)
vesikuusi kirjoitti:
Metabolix kirjoitti:
Koodissa on muistivuodot molemmissa new-kohdissa.
Mietin ja googlasin tätä, kun nämä kohdat tein. Käsittääkseni osoittimen varaama muisti vapautetaan lohkon päätyttyä (jolloin ei tarvita delete-operaattoria). Vai puhunko eri asiasta?
Eikö koko new-operaattorin idea ole, että muistia ei vapauteta lohkon lopussa? C++:n osoittimiin ei liity mitään piilotettuja ominaisuuksia, vaan kaikki on käyttäjän vastuulla. Jos on new, täytyy olla myös delete. Jos vielä löydät sivun, jossa mielestäsi väitetään muuta, voin katsoa, onko sivu väärässä vai oletko tulkinnut väärin. ;)
"Automaattisissa" osoitinluokissa kuten auto_ptr ja shared_ptr on tietenkin sisällä delete. Lisäksi C++ teoriassa sallii myös roskienkeruun, joten jollain hyvin harvinaisella C++-toteutuksella voisit ollakin oikeassa.
Vaikka muisti vapautettaisiinkin jotenkin, en nyt keksi mitään olennaista hyötyä new-operaattorin käytöstä.
vesikuusi kirjoitti:
Uskon, että osaat arvioida vinkin julkaisemisen kannattavuuden, tosin itse ainakin luin ja pistin kirjanmerkkeihin :)
No täytyy miettiä asiaa.
Hmm taisipa olla niin, että sivulla, josta asiasta luin ei puhuttu new:lla varatusta muistista. Olisihan minun pitänyt muistaa nuo new-jutut kun niistä lukenut ihan kirjasta joskus.. Ei vain ole tullut käytettyä tarpeeksi, niin pääsee unohtumaan tuommoiset, tai tulee näitä sekaannuksia :D
Korjasin lähinnä huvikseni kommenttiin jääneen virheen, ja vinkki lähti näemmä uudelleen tarkistukseen :D Pahoittelen aiheutunutta vaivaa...
Aihe on jo aika vanha, joten et voi enää vastata siihen.