Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++ ja tietuetaulukoiden käsittely

P1rkk4 [12.11.2007 21:36:36]

#

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?

map_ [12.11.2007 23:48:07]

#

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

Vastaus

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

Tietoa sivustosta