Moro!
Harjoitustyössä pitäisi tiedostosta hakea valuttamuunnoskursseja ja tallettaa ne taulukkoon. Tiedosto, josta tiedot haetaan, näyttää esim. seuraavalta:
CNY*0.1465=USD EUR*1.4464=AUD EUR*1.3337=CAD EUR*10.3327=HKD EUR*125.1100=JPY EUR*9.5910=SEK EUR*1.8260=SGD EUR*1.3311=USD THB*1.3760=PHP USD*32.2147=THB
Tarkoituksena on siis tallettaa kyseiset kurssit struct-taulukkoon. Struct valuutta on seuraava
struct valuutta { string alkuvaluutta; double muuntokerroin; string loppuvaluutta; };
Esim. 1. riviltä pitäisi saada talletettua taulukon 1. alkioon CNY, 0,1465, USD.
Ajattelin toteuttaa ratkaisun istringstream-rakenteen avulla. Ongelma on se, että en oikein osaa käyttää rakennetta. Ideana olisi siis jakaa luettu rivi syötealkioihin ja talletetaan halutut tiedot taulukkoon (kerto- ja yhtäsuuruusmerkit heitetään siis mäkeen). Samalla voidaan tarkistaa rivin oikeellisuus (virhetarkistukset) Ongelmanani on siis se, että en osaa jakaa riviä osiin. Koodini näyttää tähän asti tältä:
#include <iostream> #include <string> #include <iomanip> #include <OHJ-1100>// kirjasto, jolla voi tutkia standardoitut valuuttalyhenteet #include <fstream> #include <cstdio> #include <sstream> using namespace std; //määritellään struct valuutta seuraavilla kentillä struct valuutta { string alkuvaluutta; double muuntokerroin; int lue_valuutat(string tiedosto, valuutta tiedot[], int koko) { ifstream virta(tiedosto.c_str()); if ( ! virta ) { cerr << "Virhe: valuuttakurssitiedostoa ei saa luettua. " << endl; } int luettujen maara = 0; string rivi; while (getline(tiedosto, rivi)) { istringstream >> ws; if (rivivirta.peek() == KOMMENTTIMERKKI or rivivirta.peek () == EOF ) { continue; } } valuutta val;
Miten tästä jatketaan? Luulen, että ratkaisu on jotain seuraavaa:
if ( not (rivivirta >> val.alkuvaluutta) ) {
Mod. lisäsi kooditagit!
Viestissäsi näyttää olevan vähän eri koodeja sekaisin, korjaatko? Muista jatkossa kooditagit.
Itse en yrittäisi sählätä virroilla vaan paloittelisin rivin string-luokan funktioilla find ja substr. Keskimmäisen osan saa luvuksi funktiolla stod (tai atof). Kaikkien mainittujen funktioiden kuvaukset löytyvät netistä.
Jos pitäisi suoraan tiedostosta lukea, käyttäisin perinteisiä C:n funktioita, joilla lukeminen on erittäin helppoa.
FILE* f = fopen("tiedosto.txt", "r"); if (!f) { printf("Virhe tiedoston avaamisessa."); return 0; } int virheita = 0; while (fscanf(f, " "), !feof(f)) { char a[4] = {0}; double b; char c[4] = {0}; if (fscanf(f, "%3[A-Z]*%lf=%3[A-Z] ", a, &b, c) != 3) { virheita += 1; fscanf(f, "%*[^\r\n] "); } else { // Laita luetut arvot johonkin. } } fclose(f); if (virheita) { printf("Tiedostossa oli %d viallista riviä.\n", virheita); }
getline funktiolle voi antaa kolmantena parametrina erotinmerkin, johon asti syötettä luetaan (linkki). Tuon avulla voit siis ensin lukea erotinmerkillä '*' ja sitten '=' ja sitten normaalisti rivin loppuun. Tällöin saat luettue rivin eri osat yksi kerrallaan hyvin yksinkertaisesti.
esim.
getline(tiedosto, rivi, '*');
Nyt sain koodia hieman eteenpäin. Päätin jakaa riviä find- ja substr-funktioiden avulla. Menee kääntäjästä läpi, mutta ohjelma ei toimi oikein. Laitan koodistani ainoastaan funktion, joka sisältää tiedoston käsittelyn ja pääohjelmasta tarvittavan osan, koska ne ovat ainoa ongelmani tällä hetkellä. Taulukko on määritelty 20:n kokoiseksi, koska tehtävänannon mukaan levytiedostossa saa olla max 20 riviä.
ONGELMAT:
- Ohjelma ei tunnista tunnettuja valuuttja (esim. EUR, JPY), ainoastaan USD:llä toimii, mutta sama ongelma tulee kohdevaluuttaa kysyttäessä. Ongelma lienee siis taulukoinnissa. Käyttämäni levytiedosto löytyy tämän threadin aivan alusta.
- Kun muuta stringin doubleksi atof-funktiolla, pitäisi minun saada myös muuntokertoimen käänteisarvo (käänteiskerroin).
#include <iostream> #include <string> #include <iomanip> #include <OHJ-1100> #include <fstream> #include <cstdio> #include <sstream> #include <stdlib.h> using namespace std; //määritellään struct valuutta seuraavilla kentillä struct valuutta { string alkuvaluutta; double muuntokerroin; double kaanteiskerroin; string loppuvaluutta; }; const char KOMMENTTIMERKKI = '%'; /**void tarkista_rivi(string tiedosto) { ifstream virta(tiedosto.c_str()) string rivi; getline(tiedosto, rivi) } */ void virheilmoitus(string teksti) { cerr << "Virhe: " << teksti << endl; } void lue_valuutat(string tiedosto, valuutta tiedot[], int koko) { // tietovirtamuuttujan alustus, virhe, jos väärä tiedostomuoto ifstream virta(tiedosto.c_str()); if ( ! virta ) { virheilmoitus("valuuttakurssitiedostoa ei saa luettua."); return; } int luettujen_maara = 0; string rivi; while (getline(virta, rivi)) // luetaan rivit { if (virta.peek() == KOMMENTTIMERKKI // jos tyhjä tai kommenttimerkki or virta.peek () == EOF ) // niin hypätään yli { continue; } } // Käytetään find ja substr-funktioita rivien paloitteluun valuutta val; string::size_type alkukohta; string::size_type erotinkohta; string kentta; alkukohta = 0; erotinkohta = rivi.find('*'); // luetaan riviä kertomerkkiin asti kentta = rivi.substr(alkukohta, erotinkohta); if (erotinkohta == string::npos) // jos kertomerkkiä ei löydy, tulostuu virhe { virheilmoitus("valuuttakurssitiedostossa virheellinen rivi."); return; } // Mikäli edellinen osamerkkijono ei ole tunnettu valuutta, tulostuu virhe. if(ohj1::onko_valuutta_olemassa(kentta) == false) { virheilmoitus("valuuttakurssitiedostossa virheellinen rivi."); virta.close(); return; } tiedot[luettujen_maara].alkuvaluutta = kentta; // sijoitus taulukkoon alkukohta = erotinkohta + 1; erotinkohta = rivi.find('=', alkukohta); // luetaan '=' -merkkiin asti if (erotinkohta == string::npos) // virhe, jos ei löydy { virheilmoitus("valuuttakurssitiedostossa virheellinen rivi."); virta.close(); return; } kentta = rivi.substr(alkukohta, erotinkohta - alkukohta); double ker = atof(kentta.c_str()); // muutetaan string double-tyyppiseksi tiedot[luettujen_maara].muuntokerroin = ker; //1/ker = tiedot[luettujen_maara].kaanteiskerroin; // Kuinka saadaan käänteisarvo??? alkukohta = erotinkohta + 1; kentta = rivi.substr(alkukohta); if (ohj1::onko_valuutta_olemassa(kentta) == false) // kohdevaluutan oikeellisuuden tarkistus { virheilmoitus("valuuttakurssitiedostossa virheellinen rivi."); virta.close(); return; } tiedot[luettujen_maara].loppuvaluutta = kentta; if (luettujen_maara >= koko) // Onko taulukossa tilaa? { virheilmoitus("valuuttakurssitiedostossa liian monta rivia."); return; } ++luettujen_maara; virta.close(); // Rivi käsitelty loppuun // "tässä paljon koodia" int main() { // Määritellään tietuetaulukko, josta löytyy tunnetut muunnokset. // Lisäksi määritellään käänteisellä muunnoksella tarvittava // kerroin samaan tietuetaulukkoon. valuutta data[20]; // Alustetaan syöttöarvot string tiedoston_nimi; cout << "Syota valuuttakurssitiedoston nimi: " << endl; getline(cin, tiedoston_nimi); lue_valuutat(tiedoston_nimi, data, 20); // "tässä paljon koodia"
Tuo koodi ei näytä lukevan tiedostosta muuta kuin ensimmäisen rivin. Tarvitset toistorakenteen joka palaa alkuun, ja lukee seuraavan rivin.
muokkaus: Paremmin ilmaistuna, "käsittelee vain yhden rivin"...
Koodi kyllä lukee kaikki rivit (while getline), mutta paloittelukoodi on virheellisesti silmukan jälkeen. Koodissa on myös muita vikoja: Silmukassa oleva peek-kikkailu toimii väärin, koska rivi on jo luettu ja peek palauttaakin merkin seuraavalta riviltä; if-lauseella siis ohittaisit rivit, joiden jälkeen tulee kommentti, sekä tiedoston viimeisen rivin, jonka jälkeen tiedosto loppuu. Tiedoston päättymistä ei tarvitse tarkistaa, koska silmukan ehto käsittelee sen jo oikein. Kommenttimerkki pitäisi tarkistaa rivin alusta. Kaikki close-kutsut ovat turhia, koska virta suljetaan automaattisesti olion tuhoutuessa eli funktion päättyessä.
Järkevä rakenne voisi olla siis tällainen:
// Luetaan, kunnes tiedosto loppuu. while (getline(virta, rivi)) { // Ohitetaan tyhjät rivit ja kommentit. if (rivi.empty() || rivi[0] == KOMMENTTIMERKKI) { continue; } // Katkaisu ja rivin lisäys tulee tähän. }
Koodisi on hieman epäselvää. Kiinnitä enemmän huomiota loogiseen sisentämiseen. Yleisesti käytetty tapa on, että sisennykset vastaavat koodin lohkoja, kuten esimerkissäni yllä. (Jotkut myös kirjoittavat avaavan aaltosulun erilliselle riville, toiset taas eivät.)
Huomasinkin, että homma toimii ainoastaan ensimmäisellä rivillä olevien valuttujen (USD ja CNY) kanssa. Yritin siirtää while-silmukan sulkevan sulun funktion loppuun (ennen kirjanpitomuuttujan kasvattamista), mutta homma ei vieläkään toimi. Lisäksi miten onnistun tuon atof-funktion avulla saamaan käänteiskertoimen (ks. koodi)
Käytän Puttyn Emacs-editoria ja olen sisentänyt koodini sen automaattisella sisennyksellä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.