Tarkoituksena olisi siis tallentaa tietuetaulukkoon tietoa, välittää se aliohjelmalle joka tulostaa tiedon tekstitiedostoon ja lukee sen sieltä. Alla oleva koodiesimerkki näyttää tarkoituksettomalta, mutta sen tarkoitus on vain toimia nopeasti aukeavana esimerkkinä ongelmasta.
#include <iostream> #include <fstream> using namespace std; struct tiedot { char enimi[30]; int ika; }; // Aliohjelma, joka hoitaa tiedostoon kirjoittamisen ja sieltä lukemisen void tk (tiedot []); int main () { tiedot henkilo[2]; cout<<"Syötä etunimesi: "; cin.getline(henkilo[0].enimi, 30); cout<<"\nSyötä ikäsi: "; cin>>henkilo[0].ika; tk(henkilo); } void tk (tiedot p_taulukko[]) { /* Kirjoitetaan tiedostoon. */ ofstream kirjoitus("testi.txt", ios_base::app); if (kirjoitus.fail()) { cout<<"\nFAIL!"<<endl; } else { kirjoitus.write((char *) &p_taulukko[0], sizeof(tiedot)); /* Koska tiedostoon ei voi kirjoittaa kuin char-tyyppistä muotoa, on tietueelle tehtävä tyyppimuunnos char *. */ /* Vaikka tiedostoon yrittäisi kirjoittaa allaolevalla, kommentoidulla tavalla, niin ei onnistu. */ // kirjoitus<<p_taulukko[0].enimi; kirjoitus<<'\n'; } kirjoitus.close(); // Luodaan toinen tietuetaulukko ja yritetään lukea äsken tallennetut tiedot siihen. tiedot p_taulukko2[2]; // Avataan tiedosto lukemista varten ifstream luku("testi.txt"); if (luku.fail()) { cout<<"\nFAIL!"<<endl; } else { while (!luku.eof()) { luku.read((char *) &p_taulukko2[0], sizeof(tiedot)); } luku.close(); } // Tulostetaan luetut tiedot näytölle. cout<<p_taulukko2[0].enimi<<endl; cout<<p_taulukko2[0].ika<<endl; }
Kaikki sujuu hyvin siihen asti, kunnes on aika tallentaa taulukon sisältö tiedostoon. Syystä X tiedostoon tallentuu vain käsittämätöntä sekasotkua. Ideoita?
Koneellani tuo saa aikaan syötteillä "foo" ja 13 tällaisen tiedoston:
00000000 66 6f 6f 00 30 82 e3 b7 db 57 e3 b7 58 3d f4 bf |foo.0.ã·ÛWã·X=ô¿| 00000010 aa 8d 04 08 01 00 00 00 ff ff 00 00 68 3d f4 bf |ª.......ÿÿ..h=ô¿| 00000020 0d 00 00 00 0a |.....|
Olet varannut merkkijonolle tietueessasi 30:n merkin verran tilaa. Tiedostoon tallentui 3 merkin verran tekstiä sekä lopetusmerkki, eli nollatavu. Sen jälkeen tulee 26+2 (tuohon 2 palataan kohta) merkkiä jotain tavaraa, jota muistissa sattui siinä kohdassa olemaan. Et nimittäin alustanut etunimikentän loppualkioita missään.
0d 00 00 00 on luku 13 PC-koneiden suosimassa little-endian formaatissa. Tuo viimeinen 0a on kirjoittamasi rivinvaihtomerkki '\n'.
Tuo rivinvaihtomerkki sotkee myös lukukoodisi, joka lukee aina tasan tietueen koon verran tavaraa yhdellä kieroksella.
Voit alustaa merkkijonon tavut nolliksi ennen etunimen lukemista vaikkapa memsetillä:
memset(henkilo[0].enimi, '\0', sizeof(henkilo[0].enimi));
Tällöin tulostiedosto näyttää tältä:
00000000 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 93 bf |...............¿| 00000020 0d 00 00 00 0a |.....|
Näyttää jo siistimmältä, mutta tuolla on vielä kaksi tavua roskaa. Niiden selittäminen onkin vähän hankalampaa. Kääntäjä on nimittäin lisäänyt structiisi kaksi tavua tilaa int-muuttujan eteen, jotta int-muuttujan suhteellinen osoite structin alusta olisi kivasti nelosen moninkerta (eli tässä 32). Tämä mystiikka liittyy jotenkin järjestelmän muistinhallintaan, ja siitä minä en osaa enempää valitettavasti kertoa. On mahdollista, että jokin toinen kääntäjä tai sama kääntäjä eri alustalla ei noita tavuja tuonne lisää, tai sitten lisää vähän enemmänkin. Tarinan opetus: älä koskaan kirjoita structia suoraan tiedostoon, sillä mikään standardi ei takaa tuloksesta yhtään mitään, ja tulos voi hyvinkin muuttua kun päivität kääntäjäsi.
Parempi tapa olisi miettiä jokin järkevä tiedostoformaatti. Esimerkiksi joka toinen rivi voisi sisältää etunimen, ja joka toinen iän. Ikäkin kannattanee kirjoittaa merkkijonona selvyyden vuoksi. "<<"-operaattori, jota voit soveltaa myös kirjoitus-olioosi, hoitaa tämän muunnoksen automaattisesti.
Kun tapaat opinnoissasi std::string-luokan, niin unohda char-taulukot ja ala käyttämään heti sitä, ellei ole jotain tosi hyvää syytä olla käyttämättä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.