Olen kokeillut etsiä jotain toimivaa ja sopivan yksinkertaista tapaa tallentaa tietoja tietuetaulukosta tiedostoon. Parin kokeilun jälkeen olen saanut aina tämän tyyppisiä virheitä:
"'cout' : ambiguous symbol", sama cin, cerr yms. Mistä tälläiset johtuu?
http://tiira.cedunet.com/~jtahka/oppim/
Tuolta löytynyttä esimerkkiä soveltaen ilmenee ko. virheet. Katselin täältä keskusteluista löytyneitä esimerkkejä mutta ne on hieman haastavia tarkasteltavaksi ilman kommentteja.
Hmm. Millaista tietuetta olet tallentamassa?
#define tiedosto "save.cdb"; ofstream taltio; //esitellään tietovirtalio void tallenna(void) { int k = 0; //avataan tiedosto kirjoittamista varten taltio.open(tiedosto, ios::out); //tarkistetaan onnistuiko avaus, //osoitin 0 jos avaus ei onnistunut if(!taltio){ cerr << "Tiedostoa " << tiedosto; << "ei voinut avata !" << endl; exit(1); //poistutaan ohjemasta, vaatii <stdlib.h>:n } for (k=0;k<counter;k++) { taltio << tietokanta[k].merkki << ' ' << tietokanta[k].malli << ' ' << tietokanta[k].vuosimalli << endl; } taltio.close(); }
Tuollainen nopeasti kyhätty tekele. Tietokanta on globaali tietuetaulukko.
Otapa ensiksi define-rivin perästä puolipiste pois. Se kun ei ole C++:aa vaan esikääntäjän tekstiä.
Nyt koko homma kaatuu siihen, että jos jokin kirjoitettavista tiedosta sisältää välin, et enää tiedä, missä menee merkin ja mallin raja. Käytä siis binäärimuotoista tallennusta.
Ja koodin voisi sisentää ymmärrettävästikin.
Ja tuossa on tuo exit funktio joka vaatii sen stdlib:in jota tuossa ei ole includettu.
Ja pistäppä tuonne alkuun
using namespace std;
Meitsi kirjoitti:
Ja tuossa on tuo exit funktio joka vaatii sen stdlib:in jota tuossa ei ole includettu.
Ja pistäppä tuonne alkuun
using namespace std;
Joo, no tuo on vain ohjelman yksi aliohjelma. stdlib.h on sisällytetty ja nimiavaruuskin on käytössä.
Metabolix kirjoitti:
Otapa ensiksi define-rivin perästä puolipiste pois. Se kun ei ole C++:aa vaan esikääntäjän tekstiä.
Kappas, en muistanutkaan tuota.
lainaus:
Nyt koko homma kaatuu siihen, että jos jokin kirjoitettavista tiedosta sisältää välin, et enää tiedä, missä menee merkin ja mallin raja. Käytä siis binäärimuotoista tallennusta.
Tietoja syöttäessä tarkistetaan ettei tule välilyöntejä (kokeilin ja totesin ettei toimi 100%)
lainaus:
Ja koodin voisi sisentää ymmärrettävästikin.
Tuo nyt oli tuollein nopeasti kyhätty.
void tallenna(void) { char * buffer; long size; ifstream file (filename, ios::out|ios::binary|ios::trunc); //<-Kirjoitetaan binäärimuoteisena ja poistetaan jos tiedosto on jo olemassa size = file.tellg(); file.seekg (0, ios::beg); buffer = new char [size]; file.read (buffer, size); file.close(); cout << "the complete file is in a buffer"; delete[] buffer; }
Tälläinen koodipätkä on nyt kasassa. Miten tällä pystyy tulostamaan tiedostoon tietuetaulukon sisällön?
No niin, sain tallenna funktion toimimaan mutta nyt ohjelma kaatuu jostain syystä lataa funktioon. Ohjelman tarkoituksena on ladata vanhat tiedot tiedostosta aina ohjelma käynnistyessä ja tallentaa muutokset suljettaessa ohjelma. Mitään virheilmoitusta ohjelma ei anna vaan kääntää sorsan hyvin.
edit: Virhe löytyi strcat -kohdasta. Voisiko joku valaista miksi se kaatuu siihen?
void lataa(void) { int tapa = 1; int i = 0; int pituus = 0; int laskuri = 0; char temp[512] = ""; char merkki; int k = 0; ifstream iFile(tiedosto); //avataan tiedosto kirjoittamista varten //tarkistetaan onnistuiko avaus if(!iFile){ cerr << "Tiedostoa " << tiedosto << "ei voinut avata !" << endl; exit(EXIT_FAILURE); } do{ //Käydään tiedosto läpi merkki kerrallaan ja sijoitetaan merkki temp muuttujaan iFile.get(merkki); strcat(temp, &merkki); //<- CIRHE ON TÄSSÄ. AIHEUTTAA OHJELMAN KAATUMISEN if (merkki=='\n'){ //Rivin vaihdon sattuessa sijoitetaan tempin sisältö taulukkoon pituus = strlen(temp); for(i=0;i<pituus;i++){ switch(tapa){ case 1: tietokanta[laskuri].merkki[i] = temp[i]; break; case 2: tietokanta[laskuri].malli[i] = temp[i]; break; case 3: tietokanta[laskuri].vuosimalli[i] = temp[i]; break; default: cout << "Ohjelmassa sattui virhe ja se lopetetaan!"; exit(EXIT_FAILURE); break; } if(temp[i]=='_')//Tallennus tekee '_'-merkin välilyöntien paikalle tapa++; } for(i=0;i<512;i++){ //Tyhjennetään temp muuttuja ennen uutta riviä temp[i] = 0; } laskuri++; //Siirrytään seuraavaan taulukon alkioon } }while(!iFile.eof()); iFile.close(); }
Tulosta joka välissä konsoliin, mitä teet. Se helpottaa debuggausta olennaisesti.
Mutua: strcat
ottaa parametreikseen nollatavuihin loppuvat stringit, mutta annat sille yksittäisen merkin. Se sitten yrittää lukea tuosta merkistä muistia eteenpäin, kunnes käyttis päättää, ettei se enää käy, ja kaataa ohjelman.
Kiitoksia tuosta konsoliin tulostamisesta. Ei tullut mieleen itselle ja auttoi kivasti eteenpäin. Nyt sain ohjelman lukemaan tiedostoa rivi kerrallaan mutta se ei osaa laittaa sitä oikeaan järjestykseen:
do{ //Käydään tiedosto läpi rivi kerrallaan ja sijoitetaan rivi temp muuttujaan iFile.getline (temp, 512);// Luetaan rivi tiedostosta temp muuttujaan "fiat_brava_2004\n" pituus = strlen(temp); cout << temp << endl; //DEBUG for(i=0;i<pituus;i++){ //Sijoitetaan tempin sisältö tietokantaan switch(tapa){ case 1: if(temp[i]=='_'){ tapa++; //Jos tulee '_'-merkkin niin siirrytään seuraavaan temp alkioon ja sijoitetaan tulevat arvot malliin. break; } else{ tietokanta[counter].merkki[i] = temp[i]; break; } case 2: if(temp[i]=='_'){ tapa++; break; } else{ tietokanta[counter].malli[i] = temp[i]; break; } case 3: tietokanta[counter].vuosimalli[i] = temp[i]; break; default: cout << "Ohjelmassa sattui virhe"; break; } } counter++; tapa = 1; }while(!iFile.eof());
tiedostossa olevat tiedot ovat esim: fiat_brava_1993\nford_focus_2001 jne. Ohjelma sijoittaa .merkkiin tiedot mutta ei syötä kahteen myöhempään mitään.
eikä olisi helpompaa käyttää c:n functioita fwrite ja fread
esim.
/* auto tietue */ typedef struct auto { char *merkki; char *malli; char *vuosi; }; /* tietokanta taulu */ auto tietokanta[2]; /* asetetaan jotain arvoja */ tietokanta[0].merkki = "Ford"; tietokanta[0].malli = "Mondeo"; tietokanta[0].vuosi = "2005"; tietokanta[1].merkki = "Peugeot"; tietokanta[1].malli = "406"; tietokanta[1].vuosi = "2000"; /* tallennetaa tietokanta */ FILE *fp = fopen("tietokanta.db", "w"); fwrite(tietokanta, sizeof(tietokanta), 1, fp); fclose(fp); /* sitten kun haluat lukea */ auto tietokanta[2]; FILE *fp = fopen("tietokanta.db", "r"); fread(&tietokanta, sizeof(auto), 2, fp); fclose(fp);
Eihän tuo Mazulin koodi voi toimia. typedef ei toimi noin, eikä tuollaista arvojen asettelua voi käyttää. Tiedostoon ei myöskään tulostu mitään tekstiarvoja, vaan se osoitin, ja siitäpä ei olekaan mitään iloa kenellekään. Kannattaisi testata, ennen kuin laittaa esimerkkiä.
Ongelmasi on siinä, että kun törmäät alaviivaan, esimerkiksi i = 10, jatkat kirjoittamista seuraavaan kohtaan kohdasta 10 etkä alusta. Siksi pitäisi tehdä tähän tapaan:
int i, j; // ... for (i = j = 0; i < pituus; i++, j++) // huom. j // ... if (temp[i] == '_') { tietokanta[counter].merkki[j] = 0; // Muista lopetusmerkit tauluihin!!! j = -1; // ja seuraavan kierroksen alussa se on taas 0, eli kirjoitus alkaa seuraavan taulun alusta } // ... tietokanta[counter].merkki[j] = temp[i]; // Näissä se j käyttöön
Tuossa siis ideoita, saat itse lisäillä ja muutella ne koodiisi.
Suosittelen for-silmukoiden kirjoittamista selkeämpään muotoon, ettei tarvitse etsiä puolipisteitä siitä epämääräisestä merkkipötköstä.
switch-systeeminkin voi kirjoittaa vähän vähemmillä breakeilla, kun siirtää ne if- ja else-palikoiden ulkopuolelle. Koodikin selkenee ihmeesti.
Toinen hyvä tapa tähän olisi laittaa kaikki erikseen (tulee peräti paljon vähemmän koodia):
for (i = j = 0; i < pituus && temp[i] != '_'; i++, j++) tietokanta[counter].merkki[j] = temp[i]; tietokanta[counter].merkki[j] = 0; for (j = 0; i < pituus && temp[i] != '_'; i++, j++) tietokanta[counter].malli[j] = temp[i]; tietokanta[counter].malli[j] = 0; for (j = 0; i < pituus; i++, j++) tietokanta[counter].vuosimalli[j] = temp[i]; tietokanta[counter].vuosimalli[j] = 0;
Joo, nyt se tallentaa vielä aina lopettaessa kaksi alaviivaa tiedostoon mitä pitää tuumailla. Samoin virheentarkistus muissa funktiossa. Sitten varmaan käyn optimoimaan näitä/ opettelemaan optimoimaan näitä. Mutta kiitokset avusta.
Tälläinen ongelma on muodostunut. Pitäisi saada ohjelma tulostamaan tiedot siististi käyttäjälle ja ajattelin että tämä hoituu kätevästi sijoittamalla tietokannan sisällön string muuttujaan tähän malliin:
string esitys = .merkki
sitten stringin 20. alkiosta eteenpäin se tulostaa .mallin ja 40. eteenpäin tulostaa .vuosimallin. Välissä olevat merkit ovat tyhjiä. Lopuksi tämä stringi tulostetaan käyttäjälle.
Ongelmana on stringiin sijoittaminen. Miten tämä onnistuisi kätevästi?
string esitys = ""; esitys.data(); //Palautettaan osoitin stringin alkuun esitys = tietokanta[y].merkki; //Stringin alussa on .merkki esitys.at[20] = tietokanta[y].malli; //Virhe, ei toimi. 20. merkistä eteenpäin .malli esitys.at[30] = tietokanta[y].vuosimalli; //Virhe, ei toimi 30. merkistä eteenpäin .vuosimalli cout << esitys << endl; //Tulostetaan käyttäjälle esitys.clear(); //Tyhjennetään stringi
Metodi at on string-luokkaan kuuluva. Elä käytä hakasulkeita. Sitä paitsi at-metodi ei toimi noin.
The at() function returns a reference to the element in the string at index loc. The at() function is safer than the [] operator, because it won't let you reference items outside the bounds of the string. Lähde: http://cppreference.com/cppstring/at.html
Varmaankin kannattaa muuttaa esitys-olion kokoa resize-metodilla luonnin jälkeen. Tämän jälkeen sijoitat merkkisi kohtaan [0]. Ja resizella onnistuu noitten tyhjien merkkien asettaminen kätevästi.
string temp=""; temp.resize(20,' '); cout << temp.size(); // näyttää nyt 20, jokainen alkio sisältää merkin ' '. temp += "Tää tulee tyhjien merkkien perään.";
Seuraava kysymys mitä mietin koskee tiedostosta lukemista. Copy-Pastesin netistä listan missä näkyy auton malli, valmistaja ja vuosiluvut milloin ko. merkkiä tehty. Nyt pyrkisin käyttämään tätä työssäni avuksi selvittämään onko syötetty merkki/malli oikea. Tiedot on tallennettu tiedostoon tälläiseen ulkoasuun(eroaa omasta näkymästä):
Cab-Chassis Ford 88 89 90
Caballero GMC 85 86 87
Cabrio Volkswagen 95 96 97 98 99 00 01 02
Cabrio Yugo 90 91
Tiedot on kopioitu täältä notepadiin: http://www.kbb.com/kb/ki.dll/kw.kc.mn?kbb&&108&u
1) Näkeekö mistään mikä merkkejä mallin ja merkin välillä on? \t siinä on mutta onko se kahteen kertaan vai \n + \t.
2) Ohjelma pätkä joka lukee auton valmistajan tiedostosta ja listaa ne käyttäjälle joka voi näistä valita merkin. Tämän perusteella ohjelma listaa sitten ko. merkin mallit ja viimeiseksi vuodet milloin mallia tehty.
Aika simppeli hommahan tuo on loppupeleissä. Apua tarvitsen näin aluksi tuon tiedoston lukemisessa / käsittelyssä. Muuten uskoisin saavani itse hoidettua loppuun. Eli miten saan luettua tuollaisesta syötteestä esim merkin?
iFile.getline (temp, 512);
Tällä se varmaan lähtee liikenteeseen. Tuosta pitäisi tabuloinnit ja muu turha saada pois.
Ei välttämättä ole hyvä käyttää tuota getline-funktiota, kun ei voi tietää, jos rivi vaikka olisi pidempi. (Eihän se yleensä ole, mutta kuitenkin.) Tai ainakin pitää muistaa, ettei se välttämättä ole koko totuus. Minä edelleenkin lukisin tiedostoa binäärimuodossa pala kerrallaan ja käsittelisin sitä niin. Silloin on helpointa tehdä mitä tahansa.
Tässä esimerkki turhien poistosta:
void Poista_Turhat(char * Teksti, char * UusiTeksti, int Turhien_Maara, char * Turhat) { int i, j, k, Siirrytaan; for (i = j = 0; Teksti[i] != 0; i++) { Siirrytaan = 0; // Käydään läpi kaikki ei-halutut merkit for (k = 0; k < Turhien_Maara; k++) { // Onko tämä sellainen? if (Teksti[i] == Turhat[k]) { // Merkitään, että siirrytään, ja mennään pois tarkistussilmukasta Siirrytaan = 1; break; } } // Jos vääriä löytyi, mennään seuraavaan merkkiin if (Siirrytaan) continue; // Muuten laitetaan merkki uuteen tekstiin kohdalleen UusiTeksti[j] = Teksti[i]; j++; } UusiTeksti[j] = 0; } // Esimerkki: char Ei_Naita[] = ":\t)äln"; int Turhia = 6; char Teksti[] = "Erkki-ritari on apina! Sillä on iso nenä\t\t:)"; char Sensuroitu[100]; Poista_Turhat(Teksti, Sensuroitu, Turhia, Ei_Naita); // Sensuroitu = "Erkki-ritari o apia! Si o iso e";
Tuosta saat varmaan helposti tehtyä sellaisen, joka poistaa peräkkäiset tietyt merkit.
Kiitokset edellisestä koodista. Tuosta oli huomattava apu. Tein ohjelman joka muokkasi tiedoston sisällön hieman luettavampaan muotoon.
Käytetään nyt tätä ketjua apuna...
switch(syote){ for (i = 0; i < laskuri + 1; i++) { case i: for (k = 0; k<strlen(ehto[i].malli); k++) { tietokanta[counter].malli = ehto[i-1].malli; } break; } }
Tälläinen koodipätkä. Switch ei huoli tuollaista case rakennetta että ne tulostettaisiin for-silmukalla vaan niiden pitäsi olla vakiot. Onko mitään kiertotietä tähän?
Zmyrgel: switch-rakenteen case-lauseiden arvojen tulee olla vakioita. Ehdotan, että tuollaisessa tapauksessa teet tuon yksinkertaisesti if-lauseilla. Sitpä paitsi, mihin tuossa edes tarvitaan switch:iä?
Ja toisekseen, koodissasi on lause ehto[i - 1].malli. Entäs jos i = 0? Silloin tuo koodi osoittaa minne sattuu :/ Parhaimmillaan kaataa koko ohjelman.
Niin, ja mihin tuota k-silmukkaa tarvitaan? Tällä hetkellä se ainakin suorittaa strlen() kertaa saman, muuttumattoman lausekkeen jossa k:ta ei edes käytetä:
tietokanta[counter].malli = ehto[i-1].malli;
Eli sen silmukan voisi kait jättää pois. Ellei sen sitten ollut tarkoitus kopioida merkkijonoa?
tietokanta[counter].malli[k] = ehto[i].malli[k];
Joo, k:ta tarvitaan merkkijonon kopioimiseen. Se nähtävästi unohtu laittaa tuohon malliin. Hyvä pointti tuon i:n suhteen. En huomannutkaan sitä kun nopeesti kyhäsin tätäkin pätkää.
Pitää toteuttaa tämä sitten if-lauseilla.
Switchin tarkoitus olisi että aluksi ennen sitä ohjelma listaa auton mallit (vaihtelevia määriä... ford 37kpl audi 8 kpl jne.) Ja sitten switch silmukan perusteella sijoitetaan ehto tietueessa juuri sen alkion kohdalla oleva malli tietokantaan.
Muokataanpa tuo aiempi koodisi järkevään muotoon...
lainaus:
switch(syote){ for (i = 0; i < laskuri + 1; i++) { case i: // Tähänhän siis päästään, jos (syote == i) ja (i < laskuri + 1) for (k = 0; k<strlen(ehto[i].malli); k++) { // Täällä pitäisi kai olla molemmissa ".malli[k]" tietokanta[counter].malli = ehto[i-1].malli; } break; } }
Sama selkeämmin (ja toimivasti):
if (syote < laskuri + 1) { /* Huom seuraavassa "+ 1", jotta myös tekstin * lopetusmerkki tulee mukaan. Se ei kuulu pituuteen. * Tietenkin jos olet hoitanut sen jotenkin muuten, * niin voit toki käyttää omaakin tapaasi. */ for (k = 0; k < strlen(ehto[syote].malli) + 1; k++) { tietokanta[counter].malli[k] = ehto[syote-1].malli[k]; } }
Ja todennäköisesti tehokkaammin:
if (syote < laskuri + 1) { // string.h:sta löytyvä muistinkopiointifunktio. // memcpy(lähdeosoitin, kohdeosoitin, pituus) memcpy(tietokanta[counter].malli, ehto[syote-1].malli, strlen(ehto[syote].malli) + 1); }
Tälläinen ongelma:
Jos syötän jonkin arvon seuraavaan koodi pätkään ni se ajaa switch-lauseen kahteen kertaan. Esim jos valitsee 1. niin se menee kysy-funktioon mutta siellä kun se kysyy automerkkiä niin se antaa siihen vielä tyhjän arvon. Sama jos antaa 2. niin se tulostaa tiedot kahteen kertaan. Ensimmäisellä kerralla se tulostaa oikein ja jälkimmäisellä kerralla se valittaa että Virheellinen syöte!. Mistä ko. asia johtuu?
int main() { lataa(); //Ladataan tallennetut tiedot while(1){ char syote = NULL; cout << "\nMit\x84 haluat tehd\x84?\n\t1)Anna uusi tietue\n\t2)Tulosta annetut tiedot\n\t3)Lopeta ohjelma\n"; cin.get(syote); switch(atoi(&syote)){ case 1: kysy(counter); counter++; break; case 2: jarjesta(tietokanta, counter); tulosta(counter); break; case 3: if (tietokanta[0].merkki == "") exit(EXIT_SUCCESS); else tallenna(); exit(EXIT_SUCCESS); break; default: cout << "Virheellinen Sy\x94te!\n"; break; } }; return 0; }
3. case kaipaa aaltosulkuja else-blokkiin. Nyt exit-funktio suoritetaan ehdosta riippumatta. Pelkkä sisennys ei C:ssä auta :)
atoi ei myöskään toimi tuossa oikein. (Tai siis jos toimii, niin se on vain hyvää tuuria.) Sille pitää antaa char-taulu (eli -osoitin), jonka lopussa on 0-merkki. Muutenkin itoa on tuossa aivan turha.
switch (syote) { case '1': // jne.
lainaus:
3. case kaipaa aaltosulkuja else-blokkiin. Nyt exit-funktio suoritetaan ehdosta riippumatta. Pelkkä sisennys ei C:ssä auta :)
:) Sen on tarkoituskin poistua ohjelmasta. Se vaan jättää tallentamatta jos tietokannassa ei ole tietueita. (tallentaa/tallenti tyhjän rivin tiedostoon). Korjasin että switch-lauseen antamaasi muotoon ja syote on tyyppia char. Vieläkin tosin ohjelma suorittaa annetun vaihtoehdon kahteen kertaan. Se menee tietoja tulostaessa tuonne tulosta funktioon ja suorittaa sen oikein ja palaa alkuvalikkoon mutta antaa alkuvalikossa tyhjän syötteen josta ohjelma valittaa. Sama ekaan vaihtoehtoon, kysyttäessä automerkkiä ohjelma antaa tyhjän syötteen automaattisesti cin.getlineen ja kiertää silmukan kertaalleen läpi ennen kuin antaa käyttäjälle mahdollisuuden antaa mitään syötettä.
Se jälkimmäinen syöte, jonka saat, on enter. Kannattaisi varmaankin käyttää tuossa kaikesta huolimatta int-muuttujaa ja lukea cin:stä >>-operaattorilla jokin luku. Siitähän täällä on jo annetta vaikka miten paljon ohjeita ihan virheentarkastuksineen kaikkineen.
öh, miten voin tallentaa pelkkää tekstiä tekstitiedostoon?
esim. jos haluan tallentaa annetun lauseen tiedostoon koe.txt? tai miten tallennetaan muunlaisiin tiedostoihin, kuten .dat tiedostoihin? (tällainen noppa kysymys, muttei opi jossei kysy :P)
EgE915 kirjoitti:
miten tallennetaan muunlaisiin tiedostoihin, kuten .dat tiedostoihin?
Se ei ole tiedoston "tyypistä"/päätteestä kiinni. Ihan normaalisti merkkejä merkin perään sinne joka tapauksessa tallennetaan :P
Jos ei ole aavistustakaan, niin silloin pitää lukea jokin opas. Ei ole käytännöllistä, jos jokaiselle aloittelijalle pitää henkilökohtaisesti opettaa tiedostojen käyttö.
Aihe on jo aika vanha, joten et voi enää vastata siihen.