* Tiedostojen käsittely C++ - tietovirroilla
* sizeof strlen
* Parametrilliset makrot
* Tiedostot joissa rivillä monta kenttää
Tied. avaaminen C: FILE *f = fopen(nimi,tyyppi); C++: ifstream fi(nimi); ofstream fo(nimi); tai: ifstream fi; ... fi.open(nimi); Lukeminen C: fscanf(f,format,osoite,...); fgets(mjono,max_pit,f); C++: fi >> muuttuja; fi.getline(mjono,max_pit); Kirjoittaminen C: fprintf(f,format,lauseke,...); C++: fo << lauseke; Sulkeminen C: fclose(f); C++: fi.close(); tai automaattisesti hajottajan ansiosta Muuttujan koko sizeof(muuttuja) // tavuina Tyypin viemä tila sizeof(tyyppi) // tavuina
C: FILE *fi = fopen(nimi,"rt") - fscanf(fi,...) - fgets(...,fi) - feof(fi) - fclose(fi) FILE *fo = fopen(nimi,"wt") - fprintf(fo,...) - fclose(fo) C++: ifstream fi(nimi) - fi >>... - fi.getline(...) - fi.eof() - fi.close() ofstream fo(nimi) - fo <<... - fo.close()Pyrimme seuraavaksi lisäämään kerho- ohjelmaamme tiedostosta lukemisen ja tiedostoon tallettamisen. Tätä varten tutustumme ensin lukemiseen mahdollisesti liittyviin ongelmiin.
Jos lukija haluaa keskittyä pelkästään C++:aan, hän voi hypätä C- ongelmia käsittelevien kappaleiden ylitse. Jos luetaan molemmat osat, on kuitenkin muistettava käyttää pareina C- funktioita ja C++- funktioita (ei siis esim. ifstream fi("oma.txt") ja fclose(fi) vaan fi.close() ).
Tiedostoja on kahta päätyyppiä: tekstitiedostot ja binääritiedostot. Tekstitiedostojen etu on siinä, että ne käyttäytyvät täsmälleen samoin kuin päätesyöttökin. Binääritiedoston etu on taas siinä, että talletus saattaa viedä vähemmän tilaa (erityisesti numeerista muotoa olevat tyypit) ja suorasaannin käyttö on järkevämpää.
Keskitymme aluksi tekstitiedostoihin, koska niitä voidaan tarvittaessa editoida tavallisella editorilla. Näin ohjelman testaaminen helpottuu, kun tiedosto voidaan rakentaa valmiiksi ennenkuin ohjelmassa edes on päätesyöttöä lukevaa osaa.
FILE *f; ... f = fopen("readst.txt","rt"); if ( f == NULL ) ... toimenpiteet jos tiedosto ei aukea ...Mikäli avaus epäonnistuu, palautetaan NULL. Ensimmäinen parametri on tiedoston nimi levyllä ja toinen sen tyyppi. Tässä tapauksessa tyyppinä on "rt" eli avaus lukemista (read) varten ja tekstitiedosto (text). Lukemista varten avattaessa tiedoston täytyy olla olemassa tai avaus epäonnistuu. Tätä voidaan tietenkin käyttää hyväksi esimerkiksi tutkittaessa onko tiedostoa lainkaan olemassa.
Kelmien kerho ry 100 ; Kenttien järjestys tiedostossa on seuraava: id| nimi |sotu |katuosoite |postinumero|postiosoite|kotipuhelin... 1|Ankka Aku |010245- 123U|Ankkakuja 6 |12345 |ANKKALINNA |12- 12324 ... 2|Susi Sepe |020347- 123T| |12555 |Perämetsä | ... 3|Ponteva Veli |030455- 3333| |12555 |Perämetsä | ...Olemme lisänneet rivin, jossa kerrotaan tiedoston maksimikoko. Tätähän tarvittiin jäsenlistan luomisessa. Nyt kokoa voidaan tarvittaessa muuttaa tiedostosta tekstieditorilla tarvitsematta tietää ohjelmoinnista mitään.
Tiedoston 1. rivi on siis kerhon koko nimi. AVATUSTA tiedostosta (jos avattu kahvaan nimellä f) tämä voitaisiin lukea seuraavasti:
fgets(jono,80,f);
Toisaalta meillä on valmis funktio f_lue_jono, joka suorittaa tarvittavia korjailuja, mikäli jono ei mahdu kokonaisuudessaan luettavaan muuttujaan:
if ( f_lue_jono(f,kerho- >kerhon_nimi, sizeof(kerho- >nimi)) <OLETUS ) return TIEDOSTO_VAARIN;
fscanf(f,"%d",&kerho- >max_jasenia);Tässä ainoa riski on siinä, että riville jäisi vielä jotakin, jolloin joutuisimme poistamaan loppurivin. Siksi voitaisiin myös ensin lukea rivi merkkijonona ja tämän jälkeen sscanf- funktiolla ottaa jonosta kokonaisluku.
if ( f_lue_jono(f,jono,sizeof(jono))<OLETUS ) return TIEDOSTO_VAARIN; if ( sscanf(jono,"%d",&kerho- >max_jasenia)<1 ) return TIEDOSTO_VAARIN;
while ( !feof(f) ) { if ( f_lue_jono(f,jono,sizeof(jono)) ... ... }
Tiedosto suljetaan funktiokutsulla
fclose(f);
f = fopen("valit.dat","wt"); ... fprintf(f,"%03d - %03d\n",alku,loppu ); /* tallettaa esim rivin: 001 - 007 */
13.4 23.6 kissa 1.9 <EOF> <- ei aina välttämättä mikään merkkiTiedostossa olisi yksi luku rivillä. Tehtävä ohjelma joka tulostaa lukujen summan ja keskiarvon, mikäli tiedostossa on vääriä merkkejä, ne jätetään huomiotta:
#include <stdio.h> int main(void) { FILE *f; double luku,summa,ka; int n; f = fopen("luvut.dat","rt"); if (!f) { printf("Tiedosto ei aukea!\n"); return 1; } summa = 0.0; n = 0; ka = 0.0; while ( !feof(f) ) { if ( fscanf(f,"%lf",&luku)<=0 ) { fgetc(f); continue; } summa += luku; n++; } fclose(f); if ( n > 0 ) ka = summa/n; printf("Lukuja oli %d kappaletta.\n",n); printf("Niiden summa oli %5.2lf\n",summa); printf("ja keskiarvo oli %5.2lf.\n",ka); return 0; }
Tiedostossa oli seuraavat laittomat merkit: kissa Lukuja oli...
Kirjoitetaanpa vastaava tiedoston luvut lukeva ohjelma C++:n tietovirroilla:
// Ohjelma lukee tiedostosta LUVUT.DAT lukuja ja tulostaa niiden // summan ja keskiarvon. #include <iostream.h> #include <fstream.h> int main(void) { double luku,summa,ka; int n; ifstream fi("luvut.dat"); if ( !fi ) { cout << "Tiedosto ei aukea!" << endl; return 1; } summa = 0.0; n = 0; ka = 0.0; while ( !fi.eof() ) { fi >> luku; if ( fi.fail() ) { fi.clear(); char ch; fi.get(ch); cout << ch; continue; } summa += luku; n++; } fi.close(); if ( n > 0 ) ka = summa/n; cout.precision(2); cout.setf(ios::showpoint | ios::fixed); cout << "\n"; cout << "Lukuja oli " << n << " kappaletta\n"; cout << "Niiden summa oli " << summa << "\n"; cout << "ja keskiarvo oli " << ka << endl; return 0; }
ifstream f("luvut.dat"); // Input File STREAMTiedosto voidaan myös jättää avaamatta esittelyn yhteydessä ja avata sitten myöhemmin open- metodilla:
ifstream f; ... f.open("luvut.dat");Tiedoston aukeamisen tila voidaan testata tietovirtaolion arvosta esimerkiksi
if ( !f ) { ...
fi >> luku;Vastaavasti kirjoittamista varten avattuun tiedostoon kirjoitettaisiin
ofstream fo("tulos.dat"); ... fo << luku;Myös kaikki muut cin ja cout - olioista tutut metodit ovat käytössä, esim:
fi.getline(s,80);
while ( !fi.eof() ) {Valitettavasti arvo on tosi vasta kun kuvitteellisen loppumerkin kohdalle on saavuttu, EI silloin kun se on seuraavana. Esimerkiksi tiedostosta
13.4<EOF>saataisiin kaksi reaalilukua silmukalla:
while ( !fi.eof() ) { fi >> luku; summa += luku; n++; }
while ( 1 ) { // Jos muuta kuin lukuja f >> luku; if ( f.eof() ) break; summa += luku; n++; }Jos silmukka saadaan pysäyttää ensimmäiseen virheeseen, olisi seuraava varmin tapa lukea:
while ( f >> luku ) { summa += luku; n++; }
fi.close();
char jono[80]; ... lue_jono(jono,80); ... f_lue_jono(f,jono,80); ... kopioi_jono(jono,80,"Kissa"); ... cin.getline(jono,80);Edellisessä on vielä vaarana se, että muutettaisiin jonon maksimikokoa 80, mutta samalla unohdettaisiin päivittää aliohjelmien kutsut. Yksi ratkaisu on määritellä vakio:
#define MAX_JONO 80 char jono[MAX_JONO]; ... cin.getline(jono,MAX_JONO); ...
char jono[80]; int koko; ... koko = sizeof(jono);sijoittaisi muuttujalle koko arvon 80.
Voisimme siis kirjoittaa kutsuja:
char jono[80]; ... lue_jono(jono,sizeof(jono)); ... f_lue_jono(f,jono,sizeof(jono)); ... kopioi_jono(jono,sizeof(jono),"Kissa"); cin.getline(jono,sizeof(jono));sizeof - operaattorille voidaan antaa parametrina myös tyypin nimi:
typedef struct { int pv; char kk_nimi[20]; int vv; } Pvm_tyyppi; ... int vuosi; Pvm_tyyppi pvm; ... ... sizeof(vuosi) ... /* Esim 2 toteutuksesta riippuen */ ... sizeof(int) ... /* - " - */ ... sizeof(char) ... /* Aina 1 */ ... sizeof(Pvm_tyyppi) /* Esim. 2+20+2 == 24 tot. riip. */ ... sizeof(pvm.kk_nimi) /* 20 */ ...
char jono[80]; int koko,pituus; ... koko = sizeof(jono); /* 80 */ kopioi_jono(jono,koko,"Kissa"); pituus = strlen(jono); /* 5 */
char *viesti ="Kissat uimaan!"; char vastaus[40] ="Ei kissat ui!"; ==> sizeof(viesti) == 2 tai 4 tai vastaavaa muistimallista riippuen sizeof(vastaus) == 40 !!!Helposti tulee käytettyä osoitinta ja saadaan osoittimen koko, kun itse asiassa tarvittaisiin itse muistipaikan koko. Tällaisista virheistä kääntäjä ei varoita mitään (koska mitään syntaksivirhettä ei ole)!
char jono[80]; ... cin.getline(jono,sizeof(jono)); ... kopioi_jono(jono,sizeof(jono),"Kissa");Voisimme tietysti määritellä makron
#define JONOS jono,sizeof(jono)jolloin kutsut supistuisivat muotoon
cin.getline(JONOS); ... kopioi_jono(JONOS,"Kissa");
#define N_S(nimi) nimi,sizeof(nimi)jonka esiintymän esiprosessori muuttaa vastaavasti
char jono[80],elain[20]; f_lue_jono(f,N_S(jono)); kopioi_jono(N_S(elain),"Kissa"); cin.getline(N_S(jono)); esiprosessori ==> f_lue_jono(f,jono,sizeof(jono)); kopioi_jono(elain,sizeof(elain),"Kissa"); cin.getline(jono,sizeof(jono)); kääntäjä ==> f_lue_jono(f,jono,80); kopioi_jono(elain,20,"Kissa"); cin.getline(jono,80);Siis makron parametrilistassa olevat sanat korvataan ensin niillä sanoilla jotka ovat makron esiintymässä. Tämän jälkeen esiintymä korvataan tällä uudella merkkijonolla ja lopulta korvattu merkkijono annetaan kääntäjän käsiteltäväksi.
#define K_2(i) (i + i) ... a = K_2(4); - > a = (4 + 4); a = K_2(n++); - > a = (n++ + n++); /* !!!!!!!!!! */Onneksi C++:ssa ei enää tarvita niin paljon parametrillisia makroja kuin puhtaassa C:ssä. Sama asia voidaan useimmiten hoitaa inline- funktiolla:
inline int k_2(int i) { return (i + i); } ... a = k_2(4); - > a = 8; a = k_2(n++); - > a = 2*n; n++; // Toimii oikein!
Volvo | 12300 | 1 Audi | 55700 | 2 Saab | 1500 | 4 Volvo | 123400 | 1<EOF>Tiedosto voitaisiin lukea ja vaikkapa tulostaa näytölle seuraavalla ohjelmalla:
#include <stdio.h> #include <stdlib.h> typedef struct{ char nimike[20]; double hinta; int kpl; } Tuote_tyyppi; int tulosta_tuotteet(void) { FILE *f; Tuote_tyyppi tuote; f = fopen("TUOTTEET.DAT","rt"); if (!f) return 1; printf("\n\n\n"); printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n"); while ( !feof(f) ) { fscanf(f,"%s |%lf |%d",&tuote.nimike,&tuote.hinta,&tuote.kpl); printf("%- 20s %7.0lf %4d\n",tuote.nimike,tuote.hinta,tuote.kpl); } printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n"); printf("\n\n\n"); fclose(f); return 0; } int main(void) { if (tulosta_tuotteet()) { printf("Tuotteita ei saada luetuksi!\n"); return 1; } return 0; }Ohjelmassa on kuitenkin seuraavia huonoja puolia:
Volvo | 12300 | 1 Audi 55700 | 2 Saab | 1500 | 4 Volvo | 123400 | 1 <EOF>
FILE *f; Tuote_tyyppi tuote; char rivi[80]; ... while ( !feof(f) ) { if ( f_lue_jono(f,N_S(rivi)) <= OLETUS ) continue; sscanf(rivi,"%s |%lf |%d",&tuote.nimike,&tuote.hinta,&tuote.kpl); printf("%- 20s %7.0lf %4d\n",tuote.nimike,tuote.hinta,tuote.kpl); } ...Tässäkin vaihtoehdossa on vielä muutamia huonoja puolia:
+-------------------------------------------------+ | |V|o|l|v|o| ||| | |1|2|3|0|0| ||| |1| | | | | | | +--------------------------------------0----------+
NUL- merkkiJos saisimme erotettua tästä 3 merkkijonoa:
pala1 pala2 pala3 +-------------- ------------- ----- | |V|o|l|v|o| | |1|2|3|0|0| | |1| | +------------0- -----------0- ---0-voisimme kustakin palasesta erikseen ottaa haluamme tiedot. Esimerkiksi 1. palasesta saadaan tuotteen nimi, kun siitä poistetaan turhat välilyönnit. Hinta saataisiin 2. palasesta kutsulla
sscanf(pala2,"%lf",&tuote.hinta);
0 1 2 3 4 +-------------------+ palaset | o | o | o | o | o | +-+---+---+---------+ +-------+ | +-----------------+ | +---+ | v v v +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| | | |1| | | | | | | +--------------0-----------------0-----0----------+
NUL- merkkiTässä olisi muutamia ongelmia:
Ennen kutsua: +---rivi v +-------------------------------------------------+ | |V|o|l|v|o| ||| | |1|2|3|0|0| ||| |1| | | | | | | +--------------------------------------0----------+
1. Kutsu: p = strtok(rivi,"|");
+---rivi v +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| ||| |1| | | | | | | +--------------0-----------------------0----------+ ^ +--- p
2. Kutsu: p = strtok(NULL,"|");
+---rivi v +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| | | |1| | | | | | | +--------------0-----------------0-----0----------+ ^ p ---------+
3. Kutsu: p = strtok(NULL,"|");
+---rivi v +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| | | |1| | | | | | | +--------------0-----------------0-----0----------+ ^ p ---------------------------+
4. Kutsu: p = strtok(NULL,"|");
+---rivi v +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| | | |1| | | | | | | +--------------0-----------------0-----0----------+ p ----> NULLNäyttäisi siis siltä, että strtok sopii hyvin tarkoituksiimme:
int rivi_tuotteeksi(char *rivi, Tuote_tyyppi *tuote) { char *p; p = strtok(rivi,"|"); if (!p) return 1; poista_tyhjat(p); kopioi_jono(N_S(tuote- >nimike),p); p = strtok(NULL,"|"); if (!p) return 1; if ( sscanf(p,"%lf",&tuote- >hinta) != 1 ) return 1; p = strtok(NULL,"|"); if (!p) return 1; if ( sscanf(p,"%d",&tuote- >kpl) != 1 ) return 1; return 0; } int tulosta_tuotteet(void) { ... while ( !feof(f) ) { if ( f_lue_jono(f,N_S(rivi)) <= OLETUS ) continue; if ( rivi_tuotteeksi(rivi,&tuote) ) continue; printf("%- 20s %7.0lf %4d\n",tuote.nimike,tuote.hinta,tuote.kpl); } ... }
Audi||20
/****************************************************************************/ char /* */ *palanen( /* Osoitin merkkijonon palaseen. */ char *jono ,/* s Pätkittävä jono, turmeltuu! */ char *erottimet ,/* s Merkit joiden kohdalta katkaistaan. */ int *jaljella /* t Paljonko jonoa on vielä jäljellä (- 1 loppu)*/ ) /* ** Funktiolla pätkitään merkkijonoa osiin. 1. kutsukerralla välitetään ** tutkittava jono ja tämän jälkeen seuraavilla NULL osoitin. ** Funktio vaihtaa löytämänsä erotinmerkit null- merkeiksi! ** ** Muuttuu: jono ** Algoritmi: ** Esimerkki: 012345678901234 ** jono="Aku|Ankka||12" erottimet="|" ** 1. kutsu palanen(jono,"|",&j) - > "Aku" , j=10 ** 2. kutsu palanen(NULL,"|",&j) - > "Ankka", j=4 ** 3. kutsu palanen(NULL,"|",&j) - > "" , j=3 ** 4. kutsu palanen(NULL,"|",&j) - > "12" , j=0 ** 5. kutsu palanen(NULL,"|",&j) - > "" , j=- 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ { static char *st=""; static int p1=0,p2=0,pit=0; if (jono) { /* 1. kutsukerta, alustetaan apumuuttujat */ st = jono; pit = strlen(jono); p1 = 0; } else p1 = p2+1; /* Muilla kerroilla jatketaan siitä mihin viim. jäätiin. */ if ( p1 > pit ) { *jaljella = - 1; return st+pit; /* Tyhjä merkkijono, kun osoitetaan jonon null- tavuun. */ } p2 = p1+strcspn(st+p1,erottimet); st[p2] = 0; *jaljella = pit- p2; return st+p1; }
Funktio strcspn palauttaa ensimmäisen indeksin, josta löytyy erottimet merkkijonon jokin merkki. Mikäli merkkiä ei löydy, palautetaan jonon pituus.
Funktion toimintaidea on seuraava:
2. Kutsu: p = palanen(NULL,"|",&j); Kutsuun tultaessa: (pit == 19, p1 == 0, p2 == 7)
+---st +---st+p2+1 v v 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| ||| |1| | | | | | | +--------------0-----------------------0----------+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
Etsitään |- merkkiä alkaen paikasta 8. Löytyy jonoon st+p2+1 nähden paikasta 8. Siis alkuperäiseen jonoon nähden paikasta 16. Muutetaan arvot: (pit == 19, p1 == 8, p2 = 8+8)
+---st +---st+p1 +----st+p2 v v v 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 +-------------------------------------------------+ | |V|o|l|v|o| | | | |1|2|3|0|0| | | |1| | | | | | | +--------------0-----------------0-----0----------+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
palautetaan st+p1 ja *jaljella = 19- 16 = 3Näin olemme saaneet funktion, jota voidaan huoletta kutsua myös merkkijonon loppumisen jälkeenkin:
int rivi_tuotteeksi(char *rivi, Tuote_tyyppi *tuote) { char *p; int j; p = palanen(rivi,"|",&j); poista_tyhjat(p); kopioi_jono(N_S(tuote- >nimike),p); p = palanen(NULL,"|",&j); if ( sscanf(p,"%lf",&tuote- >hinta) != 1 ) return 1; p = palanen(NULL,"|",&j); if ( sscanf(p,"%d",&tuote- >kpl) != 1 ) return 1; return 0; }
// Projektiin +ALI\mjonot.c #include <iostream.h> #include <fstream.h> #include <strstrea.h> #include <stdio.h> #include <string> using namespace std; #include "mjonotpp.h" struct tTuote { string nimike; double hinta; int kpl; }; void alusta(tTuote &tuote) { tuote.nimike = ""; tuote.hinta = 0.0; tuote.kpl = 0; } int lue(istream &is,tTuote &tuote) { char jono[50]; alusta(tuote); is.getline(N_S(jono),'|'); tuote.nimike = poista_tyhjat(jono); is.getline(N_S(jono),'|'); if ( sscanf(jono,"%lf",&tuote.hinta) < 1 ) return 1; is.getline(N_S(jono),'\n'); if ( sscanf(jono,"%d",&tuote.kpl) < 1 ) return 1; return 0; } int rivi_tuotteeksi(char *rivi, tTuote &tuote) { istrstream sstr(rivi); return lue(sstr,tuote); } int tulosta_tuotteet(void) { tTuote tuote; char rivi[80]; ifstream fi("TUOTTEET.DAT"); if ( !fi ) return 1; cout << "\n\n\n"; cout << "-------------------------------------------\n"; while ( 1 ) { if ( !fi.getline(rivi,sizeof(rivi))) break; if ( rivi_tuotteeksi(rivi,tuote) ) continue; printf("%-20s %7.0lf %4d\n",tuote.nimike.c_str(),tuote.hinta,tuote.kpl); } cout << "-------------------------------------------\n"; cout << "\n\n\n"; return 0; } int main(void) { if ( tulosta_tuotteet() ) { cout << "Tuotteita ei saada luetuksi!\n"; return 1; } return 0; }
int rivi_tuotteeksi(char *rivi, tTuote &tuote) { istrstream sstr(rivi); return lue(sstr,tuote); }Koska tämä tietovirta käyttäytyy aivan samoin kuin päätesyöttökin, voidaan tehdä aliohjelma lue, joka lukee tuotteen tiedot ikäänkuin tiedostosta. Itse asiassa ohjelman lukusilmukassa voitaisiin aivan hyvin kutsua myös
while ( 1 ) { if ( lue(fi,tuote) ) break; printf("%-20s %7.0lf %4d\n",tuote.nimike.c_str(),tuote.hinta,tuote.kpl); }mutta tällöin lukeminen loppuisi ensimmäiseen virheeseen tai sopivalla tiedostolla rivijako menisi sekaisin. Tämän vuoksi luettaessa tiedostoja joissa rivi on "yksi tietue", kannattaa lukea ensin rivin sisältö merkkijonoon ja sitten muuttaa tämä merkkijono tietueeksi.
C:ssä merkkijonoa yritettiin purkaa sscanf- funktiolla, joka onkin erittäin helppokäyttöinen niin kauan kuin merkkijono sisältää vain lukuja, jotka voidaan kaikki erottaa kerralla. Jos kuitenkin erotettavien osien joukossa on merkkijonoja, menee ongelma tunnetusti hankalammaksi (tosin voidaan hoitaa erikoistapauksissa).
Jos kuitenkin merkkijonosta pitää saada 0-n erillistä osaa (merkkijonoa tai lukua), ei sscanf:ää voida käyttää!
C++:n strstream on taas siitä mukava, että se tekee merkkijonoon samanlaisen "lukuosoittimien" kuin muutkin tietovirrat tiedostoon. Näin voidaan palasia erotella yksi kerrallaan ja "lukemista" voidaan jatkaa siitä, mihin edellinen lukeminen jäi.
// Projektiin +ALI\mjonot.c #include <iostream.h> #include <iomanip.h> #include <fstream.h> #include <strstrea.h> #include <stdio.h> #include <string> using namespace std; #include "mjonotpp.h" //---------------------------------------------------------------------------- class cTuote { string nimike; double hinta; int kpl; void alusta() { nimike = ""; hinta = 0.0; kpl = 0; } public: int alusta(const char *rivi) { // Alustaa jonosta Volvo|4500|3 istrstream sstr((char *)rivi); return lue(sstr); } cTuote() { alusta(); } ostream &tulosta(ostream &os) const { long oldf = os.setf(ios::left); os << setw(20) << nimike << " " << setiosflags(ios::right) << setw(7) << hinta << " " << setw(4) << kpl; os.flags(oldf); return os; } int lue(istream &is) { char jono[50]; alusta(); is.getline(N_S(jono),'|'); nimike = poista_tyhjat(jono); is.getline(N_S(jono),'|'); if ( sscanf(jono,"%lf",&hinta) < 1 ) return 1; is.getline(N_S(jono),'\n'); if ( sscanf(jono,"%d",&kpl) < 1 ) return 1; return 0; } }; ostream &operator<<(ostream &os,const cTuote &tuote) { return tuote.tulosta(os); } istream &operator>>(istream &is,cTuote &tuote) { tuote.lue(is); return is; } //---------------------------------------------------------------------------- class cTuotteet { string nimi; public: cTuotteet(const char *n) { nimi = n; } int tulosta(ostream &os) const; }; int cTuotteet::tulosta(ostream &os) const { cTuote tuote; char rivi[80]; ifstream f(nimi.c_str()); if ( !f ) return 1; cout << "\n\n\n"; cout << "-------------------------------------------" << endl; while ( !f.eof() ) { if ( !f.getline(N_S(rivi)) ) continue; if ( tuote.alusta(rivi) ) continue; os << tuote << endl; } cout << "-------------------------------------------\n"; cout << "\n\n\n" << endl;; return 0; } //---------------------------------------------------------------------------- int main(void) { cTuotteet tuotteet("TUOTTEET.DAT"); if ( tuotteet.tulosta(cout) ) { cout << "Tuotteita ei saada luetuksi!" << endl; return 1; } return 0; }
// tuoterek.cpp /* Ohjelma lukee tiedostoa TUOTTEET.DAT, joka on muotoa: Volvo | 12300 | 1 Audi | 55700 | 2 Saab | 1500 | 4 Volvo | 123400 | 1 Ohjelma tulostaa kuhunkin tuoteluokkaan kuuluvien tuotteiden yhteishinnat ja kappalemäärät sekä koko varaston yhteishinnan ja kappalemäärän. Eli em. tiedostosta tulostetaan: ------------------------------------------- Volvo 135700 2 Audi 111400 2 Saab 6000 4 ------------------------------------------- Yhteensä 253100 8 ------------------------------------------- Vesa Lappalainen 15.3.1996 Projektiin: tuoterek.cpp,ALI\mjonot.c */ #include <iostream.h> #include <iomanip.h> #include <strstream.h> #include <fstream.h> #include <stdio.h> #include <string.h> #include <string> using namespace std; #include <mjonot.h> #include <mjonotpp.h> //--------------------------------------------------------------------------- class cTuote { string nimike; double hinta; int kpl; public: int alusta(const char *n,double h,int k=0) { nimike = n; hinta = h; kpl = k; return 0; } int alusta(const char *rivi) { // Alustaa jonosta Volvo|4500|3 istrstream sstr((char *)rivi); return lue(sstr); } int alusta() { return alusta("",0,0); } cTuote() { alusta(); } cTuote(const char *rivi) { alusta(rivi); } int lue(istream &is) {...} ostream &tulosta(ostream &os) const { ... } void ynnaa(const cTuote &tuote) { hinta += tuote.hinta * tuote.kpl; kpl += tuote.kpl; } cTuote &operator+=(const cTuote &tuote) { ynnaa(tuote); return *this; } const string &Nimike() const { return nimike; } }; ostream &operator<<(ostream &os,const cTuote &tuote) { return tuote.tulosta(os); } istream &operator>>(istream &is,cTuote &tuote) { tuote.lue(is); return is; } //--------------------------------------------------------------------------- const int MAX_TUOTTEITA = 10; class cTuotteet { string nimi; int tuotteita; cTuote tuotteet[MAX_TUOTTEITA]; cTuote yhteensa; public: cTuotteet(const char *n) : nimi(n), yhteensa("Yhteensä") { tuotteita = 0; } ostream &tulosta(ostream &os) const; int etsi(const string &tnimi) const; int lisaa(const string &tnimi); int ynnaa(const cTuote &tuote); int lue(const char *s=NULL); }; int cTuotteet::etsi(const string &tnimi) const { int i; for (i=0; i<tuotteita; i++) if ( tuotteet[i].Nimike() == tnimi ) return i; return -1; } int cTuotteet::lisaa(const string &tnimi) { if ( tuotteita >= MAX_TUOTTEITA ) return -1; tuotteet[tuotteita].alusta(tnimi.c_str(),0.0,0); return tuotteita++; } int cTuotteet::ynnaa(const cTuote &tuote) { if ( tuote.Nimike() == "" ) return 1; int i = etsi(tuote.Nimike()); if ( i < 0 ) i = lisaa(tuote.Nimike()); if ( i < 0 ) return 1; tuotteet[i] += tuote; yhteensa += tuote; return 0; } int cTuotteet::lue(const char *s) { char rivi[80]; if ( s ) nimi = s; ifstream f(nimi.c_str()); if ( !f ) return 1; while ( f.getline(N_S(rivi)) ) { cTuote tuote(rivi); if ( ynnaa(tuote) ) cout << "Rivillä \"" << rivi << "\" jotain pielessä!" << endl; } return 0; } ostream &cTuotteet::tulosta(ostream &os) const { int i; os << "\n\n\n"; os << "-------------------------------------------\n"; for (i=0; i<tuotteita; i++) os << tuotteet[i] << "\n"; os << "-------------------------------------------\n"; os << yhteensa << "\n"; os << "-------------------------------------------\n"; os << "\n\n" << endl; return os; } int main(void) { cTuotteet varasto("tuotteet.dat"); if ( varasto.lue() ) { cout << "Tuotteita ei saada luetuksi!" << endl;; return 1; } varasto.tulosta(cout); return 0; }
|
|
|
|
... Mittakaava ja matka>1:10000 10 cm[RET] Matka maastossa on 1.00 km. Mittakaava ja matka>1:200000 20[RET] Matka maastossa on 4.00 km. Mittakaava ja matka>loppu[RET] Kiitos!
... Mittakaava ja matka>1:10000 10 cm[RET] Matka maastossa on 1.00 km. Mittakaava ja matka>0.20 dm[RET] Matka maastossa on 0.20 km. Mittakaava ja matka>loppu[RET] Kiitos!