Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Kokoelmien käyttö

Sivun loppuun

Zmyrgel [23.11.2005 22:20:31]

#

Ok, seuraavan ohjelman tarkoituksena on tehdä yksinkertainen ajoneuvotietokanta ja tallentaa se tiedostoon. Homma on suht yksinkertainen mutta ajattelin ottaa siihen hieman lisä haastetta ja kokeilla jos sen saisi suoritettua kokoelmilla.

Kokoelmista sopivin näyttäisi olevan tuo List. Pienen testauksen jälkeen ongelmaksi on muodostunut seuraava.

Tähän väliin hieman koodia hahmottamaan hommaa:

struct Rekisteri
{
	string rekisteri;
	string merkki;
	string malli;
	int vuosimalli;
	int hinta;
};

typedef std::list<Rekisteri>Rekisterilista;

int LisaaRekisteri(Rekisterilista *lisaa);

Kysymys kuuluu että miten kannattaisi tuo tietojen syöttö hoitaa? Ajattelin että aliohjelmalla kysyy arvot rekisteri-tyyppiseen tietueeseen josta ne sitten syöttää tuohon listaan.
Kuulostaako oikealta suunnalta?
Lähinnä tuo ongelma on että miten tuon Rekisterilistan voi laittaa muuttujaparametrinä aliohjelmalle? Nyt se antaa seuraavan ilmoituksen:

error C2664: 'LisaaRekisteri' : cannot convert parameter 1 from 'class std::list<struct Rekisteri,class std::allocator<struct Rekist
eri> >' to 'class std::list<struct Rekisteri,class std::allocator<struct Rekisteri> > *'

Muuten tuo homma on sitten aika mekaanista koodaamista vain.

Samaan settiin ajattelin kokeilla opetella String -luokan ja binäärimuotoisen tiedostonkäsittelyn mutta niistä myöhemmin jos niissä ilmenee jotain ongelmaa.

Metabolix [23.11.2005 22:43:26]

#

Käytät sitä &-operaattoria eli osoitinta, kun kerran olet määritellyt parametrin osoittimeksi. Muuten tuo ajatus vaikutti aivan oikealta.

Edit: Niin, referenssikin toki kelpaa. (Käsittääkseni pointteria on tapana käyttää arvoissa, joita aiotaan muuttaa... Tyhmä perustelu, jolla ei ole mitään arvoa, mutta sanonpa kuitenkin :) Eli ihan miten parhaaksi näet.

Deewiant [23.11.2005 22:44:33]

#

Tai, kun kerran C++:aa käytät, määrittelet parametrin referenssiksi, eli:

int LisaaRekisteri(Rekisterilista & lisaa);

Zmyrgel [24.11.2005 08:13:04]

#

Ok, kiitoksia.

Vai kannattaako tuonne aliohjelmaan lähettää vain joku tietue muuttujaparametrinä johon aliohjelmassa kysytään arvot. Tämän jälkeen pääohjelmassa sitten sijoitetaan tietueen arvot listaan ja tyhjennetään tietue. Olisiko tämä kannattavampi vaihtoehto?

Metabolix [24.11.2005 11:37:20]

#

Ei ole. Kaikki vain aliohjelmiin. Tai tee vaikka monta aliohjelmaa:

int HaeTiedot(Rekisteri &R); // Siinä kysyt tiedot
int Lisaa(Rekisterilista &Lista, Rekisteri &R); // Siinä lisäät
int Hae_Ja_Lisaa(Rekisterilista &Lista, Rekisteri &R)
{
  // Tässä kutsut molempia
  HaeTiedot(R);
  Lisaa(Lista, R);
  return 0;
}

Ja virheenkäsittelyt tietysti tuonne vielä.

Zmyrgel [24.11.2005 13:00:09]

#

Joo, pitänee kokeilla vielä jos pääsee tuosta virhe ilmoituksesta ohi niin saa ainakin tämän homman käyntiin. Koodailen samaa työtä linkitetyllä listallakin mutta siinä ei opi juuri mitään uutta enää. Pitänee saada tuo ohjelma toimimaan list kokoelmalla niin selkeytyy kokoelmienkin käyttö itselle.

phadej [24.11.2005 14:59:39]

#

ja jos oikein c++:tä harrastaa niin tosta kannattaa tehdä olio jonka privatessa on tuo rekisterilista. Lisää ja Hae on taas sen olion metodeita.

Zmyrgel [24.11.2005 21:53:34]

#

Nii joo, sekin vois olla yksi vaihtoehto. Alkaa tuo olio-ohjelmointi kuitenkin ensi jaksossa niin voishan sitä katella miten tuo syntyisi.

Zmyrgel [25.11.2005 12:50:23]

#

Joo, olioilla en vielä jaksa kokeilla kun näissä kokoelmissa ja string-luokassa on ihan tarpeeksi uutta asiaa näin aluksi.

Nyt tulee tälläisiä ongelmia:
-Ohjelma kaatuu rekisterin syöttämisen jälkeen.
-Ohjelma ei tulosta listan sisältöä (kääntäjä antaa virhe ilmoituksen)

typedef list<Rekisteri> RLista;
	RLista R;

	LisaaRekisteri(R);
int LisaaRekisteri(RLista &lista)
{
	// Tallenna tieto pinon päälle
	Rekisteri uusi;
	char apu[7] = "";

	//Kysytään käyttäjältä rekisterinumero
		KysyMerkkijono("Anna uusi rekisteri: ", "\nAnna rekisteri muodossa (xxx-xxx)", 7, apu);
			strcpy((char *)uusi.rekisteri.c_str(), apu);

	//Listataan kannasta tunnetut automerkit joista käyttäjä valitsee yhden
	KysyMerkkijono("Anna uusi merkki: ", "\nTarkista syote", 7, apu);
			strcpy((char *)uusi.merkki.c_str(), apu);

	//Listataan ko. automerkin kaikki mallit
	KysyMerkkijono("Anna uusi malli: ", "\nTarkista syote", 7, apu);
			strcpy((char *)uusi.malli.c_str(), apu);

	//Listataan ko. mallin vuosimallit
	uusi.vuosimalli = KysyLuku("Anna uusi vuosimalli: ", "\nAnna vain numeroita!");

	//Kysytään käyttäjältä auton hinta
		uusi.hinta = KysyLuku("Anna auton hinta: ", "\nTarkista sy\x94te!");

	lista.push_back(uusi);	// Sijoitetaan tietue listaan
	return 0;
}

void TulostaRekisteri(RLista &lista)
{
	//Tulosta tietokannan sisältö
	copy(lista.begin(), lista.end(), ostream_iterator<Rekisteri>(cout, "\n"));
	return;
}

int KysyMerkkijono(const char *kysymys, const char *virhe, const int maxpituus, char *p_arvo)
{
	char puskuri[512] = "";

while (1)
  {
    cout << kysymys;
    cin.getline(puskuri, 512);
	if(puskuri && strlen(puskuri) <= maxpituus){
		strcpy(p_arvo, puskuri);
	}
    else{
      cout << virhe << endl;
	  KysyMerkkijono(kysymys, virhe, maxpituus, p_arvo);
	}
	return 0;
  }
}

Ongelma on varmaan tuossa strcpy -rivillä. Tulostustaminen varmaan kosahtaa siihen että lista tallentaa tietueita eikä se osaa tulostaa suoraan niitä mutta miten tämän voi suorittaa oikein.

Mazuli [25.11.2005 14:38:45]

#

Kannattaisi käyttää string luokkaa, helpottaa huomattavasti. Vertailukin onnistuu == operaattorilla eikä ylivuoto ole ongelma.

Zmyrgel [25.11.2005 15:22:45]

#

Ok, voisiko joku vääntää vaikka tuon KysyMerkkijono -funktion käyttämään string-luokkaa sillä itse en sitä saa. Kokeilin ja lopputulos on +12 uutta virhettä kuten:
getline(char *,int)' : cannot convert parameter 1 from 'const char *' to 'char *'

Mazuli [25.11.2005 19:36:26]

#

No kokeiles tätä

int KysyMerkkijono(string kysymys, string virhe, const int maxpituus, string *p_arvo)
{
  string temp;

  cout << kysymys;

  while(1) {
    cin >> temp;

    if(temp.lenght() > maxpituus) {
      cout << virhe;
    } else {
      p_arvo = temp;
      return 0;
    }
  }
}

Zmyrgel [26.11.2005 12:40:52]

#

Aika paljon sai virheitä aikaan kun suoraan kopioi mutta pienen säädön jälkeen ei ohjelma valita mistään virheistä. Esim. aliohjelma piti muuttaa toimimaan viittauksen kautta.

Muutama kysymys:
-Miten tuon listan sisällön voi tulostaa? Yllä olevassa koodissa pieni kokeilu siihen.
-Mitenkä tuo merkkijonon lukeminen eli miksi cin.getline ei toimi stringin kanssa?

Zmyrgel [26.11.2005 14:45:24]

#

Muutama kysymys lisää:
Kokoelmista:
-Toimiiko tuo list<Rekisteri> RLista? Eli voiko tietueen tallentaa listaan?
- Jos tälläisen listan järjestää (RLista.sort()) niin minkä mukaan se järjestyy? Voiko sen määrittää minkä kentän mukaan (rekisteri)?
-Miten tuollaisen listan voi tulostaa? Suoraan kun tietuetta ei voi tulostaa eli miten viitata listan tietueen kenttään?

Edit: Tälläisen sain nyt aikaan mutta antaa seuraavanlaisen virheen:

copy(lista.begin(), lista.end(), ostream_iterator<Rekisteri::rekisteri>(cout, " "));

error C2923: 'ostream_iterator' : 'Rekisteri::rekisteri' is invalid as template argument '#1', type expected

Miten tuon saa korjattua ja tietueen sisällön tulostettua muodossa:
rekisteri merkki malli vuosimalli hinta \n

Deewiant [26.11.2005 16:17:08]

#

Zmyrgel kirjoitti:

Muutama kysymys lisää:
Kokoelmista:
-Toimiiko tuo list<Rekisteri> RLista? Eli voiko tietueen tallentaa listaan?

Voi.

lainaus:

- Jos tälläisen listan järjestää (RLista.sort()) niin minkä mukaan se järjestyy? Voiko sen määrittää minkä kentän mukaan (rekisteri)?

Veikkaan, että se kutsuu jotakuta <, >, <= ja >= -operaattoreista.

lainaus:

Edit: Tälläisen sain nyt aikaan mutta antaa seuraavanlaisen virheen:

copy(lista.begin(), lista.end(), ostream_iterator<Rekisteri::rekisteri>(cout, " "));

error C2923: 'ostream_iterator' : 'Rekisteri::rekisteri' is invalid as template argument '#1', type expected

Sinun pitää antaa templatelle tyyppi, eli käsittääkseni tässä tapauksessa pelkkä Rekisteri, eikä Rekisteri::rekisteri.

Zmyrgel [26.11.2005 17:11:06]

#

Deewiant kirjoitti:

Veikkaan, että se kutsuu jotakuta <, >, <= ja >= -operaattoreista.

Vertailee siis Rekisteri tietueita keskenään?

Deewiant kirjoitti:

Sinun pitää antaa templatelle tyyppi, eli käsittääkseni tässä tapauksessa pelkkä Rekisteri, eikä Rekisteri::rekisteri.

No ohjelma antaa 7 virheilmoitusta jos tekee neuvomallasi tavalla. Virheet viittaa tuon list-kokoelman omaan koodiin. Missä tämä sitten johtuu?

Zmyrgel [29.11.2005 08:36:32]

#

Kellään antaa esimerkkiä tuosta tietueen käytöstä listassa? Tallennus onnistuu, varmaan.
Mutta kuinka tulostaa listan sisällön vaikka näytölle.
Miten listan tietueen jonkin tietyn kentän pystyy tulostamaan näytölle?
Miten järjestää lista tietueen rekisteri kentän mukaan?

Metabolix [29.11.2005 15:03:19]

#

Tässähän sitä aika meni aika mukavasti opetellessa. Laitanpa kaiken samaan kasaan:

#include <list>
#include <iostream>
#include <stdlib.h>

// Tätä ei saa huomata, kun tämä on muka huono tapa ;)
using namespace std;

// Jokin rakenne vain... Nyt suorakulmio.
struct TKulmio
{
    int A, B;

    bool TKulmio::operator < (TKulmio& toinen);
};

// Tämä määriteltiin structin sisällä
bool TKulmio::operator < (TKulmio& toinen)
{
    if (A * B < toinen.A * toinen.B)
        return true;
    return false;
}

// Kuinka struct tulostetaan cout:lla eli ostream:lla?
// Tämä on toinen tapa laittaa operaattori.
// Tätä EI määritellä structin sisällä.
ostream & operator << (ostream & ns_cout, TKulmio & R)
{
    ns_cout << R.A << " * " << R.B << " = " << R.A * R.B;
    return ns_cout;
}

// Typedef listalle
typedef std::list<TKulmio> TLista;

// Kuinka lista tulostetaan?
ostream & operator << (ostream & ns_cout, TLista & Lis)
{
    // Tällä selvitään
    TLista::iterator Ite;

    // Tulostetaan "<Lista>"
    ns_cout << "<Lista>\n";

    // Kaikki alkiot alusta loppuun
    for (Ite = Lis.begin(); Ite != Lis.end(); Ite++)
    {
        ns_cout << "  " << (*Ite) << "\n";
    }

    // Hieno lopetus
    ns_cout << "</Lista>\n";
    return ns_cout;
}

// ==============================
int main(int argc, char **argv)
{
    TLista Lista;
    TKulmio Neli;

    // Arvotaan sisältöä
    for (int a = 0; a < 10; a++)
    {
        Neli.A = rand() % 128;
        Neli.B = rand() % 128;
        Lista.push_back(Neli);
        cout << Neli << "\n";
    }
    cout << "\n";

    // Lajitellaan
    Lista.sort();

    // Tulostetaan lista
    cout << Lista;

    return 0;
}

Zmyrgel [29.11.2005 22:41:10]

#

Kiitokset, tuo koodinpätkä auttoi eteenpäin hyvän matkaa.

Sain tuon string ongelmankin hoidettua.

Seuraaviksi ongelma kohdiksi on muodostunut tuo tietyn listan alkion tietue kentän arvon muuttaminen. Teoriassa siis selaan listaa eteenpäin kunnes löytyy etsittävä alkio jossa tietue kenttä saa kaivatun arvon. Tämän jälkeen pitäisi käyttäjältä kysyä uudet arvot ja sijoittaa ne vanhojen arvojen päälle. Ongelmana on juurikin että tuo tietue. Hietasen kirjassa käsitellään kokoelmia vain string arvoilla joten siinä ei käydä miten tietueen alkioiden arvoihin esim viitataan.

Toinen ongelma on sitten tuo binäärimuotoiseen tiedostoon tallentaminen. Tälläisillä funktioilla ollaan nyt liikenteessä:

bool LataaDB(RLista &lista) {
	/*
  ifstream fin("data.cdb", ios_base::in | ios_base::binary);

  if (!fin.is_open())
    return false;


  fin.read((char*)lista, sizeof(RLista));
  fin.close();
  */
  return true;
}


bool TallennaDB(RLista &lista) {
/*
  ofstream fout("data.cdb", ios_base::out | ios_base::binary | ios_base::app);
  if (!fout.is_open())
    return false;

  fout.write((const char*)lista, sizeof(RLista));
  fout.close();
*/
  return true;
}

Nämä eivät tietenkään toimi tällä hetkellä milläänlailla.

Jos Metabolix:lla olisi aikaa niin pienimuotoinen kommentti siitä mitä nuo ostream &operator << rivit tuossa koodissasi tarkalleen ottaen tekee niin tajuaa ne vastaisuudessa.

Pitää lueskella hieman tuota kirjaa jos sieltä saisi jotain hyödyllistä irti.

Metabolix [29.11.2005 23:11:54]

#

Selittäminen on työni ja iloni (mihin voisi vastata, että "älä selitä" :)

Tietty alkio löytyy näin:

TLista::iterator Ite = Lista.begin();
for (int i = 0; i < Monesko_Halutaan; i++)
  Ite++;

Missä ensimmäinen alkio on nollas. Iteraattori on ikäänkuin osoitin alkioon sikäli, että (*Ite) on se alkio, ja siihen voi tehdä muutokset.

Pitkässä listassa tuo on tietenkin kovin hidasta (suhteellisesti), ja siksi minä käyttäisinkin jotakin muuta kuin listaa. Toisaalta listan järjestäminen on vastaavasti nopeaa, joten ehkei sillä ole niin suurta merkitystä. Tietenkin voisi tehdä jonkin "tyylikkään" purkkaratkaisun, jossa hyödyntäisi ristikkäin useampaa containeria ja sekavaa osoitinnippua... (Aina yhtä käytännöllinen minä :)

Binäärimuotoisesta tiedostosta en tässä juurikaan tiedä, mutta luulen, että kun kerran string-tyypistä on kysymys, niin suoraan kirjoittamalla ei mene. Pitää varmaan leikkiä string::c_str-funktiolla ja luettaessa lukea ensin char-tauluun ja siitä eteenpäin. Binääritiedostossa kun ei voi mistään tietää, kuinka pitkä string on tulossa. Voin toki olla väärässä tämän asian suhteen, kun ei ole C++:n tiedostoja tullut harrastettua (eikä tule; C on siihen parempi :)

<<-operaattorista:
Operaattorit määritellään aivan samalla tavalla kuin funktiot. Voisihan ==-operaation tilalla käyttää aivan hyvin jotakin funktiota. Määrittely menee siis näin:

// "Erillinen" operaattori:
ostream &    // Palautusarvo: viittaus ostream-tyyppiin
operator <<  // "Funktion nimi"
(ostream & ns_cout, TKulmio & R); // Syötteet (vasen, oikea)

// Operaattori jäsenenä:
bool                 // Palautusarvo
TKulmio::operator <  // Funktion nimi
(TKulmio& toinen);   // Syöte (oikea puoli; vasen on kutsuja)

ostream& -palautustyypin tarkoituksena on, että kutsut voidaan ketjuttaa:
cout << Matti << Pekka;
Toisin sanoen:
(cout << Matti) << Pekka;
Kun tuon lukee järjestyksessä, niin ensin laitetaan Matti couttiin, sitten laitetaan Pekka tuon edellisen palautusarvoon. Sulut ensimmäisen parin ympärillä eivät aiheuta mitään, mutta jos Matti ja Pekka laitetaan sulkuihin, seuraa todennäköisimmin virhe ("No match for 'operator<<'" ja noiden tyypit), ellei niille ole määritelty keskinäistä operaattoria.

Kukaan ei muuten pakota käyttämään juuri <<-operaattoria. Voisit määritellä siihen yhtä hyvin %-operaattorin ja tulostaa listan aina sillä:
cout % Lista;
Miksipäs ei. Käytin minäkin tuota kerran vektorien ristitulo-operaationa, kun muut kivannäköiset symbolit oli jo käytetty eikä ^ jostain syystä tuntunut niin hyvältä. Cout-tapauksessa kannattanee kuitenkin pitäytyä normaaleissa käytännöissä.

Zmyrgel [30.11.2005 10:13:46]

#

Kiitokset. Nyt nuo näyttäisi olevan aika selviä juttuja. Eli jos oikein käsitin niin tuota "cout << Matti" voisi olla vaikka "baa << Matti" eikä asiat muuttuisi? Se eka osa on vain sen tulosvirran nimi mitä halutaan käyttää? Tai todennäköisesti tuo baa pitäisi määritellä ensin siihen hommaan mutta kuitenkin.

Tuli eilen lueskeltua tuo STL:stä kertova kappale ajatuksen kanssa läpi ja todettua että tuo map kokoelma on parempi ratkaisu sillä ajattelin listata tietokannan sisällön rekisterinumeron perusteella (=avain). Tosin luin sen aika myöhään juuri ennen nukkumaan menoa silmät ristissä :) Pitänee tehdä tarvittavat muutokset siihen ja kokeilla.

Lukemalla aukeasi nuo iteraattorit ja niiden toimintakin että pääsee huomattavasti helpommin liikenteeseen.


Pitää palata tuon tiedostoon tallennuksen kimppuun kunhan saan muuten ohjelman toimimaan kunnolla.

Metabolix [30.11.2005 12:40:05]

#

Mikä tahansa ostream-tyypistä periytyvä tosiaan kelpaa.

Oikeastaan tuon funktiomäärittelyn voisi tehdä vielä hieman hankalamminkin:

template <class _Tyyppi_A>
_Tyyppi_A & operator << (_Tyyppi_A & vasen_puoli, TKulmio & R);

Noin operaattorin vasemmalla puolella voi olla aivan mikä tahansa. Vaatimuksena on vain, että siltä löytyy kaikki ne ominaisuudet, joita funktiossa yritetään käyttää. Kääntäjä siis tekee tuosta lopullisen funktion niillä tyypeillä, joita käytetään, ja antaa sitten sen mukaan virheet.

Mutta ehkäpä lopetan selittämisen... Tässä tulisi vain helposti selitettyä saman tien koko rajallinen tietämykseni, kun vauhtiin pääsee :)

Zmyrgel [30.11.2005 15:29:14]

#

Kysytään tässä välissä nopeasti seuraava eli:
stringiin koko rivin lukeminen.

string puskuri = "";
cin.getline(puskuri, 512); // Ei toimi

//Kokeilin seuraavaa mutta toimivuus on epävarma
getline(cin, puskuri);

Alla oleva malli lukee rivinvaihonkin mukaan joten tarvitsee painaa entteriä uudemman kerran että siirtyy koodissa eteenpäin. Miten tämän saisi menemään oikein?

Muutenkin on tullut kivasti ongelmia tuohon map-kokoelmaan siirtyeessä mutta kokeilen vielä itse ratkaista niitä. Tällä hetkellä ei yhtään virhettä mutta kääntäjä antaa 98 varoitusta mikä ei lupaa hyvää.


Edit: Jep, varoitukset sai pois kun sain tuon 4786 varoituksen pois päältä. Nyt koodi toimii joten kuten mutta tuo tulostus on vielä hakusessa. Olen tässä kokeillut muokata tuota metabolixen esimerkkiä mutta se on aika hankalaa kun ekaa kertaa kokeilee moista.

Sikäli että sisäistin tuon ajatuksen mikä siinä on niin pitää kertoa tuolle << operaattorille kuin kaivattu tieto tulostetaan. Eli nyt kun on kyseessä map-kokoelma joka koostuu avain kentästä (string) ja sitten tietue kentästä (struct Autotietue)...

Meneekö tämä edes lähelle:

ostream &operator << (ostream &ns_cout, map<string, Autotietue> &kanta)
{
	map<string, Autotietue>::iterator iter;

	// Tulostus asun määrittely
	for (iter = kanta.begin(); iter != kanta.end(); iter++)
	{
		ns_cout << "  " << (*iter) << endl;
    }
    return ns_cout;
}


ostream &operator << (ostream &ns_cout, string avain, Autotietue &R)
{
	// Tulostus asun määrittely
    ns_cout << avain <<" "<<R.omistaja<<"  "<<R.merkki<<" "<<R.malli<<" "<<R.vuosimalli<<" "<<R.hinta<<endl;
    return ns_cout;
}

Metabolix [30.11.2005 21:13:53]

#

Minulla ainakin tuo getline(cin, puskuri); toimii aivan halutusti:
Sano jotakin: Moi, Paavo!
Sanoit: Moi, Paavo!

Eikös tuosta tulostamisesta jo kääntäjäkin sano, että << ottaa tasan kaksi parametriä (oikean ja vasemman)? Tuossa ensimmäisessä funktiossa, hoida tulostus näin:

ns_cout << "Avain: " << Ite->first << "\n  Arvo: " << Ite->second << "\n";

Tulostat siis ensin muuttujan tyyppiä string ja sitten muuttujan tyyppiä Autotietue. Niille aivan normaalisti tulostusoperaattorit.

(Olenko minä ainut, joka osaa köyttää Googlea lähteiden etsimiseen? Tämäkin asia selvisi kahdesta ensimmäisestä tuloksesta sanoilla STL map. Ei millään pahalla; hyvähän se vain on minulle, kun tulee itsekin opittua samalla. Ihmettelen vain :)

Zmyrgel [01.12.2005 00:19:07]

#

Olen muutamia muita sivuja kokeillut tässä asiassa mutta suoraa ongelmaan liittyvää seikkaa ei ole koskaan löytynyt kuten tuota << operaattorin kuormittamista.

Yleensä juuri nuo esimerkit tehdään yksinkertaisesti kuten tallennetaan vain integer lukuja. Siitä on paha kattoa mallia tietueiden kanssa toimimiseen. Lähinnä juuri tuo syöttö operaattorin kuormitus on uusi seikka kun en ole juuri siitä kuullut tätä ennen niin hankala hahmottaa se suoraan.

Tuo antamasi rivi selkeytti asiaa kivasti ja sain homman pelittämään. Selvisi tuossa koodia tarkastellessa muutama muukin seikka mikä oli pielessä omassa koodissa (iteraattorit) mutta näillä eväillä uskon selviäväni tuohon tiedostoon tallennukseen saakka.

Metabolix [01.12.2005 00:28:21]

#

Löytyvät esimerkit ovat tosiaan sikäli hieman köyhiä. Tuo pitääkin koota aina irtopalasista. Ensin etsitään jostakin operaattorien käyttö, sitten keksitään itse, miten sitä sovelletaan cout-olion kanssa. Sitten jostakin tuo iteraattorin käyttö yleisesti ja loppuun vielä mapin avaimen ja arvon erottelu. Samasta paikasta löytää parhaimmillaan 1,5 asiaa :)

Zmyrgel [03.12.2005 10:53:28]

#

Tälläinen pulma mistä olen yrittänyt päästä eroon. Eka ongelma on että en pysty alustamaan tuon Autotietue uuden arvoja määrittelyn yhteydessä. Toinen on että nuo getline rivit sijoittavat arvot miten sattuu. Olen kokoeillut erilaisia vaihtoehtoja mutta olettaisin että sen saisi toimimaan tuolla getlinelläkin.

int LisaaRekisteri(RMap &tkanta)
{

	// Kysy ja tallenna uusi rekisteri tietokantaan
	Autotietue uusi;	// Ei suostu alustamaan Autotietue uusi = {0,0,0,0,0};
	uusi.hinta = 0;					// non-aggregates cannot be initialized with initializer list
	uusi.vuosimalli = 0;

	string avain = "";

	//Kysytään käyttäjältä rekisterinumero TARKISTUS
		cout << "Anna rekisteri: ";
		getline(cin, avain, '\n');

	//Jos rekisteriä ei löydy tietokannasta
	if (tkanta.find(avain) == tkanta.end()){

	//Kysytään käyttäjältä auton omistaja
		cout << "Anna omistajan nimi: ";
		getline(cin, uusi.omistaja, '\n');

	//Listataan kannasta tunnetut automerkit joista käyttäjä valitsee yhden
		cout << "Merkki: ";
		getline(cin, uusi.merkki, '\n');

	//Listataan ko. automerkin kaikki mallit
			cout << "Malli: ";
			getline(cin, uusi.malli, '\n');

	//Listataan ko. mallin vuosimallit
			cout << "Vuosimalli: ";
			cin >> uusi.vuosimalli;

	//Kysytään käyttäjältä auton hinta
				cout << "Hinta: ";
				cin >> uusi.hinta;

	// Lisää tietokantaan jos ei löytynyt
		pair<Ite, bool> x;
		x = tkanta.insert(Ite::value_type(avain, uusi));

		if (x.second == false){
			cout<<endl<<"Lisäys ei onnistu, rekisteri on jo olemassa" << endl;
			cout << x.first->second;
		}
		//TESTI
			cout << "Tulostetaan osoitettuna" << endl;
			cout<<uusi.omistaja<<" "<<uusi.merkki<<" "<<uusi.malli<<" "<<uusi.hinta<<" "<<uusi.vuosimalli<<endl;
			cout << "Tietue" << endl;
			cout << uusi;
	}else
		cout << "Rekisteri on jo annettu!" << endl;

	return 0;

}

Ohjelma antaa seuraavanlaiseen tulosteen

lainaus:

Anna rekisteri: FGT-456
Anna omistajan nimi: Zmyrgel
Merkki: Fiat
Malli: Stilo // Tämän vastauksen jälkeen ohjelma tulostaa alla olevat rivit ja kaatuu.
Vuosimalli: Hinta: Tulostetaan osoitettuna
FGT-456 Zmyrgel Fiat 0 0
tietue
Omistaja: FGT-456
Auton valmistaja: Zmyrgel
Auton malli: Fiat
Auton vuosimalli: 0
Auton hinta: 0

Mikä tuossa mättää? Ohjelma näyttäisi sijoittavan aina arvonsa seuraavaan muuttujaan. Avain merkkijonon arvo menee omistajajaan. Omistaja menee auton merkiksi jne. Ohjelma kaatuu koska yrittää sijoittaa auton mallia integer kenttään auton vuosimalli?

Metabolix [03.12.2005 13:38:40]

#

No ei tietenkään suostu alustamaan lähtöarvoilla 0, kun se ei kelpaa stringiin.

Rekisteri alku = {
    "", // string omistaja;
    "", // string merkki;
    "", // string malli;
    0,  // int vuosimalli;
    0   // int hinta;
};

Tuohon lukuongelmaan en osaa kyllä suoraan sanoa. Laita vielä tulostukset suoraan getlinejen perään ja ihmettele siinä lisää. Ensimmäinen lähestymistapa on aina selvittää, mitä siihen avaimeen sitten menee, jos sen arvo on muualla.

Niin ja jos ohjelmassa on monta kooditiedostoa, niin aina toisinaan Clean tekee terää :)

Zmyrgel [03.12.2005 15:06:34]

#

Metabolix kirjoitti:

No ei tietenkään suostu alustamaan lähtöarvoilla 0, kun se ei kelpaa stringiin.

No eipä alusta tuo sinunkaan esimerkki sen paremmin vaan antaa saman virheilmoituksen. Syytä en tiedä ja siksi kysynkin täältä.

lainaus:

Tuohon lukuongelmaan en osaa kyllä suoraan sanoa. Laita vielä tulostukset suoraan getlinejen perään ja ihmettele siinä lisää. Ensimmäinen lähestymistapa on aina selvittää, mitä siihen avaimeen sitten menee, jos sen arvo on muualla.
Tuossa yllähän on näyte siitä kuinka tuo ohjelma tulostaa arvot ja sorsa siinä päällä.

Avain kenttä näyttää olevan tyhjä ja avain kentän sisältö menee omistajaan. Merkki kenttä jää myös tyhjäksi ja merkin sisältö menee malliin. Mallin arvo ei näytä menevän minnekkään eikä ohjelma edes kysy auton hintaa tai vuosimallia vaan menee alkuvalikkoon ja antaa XP:n virheilmoituksen.


Kokeilin scanf:n kanssa ja sillä sen muistaakseni toimi moitteettomasti mutta haluaisin selvittää miksi se ei toimi tuollaisessa c++ läheisemmällä koodilla sitten.

Metabolix [03.12.2005 15:19:21]

#

Kokeile laittaa ennen avain-getlineä ylimääräinen getline tai tyhjentää puskuri \n-merkkiin asti muulla koodilla (aiemmassa ketjussasi).

Structin alustamisesta ei voi muuta kuin ihmetellä. Minulla toimii aivan hyvin. Löysin keskustelun (http://www.dbforums.com/showthread.php?t=536244&goto=nextnewest), jossa selitetään asiasta. Lopputuloksena: kääntäjäsi on väärässä. Jos käytät Dev-C++:aa, lataa uusin mingw-devpak. Tai noin yleisestikin, päivitä gcc. Visual C++ kuulemma toisinaan kärsii tuosta ongelmasta. Ei se nyt niin paha ole, jos nuo joutuu itse täyttämään. Ja kun kerran käytät C++:aa, niin voisit saman tien tehdä tietueesta luokan, jonka luontifunktio hoitaisi asian.

Zmyrgel [03.12.2005 16:44:26]

#

Jep, tuo puskurin tyhjennys korjasi asiaa mukavasti. Kokeilin itse aikaisemmin pelkästään tuota cin.clear() osaa mikä ei sitten toiminut.

Vielä sellainen että nyt kun tuo toimii tuolla getlinellä niin se lukee rivinvaihdon mukaan joten vastauksen annon jälkeen on painettava kaksi kertaa entteriä jotta päästään eteenpäin. Pääsisikö tästä miten yli?.

Toinen juttu on tuo binäärimuotoisena tallentaa tuo map-kokoelma mihin en ole keksinyt vielä mitään ratkaisevaa. Muutaman epäonnistuneen kokeilun pelkästään.

Metabolix [03.12.2005 19:21:58]

#

istream::clear tyhjentää virhelipun. Eli kun yrität lukea vaikkapa tekstiä kokonaislukumuuttujaan, tulee virhe, ja cin.good() palauttaa arvon false. cin.clear() poistaa virhemerkinnän, jonka jälkeen cin.good() palauttaa taas true.

Ainakaan minulla getline ei odota toista enteriä, kuten sen ei pitäisikään odottaa, eli taas voi vain ihmetellä. Hankala auttaa ongelmassa, jota ei itselleen saa aikaan.

Zmyrgel [04.12.2005 15:23:02]

#

Jepulis,

Vissiin tullut sekoitettua se noiden kokoelmien vastaavaan. Olen saanut nyt jotenkuten tehtyä tuon binääritallennuksen ja lukemisen mutta ongelmana on iteraattorilla tietueen kenttiin osoittaminen:

temp.malli = iter->second->malli;

Antaa jonkun does not have member operator virheen. Yritän tuolla koodilla sijoittaa temp tietueeseen tietoja kokoelmasta kenttä kerrallaan. iter->second osoittaa map-kokoelman tallennettavaan tietoon joka on tietue. Tämän tietueen kenttiin siis pitäisi pystyä osoittamaan.
Tuo on nopeasti päästä heitetty mutta en löytänyt kirjasta tai googlettamalla mitään tähän hätään.

Metabolix [04.12.2005 15:31:41]

#

Iter->second on ihan se tietue (ei siis osoitin), ja siitä jatketaan sitten tavalliseen tapaan pisteillä.
temp.malli = iter->second.malli;
temp = iter->second;

Zmyrgel [06.12.2005 00:07:44]

#

Täällä taas. Vieläkään ei tuo getline toimi mutta nyt tiedän jo mistä tämä johtuu. Ongelma johtuu mikkisoftan VC++ kääntäjästä. Sen mukana tulevasta string otsikkotiedostossa on virhe mikä piti käsin korjata. Olen sen saanut tehtyä mutta ongelma pysyy vielä sillä ohjelma pitäisi kääntää jotenkin "compile all" komennolla tai vastaavalla eikä minulla ole hajua kuinka tuo tapahtuu.
Ongelmaa käsitelty täällä: http://www.suomipelit.com/index.php?c­=keskusteluviestit&id=3386&s=1

Metabolix [06.12.2005 09:45:00]

#

Eikös Build-valikossa (tai jossain) ole joori tuollainen Compile All tai Build All?

Zmyrgel [06.12.2005 10:45:56]

#

Damn, ei vieläkään korjaantunut tuo ongelma. Toisena virheenä ohjelma ei sijoita ensimmäistä arvoa (avain-kenttä) minnekkään mutta muut arvot kyllä sijoittuvat hyvin vaikka vaativat sen ylimääräisen entterin painalluksen.

Arvot kysellään seuraavasti:

cout << "Anna ajoneuvon rekisterinumero muodossa XXX-XXX: ";
		getline(cin, avain, '\n');
		uusi.rekisteri = avain;
		ClearBuffer();

	//Jos rekisteriä ei löydy tietokannasta
	if (tkanta.find(avain) == tkanta.end()){

	//Kysytään käyttäjältä auton omistaja
		cout << "Anna omistajan nimi: ";
		getline(cin, uusi.omistaja, '\n');
		ClearBuffer();

...ja niin edelleen. Ainut mikä voisi vaikuttaa tuohon rekisterin kyselyyn on tuo tietokannasta etsiminen... minkä ei pitäisi vaikuttaa avaimen arvoon millään. Toinen on tietysti se että ohjelma ei voi lukea avainta syötteestä mutta kyllä se minusta näyttäisi koodillisesti olevan oikein.

Asensin ja kokeilin tuolla Dev-C++:llakin mutta sama tulos silläkin. Masentavaa kerrassaan.

Edit: Laitetaan nyt tännekkin tuon binääritiedostosta lataaminen. Kun ohjelma yrittää ladata tiedostosta niin se tulostaa oikean määrän kenttiä mutta ne kaikki on tyhjiä joten se ei sijoita arvoja kohdalleen.

while(fin.peek() != EOF)
		{
			fin.read((char*)&temp, sizeof(Autotietue));
			avain = temp.rekisteri;
			uusi.rekisteri = temp.rekisteri;
			uusi.hinta = temp.hinta;
			uusi.malli = temp.malli;
			uusi.merkki = temp.merkki;
			uusi.omistaja = temp.omistaja;
			uusi.vuosimalli = temp.vuosimalli;
			cout << avain << endl;
			cout << uusi;

			if (tkanta.find(avain) == tkanta.end()){

				// Lisää tietokantaan jos ei löytynyt
				pair<Ite, bool> x;
				x = tkanta.insert(Ite::value_type(avain, uusi));

				if (x.second == false){
					cout<<endl<<"Lisäys ei onnistu, rekisteri on jo olemassa" << endl;
					cout << x.first->second;
				}
			}

Mielestäni tuon pitäisi toimia oikein.

Metabolix [06.12.2005 11:00:42]

#

Dev-C++:aan voi ihan varmuuden vuoksi päivittää PakManilla uusimmat kirjastot.

Mutta nyt luulisin, että ongelma tulee noista ylimääräisistä bufferintyhjennyksistä. Sitä ei pidä käyttää silloin, kun bufferissa ei ole mitään, tai se odottaa uutta enteriä. Se on vain sitä varten, että bufferin saa tyhjäksi, kun on lukenut >>-operaattorilla jotakin, jonka jäljiltä on jäänyt ylimääräinen enter.

Itse asiassa tuohon näyttäisi olevan toinenkin, tässä tapauksessa parempi tapa, ws, joka tyhjentää bufferista kaikki whitespacet. Kokeile auttaako se. Eli bufferintyhjennykset pois, ja aina ennen lukemista ws(cin);

Zmyrgel [06.12.2005 11:10:36]

#

Kokeillaan...

Toimi muuten mutta ohjelma ei sijoita avain kenttään olleskaan arvoa mutta muihin arvot menevät oikein. Jos avain kenttään antaa suoraan cin >> avain niin homma toimii kyllä. Mielenkiintoista.

Metabolix [06.12.2005 15:52:00]

#

No mutta jos se avain on kuitenkin sellaisessa muodossa, niin noinhan se oikeastaan kuuluisikin tehdä. Ja muista laittaa tarkistus, että avaimessa on vähintään yksi kirjain, sitten viiva ja lopuksi vähintään yksi numero. (Jos siis on tarkoitus vain oikeinta rekisterinumeroita käsitellä.)

Zmyrgel [06.12.2005 16:15:38]

#

En ole vielä jaksanut noita virheen tarkistuksia tehdä kun aika on tehokkaasti kulunut tuon getlinen kanssa sekoillessa. Pitänee siirtyä tuohon Dev-C++:ssaan pysyvästi jos se pelittäisi paremmin.


Sivun alkuun

Vastaus

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

Tietoa sivustosta