Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: C++: Funktioita helppoon ja turvalliseen syötteenlukuun

vesikuusi [17.02.2013 21:13:46]

#

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.

Luokat

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

Input ja komentorivi (std::cin)

// 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!

Input ja tiedostot (std::ifstream)

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!

Metabolix [17.02.2013 23:58:48]

#

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:

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ä. :)

vesikuusi [18.02.2013 18:53:20]

#

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 :)

Metabolix [18.02.2013 21:47:12]

#

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.

vesikuusi [18.02.2013 22:56:31]

#

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

vesikuusi [22.02.2013 20:22:06]

#

Korjasin lähinnä huvikseni kommenttiin jääneen virheen, ja vinkki lähti näemmä uudelleen tarkistukseen :D Pahoittelen aiheutunutta vaivaa...

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta